docs: dashboard event channel, hive-fr0nt crate, mutation events, seq dedupe

This commit is contained in:
müde 2026-05-17 14:24:47 +02:00
parent 62784d4933
commit d8d393da6d
3 changed files with 167 additions and 30 deletions

View file

@ -24,9 +24,12 @@ admin socket.
## Wire protocol
JSON line-delimited over unix sockets in both directions (host admin
/ manager / agent). SSE streams (`/messages/stream`,
`/events/stream`) are `text/event-stream`. Request/response types
live in `hive-sh4re` — change them in one place.
/ manager / agent). SSE streams (`/dashboard/stream` on hive-c0re,
`/events/stream` on the per-agent web UIs) are `text/event-stream`;
each frame carries a `seq` field for the snapshot-dedupe dance
(see `docs/web-ui.md`). Request/response types live in `hive-sh4re`
— change them in one place. The dashboard event vocabulary lives
in `hive-c0re::dashboard_events::DashboardEvent`.
## Async forms

View file

@ -10,10 +10,31 @@ and the per-agent UIs (manager on :8000, sub-agents on a hashed
- `GET /``assets/index.html` (placeholders for state-driven
sections, shipped via `include_str!` so the binary has no runtime
file dependency).
- `GET /static/*.css` + `GET /static/*.js` → static assets.
- `GET /api/state` → JSON snapshot the JS app renders into the DOM.
- `GET /events/stream` (per-agent) / `GET /messages/stream`
(dashboard) → `text/event-stream` SSE for live updates.
- `GET /static/*.css` + `GET /static/*.js` → static assets. Both
pages prepend `hive_fr0nt::BASE_CSS` + `TERMINAL_CSS` to their
per-page stylesheet, and `GET /static/hive-fr0nt.js` serves the
shared `window.HiveTerminal.create` runtime. The dashboard's
`#msgflow` and the per-agent `#live` log are both backed by
this terminal — sticky-bottom auto-scroll, "↓ N new" pill,
history backfill, SSE plumbing all live there. Each page
registers a kind→renderer map; unknown kinds fall through to
a JSON-dump note row.
- `GET /api/state` → JSON snapshot the JS app renders into the
DOM. Includes a top-level `seq` (the dashboard event channel's
high-water mark at the moment the snapshot was assembled);
clients use it to dedupe their buffered SSE traffic against
the snapshot (drop frames with `seq <= snapshot.seq`).
- `GET /dashboard/stream` (dashboard) / `GET /events/stream`
(per-agent) → `text/event-stream` SSE for live updates. The
dashboard stream carries broker `Sent` / `Delivered` (mirrored
by a forwarder task from the broker's intra-process channel)
plus mutation events (`approval_added` / `approval_resolved`,
`question_added` / `question_resolved`, `transient_set` /
`transient_cleared`). Each frame carries a `seq`. The
matching backfill endpoint is `GET /dashboard/history` (last
~200 broker messages wrapped in `{ seq, events }`) on the
dashboard and `GET /events/history` (last 2000 `LiveEvent`s
also wrapped in `{ seq, events }`) on the agent.
The JS app handles all `form[data-async]` submissions via a delegated
listener: read `data-confirm`, swap the button to a spinner, POST
@ -71,26 +92,37 @@ the previous process's socket release resolves itself.
with `[cancelled]`. Questions with a `ttl_seconds` show a
`⏳ MM:SS` chip; the host-side watchdog auto-cancels with
`[expired]` when the deadline fires.
5. **0PER4T0R 1NB0X** — recent messages addressed to `operator`
(last 50, from the broker).
5. **0PER4T0R 1NB0X** — recent messages addressed to `operator`,
derived client-side from the dashboard event stream (no longer
a snapshot field). Cold load seeds from
`/dashboard/history`'s 200-message backfill; subsequent
`sent` events with `to == "operator"` are appended live. Cap
50, newest-first.
6. **P3NDING APPR0VALS** — the queue. The R3QU3ST SP4WN form
lives at the top of this section since submitting it
immediately queues an approval that lands directly below.
7. **MESS4GE FL0W** — live broker SSE tail (newest-first).
Each row is one broker event — `sent` or `delivered` — with
`from → to: body`; per-agent thinking / tool calls / claude
chatter stay out of this view, only what passes through
hive-c0re's broker. Below the stream sits a terminal-style
compose box: `@name` picks the recipient (sticky across
sends via localStorage; auto-complete from the live
container list, Tab/Enter to confirm), starting a message
with `@<name> body` retargets in one stroke, plain text
sends to the sticky recipient. `POST /op-send` drops
`{from:"operator", to, body}` into the broker — same shape
any sub-agent sees as a regular inbox message. Manager is
addressed as `@manager` (the broker recipient string), not
`@hm1nd` (the container name); the auto-complete swaps
automatically.
7. **MESS4GE FL0W** — live broker tail wrapped in a
`.terminal-wrap` (same chrome as the per-agent terminal).
Cold load backfills the last ~200 messages from
`/dashboard/history`; live frames arrive on
`/dashboard/stream` and dispatch through
`HiveTerminal.create`. Each row is one broker event —
`sent` or `delivered` — with `from → to: body`; per-agent
thinking / tool calls / claude chatter stay out of this
view, only what passes through hive-c0re's broker. Sticky-
bottom auto-scroll + "↓ N new" pill match the per-agent
page. Below the stream sits a terminal-style compose box:
`@name` picks the recipient (sticky across sends via
localStorage; auto-complete from the live container list,
Tab/Enter to confirm; `@*` broadcasts to every registered
agent), starting a message with `@<name> body` retargets
in one stroke, plain text sends to the sticky recipient.
`POST /op-send` drops `{from:"operator", to, body}` into
the broker and returns 200; the resulting SSE frame
re-renders both the terminal row and the inbox section
(no `/api/state` refetch). Manager is addressed as
`@manager` (the broker recipient string), not `@hm1nd`
(the container name); the auto-complete swaps automatically.
### Container row
@ -143,10 +175,13 @@ not ours.
### Dashboard endpoints
- `POST /approve/{id}` — approve a pending approval.
- `POST /approve/{id}` — approve a pending approval. Fires
`ApprovalResolved` on the dashboard event channel; client
updates derived approvals state from the event.
- `POST /deny/{id}` (`note=<reason>`, optional) — deny a pending
approval with an optional operator-supplied reason. The reason
travels to the manager as `HelperEvent::ApprovalResolved.note`.
travels to the manager as `HelperEvent::ApprovalResolved.note`
and also rides on the dashboard's `ApprovalResolved` event.
Dashboard prompts via `window.prompt()` on click.
- `POST /{rebuild,kill,restart,start,destroy}/{name}` — lifecycle.
`destroy` accepts `purge=on` to also wipe state dirs.
@ -157,12 +192,52 @@ not ours.
- `POST /request-spawn` — queue a Spawn approval.
- `POST /update-all` — rebuild every stale container.
- `POST /op-send` (`to=<name>`, `body=<text>`) — drop an
operator-authored message into `<name>`'s inbox. Used by the
operator-authored message into `<name>`'s inbox. `to=*` fans
out to every registered agent. Returns 200; the broker
`Sent` event re-renders both the message-flow terminal and
the operator inbox without a snapshot refetch. Used by the
compose textbox under MESS4GE FL0W.
- `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`.
- `GET /dashboard/stream` — unified live event channel:
broker `sent` / `delivered`, plus the mutation events listed
below. Each frame carries `seq`.
- `GET /dashboard/history` — last ~200 broker messages
(wrapped as `{ seq, events }`) for the message-flow
terminal's backfill on page load.
### Dashboard event channel
Wire vocabulary on `/dashboard/stream` (kind tag is in the JSON
payload):
- `sent` / `delivered` — broker traffic, mirrored from the
intra-process channel by a forwarder task. Used by the
message-flow terminal renderer and the operator-inbox
derived state.
- `approval_added` (id, agent, approval_kind, sha_short, diff,
description) / `approval_resolved` (id, agent, approval_kind,
sha_short, status, resolved_at, note, description) — pending
queue + history mutations. Client mutates a derived store and
re-renders only the approvals section.
- `question_added` (id, asker, question, options, multi,
asked_at, deadline_at) / `question_resolved` (id, answer,
answerer, answered_at, cancelled) — operator-targeted
questions only (peer-to-peer questions never fire these). The
ttl watchdog fires `question_resolved` with
`answerer = "ttl-watchdog"` on expiry.
- `transient_set` (name, transient_kind, since_unix) /
`transient_cleared` (name) — lifecycle action spinners. The
client ticks the elapsed-seconds badge off `since_unix`
client-side, no polling.
`/api/state` still serves `approvals` / `approval_history` /
`questions` / `question_history` / `transients` for cold-start
on first page load and as a safety-net resync from the 5s poll;
the client maintains the same arrays in derived stores and
applies the events on top.
Generalised form helpers: `form[data-confirm="…"]` pops
`confirm()` before submit; `form[data-prompt="…"]` pops