import { useId } from "react"; import { Bot, ChevronRight } from "lucide-react"; import { MarkdownBody } from "@/components/MarkdownBody"; import { useSecondTick } from "@/hooks/useSecondTick"; import { cn } from "@/lib/utils"; import { useTaskChatExpansion } from "./expansion-state"; import type { TaskChatActivityPhaseItem, TaskChatSubagent, TaskChatSubagentStatus, } from "./task-chat-model"; import { TaskChatRunnerActivityGroup } from "./TaskChatRunnerActivityGroup"; export type { TaskChatSubagent, TaskChatSubagentStatus } from "./task-chat-model"; /* * A delegated subagent (Claude `Task`/`Agent` tool call, Codex collab child * thread) shown as its own collapsible card inside the parent turn. Nested * tool rows reuse the runner activity group so a child reads exactly like a * parent turn, one level in. */ function formatElapsed(ms: number | null): string | null { if (ms == null || !Number.isFinite(ms)) return null; const totalSeconds = Math.max(0, Math.floor(ms / 1000)); if (totalSeconds < 60) return `${totalSeconds}s`; const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; return seconds === 0 ? `${minutes}m` : `${minutes}m ${seconds}s`; } const STATUS_LABEL: Record = { running: "Running", completed: "Done", failed: "Failed", cancelled: "Stopped", }; export function TaskChatSubagentStatusDot({ status }: { status: TaskChatSubagentStatus }) { return ( {status === "running" ? ( ) : null} {STATUS_LABEL[status]} ); } function toolCount(subagent: TaskChatSubagent): number { return subagent.activity.filter((item) => item.kind === "tool" || (item.kind === "protocol" && item.surface === "provider_activity")).length; } function useElapsed(subagent: TaskChatSubagent): string | null { const running = subagent.status === "running"; useSecondTick(running); if (subagent.startedAtMs == null) return null; const end = running ? Date.now() : (subagent.finishedAtMs ?? null); return end == null ? null : formatElapsed(end - subagent.startedAtMs); } function SubagentMeta({ subagent }: { subagent: TaskChatSubagent }) { const elapsed = useElapsed(subagent); const tools = toolCount(subagent); const parts = [tools > 0 ? `${tools} ${tools === 1 ? "tool" : "tools"}` : null, elapsed].filter(Boolean); if (parts.length === 0) return null; return {parts.join(" · ")}; } function SubagentBody({ subagent }: { subagent: TaskChatSubagent }) { const running = subagent.status === "running"; const phase: TaskChatActivityPhaseItem = { id: `${subagent.id}:activity`, kind: "activity_phase", items: subagent.activity, summary: "", active: running, }; return (
{subagent.prompt ? (
Prompt{subagent.model ? · {subagent.model} : null}
{subagent.prompt}
) : null} {subagent.activity.length > 0 ? : null} {subagent.result ? (
{subagent.result}
) : null}
); } /** One subagent row: header always visible, body on expand. */ export function TaskChatSubagentCard({ subagent, defaultExpanded, nested = false, }: { subagent: TaskChatSubagent; defaultExpanded?: boolean; /** Inside a parallel tree: drop the outer border, the tree draws it. */ nested?: boolean; }) { const running = subagent.status === "running"; const [open, setOpen] = useTaskChatExpansion(`subagent:${subagent.id}`, defaultExpanded ?? false); const bodyId = useId(); const liveLine = running ? subagent.progressSummary : undefined; return (
{open ?
: null}
); } /** Parallel subagents dispatched together, shown as a small tree. A single * subagent renders as its own card with no group header. */ export function TaskChatSubagentTree({ subagents }: { subagents: readonly TaskChatSubagent[] }) { if (subagents.length === 0) return null; if (subagents.length === 1) return ; const running = subagents.filter((subagent) => subagent.status === "running").length; const failed = subagents.filter((subagent) => subagent.status === "failed").length; const label = running > 0 ? `Running ${subagents.length} subagents · ${subagents.length - running} done` : failed > 0 ? `Ran ${subagents.length} subagents · ${failed} failed` : `Ran ${subagents.length} subagents`; return (
0 && "text-foreground")}>{label}
    {subagents.map((subagent) => (
  1. ))}
); }