feat: persistent token tracking with global stats aggregation

- Add restoreTokens() method to Session class for recovery after restart
- Add GlobalStats type for cumulative usage tracking across all sessions
- Accumulate tokens from deleted sessions into global stats
- Track lifetime session count with incrementSessionsCreated()
- Add /api/stats endpoint for global stats
- Include globalStats in /api/status response
- Frontend shows aggregate tokens + cost in header
- Fix respawn controller config to filter undefined values
- Add 7 new tests for global stats functionality (v0.1343)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-25 07:26:49 +01:00
co-authored by Claude Opus 4.5
parent 0aca55b099
commit e56c8626e4
10 changed files with 322 additions and 19 deletions
+27 -6
View File
@@ -355,6 +355,7 @@ class ClaudemanApp {
this.cases = [];
this.currentRun = null;
this.totalTokens = 0;
this.globalStats = null; // Global token/cost stats across all sessions
this.eventSource = null;
this.terminal = null;
this.fitAddon = null;
@@ -1298,6 +1299,11 @@ class ClaudemanApp {
this.respawnStatus = data.respawnStatus;
}
// Store global stats for aggregate tracking
if (data.globalStats) {
this.globalStats = data.globalStats;
}
this.totalCost = data.sessions.reduce((sum, s) => sum + (s.totalCost || 0), 0);
this.totalCost += data.scheduledRuns.reduce((sum, r) => sum + (r.totalCost || 0), 0);
@@ -2404,15 +2410,30 @@ class ClaudemanApp {
}
updateTokens() {
// Use global stats if available (includes deleted sessions)
let total = 0;
this.sessions.forEach(s => {
if (s.tokens) {
total += s.tokens.total || 0;
}
});
if (this.globalStats) {
total = this.globalStats.totalInputTokens + this.globalStats.totalOutputTokens;
} else {
// Fallback to active sessions only
this.sessions.forEach(s => {
if (s.tokens) {
total += s.tokens.total || 0;
}
});
}
this.totalTokens = total;
const display = total >= 1000 ? `${(total / 1000).toFixed(1)}k` : total;
this.$('headerTokens').textContent = `${display} tokens`;
// Show global cost if available
const costDisplay = this.globalStats ? `$${this.globalStats.totalCost.toFixed(2)}` : '';
const tokenEl = this.$('headerTokens');
if (tokenEl) {
tokenEl.textContent = costDisplay ? `${display} tokens · ${costDisplay}` : `${display} tokens`;
tokenEl.title = this.globalStats
? `Lifetime: ${this.globalStats.totalSessionsCreated} sessions created`
: 'Token usage across active sessions';
}
}
// ========== Session Options Modal ==========