feat(stats): surface "favorite tools" (most-run bash commands) on /stats

The surfacing half of the per-agent favorite-tools stat. Reads a
`bash_commands(ts INTEGER NOT NULL, head TEXT NOT NULL)` table from the
per-agent turn-stats.sqlite — one row per bash task, written by
hive-bash-mcp (the capture half, landing separately) — and rolls up the
top-10 command heads for a doughnut, mirroring the existing
tool_breakdown.

- stats.rs: new `Snapshot.bash_breakdown` + `read_bash_breakdown()`.
  The read is guarded: a missing `bash_commands` table (capture hasn't
  shipped / agent hasn't run a bash task) maps to an empty list, never
  an error — the snapshot degrades gracefully. Unit tests cover both
  the absent-table and populated cases (incl. window cutoff + ordering).
- frontend: a "favorite tools (bash)" doughnut card on the agent /stats
  page, kept hidden until bash_breakdown has data so it never shows a
  permanently-empty doughnut while capture is pending.

Part of #1433 (does not close it — pairs with the hive-bash-mcp capture
half). Inert until the capture lands; merge order with it is irrelevant.
This commit is contained in:
iris 2026-06-06 00:08:55 +02:00 committed by mara
commit a5914274ba
3 changed files with 117 additions and 0 deletions

View file

@ -33,6 +33,10 @@
<div class="stats-card wide"><h3>token cost per bucket (sum across inferences)</h3><div class="chart-wrap"><canvas id="chart-cost"></canvas></div></div>
<div class="stats-card wide"><h3>turns by model per bucket — model drives token cost</h3><div class="chart-wrap"><canvas id="chart-model"></canvas></div></div>
<div class="stats-card"><h3>top tools</h3><div class="chart-wrap"><canvas id="chart-tools"></canvas></div></div>
<!-- "favorite tools": most-run shell commands. Hidden until the
bash_commands capture (hive-bash-mcp) has recorded data, so the
card never shows a permanently-empty doughnut. -->
<div class="stats-card" id="card-bash" hidden><h3>favorite tools (bash)</h3><div class="chart-wrap"><canvas id="chart-bash"></canvas></div></div>
<div class="stats-card"><h3>wake source mix</h3><div class="chart-wrap"><canvas id="chart-wake"></canvas></div></div>
<div class="stats-card"><h3>result mix</h3><div class="chart-wrap"><canvas id="chart-result"></canvas></div></div>
<div class="stats-card wide"><h3>result trend per bucket — errors / rate-limits / compactions over time</h3><div class="chart-wrap"><canvas id="chart-result-trend"></canvas></div></div>

View file

@ -340,8 +340,26 @@ window.Chart = Chart;
});
}
// "favorite tools" doughnut: most-run shell commands. The capture
// (hive-bash-mcp -> bash_commands table) lands separately, so until
// there's data we hide the whole card rather than show an empty
// doughnut. Runs independently of turn_count (a bash task is tied to
// a turn, but we don't want to couple the two reads).
function renderBashCard(s) {
const card = document.getElementById('card-bash');
const items = s.bash_breakdown || [];
if (!items.length) {
if (card) card.hidden = true;
destroy('chart-bash');
return;
}
if (card) card.hidden = false;
renderKeyCount('chart-bash', items, 'no bash commands');
}
function render(s) {
renderSummary(s);
renderBashCard(s);
if (s.turn_count === 0) {
paintEmpty('chart-turns', 'no turns in window');
paintEmpty('chart-duration', 'no turns in window');