docs: prune landed todos + refresh scratchpad + as-built terminal-rendering
todo: drop landed entries (terminal coherence pass, get_state_file defense-in-depth, self-management of loose ends, persist+cold-load ctx-badge). claude.md: - scratchpad: new just-landed entries for ctx+cost badge split, terminal coherence pass, loose_ends rename + cancel_loose_end, whoami, reminder failure persistence, path linkify, tombstones+ meta_inputs events, agent open-threads section + container pending- reminder chip + task event rendering. drops the meta-flake "just landed" — structural facts live in the file map + approvals.md, the narrative was no longer load-bearing. - file map: hive-fr0nt now lists MARKED_JS + marked.min.js + the unified prefix-column terminal.css update. - reading paths: terminal-rendering.md description matches as-built. docs/terminal-rendering.md: rewritten as as-built reference. layout contract documents the padding-left + negative text-indent prefix column + how details inherits it. row taxonomy reflects current state (notes split into .note / .note.stderr / .note.op; .sys is amber; recv tool_results default-open with markdown body via tool_use_id correlation; rich send/ask/answer renderers). new sections for renderer dispatch flow, markdown integration, fmtArgsGeneric extra-MCP fallback, dashboard msgrow text-indent reset.
This commit is contained in:
parent
2b38805c00
commit
69a3ca7469
3 changed files with 195 additions and 212 deletions
134
CLAUDE.md
134
CLAUDE.md
|
|
@ -77,19 +77,27 @@ hive-c0re/ host daemon + CLI (one binary, subcommand-dispatched)
|
|||
assets/ index.html, dashboard.css, app.js (include_str!)
|
||||
|
||||
hive-fr0nt/ shared frontend-assets crate (browser only).
|
||||
src/lib.rs pub const BASE_CSS / TERMINAL_CSS / TERMINAL_JS
|
||||
re-exports; both binaries `include_str!` them
|
||||
and prepend to their per-page serving routes.
|
||||
src/lib.rs pub const BASE_CSS / TERMINAL_CSS / TERMINAL_JS /
|
||||
MARKED_JS re-exports; both binaries
|
||||
`include_str!` them and prepend to their per-
|
||||
page serving routes.
|
||||
assets/base.css Catppuccin palette + body typography (one source
|
||||
of truth, no per-page redeclaration).
|
||||
assets/terminal.css `.terminal-wrap` + `.live` + `.tail-pill` +
|
||||
`.row` / `details.row` styling for both
|
||||
pages' lit log panes.
|
||||
pages' lit log panes. Unified prefix-column
|
||||
(padding-left + negative text-indent) so glyph
|
||||
alignment is consistent across row kinds + a
|
||||
`.md` block scope for marked-rendered bodies.
|
||||
assets/terminal.js `window.HiveTerminal.create(opts)`: scroll-
|
||||
sticky log + "↓ N new" pill + history
|
||||
backfill + SSE subscribe-buffer-snapshot-
|
||||
dedupe dance. Pages register a kind→renderer
|
||||
map; the terminal owns the lifecycle.
|
||||
assets/marked.min.js vendored marked v4.0.2 UMD bundle. Per-agent
|
||||
terminal uses the global `marked.parse` for
|
||||
markdown bodies on send / recv / ask / answer
|
||||
/ assistant text rows.
|
||||
|
||||
hive-ag3nt/ in-container harness crate; produces TWO binaries
|
||||
src/lib.rs re-exports + DEFAULT_SOCKET, DEFAULT_WEB_PORT
|
||||
|
|
@ -141,7 +149,8 @@ read them à la carte.
|
|||
[`docs/web-ui.md`](docs/web-ui.md).
|
||||
- **"How does the per-agent terminal classify + colour
|
||||
events?"** → [`docs/terminal-rendering.md`](docs/terminal-rendering.md)
|
||||
(taxonomy + known inconsistencies + a proposed coherence pass).
|
||||
(as-built row taxonomy + layout contract + markdown +
|
||||
extra-MCP fallback).
|
||||
- **"How does claude get its prompt and what tools does it have?"** →
|
||||
[`docs/turn-loop.md`](docs/turn-loop.md).
|
||||
- **"How do config changes flow from manager to operator to
|
||||
|
|
@ -174,31 +183,96 @@ read them à la carte.
|
|||
In-flight or recent context that hasn't earned a section yet.
|
||||
Prune freely.
|
||||
|
||||
- **Just landed:** meta-flake overhaul. Each agent's applied
|
||||
repo is a module-only flake (forwards every `inputs.*`
|
||||
through to `agent.nix` as the `flakeInputs` module arg —
|
||||
manager edits `inputs` to pull in external flakes like an
|
||||
MCP server's own flake; the new sha lands in the agent's
|
||||
own `flake.lock` and rolls up to meta's). A single
|
||||
hive-c0re-owned repo at `/var/lib/hyperhive/meta/`
|
||||
declares one input per agent and one
|
||||
`nixosConfigurations.<n>` output, wrapping the agent's
|
||||
`nixosModules.default` with identity + `HIVE_PORT` /
|
||||
`HIVE_LABEL` / `HIVE_DASHBOARD_PORT` /
|
||||
`HIVE_OPERATOR_PRONOUNS`. Containers run against
|
||||
`meta#<n>`. Every approve uses two-phase staging
|
||||
(prepare → build → finalize/abort) so meta's git log only
|
||||
records successful deploys; failures + denials live as
|
||||
annotated tags in applied. All meta operations
|
||||
serialize behind a tokio mutex; stale `.git/index.lock`
|
||||
is cleared on hive-c0re startup. Manager has `/applied`
|
||||
+ `/meta` RO-bound + the `applied` remote pre-wired in
|
||||
every proposed repo. Migration runs idempotently on
|
||||
startup (`HIVE_SKIP_META_MIGRATION=1` skips). Operator
|
||||
pronouns are a NixOS module option
|
||||
(`services.hive-c0re.operatorPronouns`, default
|
||||
`"she/her"`); the harness substitutes them into the
|
||||
system prompt at boot.
|
||||
- **Just landed:** ctx + cost badges split. The per-agent
|
||||
page now shows TWO chips — `ctx · N` (last inference's
|
||||
prompt size = actual context window utilisation, parsed
|
||||
from each `assistant` event's `.message.usage`; the
|
||||
number to watch for compaction) and `cost · M` (sum
|
||||
across every inference in the turn, the previous
|
||||
behaviour now correctly labelled — tool-heavy turns
|
||||
rebill the cached prefix per call and blow past the
|
||||
model's window). Both fed by a single
|
||||
`TokenUsageChanged { ctx, cost }` SSE event at turn-end
|
||||
via `Bus::record_turn_usage`. `turn_stats` grew four
|
||||
`last_*_tokens` columns (idempotent ALTER migration) so
|
||||
cold-load seeds both badges from the most recent row.
|
||||
Pre-migration rows yield no `ctx` seed (empty badge
|
||||
until next turn) rather than a misleading zero.
|
||||
- **Just landed:** per-agent terminal coherence pass.
|
||||
Unified prefix column (padding-left + negative
|
||||
text-indent so every row kind aligns); `<details>`
|
||||
summaries drop the directional glyph and let CSS
|
||||
`▸/▾` sit in the prefix column; turn boundaries
|
||||
de-weighted (border-left rule only, no bold/margin/
|
||||
tint); stderr lines render orange `!`,
|
||||
operator-initiated notes mauve italic, catch-all `.sys`
|
||||
escalated to orange so unrecognised stream-json
|
||||
surfaces. Message-bearing tool calls
|
||||
(`send`/`ask`/`answer`/`recv`) render default-open
|
||||
with markdown bodies via vendored `marked` v4.0.2
|
||||
(`hive-fr0nt::MARKED_JS`); assistant `text` rows also
|
||||
markdown-rendered. Extra-MCP tools get a generic
|
||||
args pretty-printer (`fmtArgsGeneric`) instead of
|
||||
raw JSON. tool_use_id → name map carries through the
|
||||
stream so `renderToolResult` knows when a result came
|
||||
from `recv` and should default-open with markdown.
|
||||
See `docs/terminal-rendering.md` for the as-built
|
||||
taxonomy. Bonus: ctx badge seeded from `turn_stats`
|
||||
on cold load via `Bus::seed_usage` so the chip paints
|
||||
real numbers before the next turn finishes.
|
||||
- **Just landed:** `open_threads` → `loose_ends` rename
|
||||
(more honest about what the list is) + new
|
||||
`cancel_loose_end(kind, id)` MCP tool on both
|
||||
surfaces. `kind = "question"` posts a `[cancelled by
|
||||
<self>]` answer to unblock the asker; `kind =
|
||||
"reminder"` hard-deletes before fire. Auth: sub-agent
|
||||
must own the row (`asker == self` / `owner == self`);
|
||||
manager bypasses for hive-wide cleanup. `LooseEnd`
|
||||
enum gained a `Reminder { id, owner, message, due_at,
|
||||
age_seconds }` variant; sub-agent flavour filters by
|
||||
owner, manager unfiltered. Shared dispatch in
|
||||
`hive-c0re/src/questions.rs::handle_cancel_loose_end`.
|
||||
Per-agent web UI's `/api/open-threads` →
|
||||
`/api/loose-ends`. Closes the agent-side "I have no
|
||||
way to cancel what I queued" friction from the
|
||||
ergonomics wishlist.
|
||||
- **Just landed:** `whoami` MCP tool on both surfaces —
|
||||
returns `{ name, role, pronouns, hyperhive_rev }`.
|
||||
Lets an agent self-identify without scraping its own
|
||||
system prompt; pronouns are pulled from the
|
||||
`HIVE_OPERATOR_PRONOUNS` env that the system prompt
|
||||
also substitutes, so the two stay in sync.
|
||||
- **Just landed:** reminder delivery failures persist +
|
||||
surface. The 5s scheduler now records the failure
|
||||
reason on the `reminders` row instead of silently
|
||||
dropping; the web UI surfaces them in the loose-ends
|
||||
section so the owner agent can see "this reminder
|
||||
never landed because the target was destroyed" without
|
||||
reading journald.
|
||||
- **Just landed:** path linkify. The broker tags every
|
||||
message body server-side with `file_refs` at ingest
|
||||
via `/api/state-file/check` (validates each path,
|
||||
attaches container→host resolution). Last segment
|
||||
must look like `name.ext` so directory mentions don't
|
||||
fire spurious links. Dashboard renders refs as
|
||||
collapsible path-preview blocks below the message
|
||||
body; per-agent terminal picks them up from the same
|
||||
field. Server-side validation means the JS doesn't
|
||||
re-walk allow-lists on every render.
|
||||
- **Just landed:** tombstones + meta_inputs as
|
||||
`DashboardEvent`s. Closes the last two refetch loops
|
||||
on the dashboard side — `purge-tombstone` and
|
||||
`meta-update` POSTs now flip to 200 with
|
||||
`data-no-refresh`. The 5s `/api/state` poll is gone
|
||||
entirely; everything event-driven.
|
||||
- **Just landed:** per-agent UI gained an open-threads
|
||||
section (questions + approvals + reminders pending
|
||||
against this agent) + the container row on the
|
||||
dashboard gained a `⏰ N` pending-reminder count
|
||||
chip. task_started / task_notification stream-json
|
||||
events now pretty-render in the terminal with the
|
||||
`⌁` glyph so subagent (Task tool) activity is
|
||||
visually distinct from main-session tool calls.
|
||||
- **Just landed:** per-agent extra MCP servers via the
|
||||
`hyperhive.extraMcpServers.<key>` NixOS option in
|
||||
`agent.nix`. Declares `{ command, args, env,
|
||||
|
|
|
|||
5
TODO.md
5
TODO.md
|
|
@ -18,13 +18,11 @@
|
|||
|
||||
## Dashboard
|
||||
|
||||
- ~~**Per-agent terminal coherence pass**~~ ✓ landed — unified prefix column (padding-left + negative text-indent so every row kind aligns), `<details>` summaries drop directional glyph (CSS `▸/▾` sits in prefix column instead), turn boundaries de-weighted (border-left only, no bold/margin/tint), stderr lines render orange `!`, operator-initiated notes mauve italic, catch-all `.sys` escalated to orange so unrecognised stream-json surfaces. Message-bearing tool calls (`send`/`ask`/`answer`/`recv`) render default-open with markdown bodies via vendored `marked` v4.0.2; assistant text also markdown-rendered. Extra-MCP tools get a generic args pretty-printer instead of raw JSON. See [`docs/terminal-rendering.md`](docs/terminal-rendering.md) for the as-built taxonomy.
|
||||
- **Delivered-reminder rollups**: per-agent delivered-count chip (last 24h) + histogram of attempts-vs-successes on the container row. Needs `Broker::count_delivered_reminders_since(agent, ts)` (cheap COUNT against the `reminders` table, `WHERE agent = ?1 AND sent_at >= ?2`).
|
||||
|
||||
## Security
|
||||
|
||||
- **Privsep the dashboard from the privileged daemon**: hive-c0re runs as root (it has to — `nixos-container` create / start / destroy, the meta git repo, every per-agent bind mount). The HTTP server lives in the same process, so every read-endpoint (`/api/state-file`, `/api/journal/{name}`, `/api/agent-config/{name}`) is one allow-list bug away from serving arbitrary host files. Split the architecture: keep the privileged daemon doing lifecycle + git + ipc, run the web UI as an unprivileged user that talks to the daemon over a unix socket with a narrow request surface (`ReadAgentStateFile { agent, rel_path }` etc.). The unprivileged process can't read `/etc/shadow` even if every check in `get_state_file` is bypassed — it doesn't have the bits. Container-lifecycle POSTs (`/restart`, `/destroy`, etc.) become forwarded RPCs the privileged side authorises on its terms.
|
||||
- ~~**Defense in depth on `get_state_file`**~~ ✓ landed — `resolve_state_path` (shared by `get_state_file` + `scan_validated_paths`) now: (a) walks each path component below the matched root via `symlink_metadata` and refuses outright if any is a symlink (so an agent planting `ln -s /var/lib/hyperhive/agents/other/state/secret /agents/me/state/peek` can't have its target proxied — `canonicalize` would happily resolve past the allow-list check otherwise); (b) refuses any `..` traversal below the root with a friendlier error than "escapes allow-list"; (c) refuses files whose mode isn't world-readable (`mode & 0o004 == 0`) so a 0600 file inside `state/` doesn't leak via the endpoint; (d) bundles the metadata fetch into the resolve helper so callers don't restat. New tests in `hive-c0re/src/dashboard.rs::tests` cover leaf-symlink, mid-path-symlink, `..` traversal, and plain-dir-passthrough cases.
|
||||
|
||||
## Harness Ergonomics (agent-side wishlist)
|
||||
|
||||
|
|
@ -40,7 +38,6 @@ how often the friction bites in normal use.
|
|||
into the prompt builder in `hive-ag3nt::turn.rs`. Even better: add a
|
||||
one-shot `recv_batch(max: u32)` MCP tool that returns up to `max`
|
||||
pending messages in a single round-trip.
|
||||
- ~~**Self-management of own asks + reminders**~~ ✓ landed — unified with `get_loose_ends` (renamed from `get_open_threads` per the naming pass). `LooseEnd` enum (renamed from `OpenThread`) gained a `Reminder { id, owner, message, due_at, age_seconds }` variant (sub-agent flavour filters by `owner == self`; manager unfiltered). New `mcp__hyperhive__cancel_loose_end(kind, id)` on both surfaces — `kind` is `"question"` (asker gets `[cancelled by <self>]` answer, unblocks) or `"reminder"` (hard-deleted before fire). Auth: sub-agent must own the row; manager bypasses for hive-wide cleanup. New helpers `OperatorQuestions::cancel` + `Broker::cancel_reminder_as` push the auth check down so both flavours stay aligned. Shared dispatch in `hive-c0re/src/questions.rs::handle_cancel_loose_end`. Per-agent web UI's `/api/open-threads` → `/api/loose-ends` too, with reminder-row rendering added.
|
||||
- **Optional `in_reply_to: <msg_id>` on send** — pure wire addition; no
|
||||
behavioural change. The dashboard could render conversation threads
|
||||
(already wants this for the agent-to-agent question UI in the
|
||||
|
|
@ -57,8 +54,6 @@ how often the friction bites in normal use.
|
|||
|
||||
## Harness Behaviour
|
||||
|
||||
- **Persist + cold-load current context size on the per-agent page**: the `ctx-badge` (Claude Code's bottom-right "N tokens" indicator) currently only populates after the first `TokenUsageChanged` SSE event arrives, which is the *next* turn — until then the badge is empty. Operator can't see "this agent is at 78% context" before deciding to manually compact / reset / message it. Last known token usage should be persisted (likely a small `/state/hyperhive-token-usage` blob, or a row in turn_stats already has it — pull last row's totals on cold load) and returned by `/api/state` so the badge paints with real numbers on first render.
|
||||
|
||||
- **Auto session-reset when context is large and cache is cold**: today every turn uses `--continue`, so a long-lived agent carries its entire transcript forward indefinitely. When the next turn's context is above some threshold (rough starting point: ~50% of the session limit — hive startup alone burned ~15%, so the headroom disappears fast) *and* the prompt cache is no longer warm (last turn ended past the cache TTL), it's cheaper to start fresh than to re-send the whole history uncached. Open question: drop `--continue` vs. trigger `--compact` first — needs measurement of what each actually costs (uncached re-read of N tokens vs. a compact turn's own token spend + the post-compact uncached re-read). Decision should be data-driven, not guessed. Needs: a context-size estimate per turn (turn_stats already tracks token usage), a cache-warmth heuristic (time since last turn vs. cache TTL), and a one-shot fresh-session path in `turn.rs` mirroring the existing `↻ new session` button.
|
||||
|
||||
## Bugs
|
||||
|
|
|
|||
|
|
@ -1,196 +1,110 @@
|
|||
# Per-agent terminal: row taxonomy + inconsistencies
|
||||
# Per-agent terminal: row taxonomy (as built)
|
||||
|
||||
Snapshot of how the per-agent web UI's live pane renders each
|
||||
event kind today, written up so the next coherence pass has a
|
||||
reference to work from. Source of truth lives in
|
||||
`hive-ag3nt/assets/app.js` (`renderStream`,
|
||||
`fmtToolUse`, `renderRichToolUse`, `renderToolResult`,
|
||||
`renderTaskEvent`) + `hive-fr0nt/assets/terminal.css` (the
|
||||
shared `.live .<class>` styling).
|
||||
event kind today. Source of truth lives in
|
||||
`hive-ag3nt/assets/app.js` (`renderStream`, `fmtToolUse`,
|
||||
`renderRichToolUse`, `renderToolResult`, `renderTaskEvent`,
|
||||
`mdNode`, `detailsOpenMd`, `fmtArgsGeneric`) +
|
||||
`hive-fr0nt/assets/terminal.css` (the shared `.live .<class>`
|
||||
styling) + `hive-fr0nt/assets/marked.min.js` (markdown).
|
||||
|
||||
## Layout contract
|
||||
|
||||
Every row — flat `<div class="row …">` and expandable
|
||||
`<details class="row …">` alike — shares one prefix column.
|
||||
The mechanism is `padding-left + negative text-indent` on
|
||||
`.live .row`: the row's first character (the prefix glyph)
|
||||
gets pulled back into the column at ~0.5em, and wrapped
|
||||
continuation lines hang under the body, not under the glyph.
|
||||
|
||||
`<details>` summaries inherit those metrics. The disclosure
|
||||
marker (`▸` / `▾`) is supplied by CSS `summary::before` so it
|
||||
lands in the same column as flat-row glyphs. To make that
|
||||
work the JS-side summary text **does not** include a
|
||||
directional `→` / `←` — the row's colour (cyan = outbound,
|
||||
muted = inbound) carries the direction, and the prefix
|
||||
column never has to fit two glyphs side-by-side.
|
||||
|
||||
Child blocks inside a row (the `.md` markdown wrapper, an
|
||||
inner `<details>`) get `text-indent: 0` so their content
|
||||
lays out from the body column instead of inheriting the
|
||||
parent's negative pull.
|
||||
|
||||
## Row taxonomy
|
||||
|
||||
| CSS class | Prefix glyph | Color | Triggered by | Source |
|
||||
|---|---|---|---|---|
|
||||
| `.turn-start` | `◆ TURN ← <from>` | amber, bold, top-margin, amber left rule | `LiveEvent::TurnStart` | harness wake |
|
||||
| `.turn-body` | (inline under turn-start) | fg dimmed 85% | same | the wake-prompt body |
|
||||
| `.turn-end-ok` | `✓ turn ok` | green, green left rule | `LiveEvent::TurnEnd { ok: true }` | harness |
|
||||
| `.turn-end-fail` | `✗ turn fail — note` | red, red left rule | `LiveEvent::TurnEnd { ok: false }` | harness |
|
||||
| `.text` | none | fg/white, indented | claude `assistant.content[].text` | stream-json |
|
||||
| `.thinking` | `·` or `· thinking …` | muted, italic | claude `assistant.content[].thinking` | stream-json |
|
||||
| `.tool-use` | `→ Name args…` | cyan | `assistant.content[].tool_use` | stream-json |
|
||||
| `.tool-use` `<details>` | `→ Name path · +N` | cyan, body is diff | rich tool_use (Write/Edit) | renderRichToolUse |
|
||||
| `.tool-use` `<details>` | `→ send → to · headline` | cyan, body is text | mcp__hyperhive__send | renderRichToolUse |
|
||||
| `.tool-result` | `← <txt>` | muted | short `user.content[].tool_result` (≤120c) | stream-json |
|
||||
| `.tool-result-block` `<details>` | `▸ ← Nl · headline` | muted, body is text | long `tool_result` (>120c) | stream-json |
|
||||
| `.tool-use` | `⌁ task <id> started · <desc> [type]` | cyan | claude Task-tool subagent event | renderTaskEvent |
|
||||
| `.turn-end-ok` / `.turn-end-fail` / `.tool-result` | `⌁ task <id> ✓/✗/◌ <status> · <desc> · → <output_file>` | green / red / muted | claude Task-tool result | renderTaskEvent |
|
||||
| `.note` | `· <text>` | muted | `LiveEvent::Note` (harness chatter, /cancel /compact /new-session, stderr lines, etc.) | harness |
|
||||
| **`.sys`** | `· {json…}` | **muted** | **anything `renderStream` doesn't recognise** | catch-all |
|
||||
| `.result` | (defined, never emitted today) | green | — | — |
|
||||
| Banner shimmer | mauve | turn in flight (ref-counted) | `setBannerActive` |
|
||||
| `.turn-start` | `◆ TURN ← <from>` | amber, left rule | `LiveEvent::TurnStart` | harness wake |
|
||||
| `.turn-body` | (child div under turn-start) | fg | same | the wake-prompt body |
|
||||
| `.turn-end-ok` | `✓ turn ok` | green, left rule | `LiveEvent::TurnEnd { ok: true }` | harness |
|
||||
| `.turn-end-fail` | `✗ turn fail — note` | red, left rule | `LiveEvent::TurnEnd { ok: false }` | harness |
|
||||
| `.text` | (no prefix; markdown body) | fg | claude `assistant.content[].text` | stream-json |
|
||||
| `.thinking` | `· thinking …` | muted, italic | claude `assistant.content[].thinking` | stream-json |
|
||||
| `.tool-use` (flat) | `→ Name args…` | cyan | tool_use w/o rich renderer | stream-json |
|
||||
| `.tool-use` `<details>` | `Write/Edit <path> · +N` (no `→`) | cyan, body is +/- diff | `renderRichToolUse` Write/Edit | stream-json |
|
||||
| `.tool-use` `<details open>` | `send → to · NL`, `ask → to`, `answer #id` | cyan, body is markdown | rich renderer for send / ask / answer | stream-json |
|
||||
| `.tool-result` (flat) | `← <txt>` | muted | short `tool_result` (≤120c, non-recv) | stream-json |
|
||||
| `.tool-result-block` `<details>` | `Nl · headline` | muted, body is text | long generic `tool_result` | stream-json |
|
||||
| `.tool-result-block` `<details open>` | `recv ← <txt>` | muted, body is markdown | `tool_result` correlated to a prior `recv` tool_use via id | stream-json |
|
||||
| `.tool-use` | `⌁ task <id> started · <desc> [type]` | cyan | claude Task-tool subagent start | `renderTaskEvent` |
|
||||
| `.turn-end-ok` / `.turn-end-fail` / `.tool-result` | `⌁ task <id> ✓/✗/◌ <status> · <desc> · → <output_file>` | green / red / muted | claude Task-tool result | `renderTaskEvent` |
|
||||
| `.note` | `· <text>` | muted | harness chatter | `LiveEvent::Note` |
|
||||
| `.note.stderr` | `! stderr: <line>` | amber/orange | stderr lines off claude | `LiveEvent::Note` (`text` starts `stderr:`) |
|
||||
| `.note.op` | `· operator: <text>` | mauve italic | operator-initiated notes (/cancel, /compact, /model, new-session) | `LiveEvent::Note` (`text` starts `operator:`) |
|
||||
| `.sys` | `! {json…}` | amber/orange | catch-all for stream shapes `renderStream` didn't classify | catch-all |
|
||||
| Banner shimmer | mauve | turn in flight (ref-counted) | — | `setBannerActive` |
|
||||
|
||||
## Where the inconsistencies live
|
||||
## Renderer dispatch
|
||||
|
||||
1. **Glyph vocabulary drifts**:
|
||||
- tool_use uses `→`
|
||||
- tool_result uses `←`
|
||||
- thinking uses `·`
|
||||
- notes use `·`
|
||||
- turn-end ok/fail use `✓ / ✗`
|
||||
- turn-start uses `◆`
|
||||
- task events use `⌁`
|
||||
- The `·` glyph is overloaded across thinking, notes, sys.
|
||||
`renderStream(v, api)` walks each stream-json line:
|
||||
|
||||
2. **What gets a `<details>` block vs a flat row** is per-tool
|
||||
ad-hoc: Write/Edit always expand, send always expands, every
|
||||
other tool_use is flat regardless of input size.
|
||||
`tool_result` is flat if ≤120 chars otherwise `<details>`.
|
||||
1. Drops `system/init`, `rate_limit_event`, `result` (noise /
|
||||
handled elsewhere — `result` powers the `cost` badge).
|
||||
2. `subtype == "task_started" | "task_notification"` →
|
||||
`renderTaskEvent` (subagent activity gets the `⌁` glyph).
|
||||
3. `type == "assistant"` → walk `message.content[]`:
|
||||
- `text` → `.text` row with a markdown body via `mdNode`.
|
||||
- `thinking` → `.thinking` row.
|
||||
- `tool_use` → record `id → name` in `toolNameById`, try
|
||||
`renderRichToolUse` (Write/Edit/send/ask/answer get
|
||||
custom renderings); on miss fall through to a flat
|
||||
`.tool-use` row with `fmtToolUse → fmtArgsGeneric`.
|
||||
4. `type == "user"` → walk `message.content[]` for
|
||||
`tool_result`; `renderToolResult` correlates via
|
||||
`tool_use_id → toolNameById` to default-open `recv`
|
||||
results with a markdown body, else short = flat /
|
||||
long = collapsed details.
|
||||
5. Unrecognised shape → `.sys` row (amber, `!` glyph).
|
||||
|
||||
3. **Stderr handling**: claude's stderr lines come through as
|
||||
`LiveEvent::Note` with `text: "stderr: <line>"` — they
|
||||
render as muted `· stderr: …`, identical styling to harness
|
||||
notes about /compact / /model. No red-tinted "this is an
|
||||
error" affordance for stderr.
|
||||
## Markdown
|
||||
|
||||
4. **Catch-all `.sys` rows** are visually identical to `.note`
|
||||
rows — both muted, both `·` prefix. They look like normal
|
||||
notes despite usually being "an event renderStream couldn't
|
||||
classify". Unmatched stream shapes (rate limit warnings under
|
||||
odd type/subtype combos, future claude additions, etc.)
|
||||
silently fall through.
|
||||
`mdNode(text)` wraps `window.marked.parse(text)` (vendored
|
||||
v4.0.2 UMD via `hive-fr0nt::MARKED_JS`) in a `<div
|
||||
class="md">`. CSS in `terminal.css` scopes paragraph / code /
|
||||
list / blockquote / link styling under `.live .row .md` so
|
||||
the markdown body doesn't bleed into the row's own
|
||||
text-indent. Falls back to plain text if `marked` didn't
|
||||
load. Applied to `text` rows and to send / ask / answer /
|
||||
recv message bodies.
|
||||
|
||||
5. **`.tool-use` ranges from one-liner (`Read foo.md`) to
|
||||
multi-page collapsed diff** — same color, same prefix glyph,
|
||||
very different visual weight. A small marker on the collapsed
|
||||
`<details>` summary would help (the `▸` is present in
|
||||
`.tool-result-block` summaries but absent in tool-use
|
||||
`<details>` summaries).
|
||||
## Extra-MCP tools
|
||||
|
||||
6. **Cancel/compact/new-session notes** are styled the same as
|
||||
autonomous harness chatter; nothing flags them as "operator
|
||||
initiated."
|
||||
`fmtArgsGeneric(name, input)` is the fallback when a tool
|
||||
isn't in the built-in `fmtToolUse` switch:
|
||||
|
||||
7. **Turn-start / turn-end are visually overweight**: the
|
||||
triggering event row + the closing row both get bold text,
|
||||
top margins, and a full coloured background tint. The
|
||||
coloured left rule alone already says "this is a turn
|
||||
boundary" — the heavy chrome adds noise without information.
|
||||
Drop the bold/margin/tint, keep the left rule.
|
||||
- single string field → `name k: "v"`
|
||||
- single number/bool field → `name k: v`
|
||||
- multi-field → first 4 pairs trimmed to `k: "v"` /
|
||||
`k: [N]` / `k: {…}` with a `…+N` overflow
|
||||
|
||||
8. **Left-alignment is incoherent across row types**: the `▸`
|
||||
disclosure marker on expandable rows doesn't sit in the
|
||||
same column as flat-row prefix glyphs (`→`, `←`, `·`), so
|
||||
the prefix column wanders by row kind. Expandable tool_use
|
||||
(`→ Name …`) and expandable tool_result (`▸ ← Nl …`) use
|
||||
different layouts from each other too. Pick one prefix
|
||||
column and align every row kind into it; the disclosure
|
||||
marker should be visually inside that column, not pushed
|
||||
to the side.
|
||||
|
||||
9. **Continuation lines aren't inset**: when a row's body
|
||||
wraps (e.g. `→ foo bar baz quux …`), the second visual
|
||||
line starts at column 0 rather than under the `f` of
|
||||
`foo`, so wrapped content blurs into the next row. Want a
|
||||
`text-indent` / hanging-indent rule so continuation lines
|
||||
align with the start of the body, not the prefix glyph.
|
||||
|
||||
10. **Most message-bearing rows are collapsed by default**
|
||||
even when the body is the whole point: `send`, `recv`,
|
||||
`ask`, and friends hide their text behind a `<details>`
|
||||
summary. The summary headline is rarely enough context.
|
||||
Want these expanded by default, with collapse reserved
|
||||
for genuinely heavy payloads (multi-page diffs, long
|
||||
tool_result blocks).
|
||||
|
||||
11. **Tools from extra MCP servers aren't pretty-printed**:
|
||||
`renderRichToolUse` only special-cases the built-in
|
||||
hyperhive tools (Write/Edit/send). Anything coming from
|
||||
`extraMcpServers` (matrix, bitburner, …) falls through
|
||||
to the generic `→ Name args…` JSON dump. Either a
|
||||
plugin-style hook in the renderer map, or at minimum a
|
||||
nicer generic args-pretty-printer that handles common
|
||||
shapes (single string arg, single dict arg, etc.).
|
||||
|
||||
12. **No markdown rendering on message bodies**: `send` /
|
||||
`recv` / `ask` / `answer` / agent text content all
|
||||
arrive as markdown-flavoured prose (bullets, fenced
|
||||
code, bold, links) but render as raw text. Want a
|
||||
minimal markdown pass — at least lists, fenced code,
|
||||
inline code, bold/italic, links — applied to message
|
||||
bodies and probably to the `assistant.content[].text`
|
||||
`.text` row.
|
||||
|
||||
## Suggested coherence pass
|
||||
|
||||
Pick one scheme and audit all renderers to match. A concrete
|
||||
proposal:
|
||||
|
||||
- **`→` cyan**: outbound action (tool_use, send)
|
||||
- **`←` muted**: inbound result (tool_result)
|
||||
- **`◆` amber**: turn framing (turn_start)
|
||||
- **`✓ / ✗`**: success / failure, green / red (turn_end,
|
||||
task_notification)
|
||||
- **`⌁` mauve**: subagent / background event (task_*)
|
||||
- **`·` muted**: ambient note, italic for thinking
|
||||
- **`!` orange**: caught error (stderr lines, .sys catch-all
|
||||
that landed something the renderer didn't recognise)
|
||||
|
||||
Plus: every tool_use `<details>` summary gets `▸` so collapsed
|
||||
content is visually announced. Operator-initiated notes get a
|
||||
distinct prefix (`op·` or similar) so they're easier to spot in
|
||||
the scrollback.
|
||||
|
||||
The `.sys` catch-all should escalate visually — a louder
|
||||
"unrecognised event" rendering surfaces silently-dropped event
|
||||
shapes for future fix-up rather than hiding them in the muted
|
||||
note stream.
|
||||
|
||||
### Layout rules to apply uniformly
|
||||
|
||||
- **One prefix column for every row kind** — flat rows and
|
||||
expandable rows alike. The disclosure marker (`▸`) lives
|
||||
inside that column, not as a separate gutter, so glyph
|
||||
alignment doesn't shift between row types.
|
||||
- **Hanging indent on wrapped bodies** so continuation lines
|
||||
start under the first character of the body, not under the
|
||||
prefix glyph. Probably `display: grid` with
|
||||
`grid-template-columns: <prefix-col> 1fr` per row, or
|
||||
`text-indent`/`padding-left` with negative `text-indent`.
|
||||
- **Turn boundaries are rule-only** — drop the bold + margin
|
||||
+ tint on `.turn-start` / `.turn-body` / `.turn-end-*`.
|
||||
The coloured left rule alone carries the boundary signal.
|
||||
- **Default-expanded message rows**: `send`, `recv`, `ask`,
|
||||
`answer`, and short-ish `text` rows render their body
|
||||
inline (no `<details>`). Reserve collapse for genuinely
|
||||
heavy bodies — multi-page diffs, long tool_results,
|
||||
thinking blocks past N lines.
|
||||
|
||||
### Renderer surface for extra MCP tools
|
||||
|
||||
`renderRichToolUse` currently switches on hard-coded tool
|
||||
names. Replace with a registry keyed by
|
||||
`mcp__<server>__<tool>` (and the built-in claude tool names)
|
||||
that maps to a `(toolUse) -> Node` renderer. Per-agent extra
|
||||
MCPs can register their own renderers via a small JS hook
|
||||
(loaded the same way `extra-mcp.json` is loaded server-side);
|
||||
the fallback is a generic args-pretty-printer that handles
|
||||
single-string and single-dict shapes nicely instead of dumping
|
||||
raw JSON.
|
||||
|
||||
### Markdown rendering
|
||||
|
||||
Apply a minimal markdown pass to message bodies (`send` /
|
||||
`recv` / `ask` / `answer`) and to assistant `text` rows.
|
||||
Scope: paragraphs, lists, fenced + inline code, bold/italic,
|
||||
links. No HTML passthrough, no images, no tables —
|
||||
explicitly bounded so we don't import a kitchen-sink parser
|
||||
into the per-agent page. A small handwritten pass or a tiny
|
||||
dep (e.g. `micromark` / `marked`) both work.
|
||||
This keeps `mcp__matrix__send_message` and similar from
|
||||
dumping raw JSON.
|
||||
|
||||
## Dashboard side (not covered here)
|
||||
|
||||
The main dashboard's message-flow pane is a different beast:
|
||||
broker messages render as `.msgrow` grid lines (ts / arrow /
|
||||
from / → / to / body) with separate styling. The current file
|
||||
focuses only on the per-agent terminal.
|
||||
The main dashboard's message-flow pane is a different
|
||||
shape: broker messages render as `.msgrow` grid lines (ts /
|
||||
arrow / from / → / to / body) with their own styling.
|
||||
`.live .msgrow` explicitly resets `text-indent: 0` so the
|
||||
per-agent terminal's hanging-indent metrics don't leak into
|
||||
the flex-grid broker rows.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue