- AX Agent 하단 컨텍스트 카드에 현재 서비스·모델 기준 오늘 사용량을 함께 표시하고 hover에서 현재 모델 usage·compact 이후 usage·오늘 상위 모델 usage를 확인할 수 있게 함 - UsageStatisticsService에 오늘 통계 스냅샷 API를 추가하고 long-safe 토큰 포맷 경로를 넣어 per-model 집계가 커져도 K/M 단위로 안정적으로 표시되게 함 - README.md와 docs/DEVELOPMENT.md에 2026-04-05 00:34 (KST) 기준 이력을 반영함 - 검증: 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:
@@ -8,6 +8,11 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저
|
|||||||
`docs/claw-code-parity-plan.md`
|
`docs/claw-code-parity-plan.md`
|
||||||
|
|
||||||
|
|
||||||
|
- 업데이트: 2026-04-05 00:34 (KST)
|
||||||
|
- AX Agent 하단 컨텍스트 카드에 현재 서비스·모델 기준 오늘 사용량을 함께 표시하고, hover에는 현재 모델의 일반 사용량·compact 이후 사용량·오늘 상위 모델 사용량까지 보이도록 보강했습니다.
|
||||||
|
- 큰 수 토큰 집계는 `K/M` 단위까지 같은 카드 안에서 일관되게 읽히도록 long 전용 포맷 경로를 추가했습니다.
|
||||||
|
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0
|
||||||
|
|
||||||
- 업데이트: 2026-04-05 00:17 (KST)
|
- 업데이트: 2026-04-05 00:17 (KST)
|
||||||
- AX Agent 루프도 `claw-code`의 post-autocompact turn tracking 흐름을 참고해 compact 직후 턴을 별도 상태로 추적하도록 보강했습니다. 이제 compact 직후 첫 턴은 저노이즈 compact pill 중심으로 보이고, 불필요한 `LLM 요청 중`류 Thinking 로그는 자동으로 줄입니다.
|
- AX Agent 루프도 `claw-code`의 post-autocompact turn tracking 흐름을 참고해 compact 직후 턴을 별도 상태로 추적하도록 보강했습니다. 이제 compact 직후 첫 턴은 저노이즈 compact pill 중심으로 보이고, 불필요한 `LLM 요청 중`류 Thinking 로그는 자동으로 줄입니다.
|
||||||
- 개발자용 전체 통계에는 compact 직후 자동 축약된 Thinking 로그 건수도 함께 표시해, compact 이후 루프가 실제로 얼마나 조용해졌는지 바로 확인할 수 있게 했습니다.
|
- 개발자용 전체 통계에는 compact 직후 자동 축약된 Thinking 로그 건수도 함께 표시해, compact 이후 루프가 실제로 얼마나 조용해졌는지 바로 확인할 수 있게 했습니다.
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# AX Copilot - 媛쒕컻 臾몄꽌
|
# AX Copilot - 媛쒕컻 臾몄꽌
|
||||||
|
|
||||||
|
- Document update: 2026-04-05 00:34 (KST) - Exposed current service/model daily usage directly in the AX Agent context card, and expanded the hover panel to show current-model usage, current-model post-compaction usage, and top model usage for the day using the new per-model daily token buckets.
|
||||||
|
- Document update: 2026-04-05 00:34 (KST) - Added a long-safe token formatting path for large per-model aggregates so context usage summaries keep the same K/M style even when daily model totals exceed the older int-based formatting path.
|
||||||
|
|
||||||
|
|
||||||
- Document update: 2026-04-05 00:17 (KST) - Upgraded the AX Agent loop to track the first post-compaction turn explicitly, mirroring claw-code's post-autocompact turn handling so compact-follow-up state now survives into the next loop turn instead of ending at the condenser call.
|
- Document update: 2026-04-05 00:17 (KST) - Upgraded the AX Agent loop to track the first post-compaction turn explicitly, mirroring claw-code's post-autocompact turn handling so compact-follow-up state now survives into the next loop turn instead of ending at the condenser call.
|
||||||
- Document update: 2026-04-05 00:24 (KST) - Added compact-aware tool result trimming for the first post-compaction turn so heavy `process`, `build_run`, `test_loop`, `git_tool`, `http_tool`, and similar outputs are fed back to the loop as head/tail summaries instead of re-expanding the freshly compacted context.
|
- Document update: 2026-04-05 00:24 (KST) - Added compact-aware tool result trimming for the first post-compaction turn so heavy `process`, `build_run`, `test_loop`, `git_tool`, `http_tool`, and similar outputs are fed back to the loop as head/tail summaries instead of re-expanding the freshly compacted context.
|
||||||
- Document update: 2026-04-05 00:17 (KST) - Added post-compaction thinking suppression for boilerplate loop events (`LLM request`, prompt submit, free-tier wait) during the first compact-follow-up turn, while still surfacing a compact-specific lightweight pill and compact-noise counts in total stats.
|
- Document update: 2026-04-05 00:17 (KST) - Added post-compaction thinking suppression for boilerplate loop events (`LLM request`, prompt submit, free-tier wait) during the first compact-follow-up turn, while still surfacing a compact-specific lightweight pill and compact-noise counts in total stats.
|
||||||
|
|||||||
@@ -141,6 +141,16 @@ internal static class UsageStatisticsService
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>오늘자 통계 스냅샷을 반환합니다.</summary>
|
||||||
|
public static DailyUsageStats GetTodaySnapshot()
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
return CloneToday();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─── 내부 ────────────────────────────────────────────────────────────────
|
// ─── 내부 ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private static void EnsureInitialized()
|
private static void EnsureInitialized()
|
||||||
|
|||||||
@@ -16680,9 +16680,22 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
|
|||||||
TokenUsageArc.Stroke = progressBrush;
|
TokenUsageArc.Stroke = progressBrush;
|
||||||
TokenUsageThresholdMarker.Fill = progressBrush;
|
TokenUsageThresholdMarker.Fill = progressBrush;
|
||||||
var percentText = $"{Math.Round(usageRatio * 100):0}%";
|
var percentText = $"{Math.Round(usageRatio * 100):0}%";
|
||||||
|
var (currentService, currentModel) = _llm.GetCurrentModelInfo();
|
||||||
|
var todayUsage = Services.UsageStatisticsService.GetTodaySnapshot();
|
||||||
|
var currentModelUsageKey = BuildUsageModelKey(currentService, currentModel);
|
||||||
|
var currentModelPromptTokens = GetUsageValue(todayUsage.ModelPromptTokens, currentModelUsageKey);
|
||||||
|
var currentModelCompletionTokens = GetUsageValue(todayUsage.ModelCompletionTokens, currentModelUsageKey);
|
||||||
|
var currentModelTotalTokens = currentModelPromptTokens + currentModelCompletionTokens;
|
||||||
|
var currentModelPostPromptTokens = GetUsageValue(todayUsage.PostCompactionPromptTokens, currentModelUsageKey);
|
||||||
|
var currentModelPostCompletionTokens = GetUsageValue(todayUsage.PostCompactionCompletionTokens, currentModelUsageKey);
|
||||||
|
var currentModelPostTotalTokens = currentModelPostPromptTokens + currentModelPostCompletionTokens;
|
||||||
TokenUsagePercentText.Text = percentText;
|
TokenUsagePercentText.Text = percentText;
|
||||||
TokenUsageSummaryText.Text = $"컨텍스트 {percentText}";
|
TokenUsageSummaryText.Text = $"컨텍스트 {percentText}";
|
||||||
TokenUsageHintText.Text = $"{Services.TokenEstimator.Format(currentTokens)} / {Services.TokenEstimator.Format(maxContextTokens)}";
|
TokenUsageHintText.Text =
|
||||||
|
$"{Services.TokenEstimator.Format(currentTokens)} / {Services.TokenEstimator.Format(maxContextTokens)}" +
|
||||||
|
(currentModelTotalTokens > 0
|
||||||
|
? $" · 오늘 {FormatTokenCount(currentModelTotalTokens)}"
|
||||||
|
: "");
|
||||||
CompactNowLabel.Text = compactLabel;
|
CompactNowLabel.Text = compactLabel;
|
||||||
var compactHistory = _lastCompactionAt.HasValue && _lastCompactionBeforeTokens.HasValue && _lastCompactionAfterTokens.HasValue
|
var compactHistory = _lastCompactionAt.HasValue && _lastCompactionBeforeTokens.HasValue && _lastCompactionAfterTokens.HasValue
|
||||||
? $"\n최근 압축: {(_lastCompactionWasAutomatic ? "자동" : "수동")} · {_lastCompactionAt.Value:HH:mm:ss}\n" +
|
? $"\n최근 압축: {(_lastCompactionWasAutomatic ? "자동" : "수동")} · {_lastCompactionAt.Value:HH:mm:ss}\n" +
|
||||||
@@ -16702,6 +16715,14 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
|
|||||||
$"compact 이후 사용량: {Services.TokenEstimator.Format(_sessionPostCompactionPromptTokens)} + {Services.TokenEstimator.Format(_sessionPostCompactionCompletionTokens)} = {Services.TokenEstimator.Format(_sessionPostCompactionPromptTokens + _sessionPostCompactionCompletionTokens)} tokens"
|
$"compact 이후 사용량: {Services.TokenEstimator.Format(_sessionPostCompactionPromptTokens)} + {Services.TokenEstimator.Format(_sessionPostCompactionCompletionTokens)} = {Services.TokenEstimator.Format(_sessionPostCompactionPromptTokens + _sessionPostCompactionCompletionTokens)} tokens"
|
||||||
: "";
|
: "";
|
||||||
var pendingPostCompaction = _pendingPostCompaction ? "\ncompact 후 첫 응답 대기 중" : "";
|
var pendingPostCompaction = _pendingPostCompaction ? "\ncompact 후 첫 응답 대기 중" : "";
|
||||||
|
var currentModelUsageText = !string.IsNullOrWhiteSpace(currentModelUsageKey)
|
||||||
|
? $"\n현재 모델: {currentService} · {currentModel}\n" +
|
||||||
|
$"오늘 모델 사용량: {FormatTokenCount(currentModelPromptTokens)} + {FormatTokenCount(currentModelCompletionTokens)} = {FormatTokenCount(currentModelTotalTokens)} tokens" +
|
||||||
|
(currentModelPostTotalTokens > 0
|
||||||
|
? $"\n현재 모델 compact 이후: {FormatTokenCount(currentModelPostPromptTokens)} + {FormatTokenCount(currentModelPostCompletionTokens)} = {FormatTokenCount(currentModelPostTotalTokens)} tokens"
|
||||||
|
: "")
|
||||||
|
: "";
|
||||||
|
var topModelsText = BuildTopModelUsageSummary(todayUsage);
|
||||||
|
|
||||||
TokenUsageCard.ToolTip =
|
TokenUsageCard.ToolTip =
|
||||||
$"상태: {summary}\n" +
|
$"상태: {summary}\n" +
|
||||||
@@ -16709,6 +16730,8 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
|
|||||||
$"간단 표기: {Services.TokenEstimator.Format(currentTokens)} / {Services.TokenEstimator.Format(maxContextTokens)}\n" +
|
$"간단 표기: {Services.TokenEstimator.Format(currentTokens)} / {Services.TokenEstimator.Format(maxContextTokens)}\n" +
|
||||||
$"자동 압축 시작: {triggerPercent}%\n" +
|
$"자동 압축 시작: {triggerPercent}%\n" +
|
||||||
$"현재 입력 초안 포함" +
|
$"현재 입력 초안 포함" +
|
||||||
|
currentModelUsageText +
|
||||||
|
topModelsText +
|
||||||
compactHistory +
|
compactHistory +
|
||||||
compactSession +
|
compactSession +
|
||||||
postCompactionUsage +
|
postCompactionUsage +
|
||||||
@@ -16718,6 +16741,65 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
|
|||||||
PositionThresholdMarker(TokenUsageThresholdMarker, triggerRatio, 18, 18, 14, 3);
|
PositionThresholdMarker(TokenUsageThresholdMarker, triggerRatio, 18, 18, 14, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string BuildUsageModelKey(string? service, string? model)
|
||||||
|
{
|
||||||
|
var normalizedService = (service ?? "").Trim().ToLowerInvariant();
|
||||||
|
var normalizedModel = (model ?? "").Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(normalizedService) || string.IsNullOrWhiteSpace(normalizedModel))
|
||||||
|
return "";
|
||||||
|
return $"{normalizedService}:{normalizedModel}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long GetUsageValue(Dictionary<string, long>? source, string key)
|
||||||
|
{
|
||||||
|
if (source == null || string.IsNullOrWhiteSpace(key))
|
||||||
|
return 0;
|
||||||
|
return source.TryGetValue(key, out var value) ? value : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildTopModelUsageSummary(DailyUsageStats todayUsage)
|
||||||
|
{
|
||||||
|
if (todayUsage.ModelPromptTokens.Count == 0 && todayUsage.ModelCompletionTokens.Count == 0)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
var totals = new Dictionary<string, long>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var pair in todayUsage.ModelPromptTokens)
|
||||||
|
totals[pair.Key] = pair.Value;
|
||||||
|
foreach (var pair in todayUsage.ModelCompletionTokens)
|
||||||
|
totals[pair.Key] = totals.TryGetValue(pair.Key, out var current) ? current + pair.Value : pair.Value;
|
||||||
|
|
||||||
|
var topEntries = totals
|
||||||
|
.OrderByDescending(pair => pair.Value)
|
||||||
|
.Take(3)
|
||||||
|
.Select(pair =>
|
||||||
|
{
|
||||||
|
var label = pair.Key.Replace(":", " · ");
|
||||||
|
var postPrompt = GetUsageValue(todayUsage.PostCompactionPromptTokens, pair.Key);
|
||||||
|
var postCompletion = GetUsageValue(todayUsage.PostCompactionCompletionTokens, pair.Key);
|
||||||
|
var postTotal = postPrompt + postCompletion;
|
||||||
|
return postTotal > 0
|
||||||
|
? $"- {label}: {FormatTokenCount(pair.Value)} tokens (compact 이후 {FormatTokenCount(postTotal)})"
|
||||||
|
: $"- {label}: {FormatTokenCount(pair.Value)} tokens";
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return topEntries.Count == 0
|
||||||
|
? ""
|
||||||
|
: "\n오늘 상위 모델\n" + string.Join("\n", topEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatTokenCount(long value)
|
||||||
|
{
|
||||||
|
if (value <= int.MaxValue)
|
||||||
|
return Services.TokenEstimator.Format((int)value);
|
||||||
|
|
||||||
|
if (value >= 1_000_000)
|
||||||
|
return $"{value / 1_000_000d:0.#}M";
|
||||||
|
if (value >= 1_000)
|
||||||
|
return $"{value / 1_000d:0.#}K";
|
||||||
|
return value.ToString("N0");
|
||||||
|
}
|
||||||
|
|
||||||
private static void UpdateCircularUsageArc(System.Windows.Shapes.Path path, double ratio, double centerX, double centerY, double radius)
|
private static void UpdateCircularUsageArc(System.Windows.Shapes.Path path, double ratio, double centerX, double centerY, double radius)
|
||||||
{
|
{
|
||||||
ratio = Math.Clamp(ratio, 0, 0.9999);
|
ratio = Math.Clamp(ratio, 0, 0.9999);
|
||||||
|
|||||||
Reference in New Issue
Block a user