docs: refresh for the dashboard rework + recent harness commits
- web-ui.md: side panel, approval card + 3-way diff base, stats page, forge config links, removed agent.nix viewer, per-agent loose-ends inline answer. - approvals.md: forge mirror section + diff base toggle. - turn-loop.md: recv(max), get_logs, remind, loose-ends, whoami. - agent.md / manager.md prompts: recv(max), remind, get_logs. - CLAUDE.md: forge.rs / stats.rs / hive-forge.nix in the file map, scratchpad refresh. also: forgejo migrations.ALLOW_LOCALNETWORKS = true so an in-hive mirror of the hyperhive repo can import from a localhost source.
This commit is contained in:
parent
94781ccd08
commit
6ab3810e18
7 changed files with 205 additions and 36 deletions
|
|
@ -28,7 +28,10 @@ happens after a decision lands.
|
|||
force-push, or `rm -rf` the proposed repo and the queued
|
||||
approval still points at an immutable git object inside
|
||||
applied.
|
||||
4. Operator sees the diff on the dashboard, clicks ◆ APPR0VE (or
|
||||
4. Operator sees the proposal as a card on the dashboard — a
|
||||
full multi-file diff, toggleable between three bases (vs the
|
||||
running tree / vs the last approved proposal / vs the
|
||||
previous queued proposal) — and clicks ◆ APPR0VE (or
|
||||
`hive-c0re approve <id>` on the CLI).
|
||||
5. hive-c0re moves the working tree to `proposal/<id>` and runs
|
||||
the build under a sequence of tags (see below). On success,
|
||||
|
|
@ -158,6 +161,30 @@ approval id to retry. Because tags are first-class git objects,
|
|||
rejected and failed trees stay browsable forever — `git log
|
||||
--tags` in the applied repo is the audit trail.
|
||||
|
||||
### Forge mirror
|
||||
|
||||
When the bundled `hive-forge` container is running — on by
|
||||
default, `hyperhive.forge.enable` — hive-c0re mirrors every
|
||||
agent's applied repo into a private `agent-configs` Forgejo
|
||||
org. `forge::push_config(<name>)` pushes `applied/main` plus
|
||||
every tag to `agent-configs/<name>` after each ref mutation:
|
||||
the spawn that seeds `deployed/0`, every `request_apply_commit`
|
||||
(which plants `proposal/<id>`), every approve / deny, and a
|
||||
sweep at startup. Pushes are best-effort — a missing or stopped
|
||||
forge never blocks a deploy.
|
||||
|
||||
The org is private and agents are not members, so only the
|
||||
`core` user (a Forgejo site admin) can read it: an agent can't
|
||||
reach another agent's config — or even its own — through the
|
||||
forge. The tokenised push URL is passed inline to `git push`,
|
||||
never written into `applied/<n>/.git/config`; that repo is
|
||||
RO-bind-mounted into the manager, and a stored token would leak
|
||||
core's admin credential to an agent.
|
||||
|
||||
The dashboard deep-links into this org — a `config repo` link
|
||||
per container row and a `commit on forge` link per approval
|
||||
card. See `docs/web-ui.md`.
|
||||
|
||||
### Manager view of applied + meta
|
||||
|
||||
The manager container gets three host-side bind mounts via
|
||||
|
|
|
|||
|
|
@ -102,11 +102,14 @@ it as a stdio child via `--mcp-config`. The hyperhive socket name is
|
|||
- `send(to, body)` — message a peer (logical agent name), another
|
||||
agent, or the operator (recipient `operator`, surfaces in the
|
||||
dashboard inbox).
|
||||
- `recv(wait_seconds?)` — drain one inbox message. Without
|
||||
- `recv(wait_seconds?, max?)` — drain inbox messages. Without
|
||||
`wait_seconds` (or with `0`) returns immediately, a cheap
|
||||
"anything pending?" peek. Positive value parks the turn up
|
||||
to that many seconds (cap 180) — incoming messages wake
|
||||
instantly, otherwise returns empty at the timeout.
|
||||
instantly, otherwise returns empty at the timeout. `max`
|
||||
(default 1, server-side cap 32) drains up to N popped rows
|
||||
in one round-trip; `wait_seconds` applies to the *first*
|
||||
message, then the call drains up to `max` total.
|
||||
- `ask(question, options?, multi?, ttl_seconds?, to?)` —
|
||||
surface a structured question. Same shape as the manager's;
|
||||
recipient defaults to the operator (dashboard) but can be set
|
||||
|
|
@ -119,6 +122,21 @@ it as a stdio child via `--mcp-config`. The hyperhive socket name is
|
|||
routed to this agent. Authorisation is strict: only the
|
||||
declared target (or the operator via the dashboard) can
|
||||
answer.
|
||||
- `get_loose_ends()` — list everything still pending against
|
||||
this agent: unanswered questions it asked / was asked, plus
|
||||
reminders it scheduled. Each row carries an id + kind for
|
||||
`cancel_loose_end`.
|
||||
- `cancel_loose_end(kind, id)` — withdraw a `question`
|
||||
(posts `[cancelled by <self>]` to unblock the asker) or a
|
||||
`reminder` (hard-delete before fire). Sub-agents may only
|
||||
cancel rows they own.
|
||||
- `remind(message, due)` — schedule a reminder that lands in
|
||||
this agent's own inbox at a future time (sender shows as
|
||||
`reminder`). Large payloads spill to
|
||||
`/agents/<self>/state/reminders/` with the inbox message a
|
||||
short pointer.
|
||||
- `whoami()` — `{ name, role, pronouns, hyperhive_rev }` for
|
||||
self-identification without scraping the system prompt.
|
||||
|
||||
### Waking the agent from inside the container
|
||||
|
||||
|
|
@ -191,6 +209,16 @@ meta's.
|
|||
that was routed to the manager (a sub-agent did
|
||||
`ask(to: "manager", ...)`). Surfaces in the asker's inbox as
|
||||
the same `question_answered` event.
|
||||
- `get_logs(agent, lines?)` — fetch recent journal lines for a
|
||||
sub-agent container (diagnose MCP-registration failures,
|
||||
startup crashes, etc.). Pass the plain logical agent name;
|
||||
hive-c0re resolves the machine name (`h-<name>`, manager
|
||||
`hm1nd`). `lines` defaults to 50, host-capped at 500.
|
||||
- `remind` / `get_loose_ends` / `cancel_loose_end` / `whoami` —
|
||||
same as the sub-agent tools above, but `get_loose_ends` is
|
||||
hive-wide (every agent's pending questions + reminders, not
|
||||
just the manager's) and `cancel_loose_end` may cancel any
|
||||
agent's row.
|
||||
|
||||
The boundary: lifecycle ops on *existing* sub-agents
|
||||
(`kill`/`start`/`restart`) are at the manager's discretion — no
|
||||
|
|
|
|||
108
docs/web-ui.md
108
docs/web-ui.md
|
|
@ -54,12 +54,21 @@ soon as they blur.
|
|||
**`<details>` open-state preservation:** any collapsible element
|
||||
tagged with `data-restore-key="<stable-key>"` survives the
|
||||
refresh. `snapshotOpenDetails()` walks managed sections before
|
||||
render, `restoreOpenDetails()` re-applies after. Used today for
|
||||
the journald viewer (`journal:<container>`), the agent-config
|
||||
viewer (`agent-config:<name>`), and approval diff blocks
|
||||
(`approval-diff:<id>`). Setting `.open = true` programmatically
|
||||
also fires the `toggle` event, so any lazy-fetch wired to it
|
||||
re-runs cleanly on restore.
|
||||
render, `restoreOpenDetails()` re-applies after. Long-content
|
||||
drill-ins (file previews, diffs, journald logs) now open in the
|
||||
**side panel** (see below) rather than expanding inline, so the
|
||||
only restore-keyed `<details>` left is the answered-questions
|
||||
history list.
|
||||
|
||||
**Side panel (dashboard):** long content opens in a drawer that
|
||||
swipes in from the right — a singleton `#side-panel` with a
|
||||
titled header, a close button, and a scrollable body. Closes on
|
||||
the button, a backdrop click, or `Escape`. `Panel.open(title,
|
||||
node)` swaps the body; the JS builders for file previews,
|
||||
approval diffs, and journald logs all render into it. Markdown
|
||||
file previews (`.md` / `.markdown`) render through the vendored
|
||||
`marked` bundle (`GET /static/marked.js`) into a `.md` block;
|
||||
other files stay raw in a `<pre>`.
|
||||
|
||||
Both bind their listeners with `SO_REUSEADDR` via
|
||||
`tokio::net::TcpSocket` plus a retry loop on `AddrInUse` (12 tries,
|
||||
|
|
@ -136,21 +145,50 @@ Two-line layout (`assets/app.js::renderContainers`):
|
|||
on sub-agents, `↺ R3ST4RT` + (sub-agents) `■ ST0P` when running,
|
||||
`▶ ST4RT` when stopped. Buttons dim + disable while a transient
|
||||
lifecycle action is in flight.
|
||||
- Plus two collapsible `<details>` blocks:
|
||||
- `↳ logs · <container>` — lazy-fetches journald output via
|
||||
`GET /api/journal/{name}?unit=...&lines=...` (`journalctl -M
|
||||
<container> -b --no-pager --output=short-iso`). A unit
|
||||
dropdown switches between the harness service (default) and
|
||||
the full machine journal; refresh button re-fetches.
|
||||
- `↳ agent.nix · <name>` — lazy-fetches the applied config
|
||||
file via `GET /api/agent-config/{name}` (read-only mirror of
|
||||
`/var/lib/hyperhive/applied/<name>/agent.nix`). Mutating
|
||||
this still requires `request_apply_commit` + approval.
|
||||
- Line 3: drill-in triggers —
|
||||
- `↳ logs · <container>` — opens the side panel and lazy-
|
||||
fetches journald via `GET /api/journal/{name}?unit=&lines=`
|
||||
(`journalctl -M <container> -b --no-pager --output=short-iso`).
|
||||
A unit dropdown (harness service / full machine journal) and
|
||||
a refresh button live in the panel.
|
||||
- `↳ config repo ↗` — link to the agent's applied config repo
|
||||
on the bundled forge (`agent-configs/<name>`), opened in a
|
||||
new tab. Shown only when `forge_present`. Replaces the old
|
||||
one-file `agent.nix` viewer — the forge shows the full repo
|
||||
with history. Mutating config still requires
|
||||
`request_apply_commit` + approval.
|
||||
|
||||
`↻ UPD4TE 4LL` button appears above the containers list when any
|
||||
agent is stale. Banner pulses on each broker SSE event
|
||||
(`pulseBanner` with a 4s grace timer).
|
||||
|
||||
### Approval card
|
||||
|
||||
Each pending approval renders as a card (`assets/app.js::
|
||||
renderApprovals`) with three stacked sections:
|
||||
|
||||
- **identity header** — glyph, `#id`, agent, kind chip, and (for
|
||||
`apply_commit`) the short proposal sha as `<code>`.
|
||||
- **what-changed body** — the manager's description, then
|
||||
drill-in triggers: `↳ view diff` opens the diff in the side
|
||||
panel; `↳ commit on forge ↗` deep-links the proposal commit
|
||||
into `agent-configs/<agent>` (shown only when `forge_present`).
|
||||
Spawn approvals show a one-line "container will be created"
|
||||
note instead.
|
||||
- **decision actions** — `◆ APPR0VE` and `DENY`. Deny pops a
|
||||
`prompt()` for an optional reason carried to the manager as
|
||||
`HelperEvent::ApprovalResolved.note`.
|
||||
|
||||
The diff panel has a 3-way base toggle — **vs applied** (the
|
||||
running tree, served instantly from the diff already on the
|
||||
approval), **vs last-approved**, **vs previous proposal** — the
|
||||
latter two fetched on click from `GET /api/approval-diff/{id}
|
||||
?base=approved|previous`. Each line is classified client-side
|
||||
(`+` / `-` / `@@` / `--- ` / `+++ ` → add / del / hunk / file).
|
||||
|
||||
A `pending · N` / `history · N` tab pair switches the section
|
||||
between the live queue and the last 30 resolved approvals.
|
||||
|
||||
### Browser notifications
|
||||
|
||||
Pure frontend (`Notification` API). Three signals trigger them:
|
||||
|
|
@ -198,9 +236,13 @@ not ours.
|
|||
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`.
|
||||
a managed container; rendered in the side panel.
|
||||
- `GET /api/approval-diff/{id}?base=applied|approved|previous` —
|
||||
on-demand unified diff for an `ApplyCommit` approval against
|
||||
the chosen base (running tree / last approved proposal /
|
||||
previous queued proposal). Raw diff text, classified
|
||||
client-side. `GET /static/marked.js` serves the vendored
|
||||
`marked` bundle the side panel uses 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
|
||||
|
|
@ -343,8 +385,16 @@ Layout, top to bottom:
|
|||
- Inbox `<details>` block (collapsed): `inbox · N` — last 30
|
||||
messages addressed to this agent, fetched via
|
||||
`AgentRequest::Recent { limit: 30 }`. (Separate from
|
||||
`AgentRequest::Recv { wait_seconds }` which the harness uses
|
||||
internally to long-poll the broker.)
|
||||
`AgentRequest::Recv { wait_seconds, max }` which the harness
|
||||
uses internally to long-poll the broker.)
|
||||
- Loose-ends `<details>` block: `loose ends · N` — questions,
|
||||
approvals, and reminders pending against this agent (the
|
||||
`get_loose_ends` data, via `GET /api/loose-ends`). Question
|
||||
rows carry an inline answer form (textarea — Enter submits,
|
||||
Shift+Enter newlines); submitting POSTs cross-origin to the
|
||||
core dashboard's `/answer-question/{id}` so the operator
|
||||
answers *as operator*. The per-agent socket deliberately gets
|
||||
no operator-authority path — see `TODO-ops.md`.
|
||||
- Terminal-wrap: live event tail (sticky-bottom auto-scroll +
|
||||
`↓ N new` pill when not at bottom) followed by an
|
||||
operator-input textarea acting as a prompt.
|
||||
|
|
@ -454,3 +504,19 @@ Bus events (new vocabulary on `/events/stream`):
|
|||
totals).
|
||||
- `turn_state_changed { state, since_unix }` — drives the
|
||||
state badge (`idle`/`thinking`/`compacting`).
|
||||
|
||||
### Stats page
|
||||
|
||||
`GET /stats` is a separate per-agent page (served by the
|
||||
harness, linked from the per-agent page's `📊 stats →` and from
|
||||
each dashboard container row). Turn analytics, read-only, from
|
||||
`/state/hyperhive-turn-stats.sqlite`. `GET /api/stats?window=
|
||||
24h|7d|30d` returns a time-bucketed `Snapshot`; the page renders
|
||||
it with Chart.js (vendored from a CDN). Charts: turns,
|
||||
duration (p50 · p95 · avg), context tokens, token cost per
|
||||
bucket, a **turns-by-model** stacked bar (model choice drives
|
||||
token cost, so it sits directly under the cost chart), and
|
||||
doughnuts for tool / wake-source / result mix. A summary chip
|
||||
row carries window totals. `stats.rs` opens the sqlite db
|
||||
read-only and degrades to an empty snapshot on any error — the
|
||||
page is decorative, never authoritative.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue