diff --git a/docs/terminal-rendering.md b/docs/terminal-rendering.md index 58584e54..f3b14111 100644 --- a/docs/terminal-rendering.md +++ b/docs/terminal-rendering.md @@ -36,20 +36,20 @@ parent's negative pull. |---|---|---|---|---| | `.turn-start` | `◆ TURN ← ` | 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-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 · ` 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) | `→ Name args…` | cyan | tool_use w/o rich renderer | stream-json | -| `.tool-use` `
` | `Write/Edit · +N` (no `→`) | cyan, body is +/- diff | `renderRichToolUse` Write/Edit | stream-json | -| `.tool-use` `
` | `send → to · NL`, `ask → to`, `answer #id` | cyan, body is markdown | rich renderer for send / ask / answer | stream-json | +| `.thinking` | `💭 thinking …` | muted, italic | claude `assistant.content[].thinking` | stream-json | +| `.tool-use` (flat) | ` Name args…` | cyan | tool_use w/o rich renderer; `` from `toolIcon(name)` (📤 send · 📥 recv · ❓ ask · ⏰ remind · 🏷️ set_status · 🪢 loose-ends · 🖥️ bash · 💬 matrix · 📦 request_* · ⏱️ schedule · 🔧 default) | stream-json | +| `.tool-use` `
` | `💾/✏️ Write/Edit · +N` (no `→`) | cyan, body is +/- diff | `renderRichToolUse` Write/Edit | stream-json | +| `.tool-use` `
` | `📤 send → to · NL`, `❓ ask → to`, `✍️ answer #id` | cyan, body is markdown | rich renderer for send / ask / answer | stream-json | | `.tool-use .ask-answer-inline-slot` | (sub-block under `ask → operator`) | inherits row | inline answer form bound by `reconcileAskBinds` to the loose-end | rich renderer | | `.tool-result` (flat) | `← ` | muted | short `tool_result` (≤120c, non-recv) | stream-json | | `.tool-result-block` `
` | `Nl · headline` | muted, body is text | long generic `tool_result` | stream-json | | `.tool-result-block` `
` | `recv ← ` | muted, body is markdown | `tool_result` correlated to a prior `recv` tool_use via id | stream-json | -| `.tool-use` | `⌁ task started · [type]` | cyan | claude Task-tool subagent start | `renderTaskEvent` | -| `.turn-end-ok` / `.turn-end-fail` / `.tool-result` | `⌁ task ✓/✗/◌ · · → ` | green / red / muted | claude Task-tool result | `renderTaskEvent` | +| `.tool-use` | `⌁ task started · [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 ✓/✗/◌ · · → ` | green / red / muted | claude Task-tool result (dead path for agents) | `renderTaskEvent` | | `.note` | `· ` | muted | harness chatter | `LiveEvent::Note` | | `.note.stderr` | `! stderr: ` | amber/orange | stderr lines off claude | `LiveEvent::Note` (`text` starts `stderr:`) | | `.note.op` | `· operator: ` | mauve italic | operator-initiated notes (/cancel, /compact, /model, new-session) | `LiveEvent::Note` (`text` starts `operator:`) | diff --git a/frontend/packages/agent/src/app.js b/frontend/packages/agent/src/app.js index cffc2085..9fa3baed 100644 --- a/frontend/packages/agent/src/app.js +++ b/frontend/packages/agent/src/app.js @@ -1486,6 +1486,39 @@ window.marked = marked; const tail = keys.length > 4 ? ' …+' + (keys.length - 4) : ''; return name + ' ' + pretty.join(' · ') + tail; } + // Per-tool glyph for the tool-use row prefix — gives each hive / MCP / + // built-in tool a distinctive icon instead of a generic wrench, so the + // scrollback is scannable at a glance. Exact tool names first, then + // MCP-server family fallbacks, then a generic wrench default. + const TOOL_ICONS = { + 'mcp__hyperhive__send': '📤', + 'mcp__hyperhive__recv': '📥', + 'mcp__hyperhive__ask': '❓', + 'mcp__hyperhive__answer': '✍️', + 'mcp__hyperhive__remind': '⏰', + 'mcp__hyperhive__set_status': '🏷️', + 'mcp__hyperhive__get_loose_ends': '🪢', + 'mcp__hyperhive__cancel_loose_end': '✂️', + 'mcp__hyperhive__get_agent_meta': 'ℹ️', + 'mcp__hyperhive__request_next_turn': '⏩', + 'mcp__hyperhive__restart': '↻', + 'mcp__hyperhive__kill': '⏹️', + 'mcp__hyperhive__start': '▶️', + 'mcp__hyperhive__update': '🔄', + 'mcp__hyperhive__list_containers': '📋', + Read: '📖', Write: '💾', Edit: '✏️', Glob: '🔍', Grep: '🔍', + }; + function toolIcon(name) { + if (TOOL_ICONS[name]) return TOOL_ICONS[name]; + if (typeof name === 'string') { + if (name.startsWith('mcp__matrix__')) return '💬'; + if (name.startsWith('mcp__bash__')) return '🖥️'; + if (name.includes('schedule')) return '⏱️'; + // request_apply_commit / request_init_config / request_update_meta_inputs + if (name.startsWith('mcp__hyperhive__request_')) return '📦'; + } + return '🔧'; + } // Pretty-print a tool call: per-known-tool format, fallback to JSON // for unknown tools. function fmtToolUse(c) { @@ -1563,7 +1596,7 @@ window.marked = marked; // (`→`) — the disclosure marker (`▸/▾`) from CSS sits in the // prefix column for every row kind, and the row's cyan colour // already signals "outbound tool". - const summary = name + ' ' + path + ' · ' + const summary = toolIcon(name) + ' ' + name + ' ' + path + ' · ' + (minus ? '-' + minus + ' ' : '') + '+' + plus; return api.detailsDiff('tool-use', summary, body); } @@ -1575,7 +1608,7 @@ window.marked = marked; const body = String(input.body || ''); const lines = body.split('\n').length; return detailsOpenMd(api, 'tool-use', - 'send → ' + to + (lines > 1 ? ` · ${lines}L` : ''), + toolIcon(name) + ' send → ' + to + (lines > 1 ? ` · ${lines}L` : ''), body); } if (name === 'mcp__hyperhive__ask') { @@ -1583,7 +1616,7 @@ window.marked = marked; const q = String(input.question || ''); const lines = q.split('\n').length; const d = detailsOpenMd(api, 'tool-use', - 'ask → ' + to + (lines > 1 ? ` · ${lines}L` : ''), + toolIcon(name) + ' ask → ' + to + (lines > 1 ? ` · ${lines}L` : ''), q); // When the ask targets the operator, mount an inline answer // slot in the live terminal — see docs/web-ui.md::Per-agent @@ -1613,7 +1646,7 @@ window.marked = marked; const a = String(input.answer || ''); const lines = a.split('\n').length; return detailsOpenMd(api, 'tool-use', - 'answer #' + id + (lines > 1 ? ` · ${lines}L` : ''), + toolIcon(name) + ' answer #' + id + (lines > 1 ? ` · ${lines}L` : ''), a); } // Bash task runner — show full command in an expandable pre block so @@ -1622,7 +1655,7 @@ window.marked = marked; if (name === 'mcp__bash__run') { const cmd = String(input.cmd || ''); const firstLine = cmd.split('\n')[0]; - const summary = 'run* $ ' + trim(firstLine.trim(), 72); + const summary = toolIcon(name) + ' run* $ ' + trim(firstLine.trim(), 72); return api.details('tool-use', summary, '$ ' + cmd); } return null; @@ -1770,12 +1803,12 @@ window.marked = marked; } else if (c.type === 'thinking') { const txt = (c.thinking || c.text || '').trim(); - api.row('thinking', txt ? '· ' + txt : '· thinking …'); + api.row('thinking', txt ? '💭 ' + txt : '💭 thinking …'); } else if (c.type === 'tool_use') { if (c.id && c.name) toolNameById.set(c.id, c.name); if (!renderRichToolUse(c, api)) { - api.row('tool-use', '→ ' + fmtToolUse(c)); + api.row('tool-use', toolIcon(c.name) + ' ' + fmtToolUse(c)); } } } @@ -1846,7 +1879,7 @@ window.marked = marked; } const cls = ev.ok ? 'turn-end-ok' : 'turn-end-fail'; const row = api.row(cls, - (ev.ok ? '✓' : '✗') + ' turn ' + (ev.ok ? 'ok' : 'fail') + (ev.ok ? '✅' : '❌') + ' turn ' + (ev.ok ? 'ok' : 'fail') + (ev.note ? ' — ' + ev.note : '')); // Turn end time + duration since the paired turn-start. // Same `ts` guard as turn_start; duration only when we saw the