하단 토큰 집계와 압축 표시 정확도 수정
Some checks failed
Release Gate / gate (push) Has been cancelled

- 유휴 전환 후 하단 상태바 전체 토큰 집계가 사라지지 않도록 대화 기준 합산 복원 경로 추가
- 컨텍스트 사용량 팝업에 마지막 실제 압축 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:
2026-04-07 09:21:23 +09:00
parent b45ed524e1
commit f34878cbd5
6 changed files with 82 additions and 17 deletions

View File

@@ -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 미리보기",

View File

@@ -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

View File

@@ -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;

View File

@@ -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;
}
}