agent terminal: pretty-render task_started / task_notification

claude's Task tool spawns subagents whose progress lands as
stream-json events with subtype=task_started or
task_notification — previously fell through to the .sys
catch-all and rendered as a raw json dump that wrapped per
char in the live pane.

now matched by subtype before the catch-all:
- task_started → cyan tool-use row, ⌁ glyph, first 8 chars of
  task_id, description, and optional [task_type]
- task_notification → row styled by status: completed →
  turn-end-ok (green ✓), failed → turn-end-fail (red ✗),
  other → tool-result (muted ◌). output_file rendered inline
  if present so the operator can trace where the body landed.

matching on `v.subtype` rather than a particular `v.type` so
the renderer survives claude wrapping these under different
top-level type fields across versions.
This commit is contained in:
müde 2026-05-18 11:25:54 +02:00
parent 63f5f9a2ef
commit fd7712f5c1

View file

@ -742,12 +742,45 @@
api.details('tool-result-block', summary, txt);
}
}
// Pretty-render claude's background-task subagent events
// (`task_started`, `task_notification`). They share the same
// task_id so the operator can correlate start ↔ result; render
// each as a peer of tool_use / tool_result with a `⌁` glyph to
// mark "this happened in a subagent" rather than the main
// session.
function renderTaskEvent(v, api) {
const id = (v.task_id || '').slice(0, 8);
const kind = v.task_type ? ` [${v.task_type}]` : '';
const desc = v.description || v.summary || '(no description)';
if (v.subtype === 'task_started') {
api.row('tool-use', `⌁ task ${id} started · ${desc}${kind}`);
return true;
}
if (v.subtype === 'task_notification') {
const status = v.status || 'unknown';
const glyph = status === 'completed' ? '✓' : status === 'failed' ? '✗' : '◌';
const cls = status === 'completed' ? 'turn-end-ok'
: status === 'failed' ? 'turn-end-fail'
: 'tool-result';
const out = v.output_file ? ` · → ${v.output_file}` : '';
api.row(cls, `⌁ task ${id} ${glyph} ${status} · ${desc}${out}`);
return true;
}
return false;
}
function renderStream(v, api) {
// Drop session init, claude's result line, rate-limit — noise.
// TurnEnd communicates pass/fail; session init isn't actionable.
if (v.type === 'system' && v.subtype === 'init') return;
if (v.type === 'rate_limit_event') return;
if (v.type === 'result') return;
// Background-task subagent events (claude's `Task` tool spawns
// a separate session whose progress lands here as `task_*`
// subtypes). Match by subtype so we don't have to track which
// top-level `type` claude wraps them under across versions.
if (v.subtype === 'task_started' || v.subtype === 'task_notification') {
if (renderTaskEvent(v, api)) return;
}
if (v.type === 'assistant' && v.message && v.message.content) {
for (const c of v.message.content) {
if (c.type === 'text' && c.text && c.text.trim()) api.row('text', c.text);