From ad7a31a1e0b176c64cc17f9bf7bc36b8bd9630f0 Mon Sep 17 00:00:00 2001 From: Eric Lee Date: Sat, 27 Jun 2026 13:56:53 -0700 Subject: [PATCH] feat(ui-tui): render Agent/Task (subagent) as a Task card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the agent spawns a subagent (the Agent tool, legacy "Task"; registered in the agent-server by default), render it as a distinct Task card instead of a generic tool call: - ⏺ Task() in the permission/subagent blue-purple, with the subagent_type as a dim badge and an optional @name. - the subagent's final output flows as the normal tool result below it. Live subagent progress (the original's AgentProgressLine) needs the agent-server to stream nested-loop progress events (a ToolContext progress hook + a new message type) — a follow-up backend chapter. Co-Authored-By: Claude Opus 4.8 --- ui-tui/src/components/Message.tsx | 15 +++++++++++++++ ui-tui/src/sdkMessageAdapter.ts | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/ui-tui/src/components/Message.tsx b/ui-tui/src/components/Message.tsx index b271024ff..78ba71094 100644 --- a/ui-tui/src/components/Message.tsx +++ b/ui-tui/src/components/Message.tsx @@ -161,6 +161,21 @@ export function Message({ entry }: { entry: TranscriptEntry }): React.ReactEleme if (entry.todos) { return } + // Agent/Task (subagent spawn) renders as a Task card. + if (entry.agent) { + const a = entry.agent + const label = a.name ? `${a.description} (@${a.name})` : a.description + return ( + + + Task + ( + {label} + ) + {a.subagentType ? {` · ${a.subagentType}`} : null} + + ) + } // Collapsed summary of several same-kind calls (e.g. "Read 4 files"). if (entry.count && entry.count > 1) { const noun = TOOL_VERB[entry.toolName ?? '']?.noun || 'files' diff --git a/ui-tui/src/sdkMessageAdapter.ts b/ui-tui/src/sdkMessageAdapter.ts index 8eff5b919..46117068b 100644 --- a/ui-tui/src/sdkMessageAdapter.ts +++ b/ui-tui/src/sdkMessageAdapter.ts @@ -72,6 +72,8 @@ export interface TranscriptEntry { todos?: TodoItem[] /** /context entries: the context-window usage breakdown to visualize. */ contextData?: ContextData + /** Agent/Task tool calls: the spawned subagent's task + type. */ + agent?: { description: string; subagentType: string; name: string } /** banner only: the session info snapshot, captured once at init. */ bannerData?: { model: string; mode: string; tools: number; cwd?: string } } @@ -171,6 +173,22 @@ export function messageToEntries(msg: ServerMessage): TranscriptEntry[] { string, unknown > + // Agent/Task (subagent spawn) renders as a Task card. + if (toolName === 'Agent' || toolName === 'Task') { + out.push({ + id: nextId(), + kind: 'tool', + text: '', + toolName, + agent: { + description: String(tinput['description'] ?? ''), + subagentType: String(tinput['subagent_type'] ?? ''), + name: String(tinput['name'] ?? ''), + }, + toolUseId: String((block as { id?: string }).id ?? ''), + }) + continue + } // TodoWrite renders as a checklist, not a generic tool call. if (toolName === 'TodoWrite' && Array.isArray(tinput['todos'])) { out.push({