Simplify terminal message shape to a uniform TermMsg
Move terminal-row classification server-side into a new
hive-agent/src/term_msg.rs, replacing the old JSON-mutation
enrich()/stamped-field approach in stream_enrich.rs with one
uniform wire shape: {icon?, level: debug|info|warn|error, summary,
body?, body_format?: markdown|diff, coalesce_key?}. No more per-row
`kind` tag or raw claude-JSON passthrough — every row is the same
shape, with structural identity carried by icon + summary text
instead of a CSS class per row kind.
hive-agent/src/web_ui/stream.rs's history + SSE endpoints now both
call term_msg::classify() and serve TermEnvelope{ts, seq?, msgs}
frames; events that classify to zero rows (agent-state changes,
drop-noise) never reach the wire.
Frontend: classifyEvent.ts collapses from a large per-tool dispatch
tree to a thin TermMsg -> StreamRow adapter. streamRow.ts/Row.tsx
drop the now-dead meta/childText fields. terminal.css switches from
a dozen-odd per-row-kind classes to four level-based color rules.
Expand/collapse of a bodied row is now a uniform client-side
decision (the operator's preference), no server-side per-tool
override.
docs/terminal-rendering.md rewritten to match.
This commit is contained in:
parent
daa6eb96f8
commit
5eefaa951d
13 changed files with 886 additions and 628 deletions
|
|
@ -1,17 +1,40 @@
|
|||
# Per-agent terminal: row taxonomy (as built)
|
||||
|
||||
Snapshot of how the per-agent web UI's live pane renders each
|
||||
event kind today. The per-tool icon/summary/category (and, for a
|
||||
few rich tools, the expandable body) are pre-computed server-side by
|
||||
`hive-agent/src/stream_enrich.rs::enrich` and stamped onto the
|
||||
stream-json value as `_icon`/`_summary`/`_category`/`_body`/
|
||||
`_body_type` before SSE delivery, so the frontend just dispatches on
|
||||
those fields instead of re-deriving them. Frontend source of truth
|
||||
lives in `frontend/packages/agent/src/app.js` (`renderStream`,
|
||||
`renderRichToolUse`, `renderToolResult`, `renderTaskEvent`,
|
||||
`mdNode`, `detailsOpenMd`) +
|
||||
`frontend/packages/shared/src/terminal/terminal.css` (the shared
|
||||
`.live .<class>` styling) + the `marked` npm package (markdown).
|
||||
event kind today. Every row on the wire is one **`TermMsg`**
|
||||
(`hive-agent/src/term_msg.rs`): `{icon?, level: debug|info|warn|error,
|
||||
summary, body?, body_format?: markdown|diff, coalesce_key?}` — six
|
||||
fields, the same shape for every row, no `kind` tag. A raw claude
|
||||
`stream-json` line (`LiveEvent::Stream`) can classify into zero, one, or
|
||||
several `TermMsg`s (an assistant message with both a text block and a
|
||||
tool_use block produces two); other `LiveEvent` variants
|
||||
(`TurnStart`/`TurnEnd`/`Note`) map straight to one each, and the five
|
||||
agent-state variants (`StatusChanged`/`ModelChanged`/`EffortChanged`/
|
||||
`TokenUsageChanged`/`TurnStateChanged`) always produce zero — they never
|
||||
render as terminal rows, the header/badges read `/api/state` instead.
|
||||
|
||||
Classification happens **server-side**, once, in
|
||||
`hive-agent/src/term_msg.rs` (top-level dispatch) +
|
||||
`hive-agent/src/stream_enrich.rs` (the `Stream` payload's per-tool/
|
||||
per-event breakdown — icon, summary text, expandable body). Both the
|
||||
live SSE tail (`GET /api/events/stream`) and the paginated history
|
||||
replay (`GET /api/events/history`) call the same `classify()` and
|
||||
serve identically-shaped `TermEnvelope { ts, seq?, msgs: TermMsg[] }`
|
||||
frames (`hive-agent/src/web_ui/stream.rs`) — the sqlite event log still
|
||||
stores the raw, unclassified event, so classification logic can change
|
||||
without a DB migration.
|
||||
|
||||
The frontend (`frontend/packages/agent/src/lib/classifyEvent.ts`) is a
|
||||
thin adapter: `TermMsg` → `StreamRow` (`lib/streamRow.ts`), rendered by
|
||||
`components/Row.tsx`. It does **not** re-derive icons, summaries, or
|
||||
per-tool formatting — that all happened server-side already. Its only
|
||||
real decision is body layout: an empty-`summary` + markdown-`body` row
|
||||
renders as a flat row with just the body (assistant text); every other
|
||||
bodied row is an expandable `<details>`, opened by default according to
|
||||
the operator's expand-tool-output preference (`getExpandDetailsPref()`,
|
||||
`@hive/shared/prefs.ts`) — uniformly, no per-tool server override.
|
||||
`frontend/packages/shared/src/terminal/terminal.css` supplies the
|
||||
`.live .level-*` colour rules + shared row layout.
|
||||
|
||||
## Layout contract
|
||||
|
||||
|
|
@ -23,8 +46,8 @@ into the column at ~0.5em, and wrapped continuation lines
|
|||
hang under the body, not under the glyph.
|
||||
|
||||
Rows that carry an icon (the per-tool emoji, `🧠`/`💭`
|
||||
thinking, etc.) pass it as the `icon` argument to
|
||||
`row()` / `details()` / `detailsDiff()`, which puts it in a
|
||||
thinking, etc.) set the `StreamRow.icon` prop, which
|
||||
`components/Row.tsx` renders into a
|
||||
fixed-width `.row-glyph` cell (`display: inline-block;
|
||||
width: 1.4em`) rather than as a bare first character. The
|
||||
constant cell width means every icon's left edge lines up in
|
||||
|
|
@ -52,117 +75,108 @@ parent's negative pull.
|
|||
|
||||
## Row taxonomy
|
||||
|
||||
| CSS class | Prefix glyph | Color | Triggered by | Source |
|
||||
Every row carries a **level** (`debug`/`info`/`warn`/`error`), which is
|
||||
the *only* thing that drives its colour — `frontend/packages/shared/src/
|
||||
terminal/terminal.css`'s `.live .level-debug/-info/-warn/-error` rules.
|
||||
There's no separate per-row-kind class any more (no `.turn-start`,
|
||||
`.tool-use`, `.tool-result.error`, `.sys`, …) — structural identity
|
||||
(this is a turn boundary, this is a tool call, this is an error) is
|
||||
carried by the row's **icon** and **summary text** instead, computed
|
||||
server-side once and read verbatim by the client.
|
||||
|
||||
| Level | Colour | Used for |
|
||||
|---|---|---|
|
||||
| `debug` | muted | thinking, thinking-token ticks, plugin-install/status ticks, ambient harness chatter, unrecognised-system-subtype notes |
|
||||
| `info` | default fg | turn start/ok, assistant text, tool calls + their (non-error) results, operator-initiated notes |
|
||||
| `warn` | amber, left rule | stderr lines, an unrecognised stream-json shape, API retry backoff |
|
||||
| `error` | red, left rule | turn failed, a tool result with `is_error: true`, a hard API error |
|
||||
|
||||
Representative summaries (icon + text, `hive-agent/src/term_msg.rs` +
|
||||
`stream_enrich.rs`):
|
||||
|
||||
| Row | icon | level | summary | body |
|
||||
|---|---|---|---|---|
|
||||
| `.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 |
|
||||
| `.turn-time` | `· HH:MM:SS` on turn-start; `· HH:MM:SS · <dur>` on turn-end (child span) | muted, smaller | per-event `ts` (unix seconds) on the live frame + history row | 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) | `<icon> Name args…` | cyan | tool_use w/o rich renderer; `<icon>` from the backend's `tool_icon(name)` (`stream_enrich.rs`): 📤 send · 📥 recv · ⏰ remind · 🏷️ set_status · 🪢 loose-ends · ✂️ cancel_loose_end · ℹ️ get_agent_meta · ✅ ack_until · 📜 get_logs/get_host_journal · ↻ restart · ⏹️ kill · ▶️ start · 🔄 update · 📋 list_containers/list_rooms/list_room_members/list_invites · 📖 read_room/Read · 👁️ mark_read · 🛑 bash kill · 🖥️ bash other · 💬 matrix send/reply/dm · 📦 request_* · ⏱️ schedule · 🔧 default | stream-json |
|
||||
| `.tool-use` `<details>` | `✏️ Edit <path> · -N +N` (no `→`) | cyan, body is +/- diff | `renderRichToolUse` Edit | stream-json |
|
||||
| `.tool-use` `<details open>` | `📤 send → to · NL` | cyan, body is markdown | rich renderer for send | 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-result.error` (flat) | `✗ <msg>` | red | `tool_result` with `is_error: true` (≤120c); `<tool_use_error>` wrapper stripped | stream-json |
|
||||
| `.tool-result-block.error` `<details>` | `Nl · headline` | red, body is text | long error `tool_result` (`is_error: true`); wrapper stripped | stream-json |
|
||||
| `.tool-use` | `⌁ task <id> started · <desc> [type]` | cyan | claude Task-tool subagent start (dead path — `Task` omitted from agent allow-list) | `renderTaskEvent` |
|
||||
| `.turn-end-ok` / `.turn-end-fail` / `.tool-result` | `⌁ task <id> ✓/✗/◌ <status> · <desc> · → <output_file>` | green / red / muted | claude Task-tool result (dead path for agents) | `renderTaskEvent` |
|
||||
| `.note` | `· ⚙ plugin install · loading…` or `✓ done` | muted | `system/plugin_install` (`status` = started/completed) | stream-json |
|
||||
| `.note` | `· ⚙ commands changed · N available` (expandable list of `/name` entries) | muted | `system/commands_changed` (slash-command set updated, usually post-plugin_install) | stream-json |
|
||||
| `.note` | `· ⚙ compact · <trigger> · <pre>→<post> tokens · <dur>` | muted | `system/compact_boundary` (compaction completed; metadata includes pre/post token counts, duration, trigger) | stream-json |
|
||||
| `.note` | `· ⚙ <subtype>` | muted | other `system` subtypes (context_window_exceeded, etc.) | stream-json catch-all |
|
||||
| `.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` |
|
||||
| turn start | `◆` | info | `TURN ← <from>` | the wake-prompt text (plain) |
|
||||
| turn ok | `✅` | info | `turn ok` | — |
|
||||
| turn failed | `❌` | error | `turn fail — <note>` | — |
|
||||
| assistant text | — | info | *(empty)* | the text itself (markdown) — renders as a flat row, no prefix line |
|
||||
| thinking | `💭` | debug | the thinking text (or `thinking …`) | — |
|
||||
| thinking-token tick | `🧠` | debug | `thinking… ~N tokens` (`coalesce_key: "thinking-tok"`, in-place updates) | — |
|
||||
| tool call | per-tool, see [salient-arg formatting](#salient-arg-formatting) | info | `<short-name> <args…>` | some tools: diff (Edit) / markdown (`send`) / plain (long `mcp__bash__run` cmd) |
|
||||
| tool result, short | `←` | info | the trimmed result text | — |
|
||||
| tool result, long | — | info | `Nl · headline…` | the full text (plain) |
|
||||
| tool result, `recv`-correlated | — | info | `recv ← <summary>` | the full text (markdown) |
|
||||
| tool result, error (short) | `✗` | error | the trimmed error text | — |
|
||||
| tool result, error (long) | — | error | `Nl · headline…` | the full error text (plain) |
|
||||
| subagent task start/notify (dead path — `Task` not in the agent allow-list) | `⌁` | info | `task <id> started · <desc> [type]` / `task <id> ✓/✗/◌ <status> · <desc> · → <file>` | — |
|
||||
| plugin install / status tick | — | debug | `⚙ plugin install · loading…\|✓ done` / `⚙ status` (both `coalesce_key`-collapsed) | — |
|
||||
| commands changed | — | debug | `⚙ commands changed · N available` | one `/name` per line (plain) |
|
||||
| compact boundary / API retry / API error / unrecognised system subtype | — | debug / warn / error / debug | `⚙ compact · …` / `⚠ api retry · …` / `✗ api error · …` / `⚙ <subtype>` | — |
|
||||
| harness note | — | debug | the note text | — |
|
||||
| stderr line | — | warn | `stderr: <line>` | — |
|
||||
| operator-initiated note | — | info | `operator: <text>` | — |
|
||||
| unrecognised stream-json shape | `!` | warn | trimmed raw JSON | — |
|
||||
|
||||
The `.turn-time` span is appended to the turn-start / turn-end rows from
|
||||
the event's `ts` (unix seconds), which the backend serializes as a
|
||||
flattened sibling of `kind` on both the live SSE frame and each history
|
||||
row — so the same renderer path stamps live tail and replayed scrollback
|
||||
identically. Turn-end also shows the elapsed duration (end − start),
|
||||
paired against the most recent open turn-start. The read is guarded on a
|
||||
numeric `ts`: if a frame omits it the rows render without the time
|
||||
suffix, so the terminal degrades cleanly against older event shapes.
|
||||
Whether a bodied row renders open or collapsed is a **client-only**
|
||||
decision — the operator's expand-tool-output preference
|
||||
(`getExpandDetailsPref()`), applied uniformly to every row with a body.
|
||||
There's no server-side per-tool override any more (the old `recv`/`send`
|
||||
"always default-open" special case is gone).
|
||||
|
||||
## Renderer dispatch
|
||||
## Classification pipeline
|
||||
|
||||
`renderStream(v, api)` walks each stream-json line. Most of the
|
||||
per-event classification it used to do itself is now pre-computed
|
||||
server-side by `hive-agent/src/stream_enrich.rs::enrich` (stamped
|
||||
onto the value as `_category`/`_summary`/`_icon`/`_body`/
|
||||
`_body_type` at SSE-emit time, for both the live tail and history
|
||||
replay) — the client mostly just dispatches on those fields rather
|
||||
than re-deriving them from raw claude field names:
|
||||
1. `hive-agent/src/term_msg.rs::classify()` is the top-level dispatch,
|
||||
called once per `LiveEvent` at SSE-emit time (both the live tail and
|
||||
history replay — see `web_ui/stream.rs`):
|
||||
- `TurnStart`/`TurnEnd`/`Note` map straight to one `TermMsg` each.
|
||||
- The five agent-state variants (`StatusChanged`/…) always produce
|
||||
zero — never rendered as terminal rows.
|
||||
- `Stream(value)` delegates to `stream_enrich::classify_stream_value`.
|
||||
2. `classify_stream_value` walks one raw claude `stream-json` line:
|
||||
- top-level `result` / `rate_limit_event` → dropped (no rows).
|
||||
- `type: "system"` → `classify_system`, which computes
|
||||
`(category, summary, body)` per `subtype` (`system_fields()`) and
|
||||
maps `category` to a level + optional `coalesce_key`:
|
||||
`"thinking_tok"` → debug, coalesced; `"details"` (currently just
|
||||
`commands_changed`) → debug, with an expandable body; `"note"` →
|
||||
level varies by subtype (`api_error` → error, `api_retry` → warn,
|
||||
`plugin_install`/`status` → debug + coalesced, everything else →
|
||||
debug).
|
||||
- `subtype: "task_started" | "task_notification"` (regardless of
|
||||
top-level `type`) → `classify_task_event`.
|
||||
- `type: "assistant"` → walk `message.content[]`: `text` → info row,
|
||||
empty summary, markdown body; `thinking` → debug row with `💭`;
|
||||
`tool_use` → `classify_tool_use` (records `id → name` in the
|
||||
connection/page-scoped `ClassifyCtx` for the next step, computes
|
||||
icon + summary via `fmt_tool_use()`/`tool_icon()`, and a body via
|
||||
`rich_tool_body()` for the fixed set of tools that have one).
|
||||
- `type: "user"` → walk `message.content[]` for `tool_result`:
|
||||
`classify_tool_result` correlates `tool_use_id` against the
|
||||
`ClassifyCtx` to spot a `recv` result (rendered `recv ← …` with a
|
||||
markdown body), strips the `<tool_use_error>` wrapper on errors,
|
||||
and otherwise picks short-flat vs. long-with-body by length.
|
||||
- Unrecognised shape → warn row, trimmed raw JSON, `!` icon.
|
||||
3. `frontend/packages/agent/src/lib/classifyEvent.ts::classifyEvent`
|
||||
maps each `TermMsg` in the envelope to a `StreamRow`
|
||||
(`lib/streamRow.ts`) — `level` → `level-*` CSS class, `body_format`
|
||||
→ which of `markdown`/`diff`/`plain` body prop to set, empty-summary
|
||||
markdown body → the flat "assistant text" shape, everything else
|
||||
with a body → an expandable `<details>` gated by the operator's
|
||||
preference. `components/Row.tsx` renders the result.
|
||||
|
||||
1. `v._category === 'drop'` → dropped without rendering. Covers the
|
||||
top-level `result` / `rate_limit_event` types (`result` powers the
|
||||
`cost` badge elsewhere) and the `system` subtypes `init` /
|
||||
`result` / `rate_limit_event`.
|
||||
1a. `system` events with `_category === 'thinking_tok'`
|
||||
(`subtype == "thinking_tokens"`; claude streams a running
|
||||
`estimated_tokens` counter while thinking — many per turn) →
|
||||
collapses into a **single** `🧠 thinking … ~N tokens` `.note`
|
||||
row that updates in place, text taken verbatim from the
|
||||
backend-computed `_summary`. Consecutive ticks reuse the row only
|
||||
while it's still the last one rendered (`nextElementSibling ==
|
||||
null`); any other event after it makes the next tick start a
|
||||
fresh row. Avoids a note-per-tick scrollback flood.
|
||||
1b. `system/plugin_install` (matched on `subtype`, not `_category`,
|
||||
so start/complete can coalesce into one row) → muted note
|
||||
`⚙ plugin install · loading…` (on `started`) or
|
||||
`⚙ plugin install · ✓ done` (on `completed`), text from
|
||||
`_summary`. Emitted in pairs: started fires before the plugin
|
||||
loads, completed fires when it's ready.
|
||||
1c. `system/status` (matched on `subtype`) → muted note from
|
||||
`_summary`, except while the harness's local `turn_state` is
|
||||
`compacting`: the client overrides the text with an elapsed-time
|
||||
counter (`⚙ compact · <N>s…`) computed client-side from
|
||||
`stateSince`, since the backend can't know client wall-clock time
|
||||
at emit time.
|
||||
1d. `_category === 'details'` (currently just `system/commands_changed`)
|
||||
→ collapsible `.note` details row: `_summary` as the header
|
||||
(`⚙ commands changed · N available`), `_body` (one `/name` per
|
||||
line) as the expandable content.
|
||||
1e. Other `system/` subtypes (e.g. `compact_boundary`, `api_retry`,
|
||||
`api_error`, or an unrecognised subtype) → `_category === 'note'`,
|
||||
rendered as a single muted note from `_summary` — computed by
|
||||
`system_fields()` in `stream_enrich.rs` (e.g. `compact_boundary`
|
||||
→ `⚙ compact · <trigger> · <pre>→<post> tokens · <dur>` with each
|
||||
field guarded individually; an unrecognised subtype falls back to
|
||||
`⚙ <subtype>`).
|
||||
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`. The backend
|
||||
stamps every `tool_use` entry with `_icon` + `_summary` (via
|
||||
`fmt_tool_use()` in `stream_enrich.rs` — see [salient-arg
|
||||
formatting](#salient-arg-formatting) below) and, for a fixed set
|
||||
of tools, `_category: "rich"` + `_body`/`_body_type`. When
|
||||
`_category === "rich"`, `renderRichToolUse` dispatches on
|
||||
`_body_type` (`"diff"` → `api.detailsDiff`, `"markdown"` →
|
||||
`detailsOpenMd`, else `api.details`) to build the expandable
|
||||
row; otherwise it falls through to a flat `.tool-use` row using
|
||||
`_icon` + `_summary` as-is — no per-tool JS.
|
||||
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).
|
||||
`ClassifyCtx` (tool_use `id → name` correlation, needed for step 2's
|
||||
`recv` detection) is scoped per SSE connection on the live path and per
|
||||
page on the history path — it does not cross the live/history boundary,
|
||||
so a `recv` result whose `tool_use` fell on the other side of a
|
||||
reconnect or page load renders as a plain block instead of default-open
|
||||
markdown. Accepted, documented degradation
|
||||
(`hive-agent/src/term_msg.rs`'s `ClassifyCtx` doc), not a bug.
|
||||
|
||||
### Salient-arg formatting
|
||||
|
||||
Server-side (`fmt_tool_use()` and its per-tool-family helpers in
|
||||
`hive-agent/src/stream_enrich.rs`), computed into `_summary` and
|
||||
read verbatim by the client. The `short` name strips the
|
||||
`hive-agent/src/stream_enrich.rs`), computed into the `TermMsg`'s
|
||||
`summary` field and read verbatim by the client. The `short` name strips the
|
||||
`mcp__hyperhive__` / `mcp__bash__` / `mcp__matrix__` prefix and
|
||||
appends `*` (e.g. `recv*`, `run*`, `send_message*`). Unprefixed
|
||||
tools (Read, Write, etc.) keep their name as-is.
|
||||
|
|
@ -178,7 +192,7 @@ tools (Read, Write, etc.) keep their name as-is.
|
|||
| `Bash` | `Bash [bg] $ <cmd>` (dead path — built-in `Bash` isn't in the agent allow-list either; shell execution goes through `mcp__bash__run` / `run*` below instead) |
|
||||
| `TodoWrite` | `TodoWrite (N items)` (dead path — `TodoWrite` isn't in the agent allow-list; its state lives in claude's in-process session and evaporates on `/compact`, so agents plan in `/state` notes instead) |
|
||||
| **Core hyperhive** | |
|
||||
| `send*` | rich renderer: `send* → to · NL` (default-open body) |
|
||||
| `send*` | rich renderer: `send* → to · NL` (markdown body, expand state now follows the operator's preference like every other bodied row — see [Row taxonomy](#row-taxonomy)) |
|
||||
| `recv*` | `recv*()` · `recv* wait Ns` · `recv* max N` |
|
||||
| `remind*` | `remind* +Xm "preview"` or `remind* at HH:MMZ "preview"` |
|
||||
| `set_status*` | `set_status* "text"` |
|
||||
|
|
@ -216,19 +230,20 @@ tools (Read, Write, etc.) keep their name as-is.
|
|||
|
||||
## Markdown
|
||||
|
||||
`mdNode(text)` wraps `marked.parse(text)` (the `marked` npm dep,
|
||||
bundled by esbuild into the page's `app.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 / recv message bodies.
|
||||
`frontend/packages/agent/src/lib/markdown.ts`'s `renderMarkdown(text)`
|
||||
runs `marked.parse(text)` through DOMPurify and is rendered into a
|
||||
`<div class="md">` by `components/Row.tsx`'s `MarkdownBody`. 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. Applied to any `TermMsg` with
|
||||
`body_format: "markdown"` — assistant text, `send`'s body, a
|
||||
`recv`-correlated tool result.
|
||||
|
||||
## Extra-MCP tools
|
||||
|
||||
`fmt_args_generic(name, input)` (`hive-agent/src/stream_enrich.rs`)
|
||||
is the fallback when a tool isn't in the built-in `fmt_tool_use`
|
||||
switch, computed into `_summary` server-side:
|
||||
switch, computed into the `TermMsg`'s `summary` field server-side:
|
||||
|
||||
- single string field → `name k: "v"`
|
||||
- single number/bool field → `name k: v`
|
||||
|
|
|
|||
Loading…
Reference in a new issue