hyperhive/docs/terminal-rendering.md

9.5 KiB

Per-agent terminal: row taxonomy + inconsistencies

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).

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

Where the inconsistencies live

  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.
  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>.

  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.

  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.

  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).

  6. Cancel/compact/new-session notes are styled the same as autonomous harness chatter; nothing flags them as "operator initiated."

  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.

  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.

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.