From 75e7faff0c78c79cf3b9042d48b0772178ee5d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Fri, 15 May 2026 22:12:40 +0200 Subject: [PATCH] docs: full sync ahead of compaction + config-management overhaul readme: manager mcp surface picks up update; operator-surface recap mentions /model + last-turn + model chip + the three collapsibles (inbox / journald / agent.nix). web-ui.md: details-restore-key story under shape; port-conflict banner mention on containers; agent.nix viewer alongside journald; notifications use per-event tags + console.debug log on block/show; deny endpoint takes note=; data-prompt / data-prompt-field generalisation noted. conventions.md: data-prompt and snapshot/restoreOpenDetails added to the async-forms section. persistence.md: operator_questions row picks up deadline_at (ttl) column with a migration note. todo.md: new 'Bugs' section captures the manager-question not-rendering issue with three suspect paths to chase. claude.md scratchpad rewritten as a clean handoff for the compaction + the upcoming config-git overhaul. flags the two-repo (proposed/ + applied/) split as the thing to reconsider. --- CLAUDE.md | 42 +++++++++++++++++++---------- README.md | 15 ++++++----- TODO.md | 20 ++++++++++++++ docs/conventions.md | 11 ++++++-- docs/persistence.md | 3 ++- docs/web-ui.md | 64 ++++++++++++++++++++++++++++++++++++--------- 6 files changed, 120 insertions(+), 35 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e1f2e5b..cbf818c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -114,17 +114,31 @@ read them à la carte. In-flight or recent context that hasn't earned a section yet. Prune freely. -- 2026-05-15 ish: tombstones, multi-select ask_operator, broker + - events vacuum, docs split into `docs/`, lifecycle_action helper, - api_state split. -- Then: inline +/- diffs on Write/Edit, operator cancel + ttl on - questions, dashboard back-link, per-agent inbox view, bind-retry - + SO_REUSEADDR, journald viewer, server-side TurnState, - recv(wait_seconds) max 180s, runtime /model switch, crash - watcher, model persistence, stopped auto-allowing claude-code - unfree (operator must opt in), pure-hash agent_web_port (port - files reverted), browser notifications, focus-preserving - refresh. -- Open threads: telemetry/charts, custom per-agent MCP tools (the - groundwork for moving bitburner-agent into hyperhive), - two-step spawn, unprivileged containers, Bash allow-list. +- **Imminent:** overhaul the git management of agent configs. + Current shape: per-agent `proposed/` repo the manager edits + + `applied/` repo hive-c0re owns, with `request_apply_commit` + shuttling commits between them. Pre-compact note: keep an eye + on whether the two-repo split is still the right shape, or if + a single repo with `proposed/` and `applied/` branches (or a + shared bare repo per agent with refs/proposed and refs/applied) + would simplify the diff / approve / apply path. +- **Recent (since last compaction):** inline +/- diffs on + Write/Edit, send full body via collapsed details, operator + cancel + ttl on questions, deny-with-reason, dashboard + back-link + last-turn timing + model chip, per-agent inbox + view, bind-retry + SO_REUSEADDR, journald viewer, + agent.nix viewer, server-side TurnState, recv(wait_seconds) + max 180s, runtime /model switch + persistence to /state, + crash watcher + ContainerCrash / NeedsLogin / LoggedIn / + NeedsUpdate events, manager `update` tool, pure-hash + agent_web_port + collision banner + spawn/rebuild preflight, + browser notifications, focus-preserving refresh, generalised +
survival, prompt-on-submit pattern. +- **Open threads:** custom per-agent MCP tools (groundwork for + moving bitburner-agent into hyperhive), two-step spawn, + per-agent send allow-list, telemetry/charts, notes + compaction, unprivileged containers, Bash allow-list, + xterm.js. **Known bug** (in TODO.md): question id=5 was + queued but didn't render — likely a `pending()` row-decode + error swallowed by `unwrap_or_default`; investigate by curl + /api/state | jq '.questions' + browser console. diff --git a/README.md b/README.md index 5d83a5a..15d2926 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ host (NixOS, runs hive-c0re.service) │ ├── hm1nd hive-m1nd serve : claude turn loop + │ MCP (send / recv / request_spawn / kill / start / - │ restart / request_apply_commit / ask_operator) - │ + web UI on :8000 + │ restart / update / request_apply_commit / + │ ask_operator) + web UI on :8000 │ └── h- hive-ag3nt serve : claude turn loop + MCP (send / recv) + web UI on a hashed :8100-8999 @@ -44,10 +44,13 @@ streams JSON events into the per-agent SSE bus + a sqlite history db → claude drives any further `recv`/`send` itself via the embedded MCP server. Operator surface per agent: terminal-themed live tail with a textarea -prompt; slash commands `/help` `/clear` `/cancel` `/compact`; granular -state badge (idle / thinking / offline) with age timer; cancel-turn -button while thinking; sticky-bottom auto-scroll with "↓ N new" pill; -event history backfilled on page load. +prompt; slash commands `/help` `/clear` `/cancel` `/compact` +`/model `; granular state badge (idle / thinking / +compacting / offline) with age timer + last-turn duration chip + +model chip; cancel-turn button while thinking; sticky-bottom +auto-scroll with "↓ N new" pill; event history backfilled on page +load; collapsible inbox + collapsible journald viewer + collapsible +`agent.nix` viewer per agent on the dashboard. Config changes flow the other way: manager edits `/agents//config/agent.nix` (bind-mounted from the host's proposed repo) → commits → submits the sha as diff --git a/TODO.md b/TODO.md index 28ff89e..c38ea06 100644 --- a/TODO.md +++ b/TODO.md @@ -42,6 +42,26 @@ Pick anything from here when relevant. Cross-cutting design notes live in derived from the same config so the operator stays in control of what's exposed. +## Bugs + +- **Pending question doesn't always appear on the dashboard.** + Repro: manager calls `ask_operator`, tool result is + `question queued (id=N)` (so the row is in sqlite), but the + M1ND H4S QU3STI0NS section keeps showing "no pending + questions". Last seen with id=5. Suspected paths: + - `OperatorQuestions::pending()` returns Err and the + `unwrap_or_default()` in `api_state` hides it. Surface the + error (warn-log) and check. + - serialization: a new field in `OpQuestion` (e.g. + `deadline_at: Option`) deserializes wrong against an + old row whose columns don't match the new SELECT order → + `row.get(N)?` panics for that row, the whole iterator + errors, `pending()` returns Err. Diagnose by curl + `/api/state | jq '.questions'` and compare with sqlite + counts. + - dashboard JS swallows a render error. Open browser console + and look for exceptions during `renderQuestions`. + ## UI / UX - **xterm.js terminal** embedded per-agent, attached to a PTY exposed by diff --git a/docs/conventions.md b/docs/conventions.md index 2f20c15..8a709ec 100644 --- a/docs/conventions.md +++ b/docs/conventions.md @@ -34,8 +34,15 @@ Dashboard + per-agent mutating forms carry `data-async`; a delegated `submit` listener in `assets/app.js` intercepts, shows a spinner, POSTs `application/x-www-form-urlencoded` (axum's `Form` extractor rejects multipart), calls `refreshState()` on success. New mutating -forms should add `data-async` and optionally `data-confirm` for a -JS-side confirmation prompt. +forms should add `data-async` and optionally `data-confirm` (for a +JS-side `confirm()` prompt) or `data-prompt="…"` (for a +`window.prompt()` whose answer goes into a hidden input named by +`data-prompt-field`, default `note`). + +`refreshState` defers automatically when `document.activeElement` +sits inside a managed section so the operator's typing isn't lost; +collapsible `
` survive the re-render +via `snapshotOpenDetails` / `restoreOpenDetails`. ## `rebuild` is the reconcile verb diff --git a/docs/persistence.md b/docs/persistence.md index 01dd685..69cfe6b 100644 --- a/docs/persistence.md +++ b/docs/persistence.md @@ -14,7 +14,8 @@ Three tables, all in one file: commit_ref / requested_at / status / resolved_at / note`. - `operator_questions` — `ask_operator` queue. `asker / question / options_json / multi / asked_at / - answered_at / answer`. + deadline_at (ttl) / answered_at / answer`. Migrated via + `ALTER TABLE ADD COLUMN` against `pragma_table_info`. Retention: diff --git a/docs/web-ui.md b/docs/web-ui.md index a39906a..c333a84 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -30,6 +30,16 @@ and, if so, skips the refresh (defers 2s). The operator never has the form yanked out from under them mid-type; the update lands as soon as they blur. +**`
` open-state preservation:** any collapsible element +tagged with `data-restore-key=""` survives the +refresh. `snapshotOpenDetails()` walks managed sections before +render, `restoreOpenDetails()` re-applies after. Used today for +the journald viewer (`journal:`), the agent-config +viewer (`agent-config:`), and approval diff blocks +(`approval-diff:`). Setting `.open = true` programmatically +also fires the `toggle` event, so any lazy-fetch wired to it +re-runs cleanly on restore. + Both bind their listeners with `SO_REUSEADDR` via `tokio::net::TcpSocket` plus a retry loop on `AddrInUse` (12 tries, exponential backoff capped at 2s) so an nspawn restart that races @@ -42,6 +52,11 @@ the previous process's socket release resolves itself. inline "unsupported / blocked" message when applicable. Sits under the banner. 2. **C0NTAINERS** — live containers with their action surface. + Pulsing red banner at the top of this section if any two + sub-agents hash to the same port (`port_conflicts` from + `/api/state`): the operator must rename one of them and + rebuild. `lifecycle::{spawn,rebuild}` also preflight this and + refuse with a clear error message naming the conflicting agent. 3. **K3PT ST4T3** — destroyed-but-state-kept tombstones (size + age + claude-creds badge). Two actions: `⊕ R3V1V3` (queues a Spawn approval; existing state is reused), `PURG3` (wipes @@ -73,12 +88,16 @@ Two-line layout (`assets/app.js::renderContainers`): on sub-agents, `↺ R3ST4RT` + (sub-agents) `■ ST0P` when running, `▶ ST4RT` when stopped. Buttons dim + disable while a transient lifecycle action is in flight. -- Plus a collapsible `↳ logs · ` `
` block. - Expanding lazy-fetches journald output via `GET - /api/journal/{name}?unit=...&lines=...` (`journalctl -M - -b --no-pager --output=short-iso`). A unit dropdown - switches between the harness service (default) and the full - machine journal; refresh button re-fetches. +- Plus two collapsible `
` blocks: + - `↳ logs · ` — lazy-fetches journald output via + `GET /api/journal/{name}?unit=...&lines=...` (`journalctl -M + -b --no-pager --output=short-iso`). A unit + dropdown switches between the harness service (default) and + the full machine journal; refresh button re-fetches. + - `↳ agent.nix · ` — lazy-fetches the applied config + file via `GET /api/agent-config/{name}` (read-only mirror of + `/var/lib/hyperhive/applied//agent.nix`). Mutating + this still requires `request_apply_commit` + approval. `↻ UPD4TE 4LL` button appears above the containers list when any agent is stale. Banner pulses on each broker SSE event @@ -94,15 +113,27 @@ Pure frontend (`Notification` API). Three signals trigger them: First `/api/state` after page load seeds "seen" sets without firing — only items that arrive while the page is open count. -`tag: "hyperhive"` collapses bursts; click focuses the dashboard -tab. localStorage-backed mute toggle silences without revoking -the OS permission. Requires a secure context (HTTPS or -localhost); on other origins the controls hide themselves. +Per-event tags (`hyperhive:approval:`, `hyperhive:question:`, +`hyperhive:msg::`) so distinct events stack in the OS +notification center instead of overwriting each other. +`console.debug` logs at every block point (unsupported, +permission ungranted, muted) for in-browser debugging. Click +focuses the dashboard tab. localStorage-backed mute toggle +silences without revoking the OS permission. Requires a secure +context (HTTPS or localhost); on other origins the controls hide +themselves. Browsers typically suppress notifications while the +originating tab is focused — that's a browser-level decision, +not ours. ### Dashboard endpoints -- `POST /{approve,deny}/{id}` — approve/deny a pending approval. +- `POST /approve/{id}` — approve a pending approval. +- `POST /deny/{id}` (`note=`, optional) — deny a pending + approval with an optional operator-supplied reason. The reason + travels to the manager as `HelperEvent::ApprovalResolved.note`. + Dashboard prompts via `window.prompt()` on click. - `POST /{rebuild,kill,restart,start,destroy}/{name}` — lifecycle. + `destroy` accepts `purge=on` to also wipe state dirs. - `POST /purge-tombstone/{name}` — wipe a tombstone's state dirs. - `POST /answer-question/{id}` — answer a pending operator question. - `POST /cancel-question/{id}` — cancel a pending question with @@ -111,6 +142,13 @@ localhost); on other origins the controls hide themselves. - `POST /update-all` — rebuild every stale container. - `GET /api/journal/{name}?unit=&lines=` — journalctl viewer for a managed container. +- `GET /api/agent-config/{name}` — read-only view of the applied + `agent.nix`. + +Generalised form helpers: `form[data-confirm="…"]` pops +`confirm()` before submit; `form[data-prompt="…"]` pops +`prompt()` and stashes the answer in a hidden input named by +`data-prompt-field` (default `note`). ## Per-agent page @@ -134,7 +172,9 @@ Layout, top to bottom: POSTs `/api/cancel`. - Inbox `
` block (collapsed): `inbox · N` — last 30 messages addressed to this agent, fetched via - `AgentRequest::Recent { limit: 30 }`. + `AgentRequest::Recent { limit: 30 }`. (Separate from + `AgentRequest::Recv { wait_seconds }` which the harness uses + internally to long-poll the broker.) - Terminal-wrap: live event tail (sticky-bottom auto-scroll + `↓ N new` pill when not at bottom) followed by an operator-input textarea acting as a prompt.