stats: per-agent /stats page with chart.js trends + breakdowns

new hive-ag3nt::stats module reads turn_stats.sqlite read-only and
aggregates over 24h/7d/30d windows (hourly/daily buckets) — turn
rate, p50/p95/avg duration, ctx tokens (avg/max), cost token
components, top tools, wake mix, result mix. served by the agent
itself so per-MCP extensions can register more providers without
the host knowing their schemas.

/stats route + /api/stats?window=... on the per-agent web ui.
chart.js v4.4.4 pulled from jsdelivr (SRI hash deferred). nav
links: 📊 chip on the dashboard container row + 📊 stats → on
the per-agent header.

todo housekeeping: softened damocles-area note at the top,
new reverse-proxy + deferred reminder-rollup items, removed
the two telemetry-ui items absorbed by this page.
This commit is contained in:
müde 2026-05-19 00:27:01 +02:00
parent f9f1346eae
commit d3f90f4cc0
8 changed files with 930 additions and 3 deletions

View file

@ -0,0 +1,76 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>hyperhive agent — stats</title>
<link rel="stylesheet" href="/static/agent.css">
<style>
.stats-nav { display: flex; gap: 0.75rem; align-items: baseline; margin-bottom: 0.5rem; }
.stats-nav a { color: var(--cyan); text-decoration: none; }
.stats-nav a:hover { text-decoration: underline; }
.window-tabs { display: flex; gap: 0.4rem; margin: 0.5rem 0 1rem; }
.window-tabs button {
background: var(--bg-elev); color: var(--fg);
border: 1px solid var(--border); padding: 0.3rem 0.8rem;
font-family: inherit; cursor: pointer;
}
.window-tabs button.active { background: var(--purple-dim); border-color: var(--purple); color: var(--purple); }
.summary { display: flex; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 1rem; }
.summary .chip {
background: var(--bg-elev); border: 1px solid var(--border);
padding: 0.4rem 0.8rem; border-radius: 4px;
}
.summary .chip .label { color: var(--muted); margin-right: 0.5rem; }
.summary .chip .value { color: var(--cyan); font-weight: bold; }
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
gap: 1rem;
}
.card {
background: var(--bg-elev);
border: 1px solid var(--border);
padding: 0.75rem 1rem 1rem;
border-radius: 4px;
}
.card h3 { margin: 0 0 0.5rem; color: var(--purple); font-size: 0.95rem; font-weight: normal; }
.card .chart-wrap { position: relative; height: 220px; }
.card.wide { grid-column: 1 / -1; }
.card.wide .chart-wrap { height: 260px; }
.empty-note { color: var(--muted); font-style: italic; }
</style>
</head>
<body>
<pre class="banner">░▒▓█▓▒░ … ░▒▓█▓▒░ hyperhive ag3nt · stats ░▒▓█▓▒░</pre>
<div class="stats-nav">
<a id="back-link" href="/">← live</a>
<a id="dashboard-link" href="#">dashboard</a>
<h2 id="title" style="margin: 0;">◆ … ◆</h2>
</div>
<div class="window-tabs" id="window-tabs">
<button data-w="24h" class="active">last 24h</button>
<button data-w="7d">last 7d</button>
<button data-w="30d">last 30d</button>
</div>
<div class="summary" id="summary"></div>
<div class="grid">
<div class="card wide"><h3>turns per bucket</h3><div class="chart-wrap"><canvas id="chart-turns"></canvas></div></div>
<div class="card wide"><h3>turn duration (ms) — p50 / p95 / avg</h3><div class="chart-wrap"><canvas id="chart-duration"></canvas></div></div>
<div class="card wide"><h3>context tokens (last inference per turn) — avg / max</h3><div class="chart-wrap"><canvas id="chart-ctx"></canvas></div></div>
<div class="card wide"><h3>token cost per bucket (sum across inferences)</h3><div class="chart-wrap"><canvas id="chart-cost"></canvas></div></div>
<div class="card"><h3>top tools</h3><div class="chart-wrap"><canvas id="chart-tools"></canvas></div></div>
<div class="card"><h3>wake source mix</h3><div class="chart-wrap"><canvas id="chart-wake"></canvas></div></div>
<div class="card"><h3>result mix</h3><div class="chart-wrap"><canvas id="chart-result"></canvas></div></div>
</div>
<!-- Chart.js pinned to a fixed version from jsDelivr. SRI hash is
not set yet — add an integrity="sha384-..." attribute when we
have a way to compute it deterministically in the build. -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js"
crossorigin="anonymous"></script>
<script src="/static/stats.js" defer></script>
</body>
</html>