에이전트 선택적 탐색 구조 개선과 경고 정리 반영
Some checks failed
Release Gate / gate (push) Has been cancelled

- claude-code 선택적 탐색 흐름을 참고해 Cowork/Code 시스템 프롬프트에서 folder_map 상시 선행 지시를 완화하고 glob/grep 기반 좁은 탐색을 우선하도록 조정함

- FolderMapTool 기본 depth를 2로, include_files 기본값을 false로 낮추고 MultiReadTool 최대 파일 수를 8개로 줄여 초기 과탐색 폭을 보수적으로 조정함

- AgentLoopExplorationPolicy partial을 추가해 탐색 범위 분류, broad-scan corrective hint, exploration_breadth 성능 로그를 연결함

- AgentLoopService에 탐색 범위 가이드 주입과 실행 중 탐색 폭 추적을 추가하고, 좁은 질문에서 반복적인 folder_map/대량 multi_read를 교정하도록 정리함

- DocxToHtmlConverter nullable 경고를 수정해 Release 빌드 경고 0 / 오류 0 기준을 다시 충족함

- README와 docs/DEVELOPMENT.md에 2026-04-09 10:36 (KST) 기준 개발 이력을 반영함
This commit is contained in:
2026-04-09 14:27:59 +09:00
parent 7931566212
commit 33c1db4dae
119 changed files with 4453 additions and 6943 deletions

View File

@@ -44,8 +44,8 @@ public class SubAgentTool : IAgentTool
public Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
{
var task = args.TryGetProperty("task", out var t) ? t.GetString() ?? "" : "";
var id = args.TryGetProperty("id", out var i) ? i.GetString() ?? "" : "";
var task = args.SafeTryGetProperty("task", out var t) ? t.SafeGetString() ?? "" : "";
var id = args.SafeTryGetProperty("id", out var i) ? i.SafeGetString() ?? "" : "";
if (string.IsNullOrWhiteSpace(task) || string.IsNullOrWhiteSpace(id))
return Task.FromResult(ToolResult.Fail("task and id are required."));
@@ -72,11 +72,15 @@ public class SubAgentTool : IAgentTool
StartedAt = DateTime.Now,
};
// P2: 부모 취소 토큰 연동 — 부모 에이전트 중지 시 자식도 즉시 취소
var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
subTask.Cts = cts;
subTask.RunTask = System.Threading.Tasks.Task.Run(async () =>
{
try
{
var result = await RunSubAgentAsync(id, task, context).ConfigureAwait(false);
var result = await RunSubAgentAsync(id, task, context, cts.Token).ConfigureAwait(false);
subTask.Result = result;
subTask.Success = true;
NotifyStatus(new SubAgentStatusEvent
@@ -89,6 +93,20 @@ public class SubAgentTool : IAgentTool
Timestamp = DateTime.Now,
});
}
catch (OperationCanceledException)
{
subTask.Result = "Cancelled: parent agent was stopped.";
subTask.Success = false;
NotifyStatus(new SubAgentStatusEvent
{
Id = id,
Task = task,
Status = SubAgentRunStatus.Failed,
Summary = $"Sub-agent '{id}' cancelled.",
Result = subTask.Result,
Timestamp = DateTime.Now,
});
}
catch (Exception ex)
{
subTask.Result = $"Error: {ex.Message}";
@@ -106,8 +124,9 @@ public class SubAgentTool : IAgentTool
finally
{
subTask.CompletedAt = DateTime.Now;
cts.Dispose();
}
}, CancellationToken.None);
}, cts.Token);
lock (_lock)
_activeTasks[id] = subTask;
@@ -125,7 +144,7 @@ public class SubAgentTool : IAgentTool
$"Sub-agent '{id}' started.\nTask: {task}\nUse wait_agents later to collect the result."));
}
private static async Task<string> RunSubAgentAsync(string id, string task, AgentContext parentContext)
private static async Task<string> RunSubAgentAsync(string id, string task, AgentContext parentContext, CancellationToken ct)
{
var settings = CreateSubAgentSettings(parentContext);
using var llm = new LlmService(settings);
@@ -150,7 +169,7 @@ public class SubAgentTool : IAgentTool
}
};
var finalText = await loop.RunAsync(messages, CancellationToken.None).ConfigureAwait(false);
var finalText = await loop.RunAsync(messages, ct).ConfigureAwait(false);
var eventSummary = SummarizeEvents(loop.Events);
var sb = new StringBuilder();
@@ -451,16 +470,16 @@ public class WaitAgentsTool : IAgentTool
public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
{
List<string>? ids = null;
if (args.TryGetProperty("ids", out var idsEl) && idsEl.ValueKind == JsonValueKind.Array)
if (args.SafeTryGetProperty("ids", out var idsEl) && idsEl.ValueKind == JsonValueKind.Array)
{
ids = idsEl.EnumerateArray()
.Where(x => x.ValueKind == JsonValueKind.String)
.Select(x => x.GetString() ?? "")
.Select(x => x.SafeGetString() ?? "")
.Where(x => !string.IsNullOrWhiteSpace(x))
.ToList();
}
var completedOnly = args.TryGetProperty("completed_only", out var completedEl) &&
var completedOnly = args.SafeTryGetProperty("completed_only", out var completedEl) &&
completedEl.ValueKind == JsonValueKind.True;
var result = await SubAgentTool.WaitAsync(ids, completedOnly, ct).ConfigureAwait(false);
@@ -477,6 +496,7 @@ public class SubAgentTask
public bool Success { get; set; }
public string? Result { get; set; }
public Task? RunTask { get; set; }
public CancellationTokenSource? Cts { get; set; }
}
public enum SubAgentRunStatus