From 3f2aba4adc5b26997eb62b0471afee11b70b6e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Fri, 15 May 2026 19:14:35 +0200 Subject: [PATCH 1/4] =?UTF-8?q?todo:=20parity=20gaps=20vs=20bitburner-agen?= =?UTF-8?q?t=20=E2=80=94=20state=20badge,=20slash=20cmds,=20stats,=20nap,?= =?UTF-8?q?=20viz=20polish,=20persistent=20event=20history?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 6f5c5e68..5492d727 100644 --- a/TODO.md +++ b/TODO.md @@ -21,16 +21,67 @@ Pick anything from here when relevant. Cross-cutting design notes live in - **Model override.** Hard-coded to `haiku` in the turn loop right now. Surface as a per-agent override: operator via dashboard, manager via `request_apply_commit` setting an attr on the agent's flake (most natural - place since the flake already carries per-agent env/identity). + place since the flake already carries per-agent env/identity). Pair with + a **model status** indicator on the agent page (active / queued / last + switched) once the override is in place. ## UI / UX - **Per-agent UI substance.** Show last N inbox messages, last turn timing, link back to dashboard. +- **Delivered events history persistence.** The `events::Bus` ring + buffer (500 events, in-memory) backfills the terminal on page load + but dies on harness restart, and only ever holds the most recent + turn or two. Persist to sqlite (`events(agent, id, ts, kind, + payload_json)`) so the operator can scroll back through prior + turns, and so `/events/history` survives restart. Cap rows per + agent or auto-vacuum on age, same trade-off as the bounded broker + entry below. +- **Granular agent state badge** above the terminal: `idle 💤 / thinking 🧠 / + compacting 📦` with an age timer (`thinking · 12s`). Drives a state + channel from the harness: idle when waiting on the inbox, thinking + while claude's stream is open, compacting when `/compact` is in + flight. Replaces the binary "harness alive — turn loop running" line. +- **Terminal: slash commands + tab-completion.** Operator-facing + in-terminal commands: `/help`, `/model`, `/compact`, `/clear`. Tab + completes command names + model names (cf. bitburner-agent's pattern). +- **Terminal: multi-line input.** Replace the single-line `` with + an auto-growing textarea; Enter sends, Shift+Enter newlines. +- **Terminal: cancel-current-turn button.** Explicit "kill claude + process for this turn" control. Harness needs to track the + in-flight claude child PID and offer a `/cancel` endpoint that sends + SIGTERM; UI surfaces a button while the state badge is `thinking`. + Slash-command equivalent: `/cancel`. +- **`/compact` trigger.** Operator-initiated compaction of the current + claude session — `claude --print --continue` with `/compact` over the + same session id. Surfaces as a slash command in the terminal + a + toolbar button while the state badge is `idle`. Sets state to + `compacting` during the run. +- **Visuals.** Frosted-glass backdrop blur on the terminal wrap, + per-event fade-in slide-up animation on new rows, badge pulse + animation on state-badge transitions. - **xterm.js terminal** embedded per-agent, attached to a PTY exposed by the harness. Pairs well with the unprivileged-container work — would let the operator drop into the container without `nixos-container root-login`. +## Telemetry + +- **Harness stats per agent in sqlite, charted on the agent page.** + bitburner-agent samples 18 series; for hyperhive the generally-applicable + ones are: + - turns/min, tool calls/turn, turn duration p50/p95 + - claude exit code distribution (ok vs `--compact`-retry vs failure) + - inbox depth (current + max-over-window) + - messages sent/received per turn (split by recipient: peer / operator / + manager / system) + - approval queue length (across all agents — dashboard-level) + - per-tool usage counts (Read/Edit/Bash/send/recv/…) + - time-since-last-turn (helps spot stuck agents) + - notes file size growth (cues compaction) + Backend: a `stats` table with `(agent, ts, key, value)` written from + the harness on `TurnEnd`; `GET /api/stats?since=…` returns the + series; agent page renders with a small chart lib (uPlot is light). + ## Manager → operator question channel - **TTL / cancel on `ask_operator`.** Questions today block forever; the @@ -42,6 +93,14 @@ Pick anything from here when relevant. Cross-cutting design notes live in ## Loop substance +- **`nap` tool.** Agent-side MCP tool `mcp__hyperhive__nap(seconds)` that + parks the turn loop for a short while before next-message processing. + Use cases: agent decides it has nothing useful to do, or wants to + throttle itself between rapid wake events. Implementation: harness + records a "wake-not-before" timestamp; `recv_blocking` skips the long + poll until that ts; the state badge reads `napping · MM:SS` during. + Operator can cancel via the same `/cancel` slash command or a + dashboard button. - **Notes compaction.** `/state/` is bind-mounted persistently and agents are told (in the system prompt) to keep `/state/notes.md` for durable knowledge — but we don't currently nudge them to compact when notes From fd39226883710b4b4d0022d0998e6f97f86442c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Fri, 15 May 2026 19:20:15 +0200 Subject: [PATCH 2/4] visuals: frosted-glass terminal/msgflow, row fade-in, badge pulses agent terminal-wrap + dashboard msgflow get a translucent bg with backdrop-filter blur+saturate so page-bg glow softens behind them. new rows in the live panel and the dashboard message flow fade in with a 4px slide-up. unread badge pulses; pending-operator-questions section pulses its glow. history-backfilled rows skip the animation (.no-anim) so the page doesn't stagger 100 fade-ins on load. --- hive-ag3nt/assets/agent.css | 29 +++++++++++++++++++++++++++-- hive-ag3nt/assets/app.js | 9 +++++++-- hive-c0re/assets/dashboard.css | 16 +++++++++++++++- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/hive-ag3nt/assets/agent.css b/hive-ag3nt/assets/agent.css index 9cc0eb07..ff913b48 100644 --- a/hive-ag3nt/assets/agent.css +++ b/hive-ag3nt/assets/agent.css @@ -125,9 +125,14 @@ pre.diff { max-height: 30em; } /* Terminal-ish wrapper holding the live output + prompt input as one - unit. Crust as bg (almost-black), slightly inset, mauve phosphor glow. */ + unit. Crust as bg (almost-black), slightly inset, mauve phosphor glow. + Frosted-glass backdrop blur: the page bg behind the wrap gets softened, + so anything that bleeds through (page banner glow, scroll position) + reads as out-of-focus depth instead of sharp competing detail. */ .terminal-wrap { - background: #11111b; + background: rgba(17, 17, 27, 0.78); + -webkit-backdrop-filter: blur(8px) saturate(120%); + backdrop-filter: blur(8px) saturate(120%); border: 1px solid var(--purple-dim); box-shadow: inset 0 0 24px rgba(0, 0, 0, 0.7); border-radius: 4px; @@ -191,6 +196,26 @@ pre.diff { margin-left: 0.6em; font-size: 0.85em; text-shadow: 0 0 6px rgba(250, 179, 135, 0.55); + animation: badge-pulse 1.4s ease-in-out infinite; +} +@keyframes badge-pulse { + 0%, 100% { opacity: 1; text-shadow: 0 0 6px rgba(250, 179, 135, 0.55); } + 50% { opacity: 0.7; text-shadow: 0 0 14px rgba(250, 179, 135, 0.95); } +} +/* Per-event fade-in slide-up. Applied to every row the live panel + appends; the `.no-anim` modifier lets history-backfill skip the + animation (we don't want 100 rows fading in at once on page load). */ +.live .row, +.live details.row { + animation: row-fade-in 220ms ease-out both; +} +.live .row.no-anim, +.live details.row.no-anim { + animation: none; +} +@keyframes row-fade-in { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: translateY(0); } } details.row { white-space: normal; diff --git a/hive-ag3nt/assets/app.js b/hive-ag3nt/assets/app.js index cb89cbda..0bab2a7d 100644 --- a/hive-ag3nt/assets/app.js +++ b/hive-ag3nt/assets/app.js @@ -250,10 +250,13 @@ function clearPlaceholder() { if (placeholder) { log.innerHTML = ''; placeholder = null; } } + // Backfill replays mark rows .no-anim so we don't stagger 100 fade-ins + // on page load. Set via `currentNoAnim` before the row helpers fire. + let currentNoAnim = false; function row(cls, text) { clearPlaceholder(); const e = document.createElement('div'); - e.className = 'row ' + (cls || ''); + e.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : ''); e.textContent = text; log.appendChild(e); log.scrollTop = log.scrollHeight; @@ -262,7 +265,7 @@ function details(cls, summary, body) { clearPlaceholder(); const d = document.createElement('details'); - d.className = 'row ' + (cls || ''); + d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : ''); const s = document.createElement('summary'); s.textContent = summary; d.appendChild(s); @@ -394,11 +397,13 @@ if (!resp.ok) return; const events = await resp.json(); let openTurns = 0; + currentNoAnim = true; for (const ev of events) { handle(ev, { fromHistory: true }); if (ev.kind === 'turn_start') openTurns += 1; else if (ev.kind === 'turn_end') openTurns = Math.max(0, openTurns - 1); } + currentNoAnim = false; for (let i = 0; i < openTurns; i++) setBannerActive(true); if (events.length) row('note', '─── live (older above) ───'); else setPlaceholder('(connected — waiting for events)'); diff --git a/hive-c0re/assets/dashboard.css b/hive-c0re/assets/dashboard.css index 3036c917..10b86ac1 100644 --- a/hive-c0re/assets/dashboard.css +++ b/hive-c0re/assets/dashboard.css @@ -215,6 +215,11 @@ summary:hover { color: var(--purple); } border: 1px solid var(--amber); box-shadow: 0 0 12px -4px var(--amber); padding: 0.6em 0.9em; + animation: questions-pulse 2.4s ease-in-out infinite; +} +@keyframes questions-pulse { + 0%, 100% { box-shadow: 0 0 12px -4px rgba(250, 179, 135, 0.55); } + 50% { box-shadow: 0 0 22px -2px rgba(250, 179, 135, 0.95); } } .questions li.question { padding: 0.4em 0; @@ -261,7 +266,9 @@ summary:hover { color: var(--purple); } .inbox .msg-sep { color: var(--muted); } .inbox .msg-body { color: var(--fg); white-space: pre-wrap; word-break: break-word; } .msgflow { - background: var(--bg-elev); + background: rgba(24, 24, 37, 0.78); + -webkit-backdrop-filter: blur(8px) saturate(120%); + backdrop-filter: blur(8px) saturate(120%); border: 1px solid var(--border); padding: 0.8em; font-size: 0.85em; @@ -269,6 +276,13 @@ summary:hover { color: var(--purple); } max-height: 32em; overflow-y: auto; } +.msgflow .msgrow { + animation: row-fade-in 220ms ease-out both; +} +@keyframes row-fade-in { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: translateY(0); } +} .msgrow { display: grid; grid-template-columns: auto auto auto auto auto 1fr; gap: 0.6em; align-items: baseline; padding: 0.1em 0; } .msgrow.sent .msg-arrow { color: var(--cyan); } .msgrow.delivered .msg-arrow { color: var(--green); } From 85e1f1a8f41bcc41975cbc544ef6f3ee348b1640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Fri, 15 May 2026 19:21:00 +0200 Subject: [PATCH 3/4] agent terminal: multi-line textarea input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit swap the single-line for an auto-growing