AX Agent 도구·스킬 정합성 재구성 및 실행 품질 보강
변경 목적: - AX Agent의 도구 이름, 내부 설정, 스킬 정책, 실행 루프 사이의 불일치를 줄이고 전체 동작 품질을 높인다. - claw-code 수준의 일관된 동작 품질을 참고하되 AX 구조에 맞는 고유한 카탈로그·정규화 레이어로 재구성한다. 핵심 수정사항: - 도구 canonical id, legacy alias, 탭 노출, 설정 카테고리, read-only 분류를 중앙 카탈로그로 통합했다. - ToolRegistry, AgentLoopService, 병렬 실행 분류, 권한 처리, 훅 처리, 스킬 allowed-tools 해석이 같은 이름 체계를 사용하도록 정리했다. - Agent 설정/일반 설정/도움말의 도구 카드와 훅 편집기, 스킬 설명을 현재 런타임 구조에 맞게 갱신했다. - 컨텍스트 압축, intent gate, spawn agents, session learning, model prompt adapter, workspace context 관련 변경과 테스트 추가를 함께 반영했다. - 문서 이력과 비교/로드맵 문서를 최신 상태로 갱신했다. 검증 결과: - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_toolcat\ -p:IntermediateOutputPath=obj\verify_toolcat\ : 경고 0 / 오류 0 - dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter AgentToolCatalogTests -p:OutputPath=bin\verify_toolcat_tests\ -p:IntermediateOutputPath=obj\verify_toolcat_tests\ : 통과 8
This commit is contained in:
@@ -35,6 +35,11 @@ public class SubAgentTool : IAgentTool
|
||||
Type = "string",
|
||||
Description = "A unique sub-agent identifier used by wait_agents."
|
||||
},
|
||||
["profile"] = new ToolProperty
|
||||
{
|
||||
Type = "string",
|
||||
Description = "Execution profile: researcher (default, read-only), coder (can edit/build), writer (doc creation), reviewer (code review), planner (task decomposition)."
|
||||
},
|
||||
},
|
||||
Required = new() { "task", "id" }
|
||||
};
|
||||
@@ -46,6 +51,7 @@ public class SubAgentTool : IAgentTool
|
||||
{
|
||||
var task = args.SafeTryGetProperty("task", out var t) ? t.SafeGetString() ?? "" : "";
|
||||
var id = args.SafeTryGetProperty("id", out var i) ? i.SafeGetString() ?? "" : "";
|
||||
var profileName = args.SafeTryGetProperty("profile", out var p) ? p.SafeGetString() : null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(task) || string.IsNullOrWhiteSpace(id))
|
||||
return Task.FromResult(ToolResult.Fail("task and id are required."));
|
||||
@@ -80,7 +86,7 @@ public class SubAgentTool : IAgentTool
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await RunSubAgentAsync(id, task, context, cts.Token).ConfigureAwait(false);
|
||||
var result = await RunSubAgentAsync(id, task, context, profileName, cts.Token).ConfigureAwait(false);
|
||||
subTask.Result = result;
|
||||
subTask.Success = true;
|
||||
NotifyStatus(new SubAgentStatusEvent
|
||||
@@ -144,11 +150,15 @@ 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, CancellationToken ct)
|
||||
private static async Task<string> RunSubAgentAsync(string id, string task, AgentContext parentContext, string? profileName, CancellationToken ct)
|
||||
{
|
||||
var settings = CreateSubAgentSettings(parentContext);
|
||||
var profile = SubAgentProfileCatalog.Get(profileName);
|
||||
var settings = CreateSubAgentSettings(parentContext, profile);
|
||||
using var llm = new LlmService(settings);
|
||||
using var tools = await CreateSubAgentRegistryAsync(settings).ConfigureAwait(false);
|
||||
// P2: 프로파일별 temperature override
|
||||
if (profile.TemperatureOverride.HasValue)
|
||||
llm.PushInferenceOverride(temperature: profile.TemperatureOverride.Value);
|
||||
using var tools = await CreateSubAgentRegistryAsync(settings, profile).ConfigureAwait(false);
|
||||
|
||||
var loop = new AgentLoopService(llm, tools, settings)
|
||||
{
|
||||
@@ -160,7 +170,7 @@ public class SubAgentTool : IAgentTool
|
||||
new()
|
||||
{
|
||||
Role = "system",
|
||||
Content = BuildSubAgentSystemPrompt(task, parentContext),
|
||||
Content = BuildSubAgentSystemPrompt(task, parentContext, profile),
|
||||
},
|
||||
new()
|
||||
{
|
||||
@@ -189,93 +199,151 @@ public class SubAgentTool : IAgentTool
|
||||
return sb.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
private static SettingsService CreateSubAgentSettings(AgentContext parentContext)
|
||||
private static SettingsService CreateSubAgentSettings(AgentContext parentContext, SubAgentProfile profile)
|
||||
{
|
||||
var settings = new SettingsService();
|
||||
settings.Load();
|
||||
|
||||
var llm = settings.Settings.Llm;
|
||||
llm.WorkFolder = parentContext.WorkFolder;
|
||||
llm.FilePermission = "Deny";
|
||||
llm.FilePermission = profile.FilePermission;
|
||||
llm.AgentHooks = new();
|
||||
llm.ToolPermissions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
llm.DisabledTools = new List<string>
|
||||
{
|
||||
"spawn_agent",
|
||||
"wait_agents",
|
||||
"file_write",
|
||||
"file_edit",
|
||||
"process",
|
||||
"build_run",
|
||||
"snippet_runner",
|
||||
"memory",
|
||||
"notify",
|
||||
"open_external",
|
||||
"user_ask",
|
||||
"checkpoint",
|
||||
"diff_preview",
|
||||
"playbook",
|
||||
"http_tool",
|
||||
"clipboard",
|
||||
"sql_tool",
|
||||
};
|
||||
llm.DisabledTools = profile.DisabledToolNames.ToList();
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static async Task<ToolRegistry> CreateSubAgentRegistryAsync(SettingsService settings)
|
||||
/// <summary>도구 이름 → 팩토리 매핑 (인스턴스를 필요한 것만 생성).</summary>
|
||||
private static readonly Dictionary<string, Func<IAgentTool>> ToolFactories =
|
||||
new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["file_read"] = () => new FileReadTool(),
|
||||
["glob"] = () => new GlobTool(),
|
||||
["grep"] = () => new GrepTool(),
|
||||
["folder_map"] = () => new FolderMapTool(),
|
||||
["document_read"] = () => new DocumentReaderTool(),
|
||||
["dev_env_detect"] = () => new DevEnvDetectTool(),
|
||||
["git_tool"] = () => new GitTool(),
|
||||
["lsp_code_intel"] = () => new LspTool(),
|
||||
["code_search"] = () => new CodeSearchTool(),
|
||||
["code_review"] = () => new CodeReviewTool(),
|
||||
["project_rule"] = () => new ProjectRuleTool(),
|
||||
["skill_manager"] = () => new SkillManagerTool(),
|
||||
["json_tool"] = () => new JsonTool(),
|
||||
["regex_tool"] = () => new RegexTool(),
|
||||
["diff_tool"] = () => new DiffTool(),
|
||||
["base64_tool"] = () => new Base64Tool(),
|
||||
["hash_tool"] = () => new HashTool(),
|
||||
["datetime_tool"] = () => new DateTimeTool(),
|
||||
["math_tool"] = () => new MathTool(),
|
||||
["xml_tool"] = () => new XmlTool(),
|
||||
["multi_read"] = () => new MultiReadTool(),
|
||||
["file_info"] = () => new FileInfoTool(),
|
||||
["document_review"] = () => new DocumentReviewTool(),
|
||||
// coder 프로파일용
|
||||
["file_write"] = () => new FileWriteTool(),
|
||||
["file_edit"] = () => new FileEditTool(),
|
||||
["build_run"] = () => new BuildRunTool(),
|
||||
["process"] = () => new ProcessTool(),
|
||||
["test_loop"] = () => new TestLoopTool(),
|
||||
["snippet_runner"] = () => new SnippetRunnerTool(),
|
||||
// writer 프로파일용
|
||||
["html_create"] = () => new HtmlSkill(),
|
||||
["docx_create"] = () => new DocxSkill(),
|
||||
["markdown_create"] = () => new MarkdownSkill(),
|
||||
["csv_create"] = () => new CsvSkill(),
|
||||
["excel_create"] = () => new ExcelSkill(),
|
||||
["pptx_create"] = () => new PptxSkill(),
|
||||
["document_plan"] = () => new DocumentPlannerTool(),
|
||||
};
|
||||
|
||||
private static async Task<ToolRegistry> CreateSubAgentRegistryAsync(SettingsService settings, SubAgentProfile profile)
|
||||
{
|
||||
var registry = new ToolRegistry();
|
||||
|
||||
registry.Register(new FileReadTool());
|
||||
registry.Register(new GlobTool());
|
||||
registry.Register(new GrepTool());
|
||||
registry.Register(new FolderMapTool());
|
||||
registry.Register(new DocumentReaderTool());
|
||||
registry.Register(new DevEnvDetectTool());
|
||||
registry.Register(new GitTool());
|
||||
registry.Register(new LspTool());
|
||||
registry.Register(new CodeSearchTool());
|
||||
registry.Register(new CodeReviewTool());
|
||||
registry.Register(new ProjectRuleTool());
|
||||
registry.Register(new SkillManagerTool());
|
||||
registry.Register(new JsonTool());
|
||||
registry.Register(new RegexTool());
|
||||
registry.Register(new DiffTool());
|
||||
registry.Register(new Base64Tool());
|
||||
registry.Register(new HashTool());
|
||||
registry.Register(new DateTimeTool());
|
||||
registry.Register(new MathTool());
|
||||
registry.Register(new XmlTool());
|
||||
registry.Register(new MultiReadTool());
|
||||
registry.Register(new FileInfoTool());
|
||||
registry.Register(new DocumentReviewTool());
|
||||
// 필요한 도구만 인스턴스 생성 (기존: 전체 63개 생성 후 필터 → 개선: 필요한 것만 팩토리 호출)
|
||||
foreach (var name in profile.EnabledToolNames)
|
||||
{
|
||||
if (ToolFactories.TryGetValue(name, out var factory))
|
||||
registry.Register(factory());
|
||||
}
|
||||
|
||||
await registry.RegisterMcpToolsAsync(settings.Settings.Llm.McpServers).ConfigureAwait(false);
|
||||
return registry;
|
||||
}
|
||||
|
||||
private static string BuildSubAgentSystemPrompt(string task, AgentContext parentContext)
|
||||
private static string BuildSubAgentSystemPrompt(string task, AgentContext parentContext, SubAgentProfile profile)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("You are a focused sub-agent for AX Copilot.");
|
||||
sb.AppendLine("You are running a bounded, read-only investigation.");
|
||||
sb.AppendLine("Use tools to inspect the project, gather evidence, and produce an actionable result.");
|
||||
sb.AppendLine("Do not ask the user questions.");
|
||||
sb.AppendLine("Do not attempt file edits, command execution, notifications, or external side effects.");
|
||||
sb.AppendLine("Prefer direct evidence from files and tool results over speculation.");
|
||||
sb.AppendLine("If something is uncertain, say so briefly and identify what evidence is missing.");
|
||||
|
||||
// P2: 프로파일별 시스템 프롬프트 접두사 사용
|
||||
sb.AppendLine(profile.SystemPromptPrefix);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(parentContext.WorkFolder))
|
||||
sb.AppendLine($"Current work folder: {parentContext.WorkFolder}");
|
||||
if (!string.IsNullOrWhiteSpace(parentContext.ActiveTab))
|
||||
sb.AppendLine($"Current tab: {parentContext.ActiveTab}");
|
||||
|
||||
// P4: 워크스페이스 컨텍스트 자동 주입
|
||||
var wsContext = WorkspaceContextGenerator.LoadContext(parentContext.WorkFolder);
|
||||
if (!string.IsNullOrWhiteSpace(wsContext))
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Workspace context:");
|
||||
sb.AppendLine(wsContext.Length > 2000 ? wsContext[..2000] + "\n...(truncated)" : wsContext);
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Investigation rules:");
|
||||
sb.AppendLine("1. Start by reading the directly relevant files, not by summarizing from memory.");
|
||||
sb.AppendLine("2. If the task mentions a code path, type, method, or feature, use grep/glob to find references and callers.");
|
||||
sb.AppendLine("3. If the task is about bugs, trace likely cause -> affected files -> validation evidence.");
|
||||
sb.AppendLine("4. If the task is about implementation planning, identify the minimum file set and the main risk.");
|
||||
sb.AppendLine("5. If the task is about review, prioritize concrete defects, regressions, and missing tests.");
|
||||
|
||||
// 프로파일별 작업 규칙
|
||||
switch (profile.Name)
|
||||
{
|
||||
case "coder":
|
||||
sb.AppendLine("Coding rules:");
|
||||
sb.AppendLine("1. Read the relevant files first to understand existing patterns.");
|
||||
sb.AppendLine("2. Make the minimal correct change — do not refactor unrelated code.");
|
||||
sb.AppendLine("3. After editing, verify with build_run or test_loop.");
|
||||
sb.AppendLine("4. If the build fails, fix the issue immediately.");
|
||||
sb.AppendLine("5. Report what was changed and the verification result.");
|
||||
break;
|
||||
|
||||
case "writer":
|
||||
sb.AppendLine("Document creation rules:");
|
||||
sb.AppendLine("1. Inspect existing documents or source files for context.");
|
||||
sb.AppendLine("2. Produce well-structured, complete documents.");
|
||||
sb.AppendLine("3. Use appropriate formatting for the target format.");
|
||||
sb.AppendLine("4. Verify file was created successfully.");
|
||||
break;
|
||||
|
||||
case "reviewer":
|
||||
sb.AppendLine("Review rules:");
|
||||
sb.AppendLine("1. Start by reading the directly relevant files.");
|
||||
sb.AppendLine("2. Rate each finding P0 (critical) through P3 (minor).");
|
||||
sb.AppendLine("3. Prioritize concrete defects, regressions, and missing tests.");
|
||||
sb.AppendLine("4. Cite exact file paths and line ranges as evidence.");
|
||||
sb.AppendLine("5. Do not suggest edits — only report findings.");
|
||||
break;
|
||||
|
||||
case "planner":
|
||||
sb.AppendLine("Planning rules:");
|
||||
sb.AppendLine("1. Inspect the codebase to understand the current architecture.");
|
||||
sb.AppendLine("2. Decompose the task into ordered steps with clear dependencies.");
|
||||
sb.AppendLine("3. Identify the minimum file set for each step.");
|
||||
sb.AppendLine("4. Highlight the primary risk for each step.");
|
||||
sb.AppendLine("5. Suggest a validation strategy.");
|
||||
break;
|
||||
|
||||
default: // researcher
|
||||
sb.AppendLine("Investigation rules:");
|
||||
sb.AppendLine("1. Start by reading the directly relevant files, not by summarizing from memory.");
|
||||
sb.AppendLine("2. If the task mentions a code path, type, method, or feature, use grep/glob to find references and callers.");
|
||||
sb.AppendLine("3. If the task is about bugs, trace likely cause -> affected files -> validation evidence.");
|
||||
sb.AppendLine("4. If the task is about implementation planning, identify the minimum file set and the main risk.");
|
||||
sb.AppendLine("5. If the task is about review, prioritize concrete defects, regressions, and missing tests.");
|
||||
break;
|
||||
}
|
||||
|
||||
var workflowHints = BuildSubAgentWorkflowHints(task, parentContext.ActiveTab);
|
||||
if (!string.IsNullOrWhiteSpace(workflowHints))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user