AX Agent 상태 메시지 내러티브 고도화 및 코워크/코드 진행 이력 개선

- AgentStatusNarrativeCatalog를 추가해 agent event를 탭(Cowork/Code), 도구 카테고리, 대상 힌트 기준으로 해석하고 상태 메시지/상세 설명/phase label/meta를 한 곳에서 생성하도록 정리함
- ChatWindow의 live pulse 상태, idle 진행 힌트, readable process feed 요약이 동일 narrative 카탈로그를 재사용하도록 변경해 단조로운 도구명 중심 문구를 작업 의도 중심 문구로 치환함
- README, DEVELOPMENT, NEXT_ROADMAP에 2026-04-15 12:14 (KST) 기준 이력과 남은 UX 마감 메모를 반영함

검증 결과
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_status_narrative\\ -p:IntermediateOutputPath=obj\\verify_status_narrative\\ : 경고 0 / 오류 0
- dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "AgentStatusNarrativeCatalogTests|AgentLoopIterationPreparationServiceTests|AgentToolResultBudgetTests|ChatStorageServiceTests|AgentMessageInvariantHelperTests" -p:OutputPath=bin\\verify_status_narrative_tests\\ -p:IntermediateOutputPath=obj\\verify_status_narrative_tests\\ : 통과 15
This commit is contained in:
2026-04-15 12:15:58 +09:00
parent 717d0f2143
commit 5e40204e80
8 changed files with 681 additions and 103 deletions

View File

@@ -6632,21 +6632,18 @@ public partial class ChatWindow : Window
// ── 1단계: 경량 UI 피드백 (PulseDotBar 상태 텍스트만 갱신) ───────────
if (string.Equals(runTab, _activeTab, StringComparison.OrdinalIgnoreCase))
{
switch (evt.Type)
if (evt.Type is AgentEventType.Complete or AgentEventType.Error)
{
case AgentEventType.ToolCall when !string.IsNullOrWhiteSpace(evt.ToolName):
if (PulseDotStatusText != null && PulseDotBar?.Visibility == Visibility.Visible)
PulseDotStatusText.Text = GetStatusInfoForTool(evt.ToolName).message + "...";
break;
case AgentEventType.ToolResult when !string.IsNullOrWhiteSpace(evt.ToolName):
if (PulseDotStatusText != null && PulseDotBar?.Visibility == Visibility.Visible)
PulseDotStatusText.Text = GetToolResultMessage(evt.ToolName) + "...";
break;
case AgentEventType.Complete:
case AgentEventType.Error:
HideStreamingStatusBar();
FlushPendingAgentUiEvent();
break;
HideStreamingStatusBar();
FlushPendingAgentUiEvent();
}
else if (PulseDotBar?.Visibility == Visibility.Visible)
{
var liveStatus = AgentStatusNarrativeCatalog.BuildFromEvent(evt, runTab);
UpdateStreamingStatusBar(
liveStatus.Message,
detail: liveStatus.Detail,
subItemCategory: liveStatus.Category);
}
}
@@ -6706,15 +6703,17 @@ public partial class ChatWindow : Window
if (string.Equals(runTab, "Cowork", StringComparison.OrdinalIgnoreCase)
|| string.Equals(runTab, "Code", StringComparison.OrdinalIgnoreCase))
{
UpdateLiveAgentProgressHint("작업을 준비하는 중입니다...", "agent_wait");
var initialStatus = AgentStatusNarrativeCatalog.BuildInitial(runTab);
UpdateLiveAgentProgressHint(initialStatus.Message, "agent_wait");
ShowStreamingStatusBar(initialStatus.Message, detail: initialStatus.Detail);
}
else
{
UpdateLiveAgentProgressHint(null);
ShowStreamingStatusBar("생각하는 중...");
}
_agentProgressHintTimer.Stop();
_agentProgressHintTimer.Start();
ShowStreamingStatusBar("생각하는 중...");
}
private void StopLiveAgentProgressHints()