The pills on 4G3NTS, R3BU1LD QU3U3 and K3PT ST4T3, the poll that fed them, the two count helpers, their styles, and the tab strip's unused badge option.
622 lines
33 KiB
Markdown
622 lines
33 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 `/`.
|
|
It has a fixed chrome header at the top and a `<main>` that shows exactly
|
|
one tab pane at a time. The URL hash (`#agents`, `#rebuild`, `#meta`,
|
|
`#buildlogs`, `#kept`) drives which pane is
|
|
active; hash changes don't reload the page. ST4TS
|
|
is a standalone page reached from the pages menu, not from the
|
|
dashboard tab strip.
|
|
|
|
**Chrome header** (fixed, overlays the active tab pane):
|
|
- **Pages menu**: a link icon at the right end of the chrome, beside the
|
|
profile avatar (`links-menu.js`, `@hive/shared`'s `LinksMenu`, the same
|
|
component the swarm UI uses for its service links), opening a popover with links to **stats**
|
|
(`/stats.html`), **api docs** (`/api/docs/`) and **swarm** (the swarm
|
|
UI at the swarm apex domain, opened in a new tab). The swarm entry
|
|
comes from `state.swarm_public_url`, which hive-c0re reads from
|
|
`HIVE_SWARM_PUBLIC_URL` (set from `services.hyperhive.swarm.domain`);
|
|
with no domain set there is no swarm entry. The standalone pages link back to `/`.
|
|
- **Profile menu**: the signed-in user's avatar at the right end of the
|
|
chrome (`user-menu.js`, the same component as the swarm UI's and the agent
|
|
page's). It opens a popover with the user's name and links to authelia's
|
|
settings and sign-out pages. The name comes from `/api/whoami`, which the
|
|
dashboard's nginx vhost proxies to authelia, and the links from
|
|
`state.authelia_public_url`, which hive-c0re reads from
|
|
`HIVE_AUTHELIA_PUBLIC_URL` (set from `services.hyperhive.swarm.authelia.url`).
|
|
If the name doesn't load the avatar shows a question mark, and without the URL the
|
|
popover has no links.
|
|
- **Tab strip**: `◆ 4G3NTS ◆`, `◆ R3BU1LD QU3U3 ◆`,
|
|
`◆ BUILD L0GS ◆`, `◆ K3PT ST4T3 ◆`. In-page tabs only — ST4TS
|
|
lives on its own page, reached from the pages menu. Count pills on
|
|
4G3NTS (stale containers), R3BU1LD QU3U3 (rebuilds in flight) and
|
|
K3PT ST4T3 (kept-state entries).
|
|
- **Banner-thin** (`░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░`)
|
|
— sits below the tab strip.
|
|
- **No warning banner.** No page shows hive-level warnings (host disk
|
|
pressure, failing background sweeps, forge boot failures, agents that
|
|
need a claude login). They live in a push-based registry
|
|
(`hive-c0re`'s `warnings.rs`): any subsystem raises or clears its own
|
|
entry through an RAII guard, and each raise logs one
|
|
`hive warning raised` line. The operator sees the registry as metrics and
|
|
Grafana alerts: see
|
|
[Hive-scoped metrics](../scheduler/observability.md#hive-scoped-metrics-hive-c0re).
|
|
A container crash is a swarm-level Grafana alert on the crash watcher's
|
|
own log line, so a hive with no otel export has no operator-visible
|
|
warning signal beyond `/health/ready`.
|
|
- **Browser tab title** — `hyperhive // h1ve-c0re` by default; updated
|
|
to `<swarm> / <hive> // h1ve-c0re` once `hive_name` / `swarm_name`
|
|
arrive in the state snapshot.
|
|
|
|
## 4G3NTS 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.
|
|
|
|
## K3PT ST4T3 tab
|
|
|
|
Passive / rare-interaction state. `kept.js` renders it from the
|
|
`/api/state` snapshot and keeps it live from the `tombstones_changed`
|
|
stream event. (The rebuild queue and build logs are dashboard tabs too —
|
|
see below.)
|
|
|
|
Per-container CPU, memory and disk load isn't shown in the hive UI. The
|
|
same figures go out as OTEL metrics and appear in Grafana (see
|
|
[observability](../scheduler/observability.md)). An agent's CPU and memory
|
|
caps live per agent in the swarm UI.
|
|
|
|
**K3PT ST4T3** — 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, applied
|
|
repo, 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.
|
|
|
|
## Build tabs (`#rebuild`, `#buildlogs`)
|
|
|
|
The build lifecycle — rebuild queue, live build log, and
|
|
build log history — is two tabs of the dashboard: **R3BU1LD QU3U3**
|
|
(`#rebuild`) and **BUILD L0GS**
|
|
(`#buildlogs`). They live in `builds.js`, which `tabs.js` imports: the
|
|
dashboard's one tab strip, `/api/state` snapshot and
|
|
`/api/dashboard/stream` subscription serve them, with
|
|
`rebuild_queue_changed` on the stream alongside the dashboard's other
|
|
kinds.
|
|
The 4G3NTS tab does **not** read the graph endpoint, 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. The banner's "view queue →"
|
|
link goes to the `#rebuild` tab.
|
|
|
|
**R3BU1LD QU3U3** — pending, in-flight, and recently settled container
|
|
operations: rebuilds 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 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, 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.
|
|
|
|
|
|
**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.
|
|
|
|
## 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 dashboard's pages menu,
|
|
minimal chrome (a back-link and a title). 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.deploy.hive-controller.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.
|
|
|
|
## 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/agents.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 crash-recover rebuild, a boot-sweep 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
|
|
R3BU1LD QU3U3 tab). 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 `agents.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 lock 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-4G3NTS 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; greyed out 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 pages menu's **api docs** entry), 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.
|
|
- `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 the BUILD L0GS tab. 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 the build tabs. Works on both finished and in-progress
|
|
build logs.
|
|
- `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/dashboard/stream` — unified live event channel:
|
|
broker `sent` / `delivered`, plus the mutation events listed
|
|
below. Each frame carries `seq`.
|
|
<!-- 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.
|
|
- `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`).
|
|
- `tombstones_changed` (seq, tombstones: `Vec<TombstoneView>`) —
|
|
full snapshot of destroyed-but-state-kept agents. Emitted on
|
|
destroy, purge, and revive. The K3PT ST4T3 tab subscribes.
|
|
|
|
|
|
<!-- 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, since
|
|
tombstones aren't event-shaped yet). Every other section — transients,
|
|
containers —
|
|
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.
|
|
|