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:
iris 2026-06-21 23:04:29 +02:00
commit fb58f0e5b9
2 changed files with 49 additions and 16 deletions

View file

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