The cpu, memory and disk figures are exported as OTEL metrics and graphed in Grafana, and the caps are set in the swarm UI, so the C0NT41N3R L04D tab on the C0R3 page only duplicated both. C0R3 is now the kept-state page alone, without a tab strip.
876 lines
47 KiB
Markdown
876 lines
47 KiB
Markdown
# Dashboard layout
|
||
|
||
> Part of [Web UI](README.md). See also:
|
||
> [Shape (shared)](shape.md) · [Per-agent page](agent.md)
|
||
|
||
This page covers the per-hive UI that hive-c0re serves. Swarm-wide
|
||
surfaces — the agent roster, agent creation, linking external forge and
|
||
matrix accounts, the controller's job graph — are on the
|
||
[swarm UI](../swarm/ui.md).
|
||
|
||
hive-c0re serves the dashboard at `/dashboard.html` (with the home page at `/`).
|
||
It has a fixed chrome header at the top and a `<main>` that shows exactly
|
||
one tab pane at a time. The URL hash (`#swarm`,
|
||
`#permissions`) drives which pane is
|
||
active; hash changes don't reload the page. FL0W and the other
|
||
standalone pages are reachable from the H0M3 hub at `/`, not
|
||
from the dashboard tab strip.
|
||
|
||
**Chrome header** (fixed, overlays the active tab pane):
|
||
- **← home back-link**: top-left of the chrome, links to the H0M3 hub
|
||
at `/`. Every surface links back to the hub rather than to each other.
|
||
- **Tab strip**: `◆ SW4RM ◆`, `◆ P3RM1SS10NS ◆`. In-page tabs only —
|
||
the SYST3M panels live on the
|
||
standalone **C0R3** page (`/core.html`), and FL0W / ST4TS
|
||
live on their own pages too, all reachable from the
|
||
H0M3 hub (not the tab strip). Count pill on SW4RM
|
||
(container count).
|
||
- **Banner-thin** (`░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░`)
|
||
— sits below the tab strip.
|
||
- **Server-warnings banner** — a generic, sticky top-of-page strip shown
|
||
on **every** page (dashboard + every stand-alone page — FL0W,
|
||
H0M3, C0R3, BU1LDS, ST4TS), injected at the top
|
||
of `<body>` by `renderServerWarnings` in `common.js`. Driven by
|
||
`state.server_warnings` — a list of `{ kind, level, message }` — and
|
||
coloured by `level` (`warn` amber / `crit` red). hive-c0re owns the
|
||
threshold + message, so adding a new system warning needs no frontend
|
||
change. Warnings are a push-based registry (`hive-c0re`'s
|
||
`warnings.rs`): any subsystem raises/clears its own entry via an RAII
|
||
guard, and `host_stats::server_warnings()` is just a cheap snapshot of
|
||
that registry. Producers today include the host disk-pressure check
|
||
(a `statvfs` probe of `/nix`: ≥85% used → `warn`, ≥95% → `crit`, e.g.
|
||
`⚠ host nix store N% full (G GiB free) — garbage-collect …`), forge
|
||
provisioning/CI-runner boot failures, and an agent-state warning
|
||
(`pending_logins`) computed alongside the container snapshot. A
|
||
container crash used to have a sibling `agents_crashing` entry here —
|
||
removed in favour of a swarm-level grafana alert on the crash
|
||
watcher's own log line (`docs/scheduler/observability.md`), so a
|
||
hive with no otel export now has no operator-visible crash signal at
|
||
all. Hidden when there are no warnings.
|
||
- **Browser tab title** — `hyperhive // h1ve-c0re` by default; updated
|
||
to `<swarm> / <hive> // h1ve-c0re` once `hive_name` / `swarm_name`
|
||
arrive in the state snapshot.
|
||
|
||
The FL0W page uses a slim header (a `← home` back-link + the page
|
||
title) rather than the dashboard tab strip — it's a standalone surface,
|
||
not a tab pane.
|
||
|
||
## SW4RM tab
|
||
|
||
**C0NTAINERS** — live containers as a flat list, one row per
|
||
container, sorted alphabetically by agent name; every container the
|
||
rescan reports gets a row. 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.
|
||
|
||
`↻ UPD4TE 4LL` button appears above the containers list when any
|
||
agent is stale.
|
||
|
||
## C0R3 page (`/core.html`)
|
||
|
||
Passive / rare-interaction state, reached from the **Core** tile on the
|
||
H0M3 hub (served at
|
||
`/core.html`), with the same minimal chrome as `/builds.html`: a `← home`
|
||
back-link and a single pane, **K3PT ST4T3**. The page is its own esbuild
|
||
bundle (`core.js`) that cold-loads `/api/state` and subscribes to
|
||
`/api/dashboard/stream` for `tombstones_changed`, `capabilities_changed`,
|
||
and `tool_groups_changed` (the latter two re-render the stale-perms
|
||
sub-section when permission data changes). (Rebuild queue and meta inputs
|
||
live on the BU1LDS page — see below.)
|
||
|
||
Per-container CPU, memory and disk load is not shown in the hive UI. The
|
||
same figures are exported as OTEL metrics and graphed in Grafana (see
|
||
[observability](../scheduler/observability.md)). An agent's CPU and memory
|
||
caps are set per agent in the swarm UI.
|
||
|
||
**K3PT ST4T3** — two sub-sections on one pane:
|
||
|
||
*Tombstones*: agents with kept state and **no container** (size + age +
|
||
claude-creds badge). One action: `PURG3` (wipes state + applied
|
||
dirs; `POST /api/purge-tombstone/{name}`).
|
||
|
||
⚠️ **Not only *destroyed* agents.** Nothing records a destroy: `Provision`
|
||
writes every definition-side artifact (state subvolume, proposed + applied
|
||
repos, meta registration, topology entry) *before* the container exists,
|
||
and that artifact set survives `lifecycle::destroy`. An agent
|
||
part-way through a spawn is byte-identical on disk to a tombstone, and
|
||
the pane lists both. The pane carries a standing warning to that effect, and
|
||
the row badge says `offline` rather than `destroyed` — the absence of a
|
||
container is all hive-c0re can actually prove. Fixing it properly
|
||
needs a recorded destroy — **deferred** to the swarm-controller /
|
||
snapshot-storage rework where the problem changes shape.
|
||
|
||
*Stale permission entries*: agents with explicit capability or
|
||
tool-group JSON entries but no live container — typically renamed or
|
||
deleted agents whose entries persisted in `capabilities.json` /
|
||
`tool-groups.json`. Loaded when the page opens;
|
||
autorefreshes on `capabilities_changed` + `tool_groups_changed` SSE
|
||
events. Each ghost agent gets a `✕ clear perms` button
|
||
(`DELETE /api/permissions/{name}`).
|
||
|
||
## BU1LDS page (`/builds.html`)
|
||
|
||
The build lifecycle hub — rebuild queue, live build log, meta inputs,
|
||
and build log history on one page. Standalone page reached from the
|
||
**Builds** tile on the H0M3 hub, same minimal chrome as `/core.html`.
|
||
Three sub-tabs: **R3BU1LD QU3U3** (default), **M3T4 1NPUTS**,
|
||
**BUILD L0GS**. Its own esbuild bundle (`builds.js`); cold-loads
|
||
`/api/state` and subscribes to `/api/dashboard/stream` for
|
||
`rebuild_queue_changed`, `meta_inputs_changed`, `meta_update_running`.
|
||
The SW4RM tab (`/dashboard.html`) does **not** read this endpoint at
|
||
all, by design — the swarm view stays independent of job-queue
|
||
internals. Its per-agent pending badges are transient-only, and
|
||
its queue-summary banner reads the much narrower `GET /api/jobq/rollup`
|
||
instead (see Container row, below) — a handful of pre-tallied counts,
|
||
not the graph.
|
||
|
||
**R3BU1LD QU3U3** — pending, in-flight, and recently settled container
|
||
operations: rebuilds, meta-update cascades, and first-spawns. One
|
||
operation runs at a time; the worker drains FIFO. **Is a mounted
|
||
`JobqGraph`** (the shared generic graph-viewer Preact component,
|
||
`@hive/shared/jobq-graph.js` — the same one swarm-ui uses) —
|
||
`builds.js` doesn't render the queue itself; it just mounts the component
|
||
(`mountJobqGraph(container, props)`, since this page has no JSX
|
||
pipeline) with an `onUpdate` callback to drive the two things below it
|
||
that the generic view doesn't show. The component owns fetching, cold
|
||
and live: `GET /api/jobq/graph` on mount, and a refetch via the mount
|
||
handle's `.refresh()` on every `rebuild_queue_changed` SSE tick (that
|
||
event is a bare `{ seq }` trigger carrying no queue payload; both
|
||
dashboard pages treat the tick as a pure refetch trigger).
|
||
|
||
Each row is one root graph node (`parent: null`); a multi-step op's
|
||
per-agent subgraphs and sub-steps render as nodes within that one
|
||
entry (structural `parent` edges define the tree; sibling order is
|
||
array order, unchanged from the wire). A row shows a state glyph (`⏸`
|
||
pending / `▶` running / `◐` finishing — own work done, a sub-node
|
||
still running / `✔` done / `✖` failed / `⊘` cancelled / `·` skipped)
|
||
and each step's own label/agent. A `Node`-kind dep on a sibling shows
|
||
as a plain "waits on: `<label>`" text line under the row rather than a
|
||
gutter rail — a rail breaks visually whenever a nested subtree sits
|
||
between the two related rows, since a text line needs no continuous
|
||
vertical space to draw.
|
||
`builds.js` mounts the component with `cancellable` set, which turns on
|
||
a per-node cancel button (`✕`) on any non-terminal row — the button
|
||
calls the mount's `onCancel(id)` callback, and the page does the actual
|
||
`POST /api/rebuild-queue/{id}/cancel`, matching that callback to its
|
||
own domain concept (the component knows nothing about that endpoint).
|
||
**Rows carry no source chip, kind label, timing, or build-log
|
||
deep-link** — the generic graph wire doesn't carry those fields, and
|
||
rows deliberately present exactly what the endpoint provides rather
|
||
than reconstructing chrome hive-c0re doesn't send. Settled entries
|
||
render their **full step tree**, not just a bare summary — the wire
|
||
doesn't filter `Done` nodes out.
|
||
|
||
**State filter.** A row of per-state checkboxes above
|
||
the tree — one per lifecycle state, matching the row glyphs — lets the
|
||
operator narrow which root groups render; unchecking a state re-fetches
|
||
`GET /api/jobq/graph?states=<checked, comma-joined>` rather than
|
||
hiding rows client-side, so the `onUpdate` callback's node list (and
|
||
everything downstream of it — the count pill, the live-log panel) only
|
||
ever sees what's actually shown. Filtering is by a **root's own**
|
||
state, which is already its subtree's rolled-up answer, so the filter
|
||
keeps or drops a whole group, never splitting mid-tree. Default selection is
|
||
every state **except** `Done`/`Skipped` — a fresh queue view leads
|
||
with what's still moving or needs attention, not the settled tail;
|
||
builds.js omits the `states` param entirely (identical request to
|
||
before this filter existed) when the operator has checked every state. Server-side: the query
|
||
narrows [`Queue::graph_snapshot`]'s already-bounded (`MAX_HISTORY_DAGS`)
|
||
root set — the history cap and the state filter are independent
|
||
concerns, so a narrow filter never reaches further back in time to
|
||
compensate.
|
||
|
||
Below the queue, a **live build-log panel** (`#rebuild-live-log`,
|
||
`renderRebuildLiveLog`) shows the currently running rebuild's output
|
||
inline — collapsible, with a live/ok/fail badge and a `↓ raw`
|
||
download. It's keyed to the first `Running` node (in wire order)
|
||
whose `payload.data.build_log_id` has a value — read from the `onUpdate`
|
||
callback's node list, same source as the count pill, no separate
|
||
fetch — and **polls** `GET /api/build-log/{id}` every 2s
|
||
(`fetchAndRenderLiveLog` / `liveLogPollTimer`); not an `EventSource`
|
||
(that's the BUILD L0GS tab's own per-row expand view below — `GET
|
||
/api/build-logs/id/{id}/stream`, real SSE, replays accumulated output
|
||
on connect — a separate mechanism). The live-log panel lives in its
|
||
own container outside `#rebuild-queue-section` so the mounted
|
||
`JobqGraph`'s own re-renders never disturb the open poll; it hides
|
||
when nothing is building.
|
||
|
||
**M3T4 1NPUTS** — inputs in `meta/flake.lock` the operator can
|
||
selectively `nix flake update`, rendered as an indented tree:
|
||
every fetched input at every depth (`hyperhive`,
|
||
`hyperhive/nixpkgs`, `agent-<n>`, `agent-<n>/mcp-<x>`, …), each
|
||
shown once at its shallowest path. `read_meta_inputs` walks the
|
||
lock graph with a `visited` set, skipping `follows` aliases and rev-less
|
||
nodes. A `select all / select none`
|
||
control sits above the tree. Checking inputs + submitting bumps
|
||
the lock in `/meta/` and rebuilds the selected agents in
|
||
sequence; each outcome reaches the root agent as a `rebuilt`
|
||
system event. `POST /meta-update`. While a lock-bump ripple runs,
|
||
the panel shows a pulsing "⏳ meta-update running" banner and disables
|
||
the update button (snapshot field `meta_update_running`,
|
||
live event `meta_update_running`).
|
||
|
||
**BUILD L0GS** — all-agents build log history. Lazy-loaded on first tab
|
||
activation; autorefreshes when
|
||
`rebuild_queue_changed` fires. Fetches `GET /api/build-logs?limit=30`.
|
||
Renders a scrollable list of build entries; each row is a collapsible
|
||
button showing status badge (`live` / `ok` / `fail`), agent name,
|
||
elapsed duration, build kind, age, and the invocation command line.
|
||
Expanding a row fetches the full stdout+stderr via
|
||
`GET /api/build-logs/id/{id}`. A live in-progress build shows a `live`
|
||
badge with a ticking elapsed-time chip; expanding streams output via
|
||
`GET /api/build-logs/id/{id}/stream` with sticky-bottom autoscroll
|
||
(suspends on manual scroll-up). Deep-link: `?id=N#buildlogs` opens the
|
||
entry with that id pre-expanded.
|
||
|
||
## P3RM1SS10NS tab
|
||
|
||
Per-agent permission configuration. Two sections, each rendered as a
|
||
column-driven checkbox matrix: rows are agents, columns are the
|
||
permission names fetched from hive-c0re. The column list is
|
||
authoritative — adding a new tool-group or capability to hive-c0re
|
||
requires no UI change; the new column appears automatically.
|
||
|
||
The snapshot carries `agents` (the full manageable roster — live
|
||
containers ∪ agents with an explicit entry) and `effective` (per-agent
|
||
explicit-or-role-default values) alongside the explicit `assignments`
|
||
map. Rows come from `agents` so **agents on defaults always appear**
|
||
(not just those with an explicit entry), and checkboxes reflect the
|
||
`effective` values so a default agent shows the groups it actually runs
|
||
with rather than blank — which also means saving it won't silently
|
||
strip those defaults. The `(default)` badge keys off absence from
|
||
`assignments` (no explicit entry).
|
||
|
||
Fetches fire on tab activation (not page-load) to avoid unnecessary
|
||
work when the operator never visits this tab. Live mutations from the
|
||
rebuild-queue worker are also pushed via the `capabilities_changed` /
|
||
`tool_groups_changed` SSE events (same payload shape as the GET
|
||
endpoints), so an open P3RM1SS10NS tab reflects worker-applied changes
|
||
without requiring navigation. Tab-activation re-fetches remain as a
|
||
safety net for reconnect windows.
|
||
|
||
**C4P4B1L1T13S** — per-agent capability grants. Capabilities unlock
|
||
gated MCP tools and system-level access beyond the default agent
|
||
surface. A saving POST queues a rebuild for the affected agent so the
|
||
new `HIVE_CAPABILITIES` env var takes effect in the next session.
|
||
|
||
The current capabilities are:
|
||
|
||
| Name | Effect |
|
||
|------|--------|
|
||
| `manage_root_agent` | mounts every agent's state (rw) + config (ro) into this agent's container so it can manage/recover any agent |
|
||
|
||
Each row is one agent. Columns are the capability names returned by
|
||
`GET /api/capabilities` as `caps: Vec<String>`. Checking or unchecking
|
||
boxes only stages the change in-browser; the browser writes nothing until
|
||
the operator selects the page-level **save all** button (described below). Row
|
||
values follow the `effective`/`assignments` rule described above.
|
||
|
||
<!-- vale write-good.Passive = NO -->
|
||
**T00L GR0UPS** — per-agent tool-group permissions. Tool groups are
|
||
named buckets of MCP tools; each agent starts with a role default
|
||
(sub-agents: `messaging`, `meta`, `inbox`, `execution` —
|
||
`ToolGroup::AGENT_DEFAULT`; hive-c0re seeds the root agent to
|
||
`ToolGroup::MANAGER_DEFAULT` — `messaging`, `meta`, `inbox`,
|
||
`lifecycle`, `execution`,
|
||
that is, every group except `forge` and `web_tools`). Checking /
|
||
unchecking stages which groups are active for the agent; the
|
||
page-level **save all** button (below) commits it. Columns come from
|
||
`GET /api/tool-groups`. hive-c0re queues a rebuild so `HIVE_TOOL_GROUPS`
|
||
takes effect.
|
||
<!-- vale write-good.Passive = YES -->
|
||
|
||
The current tool groups are: `messaging`, `meta`, `inbox`, `lifecycle`,
|
||
`forge`, `execution`, `web_tools`. All listed in `ToolGroup::ALL` in `hive-sh4re`. The
|
||
`web_tools` group is special: it carries no MCP tools; instead it adds
|
||
Claude's built-in `WebFetch` and `WebSearch` to `--tools` /
|
||
`--allowedTools` for that agent session.
|
||
|
||
Both tables share the same visual shape: `.cap-table-wrap` /
|
||
`.tg-table-wrap` outer scroll container, `thead` with a label column
|
||
(`.cap-agent-col` / `.tg-agent-col`) + one column per permission
|
||
(`.cap-col` / `.tg-group-col`). Each tbody row is one agent: a name cell
|
||
and its checkbox cells, where each checkbox carries `data-baseline` (its
|
||
render-time state) and the row carries `data-agent` for dirty-tracking.
|
||
|
||
**Saving — one button for the whole page.** No per-row save
|
||
buttons exist. A single page-level `.perm-save-bar` with a **save all (N
|
||
agents)** button sits at the bottom of the pane, enabled only when some
|
||
checkbox diverges from its baseline. Selecting it diffs every checkbox
|
||
across *both* matrices and POSTs one batch to `POST /api/permissions` as
|
||
`{ changes: [ { agent, tool_groups?, capabilities? } ] }` — the batch
|
||
includes only the perm-types that actually changed for each agent (an
|
||
omitted field leaves that file untouched; an included array fully
|
||
replaces it).
|
||
hive-c0re coalesces an agent's capabilities + tool-groups into a
|
||
single rebuild, so changing both for one agent is one rebuild, not two.
|
||
The batch is atomic: it validates every change first and on any error
|
||
rejects the whole POST (`{error}`, nothing applied); a clean 200 (`ok`)
|
||
flips the bar to a queued→rebuilding state and re-fetches both tables.
|
||
Live `capabilities_changed` / `tool_groups_changed` events re-render the
|
||
matrices unless the section has unsaved edits, so an in-progress edit set
|
||
isn't clobbered.
|
||
|
||
## ST4TS page (`/stats.html`)
|
||
|
||
Hive-wide turn statistics, aggregated across every agent's
|
||
`hyperhive-turn-stats.sqlite` for the selected window. Not a dashboard
|
||
tab — a standalone page reached from the **Stats** tile on the H0M3
|
||
hub, same minimal chrome as `/flow.html`. Distinct from
|
||
each agent's own `/stats` page (which carries the per-agent trend
|
||
charts): ST4TS is the swarm-level rollup.
|
||
|
||
- **Window selector** (`1h`, `4h`, `24h`, `3d`, `7d`, `30d`, `all`) —
|
||
a hash-routed tab strip (`#1h` / `#24h` / …) — re-fetches on change.
|
||
- **Summary chips**: active agents, turns, total/input/output/cache-read
|
||
tokens, and a labelled **est cost**.
|
||
- **Busiest agents** table — one row per agent (most turns first):
|
||
turns, input / output / cache-read tokens, est cost.
|
||
- **Model mix** — turns per model across the swarm, as CSS bars.
|
||
- **Favorite tools** — most-run normalised bash-command heads across the
|
||
swarm (top 10, as CSS bars), aggregated from each agent's
|
||
`bash_commands` table (written by the hive-bash-daemon capture). The
|
||
header + list stay hidden until at least one agent has recorded a
|
||
command, so the section never shows an empty block on a fresh hive.
|
||
|
||
Backed by `GET /api/stats-hive?window=<w>` in `hive-c0re`
|
||
(`hive_stats.rs`): for every name from
|
||
`Coordinator::kept_state_names()` it opens
|
||
`agent_harness_dir(name)/hyperhive-turn-stats.sqlite` read-only (with a
|
||
500 ms `busy_timeout`, since `turn_stats` is rollback-journal) and rolls
|
||
the rows up, skipping missing / unreadable / zero-turn dbs so one
|
||
bad db never fails the endpoint. This is a **pull** surface (no SSE):
|
||
the page fetches the data on page load and on window change. Rendered
|
||
with plain tables + CSS bars — the dashboard bundle ships no chart
|
||
library.
|
||
|
||
The cost figure is a deliberately rough estimate from a per-model
|
||
price table (`est_cost_usd`); it drifts with list pricing and the page
|
||
labels it accordingly. The table is operator-tunable via the
|
||
`services.hyperhive.c0re.modelPrices` nix option — each key is a
|
||
model-family short name (matched case-insensitively as a substring of
|
||
the model id, longest match wins) mapping to
|
||
`{ input, output, cache_read, cache_write }` USD-per-million-token
|
||
prices. Models not covered fall back to hive-c0re's built-in estimate.
|
||
|
||
## FL0W page (`/flow.html`)
|
||
|
||
A dedicated full-page terminal (not a tab pane — a separate HTML
|
||
page). Slim chrome: a `← home` back-link to the H0M3 hub, the FL0W
|
||
title, and the agent-filter select (see below). No dashboard tab
|
||
strip — FL0W is a standalone surface reached from the hub.
|
||
|
||
**MESS4GE FL0W** — live broker tail wrapped in a `.terminal-wrap`.
|
||
Cold load backfills the last ~200 messages from `/api/dashboard/history`;
|
||
live frames arrive on `/api/dashboard/stream`. Each row is one broker
|
||
event — `sent` or `delivered` — with `from → to: body`. When a `sent`
|
||
and `delivered` event for the same message arrive within 3 seconds
|
||
(immediate delivery to a live recipient), the page upgrades the row in
|
||
place (arrow becomes green ✓, title reads "sent + delivered") instead of
|
||
rendering two near-identical lines — genuine delivery latency (recipient
|
||
was busy) still appears as a second row. Each row carries `data-from` /
|
||
`data-to` attributes; an **agent filter select in the FL0W header** narrows
|
||
the timeline to messages involving the chosen agent (matched on `from` OR
|
||
`to`), with non-matching rows hidden (`.flow-hidden` class). The selection
|
||
persists in localStorage across reloads; new rows pick up the active filter
|
||
at render time. The dropdown populates from the live container list and
|
||
stays current on add/remove; a saved selection survives even if that agent
|
||
isn't currently listed.
|
||
|
||
The row is a `flex-wrap: wrap` container holding ts / arrow / from / sep
|
||
/ to chips inline; the **body wraps to its own full-width line below** the
|
||
chips (`flex: 1 1 100%`) so long timestamps and agent names never push the
|
||
body into a narrow trailing column. `min-width: 0` keeps
|
||
`word-break: break-word` effective so the body doesn't force the row wider
|
||
than its container. Sticky-bottom autoscroll + "↓ N new" pill. Below the
|
||
stream sits a terminal-style compose box: `@name` picks the recipient
|
||
(sticky via localStorage; autocomplete from the live container list,
|
||
Tab/Enter to confirm; `@*` broadcasts). `POST /api/op-send` drops
|
||
`{from:"operator", to, body}` into the broker; the resulting SSE frame
|
||
re-renders the terminal row. The operator addresses the root agent as `@root`.
|
||
|
||
## H0M3 page (`/`)
|
||
|
||
The H0M3 hub is the primary landing page (served at `/` by default). A
|
||
responsive grid of link tiles — Dashboard, Flow, Builds, Stats,
|
||
Core, API — each pointing to their respective
|
||
surfaces, all unconditionally shown (no gating). The page is a pure
|
||
portal with no tab-bar or SSE subscriptions. Typography + colours inherit
|
||
from the shared theme (Catppuccin Mocha via `common.css` + `theme.css`).
|
||
`home.js` fills the swarm/hive identity line at the top. All dashboard
|
||
sub-pages include a `← Home` back-link for navigation.
|
||
|
||
## Container row
|
||
|
||
A full-height **square agent icon** (5em, capped) on the left. The
|
||
icon is the **selection toggle**: select (or Enter/Space) adds/removes
|
||
the agent from the selection set; `aria-pressed` reflects the state;
|
||
the tooltip says "select … for bulk actions" or "deselect … (or press
|
||
Esc to clear all)." The `<img>` points at `<url>/icon`; load failure
|
||
falls back to the dimmed hyperhive mark (`/favicon.svg`). The card
|
||
body sits to the right with three stacked lines
|
||
(`assets/swarm.js::renderContainers`).
|
||
|
||
<!-- vale write-good.Passive = NO -->
|
||
**Icon layout + load strategy:** the `<img>` is absolutely
|
||
positioned (`inset: 0`) inside the `.container-icon` wrapper —
|
||
the wrapper is the flex child and sizes itself via `width: 5em` +
|
||
`aspect-ratio: 1`, the `<img>` is out of flow so its load state
|
||
(pending, loaded, broken) can never contribute intrinsic size or
|
||
reflow the row. Without that, the row would briefly grow as the
|
||
image's natural dimensions arrived, then snap back on
|
||
`object-fit: contain`. The load itself is fire-and-forget: the
|
||
dashboard doesn't pre-check whether the agent is reachable, it
|
||
just lets the `<img>` try and listens for an `error` event. On
|
||
failure the handler swaps the `src` to `/favicon.svg` (served by
|
||
the dashboard itself, always reachable) and adds the
|
||
`icon-unreachable` class for the dimmed look. When the dashboard
|
||
already knows the container is stopped (`ContainerView.running = false`)
|
||
the fallback fires immediately, skipping the doomed `<url>/icon`
|
||
fetch entirely.
|
||
<!-- vale write-good.Passive = YES -->
|
||
|
||
<!-- vale write-good.Passive = NO -->
|
||
**Line 1** — agent name (link → new tab), m1nd/ag3nt chip, an
|
||
**icon-only nav strip** plus live agent-owned state, all populated
|
||
async from a single `GET /api/dashboard-state` call to the agent's
|
||
own hive-agent. The response (`DashboardState`) carries: `links` (nav
|
||
strip entries — `📊 stats`, `🖥 screen` when GUI is enabled, `⬡ forge
|
||
profile`, `↳ agent-configs mirror`, plus any agent-declared
|
||
`dashboardLinks` extras), `status_text` / `status_set_at` (agent
|
||
self-reported status — the `(set N ago)` chip carries a `data-set-at`
|
||
stamp and ticks every 30s to stay fresh across the long-lived keyed row
|
||
cache), `rate_limited`, `ctx_tokens` / `context_window_tokens`
|
||
(context-window badge data). hive-agent is the single source
|
||
of truth for all of these. The dashboard resolves each `AgentLink.kind`
|
||
against a per-agent base URL depending on whether hive-gateway is in
|
||
front (`StateSnapshot.gateway_enabled`, sourced from the
|
||
`HIVE_GATEWAY_ENABLED` env). The c0re NixOS module sets that env
|
||
unconditionally — the gateway always runs; the flag stays so the
|
||
dashboard doesn't have to learn that. Gateway-on: base URL is
|
||
`/agent/<name>` (same origin, gateway proxies to the
|
||
per-agent harness — TCP or unix-domain depending on the agent's
|
||
`HIVE_WEB_SOCKET` opt-in, see
|
||
`docs/networking/gateway.md::Per-agent unix-socket upstream`).
|
||
Gateway-off is the flag-absent fallback (a dashboard served outside a
|
||
hyperhive deploy): base URL is
|
||
`http://<host>:<container.port>` (direct TCP). Forge
|
||
links resolve against `state.forge_public_url` (sourced from
|
||
`services.hyperhive.swarm.forge.publicUrl`) and disappear entirely when
|
||
that's unset — never guessed from `<host>:3000`. External links are
|
||
already absolute. The same base URL drives the primary agent-name
|
||
link + favicon fetch, so the whole row routes through the gateway
|
||
as a unit.
|
||
<!-- vale write-good.Passive = YES -->
|
||
|
||
<!-- vale write-good.Passive = NO -->
|
||
**When the container is stopped** (`ContainerView.running = false`),
|
||
the dashboard skips the async `dashboard-state` fetch entirely (the agent
|
||
web server is down), so a single badge replaces the badge chain,
|
||
the nav strip is empty, and status text / rate-limited / ctx
|
||
badges disappear. The agent icon goes straight to the dimmed
|
||
`/favicon.svg` fallback instead of attempting a doomed load from the
|
||
container's URL. Static fields — `needs_update`, `deployed_sha`,
|
||
`pending_reminders`, `parent`, `config` link — remain visible
|
||
regardless of run state.
|
||
<!-- vale write-good.Passive = YES -->
|
||
|
||
**Line 2** — status badges only (no per-card action buttons — actions
|
||
live in the **selection bar** or the **per-agent `⋮` menu**, see
|
||
below).
|
||
|
||
### Status badges
|
||
|
||
**Which single badge:** `ContainerView.failed`
|
||
(systemd `ActiveState=failed` — the unit exhausted its bounded
|
||
restarts and gave up on its own) draws a red `✖ gave up` badge;
|
||
otherwise a plain muted `■ not running` — a container an operator
|
||
stopped deliberately. Both states read `running: false`; `failed` is
|
||
the orthogonal fact (a fifth one alongside `paused`/`needs_update`/
|
||
`needs_login`, same "independent flags, no state machine" shape —
|
||
see `ContainerView`'s own doc comment) that tells them apart. An
|
||
older hive-c0re without the field serves `failed: undefined`, which
|
||
reads falsy — degrades cleanly to the single `not running` badge.
|
||
|
||
When the container is running, status badges follow — `⊘ rate
|
||
limited` (red, while the harness parks itself after a 429), `needs
|
||
login`, `needs update` — plus **one `◐ pending-state…` pill per
|
||
active transient** (replaces buttons during operator-initiated
|
||
start / stop / restart / rebuild / destroy). An agent can carry
|
||
**multiple transients at once** — for example a lease-exempt `prebuild`
|
||
running alongside a `stop_for_update` on the same agent — and each
|
||
renders as its own independent badge instead of collapsing
|
||
into one label, matching the existing multi-badge convention this
|
||
line already uses for `paused`/`needs_update`/model/ctx.
|
||
|
||
Any pending badge means the row is actually **running** something
|
||
right now (see **Pending-badge derivation** below); every row
|
||
carrying ≥1 badge keeps the amber row tint AND draws a **rotating
|
||
amber ring** around the agent icon.
|
||
|
||
**Pending-badge derivation:** transients only (`transientsState`,
|
||
keyed `agent -> Map<kind, since_unix>`) — a transient is **derived
|
||
from a job-queue node currently `Running`** against that agent, not
|
||
declared per request, so its label follows the operation as it
|
||
progresses (a rebuild reads `stop_for_update`, then `swap`, then
|
||
`reconcile` rather than one constant `rebuilding` for its whole
|
||
life). Two consequences for anything rendering it:
|
||
|
||
- The label vocabulary is **open** — it's the node's own wire tag
|
||
(`NodeKind`'s `&str` conversion, the same strings the graph wire's node
|
||
labels carry), not a fixed set. Treat it as an opaque display
|
||
string; don't switch on specific values.
|
||
- It's **not** exclusively operator-initiated, and **not** limited
|
||
to rebuild-shaped work — `running_transients()` in hive-c0re is
|
||
a status-only test (any `Running` node whose payload names a
|
||
non-empty agent lights a pill), so work the operator never
|
||
triggered (a meta-update cascade, a crash-recover rebuild, a
|
||
lease-exempt `prebuild`) lights the same mechanism.
|
||
|
||
Ops with no queue node behind them (destroy, migration) supply
|
||
their own label directly via `TransientSet`/`TransientCleared`
|
||
events carrying no backing node at all.
|
||
|
||
**Queued (not-yet-started) work shows nothing on the card.** Only
|
||
running work gets a per-agent pending badge, by design — the swarm
|
||
view stays independent of job-queue internals. The queue-summary
|
||
banner below is the only queued-work indicator on this tab, and it
|
||
reads the narrow `/api/jobq/rollup` endpoint rather than the full
|
||
graph.
|
||
|
||
**Active model badge** (`model · <name>`, blue) appears when the
|
||
container is running and the harness has persisted a model name (the
|
||
`active_model` field of `hyperhive-harness.json`, the consolidated
|
||
harness state file in the agent's state dir). Read by hive-c0re's
|
||
`ContainerView` (`read_active_model`); absent until the agent has
|
||
completed at least one turn, and hive-c0re suppresses stale values
|
||
for stopped containers.
|
||
|
||
**`ctx · Nk` chip** shows the agent's last-turn context size,
|
||
populated from `DashboardState.ctx_tokens` (absent until the
|
||
agent has completed at least one turn). The chip colour (green /
|
||
yellow / red) keys off `DashboardState.context_window_tokens`
|
||
(the real context window for the model the agent last ran on,
|
||
authoritative from the agent side); the badge goes yellow ≥ 50%
|
||
and red ≥ 75% of that window, matching the harness compaction
|
||
watermarks. When the window value is absent the badge falls back
|
||
to fixed 100k / 150k thresholds.
|
||
|
||
**Per-agent `⋮` overflow menu** — a `⋮` button appears on the right
|
||
edge of each container row. Selecting it opens a small dropdown with
|
||
per-agent actions and navigation links. Contents:
|
||
|
||
- `↺ R3ST4RT` (running agents only) / `■ ST0P` (running only) /
|
||
`▶ ST4RT` (stopped only) — single-agent run-state toggles.
|
||
Identical to the bulk actions on the selection bar but operate
|
||
on one agent without requiring a selection.
|
||
- `↻ R3BU1LD` — always available; queues a rebuild for this agent.
|
||
- `DESTR0Y` / `PURG3` — destructive, each prompts for confirmation
|
||
via the themed dialog (see **Themed dialogs** below).
|
||
- `deployed:<sha> ↗` — present when the agent has a `deployed_sha`
|
||
and the forge is reachable; links the deployed commit on the forge
|
||
agent-configs mirror.
|
||
|
||
`↻ UPD4TE 4LL` button appears above the containers list when any
|
||
agent is stale. Banner pulses on each broker SSE event
|
||
(`pulseBanner` with a 4s grace timer).
|
||
|
||
**Build-queue summary banner** — when the job queue has any active
|
||
work, a compact amber banner sits above the container list: `◐ build
|
||
queue — N running · M queued — view queue →` (the link goes to the
|
||
BU1LDS page's R3BU1LD QU3U3). The shared `JobqRollup` Preact component
|
||
(`@hive/shared/jobq-rollup.js` — the same one swarm-ui's `/jobs` page
|
||
mounts, pointed at swarm-controller's own rollup endpoint instead),
|
||
mounted once into `#jobq-rollup-section` by `swarm.js::initJobqRollup`
|
||
and refreshed via its own handle rather than being re-rendered by
|
||
`renderContainers`. Reads `GET /api/jobq/rollup`
|
||
(`hive-jobq-wire::state_rollup`) — `Vec<{ state, nodes, roots }>`,
|
||
every lifecycle state present in a fixed order, zero counts included —
|
||
rather than the full `/api/jobq/graph` tree: `running` sums the
|
||
`Running` and `Finishing` entries' `roots` (`Finishing` = own work
|
||
done, subtree still going, still in flight), `queued` reads the
|
||
`Pending` entry's `roots`. `roots` specifically, not `nodes` — the
|
||
banner means *N whole operations*, not raw steps (one rebuild is ~7
|
||
nodes but 1 root); `nodes` exists on the same endpoint for a consumer
|
||
that wants step-level counts instead, unused here.
|
||
|
||
### Themed dialogs
|
||
|
||
All confirmations and transient error notices use an in-app themed
|
||
dialog system (`@hive/shared/modal.js`) rather than the browser's
|
||
native `confirm()` / `alert()` chrome, so they match the Catppuccin
|
||
palette and the OS can't style them away. Two primitives, both built
|
||
on the `openDialog` core:
|
||
|
||
- `themedConfirm({ message, danger, confirmLabel, checkboxes })`
|
||
— a modal confirm that resolves to `null` on cancel or an object
|
||
of checkbox states on confirm. Destructive actions pass
|
||
`danger: true` (the confirm button turns red and the cancel
|
||
button takes focus). Selecting the backdrop and `Esc` both cancel.
|
||
- `themedToast(message, { type, ... })` — a non-blocking toast (top-right,
|
||
`info` / `error` / `ok`) for transient validation + action
|
||
failures, so an error doesn't trap the operator behind a modal.
|
||
Single-action errors autodismiss; bulk / partial-failure
|
||
summaries are sticky (select to dismiss) so they aren't missed.
|
||
|
||
Every destructive run-state action (`ST0P`, `R3ST4RT`, `R3BU1LD`,
|
||
`DESTR0Y`, `PURG3`) routes through `themedConfirm`, on both
|
||
the per-agent `⋮` menu and the bulk selection bar.
|
||
|
||
**Graceful stop** — the `■ ST0P` confirm dialog (per-agent and
|
||
bulk) carries a `stop gracefully — let the agent finish its turn
|
||
and flush state before the container stops` checkbox. When ticked,
|
||
the action POSTs `/api/kill/<name>?graceful=true` (the bulk path
|
||
appends the flag per-agent); unticked is the instant hard stop
|
||
(`/api/kill/<name>` with no query). hive-c0re enqueues a
|
||
`Signal` → `Drain` job-queue node pair (`NodeKind::Signal` /
|
||
`NodeKind::Drain`): `Signal` sets the graceful-stop fence and kicks
|
||
the harness so it runs one stop-checkpoint turn (so the agent can
|
||
flush `/state`); `Drain` awaits the harness clearing that fence,
|
||
bounded by a 3-minute timeout (`GRACEFUL_STOP_TIMEOUT`) that
|
||
resolves either way and falls back to the downstream mechanical
|
||
stop. The quiescing progress surfaces through the same
|
||
rebuild-queue pending-badge mechanism the card already reads for a
|
||
rebuild, driven by the `Signal`/`Drain` pair rather than a nix
|
||
build. (The `hivectl stop --graceful` CLI flag enqueues the same
|
||
`Signal`/`Drain` pair, so the dashboard and CLI paths behave
|
||
identically.)
|
||
|
||
## Selection bar
|
||
|
||
Bulk actions (`R3ST4RT` / `ST0P` / `ST4RT` / `P4US3` / `R3SUM3` /
|
||
`R3BU1LD` / `DESTR0Y` / `PURG3`) live here rather than as per-card
|
||
buttons — see **Container row** above. Selecting an agent's icon
|
||
toggles its selection (an in-memory `Set<name>`); `Esc` or the bar's
|
||
`✕ clear` button drops everything. The selection persists across tab
|
||
switches in-memory — the bar just hides on non-SW4RM tabs since other
|
||
tabs don't show the agent cards needed to cross-reference.
|
||
|
||
When the operator selects one or more agents (via the icon), a sticky
|
||
frosted-mauve bar slides up from the bottom of the viewport
|
||
(`#selection-bar`, `position: fixed; bottom: 0`). It shows:
|
||
|
||
<!-- vale write-good.Passive = NO -->
|
||
- **Count + names** — "N agents selected · name1, name2, …"
|
||
- **Bulk action buttons** — only enabled when ALL selected agents
|
||
support the action; disabled with a tooltip naming the blockers
|
||
when the selection is mixed:
|
||
- `↺ R3ST4RT` — running agents only
|
||
- `■ ST0P` — running agents only
|
||
- `▶ ST4RT` — stopped agents only
|
||
- `⏸ P4US3` — unpaused agents only (orthogonal to running/stopped —
|
||
see **Container row**'s own pause/resume note)
|
||
- `▶ R3SUM3` — paused agents only
|
||
- `↻ R3BU1LD` — always available
|
||
- `DESTR0Y` / `PURG3` — always available
|
||
<!-- vale write-good.Passive = YES -->
|
||
|
||
That list is the whole set — the bar offers no other bulk action.
|
||
- **`✕ clear`** button + `Esc` key clear the entire selection.
|
||
|
||
Every render prunes stale selections (agents destroyed while
|
||
selected) before the bar appears.
|
||
|
||
## Dashboard endpoints
|
||
|
||
Also browsable interactively at `/api/docs` (a Swagger UI, linked from
|
||
the H0M3 hub's **API** tile), with the raw spec at `/api/openapi.json`.
|
||
It's a growing supplement, not yet a full replacement for the list
|
||
below — some endpoints aren't in it yet.
|
||
|
||
- `POST /api/{rebuild,kill,restart,start,destroy}/{name}` — lifecycle.
|
||
`destroy` accepts `purge=on` to also wipe state dirs.
|
||
- `POST /api/purge-tombstone/{name}` — wipe a tombstone's state dirs.
|
||
- `POST /api/update-all` — rebuild every stale container.
|
||
- `POST /api/rebuild-queue/{id}/cancel` — drop a `Queued` entry.
|
||
Refuses `Running` / terminal-state entries (in-flight
|
||
rebuilds can't be safely interrupted). Always 200; body is
|
||
`{"cancelled": true}` on a successful flip or
|
||
`{"cancelled": false}` when the entry wasn't in `Queued` state.
|
||
- `POST /api/agent/{name}/mark-all-read` — ack all pending broker
|
||
messages for `{name}`. Backfills `delivered_at` for rows
|
||
not yet delivered and sets `acked_at = now`. Returns
|
||
`{ "marked": N }`. Agent name validated against
|
||
`[a-z0-9_-]`, 1-63 chars; 400 on bad input.
|
||
- `POST /api/op-send` (`to=<name>`, `body=<text>`) — drop an
|
||
operator-authored message into `<name>`'s inbox. `to=*` fans
|
||
out to every registered agent. Returns 200; the broker
|
||
`Sent` event re-renders the message-flow terminal without a
|
||
snapshot refetch. Used by the compose textbox under MESS4GE FL0W.
|
||
- `GET /api/build-logs/{agent}?limit=N` — most-recent build log
|
||
headers for one agent, newest first. Returns
|
||
`Vec<BuildLogHeader>` (JSON): `id`, `agent`, `kind`, `cmdline`,
|
||
`started_at`, `finished_at`, `status` (`"ok"` / `"fail"` /
|
||
`null` while in-progress). `limit` defaults to 10, server-side
|
||
cap at 50. Agent name validated (`[a-z0-9_-]`, 1-63 chars).
|
||
- `GET /api/build-logs/id/{id}` — full build log by id. Returns
|
||
`BuildLogFull` (JSON): all header fields plus `stdout` and
|
||
`stderr` as plain text (newline-terminated lines, utf-8). HTTP
|
||
404 when the row is missing (vacuum-reaped or stale id).
|
||
- `GET /api/build-logs/id/{id}/stream` — live SSE stream of
|
||
stdout+stderr for an in-progress build (newline-delimited JSON
|
||
frames). Used by the expanding live-row view in BU1LDS. The
|
||
stream closes when the build finishes or errors.
|
||
- `GET /api/build-logs/id/{id}/raw` — raw concatenated
|
||
stdout+stderr as `text/plain`. Used by the `↓ raw` / `↓ download
|
||
raw` links in BU1LDS. Works on both finished and in-progress
|
||
build logs.
|
||
- `GET /static/marked.js` serves the vendored `marked` bundle used
|
||
for markdown previews.
|
||
- `GET /api/state-file?path=<host-or-container-path>` — bounded
|
||
text read of a file under the per-agent `state/` subtree or
|
||
the shared `/var/lib/hyperhive/shared/`. Accepts the
|
||
container-view forms (`/agents/<n>/state/...`, `/shared/...`)
|
||
and the host form. Canonicalises + verifies the path stays
|
||
inside the allow-list, refuses anything but a regular file,
|
||
refuses `/agents/<n>/claude` / `config` subtrees, truncates
|
||
bodies at 1 MiB. Selection-time backing for the inline path-link
|
||
preview.
|
||
|
||
<!-- vale write-good.Passive = NO -->
|
||
The broker forwarder detects which tokens *are* path links
|
||
**server-side at broker-message ingest**, not client-side:
|
||
it calls `scan_validated_paths(body)` —
|
||
same allow-list helper the read endpoint uses — and attaches
|
||
the verified file tokens to the event as `file_refs: Vec<String>`.
|
||
The client trusts that list and linkifies only those tokens,
|
||
so directories, missing files, and forbidden subtrees never
|
||
become anchors. No probe endpoint, no client-side regex
|
||
heuristics. Historical messages get the same treatment on
|
||
`/api/dashboard/history` backfill.
|
||
- `GET /api/reminders` — list pending reminders for the
|
||
dashboard's queued-reminders panel.
|
||
- `GET /api/stats-hive?window=<1h|4h|24h|3d|7d|30d|all>` — hive-wide
|
||
turn-stats rollup for the ST4TS tab. Aggregates every agent's
|
||
`hyperhive-turn-stats.sqlite` read-only (skips missing / unreadable /
|
||
zero-turn dbs); returns swarm totals, a busiest-first per-agent
|
||
rollup, swarm model mix, and a labelled `est_cost_usd`. Window
|
||
defaults to `24h`.
|
||
- `POST /api/cancel-reminder/{id}` — hard-delete a pending reminder.
|
||
- `POST /api/retry-reminder/{id}` — re-arm a reminder whose delivery
|
||
failed (clears the failure state so the scheduler retries).
|
||
- `GET /api/tool-groups` — returns `{ groups: Vec<String>,
|
||
assignments: BTreeMap<String, Vec<String>>,
|
||
descriptions: BTreeMap<String, String> }`. `groups` is the
|
||
ordered list of all known tool-group names (drives the column
|
||
headers in the P3RM1SS10NS tab); `assignments` is the per-agent
|
||
override map (absent agents use the role default); `descriptions`
|
||
maps each group name to a short human-readable string surfaced as
|
||
a column-header tooltip on hover.
|
||
- `POST /api/tool-groups/{agent}` — body `{ groups: ["name", …] }`.
|
||
Writes the tool-group set for `{agent}` to
|
||
`/var/lib/hyperhive/meta/tool-groups.json` and queues a rebuild so
|
||
`HIVE_TOOL_GROUPS` takes effect. Agent name validated;
|
||
`guard_agent_name` applied.
|
||
- `GET /api/capabilities` — returns `{ caps: Vec<String>,
|
||
assignments: BTreeMap<String, Vec<String>>,
|
||
descriptions: BTreeMap<String, String> }`. `caps` is the
|
||
ordered list of all known capability names; `assignments` is the
|
||
per-agent grant map (absent agents have no extra capabilities);
|
||
`descriptions` maps each capability name to a short human-readable
|
||
string surfaced as a column-header tooltip on hover.
|
||
- `POST /api/capabilities/{agent}` — body `{ caps: ["name", …] }`.
|
||
Writes the capability set for `{agent}` to
|
||
`/var/lib/hyperhive/meta/capabilities.json` and queues a rebuild so
|
||
`HIVE_CAPABILITIES` takes effect. Agent name validated;
|
||
unknown capability strings rejected (400). `guard_agent_name`
|
||
applied.
|
||
- `POST /api/permissions` — batch perm apply for the save-all
|
||
permissions button. Body
|
||
`{ changes: [{ agent, tool_groups?: ["name", …], capabilities?: ["name", …] }] }`.
|
||
Sparse per agent: an omitted field leaves that perm-type untouched,
|
||
an empty array clears it, a populated array fully replaces it (same
|
||
replace semantics as the per-agent endpoints above). Each affected
|
||
agent gets ONE combined `PermChange` queue entry, so changing both
|
||
an agent's tool-groups and capabilities triggers a single rebuild,
|
||
not two. **Atomic**: the endpoint validates every change first (agent names via
|
||
`guard_agent_name`, group + capability names) and on any validation
|
||
error writes or enqueues nothing (non-2xx `{ error }`); it skips
|
||
rows with both fields omitted rather than erroring on them.
|
||
Returns `200 "ok"` on
|
||
success.
|
||
- `POST /api/meta-update` — `nix flake update` the selected
|
||
`meta/flake.lock` inputs, then rebuild the affected agents.
|
||
- `GET /api/dashboard/stream` — unified live event channel:
|
||
broker `sent` / `delivered`, plus the mutation events listed
|
||
below. Each frame carries `seq`.
|
||
- `GET /api/dashboard/history` — last ~200 broker messages
|
||
(wrapped as `{ seq, events }`) for the message-flow
|
||
terminal's backfill on page load.
|
||
<!-- vale write-good.Passive = YES -->
|
||
|
||
## Dashboard event channel
|
||
|
||
Wire vocabulary on `/api/dashboard/stream` (kind tag is in the JSON
|
||
payload):
|
||
|
||
- `sent` / `delivered` — broker traffic, mirrored from the
|
||
intra-process channel by a forwarder task. Both carry `id: i64`
|
||
(the broker row id) and `in_reply_to: Option<i64>` for thread
|
||
rendering. The dashboard message-flow terminal renders reply
|
||
rows with a `↳ reply` tag that scroll-highlights the parent
|
||
row on selection. Used by the message-flow terminal renderer.
|
||
- `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. `transient_kind` is an **open**
|
||
display string (the running node's own tag), not a fixed
|
||
enum — render it, don't branch on it.
|
||
- `container_state_changed` (container: ContainerView) /
|
||
`container_removed` (name) — per-row container mutations,
|
||
emitted by `Coordinator::rescan_containers_and_emit` from
|
||
the job queue's own node execution (`job_queue::exec`, for example
|
||
after a rebuild's stop/swap/start steps or a destroy's teardown
|
||
step) — and from the 10s
|
||
`crash_watch` poll. Client upserts/removes by name and
|
||
reads the pending overlay from `transientsState` since the
|
||
payload doesn't carry it.
|
||
- `rebuild_queue_changed` (seq) — **payload-free trigger**, fired on
|
||
every queue mutation (enqueue, state transition, dedup collapse,
|
||
terminal-history trim). Unlike the snapshot events below it ships
|
||
no state at all: the client re-fetches `GET /api/jobq/graph`, which
|
||
is where it reads the queue from cold too.
|
||
- `reminders_changed` (seq, reminders: `Vec<PendingReminder>`) —
|
||
full snapshot of all pending reminders. Emitted after every
|
||
reminder mutation: agent `remind` calls (`agent_server`),
|
||
operator cancel / retry (`/api/system/reminders/*`), `cancel_loose_end`
|
||
with Reminder kind, and the scheduler tick after each delivery
|
||
batch (`reminder_scheduler`).
|
||
- `capabilities_changed` (seq, caps: `Vec<str>`, descriptions: map,
|
||
assignments: `BTreeMap<String, Vec<String>>`) — full snapshot of
|
||
capability grants. Emitted from the rebuild-queue worker after a
|
||
`PermChange` / Capabilities entry commits the JSON file. Payload
|
||
matches `GET /api/capabilities` shape so the client can call
|
||
`renderCapabilities` directly. P3RM1SS10NS tab subscribes; activation
|
||
re-fetch still runs as a safety net.
|
||
- `tool_groups_changed` (seq, groups: `Vec<str>`, descriptions: map,
|
||
assignments: `BTreeMap<String, Vec<String>>`) — full snapshot of
|
||
tool-group assignments. Emitted from the rebuild-queue worker after
|
||
a `PermChange` / ToolGroups entry commits the JSON file. Same
|
||
shape as `GET /api/tool-groups`; P3RM1SS10NS tab subscribes.
|
||
- `tombstones_changed` (seq, tombstones: `Vec<TombstoneView>`) —
|
||
full snapshot of destroyed-but-state-kept agents. Emitted on
|
||
destroy, purge, and revive. C0R3 K3PT ST4T3 tab subscribes.
|
||
- `meta_inputs_changed` (seq, inputs: `Vec<MetaInput>`) — full
|
||
snapshot of `meta/flake.lock` inputs. BU1LDS M3T4 1NPUTS tab
|
||
subscribes; same shape as `GET /api/meta-inputs`.
|
||
- `meta_update_running` (running: bool) — emitted when a
|
||
`nix flake update` ripple starts or completes. BU1LDS M3T4 1NPUTS
|
||
tab uses this to show/hide the "⏳ meta-update running" banner.
|
||
|
||
<!-- vale write-good.Passive = NO -->
|
||
`/api/state` is **only fetched on cold-load and on the few
|
||
forms that mutate non-event-derived state** (PURG3 +
|
||
meta-update, since tombstones + meta_inputs aren't event-
|
||
shaped yet). Every other section — transients,
|
||
containers, message flow —
|
||
derives from `/api/dashboard/stream` after the initial snapshot,
|
||
maintaining its own client-side store and applying events on
|
||
top.
|
||
<!-- vale write-good.Passive = YES -->
|
||
|
||
Generalised form helper: `form[data-confirm="…"]` pops
|
||
`confirm()` before submit.
|
||
|