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:
@@ -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);
|
||||
};
|
||||
|
||||
// 타이머/알람 풍선 알림 서비스 연결
|
||||
|
||||
Reference in New Issue
Block a user