agent-ui: per-tool emoji icons + thinking/turn-end glyphs in terminal
Following the 🧠 thinking-tokens indicator (operator liked it), give terminal rows evocative glyphs: - 💭 thinking prose (pairs with the 🧠 token counter) - ✅/❌ turn-end ok/fail (was ✓/✗) - per-tool icons via toolIcon(name): 📤 send · 📥 recv · ❓ ask · ✍️ answer · ⏰ remind · 🏷️ set_status · 🪢 loose-ends · ↻/⏹️/▶️/🔄 lifecycle · 📦 request_* · ⏱️ schedule · 🖥️ bash · 💬 matrix · 📖 Read · 💾 Write · ✏️ Edit · 🔍 Glob/Grep · 🔧 default — on both flat rows and the rich send/ask/answer/Write/Edit summaries. Dropped the original 🐝 task-row glyphs: claude's nested-agent Task tool is intentionally omitted from agents' allow-list (mcp.rs), so those rows never render for agents. Doc table updated. Closes the emojify-terminal request.
This commit is contained in:
parent
eb1d913b47
commit
fb58f0e5b9
2 changed files with 49 additions and 16 deletions
|
|
@ -36,20 +36,20 @@ parent's negative pull.
|
|||
|---|---|---|---|---|
|
||||
| `.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-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) | `→ 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 |
|
||||
| `.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 `toolIcon(name)` (📤 send · 📥 recv · ❓ ask · ⏰ remind · 🏷️ set_status · 🪢 loose-ends · 🖥️ bash · 💬 matrix · 📦 request_* · ⏱️ schedule · 🔧 default) | 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-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) | `← <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` |
|
||||
| `.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` | `· <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:`) |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue