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:
@@ -6,6 +6,7 @@ using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Services.Agent;
|
||||
using AxCopilot.ViewModels;
|
||||
|
||||
namespace AxCopilot.Views;
|
||||
@@ -429,73 +430,21 @@ public partial class SettingsWindow : Window
|
||||
{
|
||||
if (AgentEtcContent == null) return;
|
||||
|
||||
// 도구 목록 데이터 (카테고리별)
|
||||
var toolGroups = new (string Category, string Icon, string IconColor, (string Name, string Desc)[] Tools)[]
|
||||
{
|
||||
("파일/검색", "\uE8B7", "#F59E0B", new[]
|
||||
using var registry = Services.Agent.ToolRegistry.CreateDefault();
|
||||
var toolGroups = registry.All
|
||||
.Select(tool => new { Tool = tool, Meta = AgentToolCatalog.GetMetadata(tool.Name) })
|
||||
.GroupBy(x => x.Meta.SettingsCategory, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(group =>
|
||||
{
|
||||
("file_read", "파일 읽기 ? 텍스트/바이너리 파일의 내용을 읽습니다"),
|
||||
("file_write", "파일 쓰기 ? 새 파일을 생성하거나 기존 파일을 덮어씁니다"),
|
||||
("file_edit", "파일 편집 ? 줄 번호 기반으로 파일의 특정 부분을 수정합니다"),
|
||||
("glob", "파일 패턴 검색 ? 글로브 패턴으로 파일을 찾습니다 (예: **/*.cs)"),
|
||||
("grep_tool", "텍스트 검색 ? 정규식으로 파일 내용을 검색합니다"),
|
||||
("folder_map", "폴더 구조 ? 프로젝트의 디렉토리 트리를 조회합니다"),
|
||||
("document_read", "문서 읽기 ? PDF, DOCX 등 문서 파일의 텍스트를 추출합니다"),
|
||||
}),
|
||||
("프로세스/빌드", "\uE756", "#06B6D4", new[]
|
||||
{
|
||||
("process", "프로세스 실행 ? 외부 명령/프로그램을 실행합니다"),
|
||||
("build_run", "빌드/테스트 ? 프로젝트 타입을 감지하여 빌드/테스트를 실행합니다"),
|
||||
("dev_env_detect", "개발 환경 감지 ? IDE, 런타임, 빌드 도구를 자동으로 인식합니다"),
|
||||
}),
|
||||
("코드 분석", "\uE943", "#818CF8", new[]
|
||||
{
|
||||
("search_codebase", "코드 키워드 검색 ? TF-IDF 기반으로 관련 코드를 찾습니다"),
|
||||
("code_review", "AI 코드 리뷰 ? Git diff 분석, 파일 정적 검사, PR 요약"),
|
||||
("lsp", "LSP 인텔리전스 ? 정의 이동, 참조 검색, 심볼 목록 (C#/TS/Python)"),
|
||||
("test_loop", "테스트 루프 ? 테스트 생성/실행/분석 자동화"),
|
||||
("git_tool", "Git 작업 ? status, log, diff, commit 등 Git 명령 실행"),
|
||||
("snippet_runner", "코드 실행 ? C#/Python/JavaScript 스니펫 즉시 실행"),
|
||||
}),
|
||||
("문서 생성", "\uE8A5", "#34D399", new[]
|
||||
{
|
||||
("excel_create", "Excel 생성 ? .xlsx 스프레드시트를 생성합니다"),
|
||||
("docx_create", "Word 생성 ? .docx 문서를 생성합니다"),
|
||||
("csv_create", "CSV 생성 ? CSV 파일을 생성합니다"),
|
||||
("markdown_create", "마크다운 생성 ? .md 파일을 생성합니다"),
|
||||
("html_create", "HTML 생성 ? HTML 파일을 생성합니다"),
|
||||
("chart_create", "차트 생성 ? 데이터 시각화 차트를 생성합니다"),
|
||||
("batch_create", "배치 생성 ? 스크립트 파일을 생성합니다"),
|
||||
("document_review", "문서 검증 ? 문서 품질을 검사합니다"),
|
||||
("format_convert", "포맷 변환 ? 문서 형식을 변환합니다"),
|
||||
}),
|
||||
("데이터 처리", "\uE9F5", "#F59E0B", new[]
|
||||
{
|
||||
("json_tool", "JSON 처리 ? JSON 파싱, 변환, 검증, 포맷팅"),
|
||||
("regex_tool", "정규식 ? 정규식 테스트, 추출, 치환"),
|
||||
("diff_tool", "텍스트 비교 ? 두 파일/텍스트 비교, 통합 diff 출력"),
|
||||
("base64_tool", "인코딩 ? Base64/URL 인코딩, 디코딩"),
|
||||
("hash_tool", "해시 계산 ? MD5/SHA256 해시 계산 (파일/텍스트)"),
|
||||
("datetime_tool", "날짜/시간 ? 날짜 변환, 타임존, 기간 계산"),
|
||||
}),
|
||||
("시스템/환경", "\uE770", "#06B6D4", new[]
|
||||
{
|
||||
("clipboard_tool", "클립보드 ? Windows 클립보드 읽기/쓰기 (텍스트/이미지)"),
|
||||
("notify_tool", "알림 ? Windows 시스템 알림 전송"),
|
||||
("env_tool", "환경변수 ? 환경변수 읽기/설정 (프로세스 범위)"),
|
||||
("zip_tool", "압축 ? 파일 압축(zip) / 해제"),
|
||||
("http_tool", "HTTP ? 로컬/사내 HTTP API 호출 (GET/POST)"),
|
||||
("sql_tool", "SQLite ? SQLite DB 쿼리 실행 (로컬 파일)"),
|
||||
}),
|
||||
("에이전트", "\uE99A", "#F472B6", new[]
|
||||
{
|
||||
("spawn_agent", "서브에이전트 ? 하위 작업을 병렬로 실행하는 서브에이전트를 생성합니다"),
|
||||
("wait_agents", "에이전트 대기 ? 실행 중인 서브에이전트의 결과를 수집합니다"),
|
||||
("memory", "에이전트 메모리 ? 프로젝트 규칙, 선호도를 저장/검색합니다"),
|
||||
("skill_manager", "스킬 관리 ? 스킬 목록 조회, 상세 정보, 재로드"),
|
||||
("project_rules", "프로젝트 지침 ? AGENTS.md 개발 지침을 읽고 씁니다"),
|
||||
}),
|
||||
};
|
||||
var meta = group.First().Meta;
|
||||
var tools = group
|
||||
.OrderBy(x => x.Tool.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(x => (Name: x.Tool.Name, Description: x.Tool.Description))
|
||||
.ToArray();
|
||||
return (Category: meta.SettingsCategory, Icon: meta.SettingsIcon, IconColor: meta.SettingsIconColor, Tools: tools);
|
||||
})
|
||||
.OrderBy(group => group.Category, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
// 도구 목록을 섹션으로 그룹화
|
||||
var toolCards = new List<UIElement>();
|
||||
@@ -599,8 +548,10 @@ public partial class SettingsWindow : Window
|
||||
});
|
||||
|
||||
// 도구 아이템
|
||||
foreach (var (name, toolDesc) in group.Tools)
|
||||
foreach (var toolInfo in group.Tools)
|
||||
{
|
||||
var name = toolInfo.Name;
|
||||
var toolDesc = toolInfo.Description;
|
||||
var row = new Grid { Margin = new Thickness(0, 3, 0, 3) };
|
||||
row.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) });
|
||||
row.ColumnDefinitions.Add(new ColumnDefinition());
|
||||
@@ -666,7 +617,7 @@ public partial class SettingsWindow : Window
|
||||
skillItems.Add(new TextBlock
|
||||
{
|
||||
Text = "/ 명령으로 호출할 수 있는 스킬 목록입니다. 앱 내장 + 사용자 추가 스킬이 포함됩니다.\n" +
|
||||
"(스킬은 사용자가 직접 /명령어를 입력해야 실행됩니다. LLM이 자동 호출하지 않습니다.)",
|
||||
"(직접 호출 가능한 스킬과 런타임 정책에 연결되는 스킬을 함께 표시합니다.)",
|
||||
FontSize = 11,
|
||||
Foreground = new SolidColorBrush(subtleText),
|
||||
Margin = new Thickness(2, 0, 0, 10),
|
||||
@@ -1817,6 +1768,7 @@ public partial class SettingsWindow : Window
|
||||
EncryptedModelName = Services.CryptoService.EncryptIfEnabled(dlg.ModelName, IsEncryptionEnabled),
|
||||
Service = currentService,
|
||||
ExecutionProfile = dlg.ExecutionProfile,
|
||||
PromptFamily = dlg.PromptFamily,
|
||||
Endpoint = dlg.Endpoint,
|
||||
ApiKey = dlg.ApiKey,
|
||||
AllowInsecureTls = dlg.AllowInsecureTls,
|
||||
@@ -1841,7 +1793,7 @@ public partial class SettingsWindow : Window
|
||||
var dlg = new ModelRegistrationDialog(currentService, row.Alias, currentModel,
|
||||
row.Endpoint, row.ApiKey, row.AllowInsecureTls,
|
||||
row.AuthType ?? "bearer", row.Cp4dUrl ?? "", row.Cp4dUsername ?? "", cp4dPw,
|
||||
row.ExecutionProfile ?? "balanced");
|
||||
row.ExecutionProfile ?? "balanced", row.PromptFamily ?? "");
|
||||
dlg.Owner = this;
|
||||
if (dlg.ShowDialog() == true)
|
||||
{
|
||||
@@ -1849,6 +1801,7 @@ public partial class SettingsWindow : Window
|
||||
row.EncryptedModelName = Services.CryptoService.EncryptIfEnabled(dlg.ModelName, IsEncryptionEnabled);
|
||||
row.Service = currentService;
|
||||
row.ExecutionProfile = dlg.ExecutionProfile;
|
||||
row.PromptFamily = dlg.PromptFamily;
|
||||
row.Endpoint = dlg.Endpoint;
|
||||
row.ApiKey = dlg.ApiKey;
|
||||
row.AllowInsecureTls = dlg.AllowInsecureTls;
|
||||
@@ -1956,52 +1909,17 @@ public partial class SettingsWindow : Window
|
||||
var app = System.Windows.Application.Current as App;
|
||||
var settings = app?.SettingsService?.Settings.Llm;
|
||||
using var tools = Services.Agent.ToolRegistry.CreateDefault();
|
||||
_disabledTools = new HashSet<string>(settings?.DisabledTools ?? new(), StringComparer.OrdinalIgnoreCase);
|
||||
_disabledTools = new HashSet<string>(AgentToolCatalog.CanonicalizeMany(settings?.DisabledTools ?? new()), StringComparer.OrdinalIgnoreCase);
|
||||
var disabled = _disabledTools;
|
||||
|
||||
// 카테고리 매핑
|
||||
var categories = new Dictionary<string, List<Services.Agent.IAgentTool>>
|
||||
{
|
||||
["파일/검색"] = new(),
|
||||
["문서 생성"] = new(),
|
||||
["문서 품질"] = new(),
|
||||
["코드/개발"] = new(),
|
||||
["데이터/유틸"] = new(),
|
||||
["시스템"] = new(),
|
||||
};
|
||||
|
||||
var toolCategoryMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
// 파일/검색
|
||||
["file_read"] = "파일/검색", ["file_write"] = "파일/검색", ["file_edit"] = "파일/검색",
|
||||
["glob"] = "파일/검색", ["grep"] = "파일/검색", ["folder_map"] = "파일/검색",
|
||||
["document_read"] = "파일/검색", ["file_watch"] = "파일/검색",
|
||||
// 문서 생성
|
||||
["excel_skill"] = "문서 생성", ["docx_skill"] = "문서 생성", ["csv_skill"] = "문서 생성",
|
||||
["markdown_skill"] = "문서 생성", ["html_skill"] = "문서 생성", ["chart_skill"] = "문서 생성",
|
||||
["batch_skill"] = "문서 생성", ["pptx_skill"] = "문서 생성",
|
||||
["document_planner"] = "문서 생성", ["document_assembler"] = "문서 생성",
|
||||
// 문서 품질
|
||||
["document_review"] = "문서 품질", ["format_convert"] = "문서 품질",
|
||||
["template_render"] = "문서 품질", ["text_summarize"] = "문서 품질",
|
||||
// 코드/개발
|
||||
["dev_env_detect"] = "코드/개발", ["build_run"] = "코드/개발", ["git_tool"] = "코드/개발",
|
||||
["lsp"] = "코드/개발", ["sub_agent"] = "코드/개발", ["wait_agents"] = "코드/개발",
|
||||
["code_search"] = "코드/개발", ["test_loop"] = "코드/개발",
|
||||
["code_review"] = "코드/개발", ["project_rule"] = "코드/개발",
|
||||
// 시스템
|
||||
["process"] = "시스템", ["skill_manager"] = "시스템", ["memory"] = "시스템",
|
||||
["clipboard"] = "시스템", ["notify"] = "시스템", ["env"] = "시스템",
|
||||
["image_analyze"] = "시스템",
|
||||
};
|
||||
var categories = new Dictionary<string, List<Services.Agent.IAgentTool>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var tool in tools.All)
|
||||
{
|
||||
var cat = toolCategoryMap.TryGetValue(tool.Name, out var c) ? c : "데이터/유틸";
|
||||
if (categories.ContainsKey(cat))
|
||||
categories[cat].Add(tool);
|
||||
else
|
||||
categories["데이터/유틸"].Add(tool);
|
||||
var cat = AgentToolCatalog.GetMetadata(tool.Name).SettingsCategory;
|
||||
if (!categories.ContainsKey(cat))
|
||||
categories[cat] = new List<Services.Agent.IAgentTool>();
|
||||
categories[cat].Add(tool);
|
||||
}
|
||||
|
||||
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
||||
@@ -3368,11 +3286,11 @@ public partial class SettingsWindow : Window
|
||||
stack.Children.Add(new TextBlock { Text = "대상 도구 (* = 모든 도구)", FontSize = 12, Foreground = subFgBrush, Margin = new Thickness(0, 10, 0, 4) });
|
||||
var toolBox = new TextBox
|
||||
{
|
||||
Text = existing?.ToolName ?? "*", FontSize = 13,
|
||||
Text = AgentToolCatalog.CanonicalizeHookTarget(existing?.ToolName ?? "*"), FontSize = 13,
|
||||
Foreground = fgBrush, Background = itemBg,
|
||||
BorderBrush = borderBrush, Padding = new Thickness(12, 8, 12, 8),
|
||||
};
|
||||
var toolHolder = CreatePlaceholder("예: file_write, grep_tool", subFgBrush, existing?.ToolName ?? "*");
|
||||
var toolHolder = CreatePlaceholder("예: file_write, grep", subFgBrush, AgentToolCatalog.CanonicalizeHookTarget(existing?.ToolName ?? "*"));
|
||||
toolBox.TextChanged += (_, _) => toolHolder.Visibility = string.IsNullOrEmpty(toolBox.Text) ? Visibility.Visible : Visibility.Collapsed;
|
||||
var toolGrid = new Grid();
|
||||
toolGrid.Children.Add(toolBox);
|
||||
@@ -3469,7 +3387,7 @@ public partial class SettingsWindow : Window
|
||||
var entry = new Models.AgentHookEntry
|
||||
{
|
||||
Name = nameBox.Text.Trim(),
|
||||
ToolName = string.IsNullOrWhiteSpace(toolBox.Text) ? "*" : toolBox.Text.Trim(),
|
||||
ToolName = AgentToolCatalog.CanonicalizeHookTarget(toolBox.Text),
|
||||
Timing = preRadio.IsChecked == true ? "pre" : "post",
|
||||
ScriptPath = pathBox.Text.Trim(),
|
||||
Arguments = argsBox.Text.Trim(),
|
||||
@@ -3498,6 +3416,7 @@ public partial class SettingsWindow : Window
|
||||
if (HookListPanel == null) return;
|
||||
HookListPanel.Children.Clear();
|
||||
|
||||
_vm.Service.Settings.Llm.AgentHooks = AgentToolCatalog.CanonicalizeHooks(_vm.Service.Settings.Llm.AgentHooks);
|
||||
var hooks = _vm.Service.Settings.Llm.AgentHooks;
|
||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.Black;
|
||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
@@ -3895,7 +3814,7 @@ public partial class SettingsWindow : Window
|
||||
|
||||
// 도구 비활성 목록 저장
|
||||
if (_toolCardsLoaded)
|
||||
llm.DisabledTools = _disabledTools.ToList();
|
||||
llm.DisabledTools = AgentToolCatalog.CanonicalizeMany(_disabledTools).ToList();
|
||||
}
|
||||
|
||||
private void AddSnippet_Click(object sender, RoutedEventArgs e)
|
||||
@@ -4168,5 +4087,3 @@ public partial class SettingsWindow : Window
|
||||
base.OnClosed(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user