diff --git a/hive-ag3nt/assets/app.js b/hive-ag3nt/assets/app.js index 34b4ca5..f5a2afd 100644 --- a/hive-ag3nt/assets/app.js +++ b/hive-ag3nt/assets/app.js @@ -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);