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

@@ -47,19 +47,39 @@ public partial class ChatWindow
_elementCache.Clear();
}
// ★ 패널이 비어있는데 V2 캐시가 남아있는 경우 강제 리셋
// 탭 전환 시 ClearTranscriptElements()로 패널이 비워지지만
// 빈 대화 탭을 거치면 V2가 호출되지 않아 캐시가 잔류하는 버그 방지
if (GetTranscriptElementCount() == 0 && _v2LastRenderedKeys.Count > 0)
{
LogService.Info($"[V2Render] CACHE STALE RESET: panel=0 but cachedKeys={_v2LastRenderedKeys.Count}, forcing full rebuild");
_v2LastRenderedKeys.Clear();
_v2LastRenderedMessageCount = 0;
_v2LastRenderedEventCount = 0;
}
// 통합 타임라인 빌드 (메시지 + 이벤트를 시간순 병합)
var timeline = BuildV2Timeline(visibleMessages, visibleEvents);
LogService.Info($"[V2Render] timeline={timeline.Count}, msgs={visibleMessages.Count}, evts={visibleEvents.Count}, convId={conv.Id[..Math.Min(8, conv.Id.Length)]}, caller={caller}");
// 새 키 목록 생성
var newKeys = new List<string>(timeline.Count);
foreach (var item in timeline)
newKeys.Add(item.Key);
// 인크리멘탈 렌더 시도: 기존 키가 새 키의 접두사인 경우 추가분만 렌더
// ★ 패널에 실제 엘리먼트가 있어야 인크리멘탈 가능 —
// 탭 전환 시 ClearTranscriptElements()로 패널이 비워지지만
// V2 캐시(_v2LastRenderedKeys)는 유지되어 "이미 렌더됨"으로 오판하는 버그 방지
var actualElementCount = GetTranscriptElementCount();
var canIncremental = _v2LastRenderedKeys.Count > 0
&& actualElementCount > 0
&& newKeys.Count >= _v2LastRenderedKeys.Count
&& KeysArePrefixMatch(_v2LastRenderedKeys, newKeys);
LogService.Info($"[V2Render] canIncremental={canIncremental}, prevKeys={_v2LastRenderedKeys.Count}, newKeys={newKeys.Count}, preElementCount={GetTranscriptElementCount()}");
if (canIncremental)
{
// 라이브 컨테이너가 있으면 임시 제거 (맨 끝에 다시 추가)
@@ -110,6 +130,8 @@ public partial class ChatWindow
_v2LastRenderedKeys.AddRange(newKeys);
_v2LastRenderedMessageCount = visibleMessages.Count;
_v2LastRenderedEventCount = visibleEvents.Count;
LogService.Info($"[V2Render] DONE postElementCount={GetTranscriptElementCount()}, savedKeys={_v2LastRenderedKeys.Count}");
}
catch (Exception ex)
{
@@ -119,12 +141,13 @@ public partial class ChatWindow
_lastRenderTicks = Environment.TickCount64;
_lastRenderedMessageCount = visibleMessages.Count;
_lastRenderedEventCount = visibleEvents.Count;
_lastRenderedShowHistory = conv?.ShowExecutionHistory ?? true;
renderStopwatch.Stop();
if (renderStopwatch.ElapsedMilliseconds >= (_isStreaming ? 100 : 24))
{
AgentPerformanceLogService.LogMetric(
"transcript", "render_messages_v2", conv.Id, _activeTab ?? "",
"transcript", "render_messages_v2", conv?.Id ?? "", _activeTab ?? "",
renderStopwatch.ElapsedMilliseconds,
new { preserveViewport, streaming = _isStreaming, visibleMessages = visibleMessages.Count, visibleEvents = visibleEvents.Count });
}
@@ -172,12 +195,21 @@ public partial class ChatWindow
}
// 2. 실행 이벤트 추가 — ToolCall+ToolResult 쌍을 병합
// 스트리밍 중이면 라이브 카드가 이미 표시하는 현재 실행 이벤트는 타임라인에서 제외
var eventIndex = 0;
var events = visibleEvents.ToList();
var liveCardCutoff = (_isStreaming && _v2LiveContainer != null)
? _v2LiveStartTime
: DateTime.MaxValue;
for (int i = 0; i < events.Count; i++)
{
var executionEvent = events[i];
// 스트리밍 중: 라이브 카드 시작 이후 이벤트는 라이브 카드에서 표시하므로 스킵
if (executionEvent.Timestamp.ToUniversalTime() >= liveCardCutoff)
continue;
var agentEvent = ToAgentEvent(executionEvent);
// SessionStart / UserPromptSubmit 숨김