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:
2026-04-14 17:52:46 +09:00
parent fa33b98f7e
commit 8cb08576d5
200 changed files with 13522 additions and 5764 deletions

View File

@@ -378,87 +378,72 @@ public partial class App : System.Windows.Application
var launcherSettings = _settings?.Settings.Launcher;
var enableTextAction = launcherSettings?.EnableTextAction == true;
// ── 런처 토글(닫기)은 텍스트 감지 없이 즉시 처리 ──
bool isVisible = false;
Dispatcher.Invoke(() => { isVisible = _launcher?.IsVisible == true; });
if (isVisible)
{
Dispatcher.Invoke(() => _launcher?.Hide());
return;
}
// ── 텍스트 감지 활성: 런처를 먼저 열고, 텍스트 감지를 비동기로 처리 ──
string? selectedText = enableTextAction ? TryGetSelectedText() : null;
// ── 텍스트 감지가 비활성이면 즉시 런처 표시 ──
if (!enableTextAction)
// BeginInvoke 사용 — Dispatcher.Invoke 데드락 방지
Dispatcher.BeginInvoke(() =>
{
Dispatcher.Invoke(() =>
// 런처 토글(닫기)
if (_launcher?.IsVisible == true)
{
_launcher.Hide();
return;
}
// 텍스트 감지 비활성 또는 선택 텍스트 없음
if (!enableTextAction || string.IsNullOrWhiteSpace(selectedText))
{
if (_launcher == null) return;
UsageStatisticsService.RecordLauncherOpen();
ShowLauncherWindow();
});
return;
}
return;
}
// 텍스트 감지 활성 + 선택 텍스트 있음 → 텍스트 액션 처리
var enabledCmds = launcherSettings?.TextActionCommands ?? new();
// ── 텍스트 감지 활성: 런처를 먼저 열고, 텍스트 감지를 비동기로 처리 ──
string? selectedText = TryGetSelectedText();
Dispatcher.Invoke(() =>
{
if (_launcher == null) return;
if (!string.IsNullOrWhiteSpace(selectedText))
// 활성 명령이 1개뿐이면 팝업 없이 바로 실행
if (enabledCmds.Count == 1)
{
var enabledCmds = launcherSettings?.TextActionCommands ?? new();
// 활성 명령이 1개뿐이면 팝업 없이 바로 실행
if (enabledCmds.Count == 1)
var directAction = TextActionPopup.AvailableCommands
.FirstOrDefault(c => c.Key == enabledCmds[0]);
if (!string.IsNullOrEmpty(directAction.Key))
{
var directAction = TextActionPopup.AvailableCommands
.FirstOrDefault(c => c.Key == enabledCmds[0]);
if (!string.IsNullOrEmpty(directAction.Key))
var actionResult = enabledCmds[0] switch
{
var actionResult = enabledCmds[0] switch
{
"translate" => TextActionPopup.ActionResult.Translate,
"summarize" => TextActionPopup.ActionResult.Summarize,
"grammar" => TextActionPopup.ActionResult.GrammarFix,
"explain" => TextActionPopup.ActionResult.Explain,
"rewrite" => TextActionPopup.ActionResult.Rewrite,
_ => TextActionPopup.ActionResult.None,
};
if (actionResult != TextActionPopup.ActionResult.None)
{
ExecuteTextAction(actionResult, selectedText);
return;
}
"translate" => TextActionPopup.ActionResult.Translate,
"summarize" => TextActionPopup.ActionResult.Summarize,
"grammar" => TextActionPopup.ActionResult.GrammarFix,
"explain" => TextActionPopup.ActionResult.Explain,
"rewrite" => TextActionPopup.ActionResult.Rewrite,
_ => TextActionPopup.ActionResult.None,
};
if (actionResult != TextActionPopup.ActionResult.None)
{
ExecuteTextAction(actionResult, selectedText);
return;
}
}
}
// 여러 개 → 팝업 표시
var popup = new TextActionPopup(selectedText, enabledCmds);
popup.Closed += (_, _) =>
{
switch (popup.SelectedAction)
{
case TextActionPopup.ActionResult.OpenLauncher:
UsageStatisticsService.RecordLauncherOpen();
ShowLauncherWindow();
break;
case TextActionPopup.ActionResult.None:
break; // Esc 또는 포커스 잃음
default:
// AI 명령 실행 → AX Agent 대화로 전달
ExecuteTextAction(popup.SelectedAction, popup.SelectedText);
break;
}
};
popup.Show();
}
else
// 여러 개 → 팝업 표시
var popup = new TextActionPopup(selectedText, enabledCmds);
popup.Closed += (_, _) =>
{
UsageStatisticsService.RecordLauncherOpen();
ShowLauncherWindow();
}
switch (popup.SelectedAction)
{
case TextActionPopup.ActionResult.OpenLauncher:
UsageStatisticsService.RecordLauncherOpen();
ShowLauncherWindow();
break;
case TextActionPopup.ActionResult.None:
break;
default:
ExecuteTextAction(popup.SelectedAction, popup.SelectedText);
break;
}
};
popup.Show();
});
}
@@ -600,9 +585,9 @@ public partial class App : System.Windows.Application
_trayMenu
.AddHeader(versionText)
.AddItem("\uE7C5", "AX Commander 호출하기", () =>
Dispatcher.Invoke(ShowLauncherWindow))
Dispatcher.Invoke(ShowLauncherWindow), hint: "double-click")
.AddItem("\uE8BD", "AX Agent 대화하기", () =>
Dispatcher.Invoke(OpenAiChat), out var aiTrayItem)
Dispatcher.Invoke(OpenAiChat), out var aiTrayItem, hint: "click")
.AddItem("\uE8A7", "독 바 표시", () =>
Dispatcher.Invoke(() => ToggleDockBar()))
.AddSeparator()
@@ -649,21 +634,50 @@ public partial class App : System.Windows.Application
: System.Windows.Visibility.Collapsed;
};
// 클릭 → WPF 메뉴 표시, 좌클릭 → 런처 토글
// 클릭 → 대화창, 더블클릭 → 런처, 우클릭 → 메뉴
// MouseClick은 버튼 정보를 제공하므로 Click 대신 사용 — Click은 모든 버튼에 발생하여 우클릭 메뉴와 충돌
System.Windows.Forms.Timer? _trayClickTimer = null;
_trayIcon.MouseClick += (_, e) =>
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
Dispatcher.Invoke(() =>
// 우클릭: 컨텍스트 메뉴 표시 (싱글클릭 타이머 간섭 방지)
_trayClickTimer?.Stop();
_trayClickTimer?.Dispose();
_trayClickTimer = null;
Dispatcher.Invoke(() => _trayMenu?.ShowWithUpdate());
return;
}
if (e.Button != System.Windows.Forms.MouseButtons.Left) return;
// 싱글/더블 클릭 구분: WinForms NotifyIcon은 DoubleClick 시에도 Click을 먼저 발생시키므로
// 타이머를 사용하여 더블클릭 대기 후 싱글 클릭 액션을 실행
_trayClickTimer?.Stop();
_trayClickTimer?.Dispose();
_trayClickTimer = new System.Windows.Forms.Timer { Interval = SystemInformation.DoubleClickTime };
_trayClickTimer.Tick += (s, _) =>
{
_trayClickTimer?.Stop();
_trayClickTimer?.Dispose();
_trayClickTimer = null;
// 싱글 클릭: 대화창 열기
Dispatcher.BeginInvoke(() =>
{
if (settings.Settings.AiEnabled)
OpenAiChat();
else
ShowLauncherWindow();
});
}
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
Dispatcher.Invoke(() => _trayMenu?.ShowWithUpdate());
};
_trayClickTimer.Start();
};
_trayIcon.MouseDoubleClick += (_, e) =>
{
if (e.Button != System.Windows.Forms.MouseButtons.Left) return;
// 싱글 클릭 타이머 취소 → 런처만 열림
_trayClickTimer?.Stop();
_trayClickTimer?.Dispose();
_trayClickTimer = null;
Dispatcher.BeginInvoke(ShowLauncherWindow);
};
// 타이머/알람 풍선 알림 서비스 연결