This commit is contained in:
parent
91a72b5cbb
commit
e9cce17828
1 changed files with 194 additions and 184 deletions
330
docs/web-ui.md
330
docs/web-ui.md
|
|
@ -100,23 +100,69 @@ Both bind their listeners with `SO_REUSEADDR` via
|
||||||
exponential backoff capped at 2s) so an nspawn restart that races
|
exponential backoff capped at 2s) so an nspawn restart that races
|
||||||
the previous process's socket release resolves itself.
|
the previous process's socket release resolves itself.
|
||||||
|
|
||||||
## Dashboard sections (top to bottom)
|
## Dashboard layout
|
||||||
|
|
||||||
1. **Notification row** — `🔔 enable notifications` button when
|
The dashboard (`/`) has a fixed chrome header at the top and a
|
||||||
permission ungranted; `🔕 mute / 🔔 unmute` toggle once granted;
|
`<main>` that shows exactly one tab pane at a time. The URL hash
|
||||||
inline "unsupported / blocked" message when applicable. Sits
|
(`#swarm`, `#call`, `#system`) drives which pane is active; hash
|
||||||
under the banner.
|
changes don't reload the page. FL0W is a separate full-page
|
||||||
2. **C0NTAINERS** — live containers with their action surface.
|
terminal at `/flow.html` — its tab-strip entry is a cross-page
|
||||||
Pulsing red banner at the top of this section if any two
|
link (`◆ FL0W ◆ →`), not a pane swap.
|
||||||
sub-agents hash to the same port (`port_conflicts` from
|
|
||||||
`/api/state`): the operator must rename one of them and
|
**Chrome header** (fixed, overlays the active tab pane):
|
||||||
rebuild. `lifecycle::{spawn,rebuild}` also preflight this and
|
- **Tab strip**: `◆ SW4RM ◆`, `◆ Y3R C4LL ◆`, `◆ SYST3M ◆`, and
|
||||||
refuse with a clear error message naming the conflicting agent.
|
`◆ FL0W ◆ →` (page link). Count pills on SW4RM (container count)
|
||||||
3. **K3PT ST4T3** — destroyed-but-state-kept tombstones (size +
|
and Y3R C4LL (pending approvals + questions); FL0W pill mirrors
|
||||||
age + claude-creds badge). Two actions: `⊕ R3V1V3` (queues a
|
the operator inbox length (hidden when zero).
|
||||||
Spawn approval; existing state is reused), `PURG3` (wipes
|
- **Notification controls**: `🔔 enable notifications` when
|
||||||
state + applied dirs; `POST /purge-tombstone/{name}`).
|
permission ungranted; `🔕 mute / 🔔 unmute` toggle once granted.
|
||||||
4. **M3T4 1NPUTS** — inputs in `meta/flake.lock` the operator can
|
Always visible in the chrome regardless of active tab.
|
||||||
|
- **Banner-thin** (`░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░`)
|
||||||
|
— sits below the tab strip.
|
||||||
|
|
||||||
|
### SW4RM tab
|
||||||
|
|
||||||
|
**C0NTAINERS** — live containers rendered as a depth-first
|
||||||
|
tree using `ContainerView.parent` (populated by `topology.rs`).
|
||||||
|
Each container's row is prefixed with ASCII tree glyphs (`├─`,
|
||||||
|
`└─`, `│ ` continuation columns) showing the agent
|
||||||
|
parent/child hierarchy. When every container has `parent = null`
|
||||||
|
(flat topology) the tree collapses to a plain list with no
|
||||||
|
glyphs. Children are sorted alphabetically within each parent;
|
||||||
|
roots likewise. Cycles in the parent graph are tolerated —
|
||||||
|
orphaned containers (not reachable from any root) are appended
|
||||||
|
as roots so no agent disappears. 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.
|
||||||
|
|
||||||
|
### Y3R C4LL tab
|
||||||
|
|
||||||
|
Things blocked on operator decision — approvals and questions
|
||||||
|
share a tab because they're the same concept ("something is
|
||||||
|
waiting on you").
|
||||||
|
|
||||||
|
**P3NDING APPR0VALS** — the queue (see "Approval card" below).
|
||||||
|
The R3QU3ST SP4WN form lives at the top of this section.
|
||||||
|
|
||||||
|
**M1ND H4S QU3STI0NS** — pending operator-targeted `ask`
|
||||||
|
questions (amber pulsing border). Free-text fallback always
|
||||||
|
rendered alongside any option list; `multi=true` renders options
|
||||||
|
as checkboxes; submit merges selections + free text
|
||||||
|
comma-joined. Each row has a `✗ CANC3L` button. Questions with
|
||||||
|
a `ttl_seconds` show a `⏳ MM:SS` chip; the host-side watchdog
|
||||||
|
auto-cancels with `[expired]` when the deadline fires.
|
||||||
|
|
||||||
|
### SYST3M tab
|
||||||
|
|
||||||
|
Passive / rare-interaction state.
|
||||||
|
|
||||||
|
**M3T4 1NPUTS** — inputs in `meta/flake.lock` the operator can
|
||||||
selectively `nix flake update`, rendered as an indented tree:
|
selectively `nix flake update`, rendered as an indented tree:
|
||||||
every fetched input at every depth (`hyperhive`,
|
every fetched input at every depth (`hyperhive`,
|
||||||
`hyperhive/nixpkgs`, `agent-<n>`, `agent-<n>/mcp-<x>`, …), each
|
`hyperhive/nixpkgs`, `agent-<n>`, `agent-<n>/mcp-<x>`, …), each
|
||||||
|
|
@ -126,13 +172,12 @@ the previous process's socket release resolves itself.
|
||||||
control sits above the tree. Checking inputs + submitting bumps
|
control sits above the tree. Checking inputs + submitting bumps
|
||||||
the lock in `/meta/` and rebuilds the selected agents in
|
the lock in `/meta/` and rebuilds the selected agents in
|
||||||
sequence; each outcome reaches the manager as a `rebuilt`
|
sequence; each outcome reaches the manager as a `rebuilt`
|
||||||
system event.
|
system event. `POST /meta-update`. While a lock-bump ripple runs,
|
||||||
`POST /meta-update`. The lock bump + rebuild ripple runs in the
|
the panel shows a pulsing "⏳ meta-update running" banner and the
|
||||||
background; while it does, the panel shows a pulsing "⏳
|
update button is disabled (snapshot field `meta_update_running`,
|
||||||
meta-update running" banner and the update button is disabled
|
live event `meta_update_running`).
|
||||||
(snapshot field `meta_update_running`, live event
|
|
||||||
`meta_update_running`).
|
**R3BU1LD QU3U3** — pending and recently-completed container
|
||||||
5. **R3BU1LD QU3U3** — pending and recently-completed container
|
|
||||||
operations: rebuilds, meta-update cascades, and first-spawns.
|
operations: rebuilds, meta-update cascades, and first-spawns.
|
||||||
One operation runs at a time; the worker drains FIFO. Each row
|
One operation runs at a time; the worker drains FIFO. Each row
|
||||||
shows a state glyph (`⏸` queued / `▶` running / `✔` done /
|
shows a state glyph (`⏸` queued / `▶` running / `✔` done /
|
||||||
|
|
@ -143,57 +188,48 @@ the previous process's socket release resolves itself.
|
||||||
rebuilds nest under their parent entry (`parent_id` grouping;
|
rebuilds nest under their parent entry (`parent_id` grouping;
|
||||||
`rqe-child` CSS class). Dedup: re-enqueueing a still-queued op
|
`rqe-child` CSS class). Dedup: re-enqueueing a still-queued op
|
||||||
for the same agent collapses into the existing entry. Running
|
for the same agent collapses into the existing entry. Running
|
||||||
entries tick elapsed seconds live (same pattern as the TTL
|
entries tick elapsed seconds live. Cold-loaded from
|
||||||
countdown). Cold-loaded from `/api/state.rebuild_queue`; live
|
`/api/state.rebuild_queue`; live updates via `rebuild_queue_changed`
|
||||||
updates via `rebuild_queue_changed` snapshot event.
|
snapshot event.
|
||||||
6. **M1ND H4S QU3STI0NS** — pending operator-targeted `ask`
|
|
||||||
questions, i.e. rows with `target IS NULL` (peer-to-peer
|
**QU3U3D R3M1ND3RS** — reminders agents have scheduled for
|
||||||
questions live in the same table but never surface here)
|
|
||||||
(amber pulsing border). Free-text fallback always rendered
|
|
||||||
alongside any option list; `multi=true` renders options as
|
|
||||||
checkboxes; submit merges selections + free text comma-joined.
|
|
||||||
Each row has a `✗ CANC3L` button that resolves the question
|
|
||||||
with `[cancelled]`. Questions with a `ttl_seconds` show a
|
|
||||||
`⏳ MM:SS` chip; the host-side watchdog auto-cancels with
|
|
||||||
`[expired]` when the deadline fires.
|
|
||||||
7. **QU3U3D R3M1ND3RS** — reminders agents have scheduled for
|
|
||||||
themselves (via the `remind` tool) but not yet delivered.
|
themselves (via the `remind` tool) but not yet delivered.
|
||||||
Each row shows the owner, due time, and message; a `CANC3L`
|
Each row shows the owner, due time, and message; a `CANC3L`
|
||||||
button hard-deletes (`POST /cancel-reminder/{id}`) and a
|
button hard-deletes (`POST /cancel-reminder/{id}`) and a
|
||||||
`R3TRY` button re-arms one whose delivery failed
|
`R3TRY` button re-arms one whose delivery failed
|
||||||
(`POST /retry-reminder/{id}`). Backed by `GET /api/reminders`.
|
(`POST /retry-reminder/{id}`). Backed by `GET /api/reminders`.
|
||||||
8. **P3NDING APPR0VALS** — the queue (see "Approval card"
|
|
||||||
below). The R3QU3ST SP4WN form lives at the top of this
|
**K3PT ST4T3** — destroyed-but-state-kept tombstones (size +
|
||||||
section since submitting it immediately queues an approval
|
age + claude-creds badge). Two actions: `⊕ R3V1V3` (queues a
|
||||||
that lands directly below.
|
Spawn approval; existing state is reused), `PURG3` (wipes
|
||||||
9. **0PER4T0R 1NB0X** — recent messages addressed to `operator`,
|
state + applied dirs; `POST /purge-tombstone/{name}`).
|
||||||
derived client-side from the dashboard event stream (no longer
|
|
||||||
a snapshot field). Cold load seeds from
|
### FL0W page (`/flow.html`)
|
||||||
`/dashboard/history`'s 200-message backfill; subsequent
|
|
||||||
`sent` events with `to == "operator"` are appended live. Cap
|
A dedicated full-page terminal (not a tab pane — a separate HTML
|
||||||
50, newest-first.
|
page). Reuses the same `<header class="dashboard-chrome">` chrome
|
||||||
10. **MESS4GE FL0W** — live broker tail wrapped in a
|
as the dashboard so the tab strip remains visible; SW4RM / Y3R
|
||||||
`.terminal-wrap` (same chrome as the per-agent terminal).
|
C4LL / SYST3M are cross-page links back to `/#<tab>`, and the
|
||||||
Cold load backfills the last ~200 messages from
|
FL0W entry is marked active (`aria-current="page"`).
|
||||||
`/dashboard/history`; live frames arrive on
|
|
||||||
`/dashboard/stream` and dispatch through
|
**0PER4T0R 1NB0X** — recent messages addressed to `operator`,
|
||||||
`HiveTerminal.create`. Each row is one broker event —
|
derived client-side from the dashboard event stream. Cold load
|
||||||
`sent` or `delivered` — with `from → to: body`; per-agent
|
seeds from `/dashboard/history`'s 200-message backfill; subsequent
|
||||||
thinking / tool calls / claude chatter stay out of this
|
`sent` events with `to == "operator"` are appended live. Cap 50,
|
||||||
view, only what passes through hive-c0re's broker. Sticky-
|
newest-first.
|
||||||
bottom auto-scroll + "↓ N new" pill match the per-agent
|
|
||||||
page. Below the stream sits a terminal-style compose box:
|
**MESS4GE FL0W** — live broker tail wrapped in a `.terminal-wrap`.
|
||||||
`@name` picks the recipient (sticky across sends via
|
Cold load backfills the last ~200 messages from `/dashboard/history`;
|
||||||
localStorage; auto-complete from the live container list,
|
live frames arrive on `/dashboard/stream`. Each row is one broker
|
||||||
Tab/Enter to confirm; `@*` broadcasts to every registered
|
event — `sent` or `delivered` — with `from → to: body`. Sticky-
|
||||||
agent), starting a message with `@<name> body` retargets
|
bottom auto-scroll + "↓ N new" pill. Below the stream sits a
|
||||||
in one stroke, plain text sends to the sticky recipient.
|
terminal-style compose box: `@name` picks the recipient (sticky via
|
||||||
`POST /op-send` drops `{from:"operator", to, body}` into
|
localStorage; auto-complete from the live container list, Tab/Enter
|
||||||
the broker and returns 200; the resulting SSE frame
|
to confirm; `@*` broadcasts). `POST /op-send` drops
|
||||||
re-renders both the terminal row and the inbox section
|
`{from:"operator", to, body}` into the broker; the resulting SSE
|
||||||
(no `/api/state` refetch). Manager is addressed as
|
frame re-renders both the terminal row and the inbox section.
|
||||||
`@manager` (the broker recipient string), not `@hm1nd`
|
Manager is addressed as `@manager` (the broker recipient string),
|
||||||
(the container name); the auto-complete swaps automatically.
|
not `@hm1nd` (the container name).
|
||||||
|
|
||||||
### Container row
|
### Container row
|
||||||
|
|
||||||
|
|
@ -436,101 +472,75 @@ Generalised form helpers: `form[data-confirm="…"]` pops
|
||||||
|
|
||||||
## Per-agent page
|
## Per-agent page
|
||||||
|
|
||||||
Layout, top to bottom:
|
Three fixed-position layers frame a full-viewport terminal:
|
||||||
|
|
||||||
- Banner (gradient shimmer while state=thinking).
|
**Fixed-overlay header** (`<header class="agent-header">`): frosted
|
||||||
- Title with `↑ DASHB04RD` back-link (new tab) + `↻ R3BU1LD`.
|
glass — `backdrop-filter: blur` lets scrolled terminal rows show
|
||||||
- Meta links row: backend-supplied `StateSnapshot.links` (issue
|
through. Left to right:
|
||||||
#262) rendered as `<icon> label →`. Always includes `📊 stats`
|
- Agent icon (`<img src="/icon">`).
|
||||||
(`kind = Container`); `🖥 screen` when the VNC compositor is
|
- Title (`<h2 id="title">`) + meta-nav (`<nav id="meta-links">`):
|
||||||
enabled; `⬡ forge` (profile) + `↳ config` (agent-configs
|
backend-supplied `StateSnapshot.links` rendered as icon-only
|
||||||
mirror, repo root since the agent doesn't know its own deployed
|
anchors. Always includes `📊 stats` (`kind = Container`);
|
||||||
sha) when the agent has a forge account, both `kind = Forge`;
|
`🖥 screen` when the VNC compositor is enabled; `⬡ forge` (profile)
|
||||||
followed by any `hyperhive.dashboardLinks` extras
|
+ `↳ config` (agent-configs mirror) when the agent has a forge
|
||||||
(`kind = External`) read from
|
account; followed by any `hyperhive.dashboardLinks` extras
|
||||||
`{state_dir}/hyperhive-dashboard-links.json`. The same list
|
(`kind = External`). The dashboard card's icon strip is the same
|
||||||
feeds the dashboard card's icon strip via the host's
|
list via `GET /api/agent/{name}/links` — agent backend is the
|
||||||
`GET /api/agent/{name}/links` passthrough proxy — agent backend
|
single source of truth.
|
||||||
is the single source of truth for what links it exposes.
|
- **State row** (`<div id="state-row">`): alive badge + state badge
|
||||||
Frontend resolves each `kind` (`container` → same-origin path,
|
+ model chip + ctx badge + cost badge + last-turn chip + cancel
|
||||||
`forge` → `http://host:3000`, `external` → absolute) via DOM
|
button + new-session button.
|
||||||
building, so agent-declared strings never reach `innerHTML`.
|
- Alive badge: `● alive` (green) / `⊘ rate limited` (red) /
|
||||||
- Status section: empty when online (alive-badge in the state
|
`◌ needs login` / `◌ logging in` / `○ offline` / `… connecting`.
|
||||||
row carries the signal), populated with the login form /
|
Driven by `LiveEvent::StatusChanged`.
|
||||||
OAuth URL when `status` is `needs_login_*`.
|
|
||||||
- **State row**: alive badge + state badge + model chip + ctx
|
|
||||||
badge + last-turn timing + cancel-turn button + new-session
|
|
||||||
button. Every chip carries a `title=...` tooltip with the
|
|
||||||
detailed breakdown.
|
|
||||||
- Alive badge: `● alive` (green) / `⊘ rate limited` (red, while
|
|
||||||
the harness is parked after a 429 — clears automatically when
|
|
||||||
the sleep expires) / `◌ needs login` (amber) / `◌ logging in` /
|
|
||||||
`○ offline` / `… connecting`. Driven by
|
|
||||||
`LiveEvent::StatusChanged`; replaces the old "harness alive
|
|
||||||
— turn loop running" paragraph so the state row carries
|
|
||||||
every reachability signal.
|
|
||||||
- State badge: `💤 idle` / `🧠 thinking` / `📦 compacting` /
|
- State badge: `💤 idle` / `🧠 thinking` / `📦 compacting` /
|
||||||
`○ offline` / `… booting`, with an age suffix (`12s`,
|
`○ offline` / `… booting` + age suffix. Driven by
|
||||||
`2m 14s`). Driven by `LiveEvent::TurnStateChanged`
|
`LiveEvent::TurnStateChanged ({ state, since_unix })`.
|
||||||
(`{state, since_unix}`) — the bus emits on every
|
- Model chip: `model · <name>`. Driven by `LiveEvent::ModelChanged`.
|
||||||
`Bus::set_state` so the badge updates without a /api/state
|
- Ctx badge: `ctx · 142k` — last inference's prompt size (the
|
||||||
refetch. Cold-load via `/api/state.turn_state` +
|
context window utilisation number to watch before compacting).
|
||||||
`turn_state_since`.
|
Tooltip shows % of window when `context_window_tokens` is known.
|
||||||
- Model chip: `model · <name>` (e.g. `model · haiku`). Driven
|
- Cost badge: `cost · 1.3M` — cumulative tokens billed across every
|
||||||
by `LiveEvent::ModelChanged`; emitted from `Bus::set_model`.
|
inference in the last turn. Tool-heavy turns rebill the cached
|
||||||
- Ctx badge: `ctx · 142k` — last inference's prompt size
|
prefix per call, so this routinely exceeds the window — cost
|
||||||
(input + cache_read + cache_write of the most recent
|
signal, not size signal.
|
||||||
model call in the just-ended turn). This is the **actual
|
- Both driven by `LiveEvent::TokenUsageChanged { ctx, cost }` at
|
||||||
context window utilisation** — the number to watch when
|
turn-end.
|
||||||
deciding whether to compact. When `context_window_tokens`
|
- `■ cancel turn` (visible while thinking) → `POST /api/cancel`.
|
||||||
is available from `/api/state`, the badge tooltip shows the
|
- `↻ new session` (always, amber) → `POST /api/new-session`; next
|
||||||
percentage of window used.
|
turn drops `--continue`.
|
||||||
- Cost badge: `cost · 1.3M` — cumulative tokens billed
|
- **Inbox pill** (`📬 inbox · N`): hidden when empty; click opens the
|
||||||
across **every inference** in the last turn (sum of all
|
inbox flyout in the side panel.
|
||||||
per-call prompts). Tool-heavy turns rebill the cached
|
- **Loose-ends pill** (`🪢 loose ends · N`): hidden when empty; click
|
||||||
prefix per call, so this routinely exceeds the model's
|
opens the loose-ends flyout in the side panel.
|
||||||
window — it's a cost signal, not a size signal.
|
|
||||||
- Both badges driven by `LiveEvent::TokenUsageChanged {
|
|
||||||
ctx, cost }`, emitted once at turn-end from
|
|
||||||
`Bus::record_turn_usage`. The harness tracks per-inference
|
|
||||||
usage by walking `assistant` events in the stream-json
|
|
||||||
and updating `last_inference` on each one; the `result`
|
|
||||||
event supplies `cost` and triggers the emit.
|
|
||||||
- Last-turn chip: `last turn 12.3s` appears after the first
|
|
||||||
turn ends, computed from the state-since deltas.
|
|
||||||
- `■ cancel turn` button: visible only while state=thinking,
|
|
||||||
POSTs `/api/cancel`.
|
|
||||||
- `↻ new session` button: always visible, amber. Confirms
|
|
||||||
via `window.confirm()` then POSTs `/api/new-session` to
|
|
||||||
arm a one-shot Bus flag — the next turn drops
|
|
||||||
`--continue`, starting a fresh claude session. Subsequent
|
|
||||||
turns resume normal `--continue`.
|
|
||||||
|
|
||||||
Polling: `/api/state` is fetched **once** on cold load, and
|
`/api/state` is fetched once on cold load (+ while
|
||||||
again while `status === 'needs_login_in_progress'` (login
|
`status === 'needs_login_in_progress'`); all other updates arrive via
|
||||||
session output isn't event-shaped yet). Every other badge
|
SSE. Snapshot includes `context_window_tokens` for the ctx badge tooltip.
|
||||||
updates from SSE; no periodic refresh timer runs. Snapshot
|
|
||||||
includes `context_window_tokens` (effective window size for
|
**Main content** (`<main class="agent-main">`): fills the viewport
|
||||||
the agent's current model, from `events::context_window_tokens`)
|
and scrolls behind the fixed header + footer.
|
||||||
used to compute percentage-of-window in the ctx badge tooltip.
|
- `#status` overlay: empty when online; shows the login form / OAuth
|
||||||
- Inbox `<details>` block (collapsed): `inbox · N` — last 30
|
URL when `status` is `needs_login_*`.
|
||||||
messages addressed to this agent, fetched via
|
|
||||||
`AgentRequest::Recent { limit: 30 }`. Reply messages (those
|
|
||||||
with a non-null `in_reply_to`) are indented and prefixed with
|
|
||||||
`↳ reply ·` in amber. (Separate from
|
|
||||||
`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 `docs/boundary.md`.
|
|
||||||
- Terminal-wrap: live event tail (sticky-bottom auto-scroll +
|
- Terminal-wrap: live event tail (sticky-bottom auto-scroll +
|
||||||
`↓ N new` pill when not at bottom) followed by an
|
`↓ N new` pill when not at bottom).
|
||||||
operator-input textarea acting as a prompt.
|
|
||||||
|
**Fixed-overlay footer** (`<footer class="agent-composer">`): frosted
|
||||||
|
glass, symmetric with the header. Contains the operator-input
|
||||||
|
textarea (`#term-input`) — multi-line, Enter sends, Shift+Enter
|
||||||
|
newlines, Tab-completes slash commands (see "Terminal-embedded
|
||||||
|
prompt" below).
|
||||||
|
|
||||||
|
**Side panel** (slide-in from right): singleton shared with the
|
||||||
|
dashboard's side panel shape. Carries inbox and loose-ends flyouts
|
||||||
|
(opened via the header pills) as well as long content (file previews,
|
||||||
|
diffs, journald logs). Inbox flyout: last 30 messages addressed to
|
||||||
|
this agent (`AgentRequest::Recent { limit: 30 }`); reply messages
|
||||||
|
indented with `↳ reply ·` in amber. Loose-ends flyout: questions,
|
||||||
|
approvals, and reminders pending against this agent (`GET /api/loose-ends`);
|
||||||
|
question rows carry an inline answer form that POSTs cross-origin to
|
||||||
|
the core dashboard's `/answer-question/{id}` so the operator answers
|
||||||
|
*as operator* (see `docs/boundary.md`).
|
||||||
|
|
||||||
### Live view
|
### Live view
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue