- 유휴 전환 후 하단 상태바 전체 토큰 집계가 사라지지 않도록 대화 기준 합산 복원 경로 추가 - 컨텍스트 사용량 팝업에 마지막 실제 압축 before/after 및 누적 절감량 표시 - total_stats 이벤트가 진행 피드에 흡수되지 않고 전용 통계 카드로 다시 노출되게 수정 - 관련 README 및 개발 문서 이력 갱신 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\ (경고 0, 오류 0)
This commit is contained in:
@@ -7,6 +7,11 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저
|
||||
개발 참고: Claw Code 동등성 작업 추적 문서
|
||||
`docs/claw-code-parity-plan.md`
|
||||
|
||||
- 업데이트: 2026-04-07 09:19 (KST)
|
||||
- AX Agent 하단 상태바의 전체 토큰 집계가 유휴 전환 후 사라지지 않도록 conversation aggregate 복원 경로를 추가했습니다. 실행 중 누적 토큰이 0이어도 현재 대화 전체의 prompt/completion 합계를 다시 계산해 상태바에 유지합니다.
|
||||
- 컨텍스트 사용량 팝업이 마지막 실제 압축 결과와 맞지 않던 문제를 수정했습니다. 이제 최근 압축의 실제 before/after 토큰과 자동/수동 여부, 누적 압축 횟수와 절감 토큰을 기준으로 표시합니다.
|
||||
- `전체 통계(total_stats)` 이벤트가 일반 진행 줄에 흡수되던 문제를 수정했습니다. 다시 전용 통계 카드/배너 경로로 노출되어 하단 전체 집계와 transcript 요약이 서로 더 일치합니다.
|
||||
|
||||
- 업데이트: 2026-04-07 02:23 (KST)
|
||||
- AX Agent 직접 대화(Chat 탭) 경로에 실제 스트리밍 응답 연결을 복구했습니다. LLM 서비스는 원래 SSE/스트리밍을 지원하고 있었지만 UI 실행 경로가 최종 문자열만 받아 한 번에 붙이던 상태였고, 이제 설정상 스트리밍이 켜져 있으면 채팅 응답이 타자 치듯 점진적으로 표시됩니다.
|
||||
- Cowork/Code는 기존처럼 agent loop 진행 메시지 중심을 유지하고, 직접 대화 재생성은 같은 스트리밍 경로를 공유하도록 정리했습니다.
|
||||
|
||||
@@ -5324,3 +5324,6 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
|
||||
- Chat/Cowork/Code 탭 전환 시 실행 중 작업을 즉시 취소하던 `StopStreamingIfActive()` 호출을 제거했다. 이제 Cowork/Code 작업은 시작한 탭 기준으로 백그라운드에서 계속 진행된다.
|
||||
- 실행 중 컨트롤 표시를 `RefreshStreamingControlsForActiveTab()`로 분리해, 현재 탭이 실행 소유 탭일 때만 정지/일시정지 버튼이 보이고 다른 탭에서는 일반 입력 상태처럼 보이도록 정리했다.
|
||||
- 라이브 진행 힌트는 `_streamRunTab`과 현재 활성 탭이 일치할 때만 transcript에 렌더되도록 바꿔, Cowork 작업 중 Code 탭으로 이동했을 때 Code 탭에도 `처리 중...`이 따라 보이던 문제를 막았다.
|
||||
- Document update: 2026-04-07 09:19 (KST) - Restored the AX Agent footer/status total token aggregate so it no longer disappears after runs return to idle. The status strip now rehydrates totals from the current conversation message token sums when live loop counters are empty.
|
||||
- Document update: 2026-04-07 09:19 (KST) - Corrected context-compaction popup accuracy by switching its detail copy to the last real compaction metrics (`before -> after`, automatic/manual kind, cumulative compaction count, cumulative saved tokens) instead of only the generic trigger-threshold text.
|
||||
- Document update: 2026-04-07 09:19 (KST) - Prevented `total_stats` loop events from being swallowed into the generic process-feed path. AX Agent now routes those events back through the dedicated total-stats presentation so transcript summaries and footer token totals stay aligned.
|
||||
|
||||
@@ -32,7 +32,7 @@ internal static class AgentTranscriptDisplayCatalog
|
||||
|
||||
"build_run" => "빌드/실행",
|
||||
"test_loop" => "테스트 루프",
|
||||
"dev_env_detect" => "개발 환경 점검",
|
||||
"dev_env_detect" => "개발 환경 감지",
|
||||
"git_tool" => "Git",
|
||||
"diff_tool" => "Diff",
|
||||
"diff_preview" => "Diff 미리보기",
|
||||
|
||||
@@ -121,6 +121,10 @@ public partial class ChatWindow
|
||||
|
||||
private static bool IsProcessFeedEvent(AgentEvent evt)
|
||||
{
|
||||
if (evt.Type == AgentEventType.StepDone
|
||||
&& string.Equals(evt.ToolName, "total_stats", StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
|
||||
return evt.Type is AgentEventType.Planning
|
||||
or AgentEventType.StepStart
|
||||
or AgentEventType.StepDone
|
||||
|
||||
@@ -52,7 +52,7 @@ public partial class ChatWindow
|
||||
else if (usageRatio >= triggerRatio)
|
||||
{
|
||||
progressBrush = Brushes.DarkOrange;
|
||||
summary = llm.EnableProactiveContextCompact ? "곧 자동 압축" : "압축 임계 도달";
|
||||
summary = llm.EnableProactiveContextCompact ? "곧 자동 압축" : "압축 한계 도달";
|
||||
compactLabel = "압축 권장";
|
||||
}
|
||||
else if (usageRatio >= triggerRatio * 0.7)
|
||||
@@ -67,6 +67,17 @@ public partial class ChatWindow
|
||||
compactLabel = "압축";
|
||||
}
|
||||
|
||||
string detailText;
|
||||
if (_lastCompactionAt.HasValue && _lastCompactionBeforeTokens.HasValue && _lastCompactionAfterTokens.HasValue)
|
||||
{
|
||||
var compactType = _lastCompactionWasAutomatic ? "자동" : "수동";
|
||||
detailText = $"{compactType} 압축 {Services.TokenEstimator.Format(_lastCompactionBeforeTokens.Value)} → {Services.TokenEstimator.Format(_lastCompactionAfterTokens.Value)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
detailText = $"자동 압축 시작 {triggerPercent}%";
|
||||
}
|
||||
|
||||
TokenUsageArc.Stroke = progressBrush;
|
||||
TokenUsageThresholdMarker.Fill = progressBrush;
|
||||
var percentText = $"{Math.Round(usageRatio * 100):0}%";
|
||||
@@ -80,9 +91,13 @@ public partial class ChatWindow
|
||||
if (TokenUsagePopupUsage != null)
|
||||
TokenUsagePopupUsage.Text = $"{Services.TokenEstimator.Format(currentTokens)}/{Services.TokenEstimator.Format(maxContextTokens)}";
|
||||
if (TokenUsagePopupDetail != null)
|
||||
TokenUsagePopupDetail.Text = _pendingPostCompaction ? "compact 후 첫 응답 대기 중" : $"자동 압축 시작 {triggerPercent}%";
|
||||
TokenUsagePopupDetail.Text = _pendingPostCompaction
|
||||
? $"compact 후 첫 응답 대기 중 · {detailText}"
|
||||
: detailText;
|
||||
if (TokenUsagePopupCompact != null)
|
||||
TokenUsagePopupCompact.Text = "AX Agent가 컨텍스트를 자동으로 관리합니다";
|
||||
TokenUsagePopupCompact.Text = _sessionCompactionCount > 0
|
||||
? $"누적 압축 {_sessionCompactionCount}회 · 절감 {FormatTokenCount(_sessionCompactionSavedTokens)} tokens"
|
||||
: "AX Agent가 컨텍스트를 자동으로 관리합니다";
|
||||
|
||||
TokenUsageCard.ToolTip = null;
|
||||
|
||||
|
||||
@@ -136,10 +136,50 @@ public partial class ChatWindow
|
||||
if (IsDecisionApproved(summary))
|
||||
return "계획 승인됨 · 실행 시작";
|
||||
if (IsDecisionRejected(summary))
|
||||
return "계획 반려됨 · 계획 재작성";
|
||||
return "계획 반려됨 · 계획 수정";
|
||||
return string.IsNullOrWhiteSpace(summary) ? "사용자 의사결정 대기 중" : TruncateForStatus(summary);
|
||||
}
|
||||
|
||||
private (int PromptTokens, int CompletionTokens) GetConversationTokenAggregate()
|
||||
{
|
||||
lock (_convLock)
|
||||
{
|
||||
if (_currentConversation?.Messages == null || _currentConversation.Messages.Count == 0)
|
||||
return (0, 0);
|
||||
|
||||
var prompt = 0;
|
||||
var completion = 0;
|
||||
foreach (var message in _currentConversation.Messages)
|
||||
{
|
||||
prompt += Math.Max(0, message.PromptTokens);
|
||||
completion += Math.Max(0, message.CompletionTokens);
|
||||
}
|
||||
|
||||
return (prompt, completion);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshStatusTokenAggregate()
|
||||
{
|
||||
var promptTokens = Math.Max(0, _agentCumulativeInputTokens);
|
||||
var completionTokens = Math.Max(0, _agentCumulativeOutputTokens);
|
||||
|
||||
if (promptTokens == 0 && completionTokens == 0)
|
||||
{
|
||||
var aggregate = GetConversationTokenAggregate();
|
||||
promptTokens = aggregate.PromptTokens;
|
||||
completionTokens = aggregate.CompletionTokens;
|
||||
}
|
||||
|
||||
if (promptTokens > 0 || completionTokens > 0)
|
||||
UpdateStatusTokens(promptTokens, completionTokens);
|
||||
else if (StatusTokens != null)
|
||||
{
|
||||
StatusTokens.Text = "";
|
||||
StatusTokens.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetStatusIdle()
|
||||
{
|
||||
StopStatusAnimation();
|
||||
@@ -150,11 +190,8 @@ public partial class ChatWindow
|
||||
StatusElapsed.Text = "";
|
||||
StatusElapsed.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
if (StatusTokens != null)
|
||||
{
|
||||
StatusTokens.Text = "";
|
||||
StatusTokens.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
RefreshStatusTokenAggregate();
|
||||
RefreshContextUsageVisual();
|
||||
ScheduleGitBranchRefresh(250);
|
||||
}
|
||||
@@ -173,6 +210,7 @@ public partial class ChatWindow
|
||||
StatusTokens.Visibility = Visibility.Visible;
|
||||
RefreshContextUsageVisual();
|
||||
}
|
||||
|
||||
private void UpdateStatusBar(AgentEvent evt)
|
||||
{
|
||||
var toolLabel = evt.ToolName switch
|
||||
@@ -200,16 +238,16 @@ public partial class ChatWindow
|
||||
SetStatus("생각 중...", spinning: true);
|
||||
break;
|
||||
case AgentEventType.Planning:
|
||||
SetStatus($"계획 수립 중 — {evt.StepTotal}단계", spinning: true);
|
||||
SetStatus($"계획 정리 중... {evt.StepTotal}단계", spinning: true);
|
||||
break;
|
||||
case AgentEventType.PermissionRequest:
|
||||
SetStatus($"권한 확인 중: {toolLabel}", spinning: false);
|
||||
SetStatus($"권한 확인 중 · {toolLabel}", spinning: false);
|
||||
break;
|
||||
case AgentEventType.PermissionGranted:
|
||||
SetStatus($"권한 승인됨: {toolLabel}", spinning: false);
|
||||
SetStatus($"권한 승인됨 · {toolLabel}", spinning: false);
|
||||
break;
|
||||
case AgentEventType.PermissionDenied:
|
||||
SetStatus($"권한 거부됨: {toolLabel}", spinning: false);
|
||||
SetStatus($"권한 거부됨 · {toolLabel}", spinning: false);
|
||||
StopStatusAnimation();
|
||||
break;
|
||||
case AgentEventType.Decision:
|
||||
@@ -232,7 +270,7 @@ public partial class ChatWindow
|
||||
case AgentEventType.SkillCall:
|
||||
if (!isDebugLogLevel)
|
||||
break;
|
||||
SetStatus($"스킬 실행 중: {TruncateForStatus(evt.Summary)}", spinning: true);
|
||||
SetStatus($"스킬 실행 중 · {TruncateForStatus(evt.Summary)}", spinning: true);
|
||||
break;
|
||||
case AgentEventType.Complete:
|
||||
SetStatus("작업 완료", spinning: false);
|
||||
@@ -245,12 +283,12 @@ public partial class ChatWindow
|
||||
case AgentEventType.Paused:
|
||||
if (!isDebugLogLevel)
|
||||
break;
|
||||
SetStatus("⏸ 일시정지", spinning: false);
|
||||
SetStatus("일시정지", spinning: false);
|
||||
break;
|
||||
case AgentEventType.Resumed:
|
||||
if (!isDebugLogLevel)
|
||||
break;
|
||||
SetStatus("▶ 재개됨", spinning: true);
|
||||
SetStatus("재개", spinning: true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user