feat(stats): hive-wide "favorite tools" rollup on ST4TS

Swarm-level companion to the per-agent favorite-tools doughnut (#1433).
Aggregates each agent's bash_commands(ts, head) table (written by
hive-bash-mcp) across the whole hive and surfaces the top-10 most-run
command heads on the dashboard ST4TS tab, alongside the existing model
mix.

- hive_stats.rs: AgentAgg gains a `bash` head→count map, filled by a new
  guarded `read_bash_heads()` that reuses read_agent's read-only
  connection. A missing `bash_commands` table (capture hasn't run for
  that agent) or any read error yields an empty map — isolated from
  read_agent's error path so it never drops an agent from the rollup.
  HiveStats gains `bash_mix: Vec<KeyCount>` (busiest-first, top 10).
  Unit tests cover the per-head tally + window cutoff and the
  missing-table degrade-to-empty path (in-memory sqlite).
- dashboard: a "favorite tools (bash commands across the swarm)" CSS-bar
  list on the ST4TS pane, mirroring the model-mix bars. Header + list
  stay hidden until bash_mix has data, so a fresh hive shows no empty
  block. (Dashboard ships no chart lib — bars, not a doughnut.)
- docs: dashboard.md ST4TS section documents the new rollup.

Closes #1449. Inert until the hive-bash-mcp capture (#1448, merged) has
recorded data across agents.
This commit is contained in:
iris 2026-06-06 07:47:43 +02:00 committed by mara
commit d9d2a52221
4 changed files with 130 additions and 0 deletions

View file

@ -289,6 +289,11 @@
<div id="hive-stats-agents"><p class="meta">loading…</p></div>
<h3>◇ model mix (turns across the swarm)</h3>
<div id="hive-stats-models"></div>
<!-- "favorite tools": most-run bash commands across the swarm.
Header + list hidden until the hive-bash-mcp capture has
recorded data, so the section never shows an empty block. -->
<h3 id="hive-stats-bash-h" hidden>◇ favorite tools (bash commands across the swarm)</h3>
<div id="hive-stats-bash" hidden></div>
</section>
<!-- FL0W: lives on its own page now (`/flow.html`). The

View file

@ -4216,6 +4216,36 @@ window.marked = marked;
}
}
}
// "favorite tools": most-run bash commands across the swarm. Hidden
// (header + list) until the capture has recorded data, so the
// section never shows an empty block while capture is pre-data.
const bh = $('hive-stats-bash');
const bhH = $('hive-stats-bash-h');
if (bh) {
const bmix = s.bash_mix || [];
if (!bmix.length) {
bh.replaceChildren();
bh.hidden = true;
if (bhH) bhH.hidden = true;
} else {
bh.hidden = false;
if (bhH) bhH.hidden = false;
bh.replaceChildren();
const max = bmix[0].count || 1;
for (const kc of bmix) {
const row = document.createElement('div'); row.className = 'hive-stats-bar';
const lbl = document.createElement('span'); lbl.className = 'lbl'; lbl.textContent = kc.key;
const track = document.createElement('span'); track.className = 'track';
const fill = document.createElement('span'); fill.className = 'fill';
fill.style.width = Math.max(2, Math.round(100 * kc.count / max)) + '%';
track.append(fill);
const cnt = document.createElement('span'); cnt.className = 'cnt'; cnt.textContent = hsFmtInt(kc.count);
row.append(lbl, track, cnt);
bh.append(row);
}
}
}
}
async function refreshHiveStats() {