{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/tui/render.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,KAAK,gBAAgB,EAA6B,MAAM,2BAA2B,CAAC;AAC7F,OAAO,EAAE,KAAK,SAAS,EAAmD,MAAM,kBAAkB,CAAC;AAkBnG,OAAO,EAEN,KAAK,aAAa,EAGlB,KAAK,OAAO,EAMZ,MAAM,oBAAoB,CAAC;AAI5B,KAAK,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;AAiB7C;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CA6ChE;AAwED,UAAU,4BAA4B;IACrC,KAAK,EAAE;QAAE,4BAA4B,CAAC,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAA;KAAE,CAAC;CACzE;AAED,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,4BAA4B,GAAG,IAAI,CAK3F;AA6OD,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA0B1D;AA4sCD,wBAAgB,gBAAgB,CAC/B,IAAI,EAAE,aAAa,EAAE,EACrB,KAAK,EAAE,KAAK,EACZ,KAAK,SAAiB,EACtB,QAAQ,UAAQ,EAChB,KAAK,CAAC,EAAE,MAAM,GACZ,MAAM,EAAE,CAgFV;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,IAAI,CAQ/E;AAsUD,wBAAgB,qBAAqB,CACpC,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,EAChC,OAAO,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,EAChC,KAAK,EAAE,KAAK,GACV,SAAS,CAyCX;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CACnC,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,EAChC,OAAO,EAAE;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,EAC9B,KAAK,EAAE,KAAK,EACZ,KAAK,CAAC,EAAE,MAAM,GACZ,SAAS,CAwYX","sourcesContent":["/**\n * Rendering functions for subagent results\n */\n\nimport * as path from \"node:path\";\nimport type { AgentToolResult } from \"@lpb-work/pi-agent-core\";\nimport { type ExtensionContext, getMarkdownTheme, keyText } from \"@lpb-work/pi-coding-agent\";\nimport { type Component, Container, Markdown, Spacer, Text, visibleWidth } from \"@lpb-work/pi-tui\";\nimport { flatToLogicalStepIndex } from \"../runs/background/parallel-groups.ts\";\nimport { contextModeBadge, contextModePrefix } from \"../runs/shared/context-mode.ts\";\nimport { formatNestedAggregate } from \"../runs/shared/nested-render.ts\";\nimport {\n\tformatDuration,\n\tformatModelThinking,\n\tformatTokens,\n\tformatToolCall,\n\tformatUsage,\n\tshortenPath,\n} from \"../shared/formatters.ts\";\nimport {\n\taggregateStepStatus,\n\tformatActivityLabel,\n\tformatAgentRunningLabel,\n\tformatParallelOutcome,\n} from \"../shared/status-format.ts\";\nimport {\n\ttype AgentProgress,\n\ttype AsyncJobState,\n\ttype AsyncJobStep,\n\ttype AsyncParallelGroupStatus,\n\ttype Details,\n\tMAX_WIDGET_JOBS,\n\ttype NestedRunSummary,\n\ttype NestedStepSummary,\n\tWIDGET_KEY,\n\ttype WorkflowNodeStatus,\n} from \"../shared/types.ts\";\nimport { getDisplayItems, getSingleResultOutput } from \"../shared/utils.ts\";\nimport { buildWorkflowChatProgressRows, type WorkflowChatProgressRow } from \"../workflows/chat-progress.ts\";\n\ntype Theme = ExtensionContext[\"ui\"][\"theme\"];\n\nfunction liveDetailKeyText(): string {\n\treturn keyText(\"app.tools.expand\");\n}\n\nfunction liveDetailHintText(): string {\n\treturn `Press ${liveDetailKeyText()} for live detail`;\n}\n\nfunction getTermWidth(): number {\n\treturn process.stdout.columns || 120;\n}\n\nconst segmenter = new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\nconst ansiStylePattern = /\\x1b\\[[0-9;]*m/y;\n\n/**\n * Truncate a line to maxWidth, preserving ANSI styling through the ellipsis.\n *\n * pi-tui's truncateToWidth adds \\x1b[0m before ellipsis which resets all styling,\n * causing background color bleed in the TUI. This implementation tracks active\n * ANSI styles and re-applies them before the ellipsis.\n *\n * Uses Intl.Segmenter for proper Unicode/emoji handling (not char-by-char).\n */\nexport function truncLine(text: string, maxWidth: number): string {\n\tif (maxWidth <= 0) return \"\";\n\tif (visibleWidth(text) <= maxWidth) return text;\n\n\tconst targetWidth = maxWidth - 1;\n\tlet result = \"\";\n\tlet currentWidth = 0;\n\tlet activeStyles: string[] = [];\n\tlet i = 0;\n\n\twhile (i < text.length) {\n\t\tansiStylePattern.lastIndex = i;\n\t\tconst ansiMatch = ansiStylePattern.exec(text);\n\t\tif (ansiMatch) {\n\t\t\tconst code = ansiMatch[0];\n\t\t\tresult += code;\n\n\t\t\tif (code === \"\\x1b[0m\" || code === \"\\x1b[m\") {\n\t\t\t\tactiveStyles = [];\n\t\t\t} else {\n\t\t\t\tactiveStyles.push(code);\n\t\t\t}\n\t\t\ti += code.length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet end = text.indexOf(\"\\x1b[\", i);\n\t\tif (end === i) end = text.indexOf(\"\\x1b[\", i + 2);\n\t\tif (end === -1) end = text.length;\n\t\tconst textPortion = text.slice(i, end);\n\t\tfor (const seg of segmenter.segment(textPortion)) {\n\t\t\tconst grapheme = seg.segment;\n\t\t\tconst graphemeWidth = grapheme === \"\\x1b\" ? 0 : visibleWidth(grapheme);\n\n\t\t\tif (currentWidth + graphemeWidth > targetWidth) {\n\t\t\t\treturn `${result + activeStyles.join(\"\")}…`;\n\t\t\t}\n\n\t\t\tresult += grapheme;\n\t\t\tcurrentWidth += graphemeWidth;\n\t\t}\n\t\ti = end;\n\t}\n\n\treturn `${result + activeStyles.join(\"\")}…`;\n}\n\nfunction wrapPlainText(text: string, maxWidth: number): string[] {\n\tif (maxWidth <= 0) return [\"\"];\n\tconst lines: string[] = [];\n\tfor (const rawLine of text.split(\"\\n\")) {\n\t\tif (rawLine.length === 0) {\n\t\t\tlines.push(\"\");\n\t\t\tcontinue;\n\t\t}\n\t\tlet current = \"\";\n\t\tlet currentWidth = 0;\n\t\tfor (const seg of segmenter.segment(rawLine)) {\n\t\t\tconst grapheme = seg.segment;\n\t\t\tconst graphemeWidth = visibleWidth(grapheme);\n\t\t\tif (graphemeWidth > maxWidth) continue;\n\t\t\tif (currentWidth > 0 && currentWidth + graphemeWidth > maxWidth) {\n\t\t\t\tlines.push(current);\n\t\t\t\tcurrent = grapheme;\n\t\t\t\tcurrentWidth = graphemeWidth;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcurrent += grapheme;\n\t\t\tcurrentWidth += graphemeWidth;\n\t\t}\n\t\tlines.push(current);\n\t}\n\treturn lines;\n}\n\nconst RUNNING_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\nconst STATIC_RUNNING_GLYPH = \"●\";\n\ntype ProgressSeedSource = Partial<\n\tPick<\n\t\tAgentProgress,\n\t\t\"index\" | \"toolCount\" | \"tokens\" | \"durationMs\" | \"lastActivityAt\" | \"currentToolStartedAt\" | \"turnCount\"\n\t>\n>;\n\nfunction runningSeed(...values: Array<number | undefined>): number | undefined {\n\tlet seed: number | undefined;\n\tfor (const value of values) {\n\t\tif (value === undefined || !Number.isFinite(value)) continue;\n\t\tseed = (seed ?? 0) + Math.trunc(value);\n\t}\n\treturn seed;\n}\n\nfunction runningGlyph(seed?: number): string {\n\tif (seed === undefined) return STATIC_RUNNING_GLYPH;\n\treturn RUNNING_FRAMES[Math.abs(seed) % RUNNING_FRAMES.length]!;\n}\n\nfunction animatedSeed(seed: number | undefined, frame: number | undefined): number | undefined {\n\tif (frame === undefined) return seed;\n\treturn (seed ?? 0) + frame;\n}\n\nfunction progressRunningSeed(progress: ProgressSeedSource | undefined): number | undefined {\n\tif (!progress) return undefined;\n\treturn runningSeed(\n\t\tprogress.index,\n\t\tprogress.toolCount,\n\t\tprogress.tokens,\n\t\tprogress.durationMs,\n\t\tprogress.lastActivityAt,\n\t\tprogress.currentToolStartedAt,\n\t\tprogress.turnCount,\n\t);\n}\n\ninterface LegacyResultAnimationContext {\n\tstate: { subagentResultAnimationTimer?: ReturnType<typeof setInterval> };\n}\n\nexport function clearLegacyResultAnimationTimer(context: LegacyResultAnimationContext): void {\n\tconst timer = context.state.subagentResultAnimationTimer;\n\tif (!timer) return;\n\tclearInterval(timer);\n\tcontext.state.subagentResultAnimationTimer = undefined;\n}\n\nfunction extractOutputTarget(task: string): string | undefined {\n\tconst writeToMatch = task.match(/\\[Write to:\\s*([^\\]\\n]+)\\]/i);\n\tif (writeToMatch?.[1]?.trim()) return writeToMatch[1].trim();\n\tconst findingsMatch = task.match(/Write your findings to(?: exactly this path)?:\\s*([^\\r\\n]+)/i);\n\tif (findingsMatch?.[1]?.trim()) return findingsMatch[1].trim();\n\tconst outputMatch = task.match(/[Oo]utput(?:\\s+to)?\\s*:\\s*(\\S+)/i);\n\tif (outputMatch?.[1]?.trim()) return outputMatch[1].trim();\n\treturn undefined;\n}\n\nfunction hasEmptyTextOutputWithoutOutputTarget(task: string, output: string): boolean {\n\tif (output.trim()) return false;\n\treturn !extractOutputTarget(task);\n}\n\nfunction getToolCallLines(\n\tresult: Pick<Details[\"results\"][number], \"messages\" | \"toolCalls\">,\n\texpanded: boolean,\n): string[] {\n\tif (result.messages) {\n\t\treturn getDisplayItems(result.messages)\n\t\t\t.filter((item): item is { type: \"tool\"; name: string; args: Record<string, unknown> } => item.type === \"tool\")\n\t\t\t.map((item) => formatToolCall(item.name, item.args, expanded));\n\t}\n\treturn result.toolCalls?.map((toolCall) => (expanded ? toolCall.expandedText : toolCall.text)) ?? [];\n}\n\nfunction snapshotNowForProgress(\n\tprogress: Pick<AgentProgress, \"currentToolStartedAt\" | \"durationMs\" | \"lastActivityAt\">,\n): number | undefined {\n\tif (progress.currentToolStartedAt !== undefined && progress.durationMs !== undefined)\n\t\treturn progress.currentToolStartedAt + progress.durationMs;\n\treturn progress.lastActivityAt;\n}\n\nfunction formatCurrentToolLine(\n\tprogress: Pick<AgentProgress, \"currentTool\" | \"currentToolArgs\" | \"currentToolStartedAt\">,\n\tavailableWidth: number,\n\texpanded: boolean,\n\tsnapshotNow?: number,\n): string | undefined {\n\tif (!progress.currentTool) return undefined;\n\tconst maxToolArgsLen = Math.max(50, availableWidth - 20);\n\tconst toolArgsPreview = progress.currentToolArgs\n\t\t? expanded || progress.currentToolArgs.length <= maxToolArgsLen\n\t\t\t? progress.currentToolArgs\n\t\t\t: `${progress.currentToolArgs.slice(0, maxToolArgsLen)}...`\n\t\t: \"\";\n\tconst durationSuffix =\n\t\tprogress.currentToolStartedAt !== undefined && snapshotNow !== undefined\n\t\t\t? ` | ${formatDuration(Math.max(0, snapshotNow - progress.currentToolStartedAt))}`\n\t\t\t: \"\";\n\treturn toolArgsPreview\n\t\t? `${progress.currentTool}: ${toolArgsPreview}${durationSuffix}`\n\t\t: `${progress.currentTool}${durationSuffix}`;\n}\n\nfunction buildLiveStatusLine(\n\tprogress: Pick<AgentProgress, \"activityState\" | \"lastActivityAt\">,\n\tsnapshotNow?: number,\n): string | undefined {\n\tif (progress.lastActivityAt !== undefined && snapshotNow !== undefined)\n\t\treturn formatActivityLabel(progress.lastActivityAt, progress.activityState, snapshotNow);\n\tif (progress.activityState === \"needs_attention\") return \"needs attention\";\n\tif (progress.activityState === \"active_long_running\") return \"active but long-running\";\n\tif (progress.lastActivityAt !== undefined) return \"active\";\n\treturn undefined;\n}\n\nfunction themeBold(theme: Theme, text: string): string {\n\treturn (theme as { bold?: (value: string) => string }).bold?.(text) ?? text;\n}\n\nfunction statJoin(theme: Theme, parts: string[]): string {\n\treturn parts\n\t\t.filter(Boolean)\n\t\t.map((part) => theme.fg(\"dim\", part))\n\t\t.join(` ${theme.fg(\"dim\", \"·\")} `);\n}\n\nfunction formatTokenStat(tokens: number): string {\n\treturn `${formatTokens(tokens)} token`;\n}\n\nfunction formatToolUseStat(count: number): string {\n\treturn `${count} tool use${count === 1 ? \"\" : \"s\"}`;\n}\n\nfunction formatTotalCostStat(totalCost: Details[\"totalCost\"] | undefined): string {\n\tif (!totalCost || (totalCost.inputTokens === 0 && totalCost.outputTokens === 0 && totalCost.costUsd === 0))\n\t\treturn \"\";\n\tconst parts: string[] = [];\n\tif (totalCost.inputTokens) parts.push(`in:${formatTokens(totalCost.inputTokens)}`);\n\tif (totalCost.outputTokens) parts.push(`out:${formatTokens(totalCost.outputTokens)}`);\n\tif (totalCost.costUsd) parts.push(`$${totalCost.costUsd.toFixed(4)}`);\n\treturn parts.join(\" \");\n}\n\nfunction formatProgressStats(\n\ttheme: Theme,\n\tprogress: Pick<AgentProgress, \"toolCount\" | \"tokens\" | \"durationMs\"> | undefined,\n\tincludeDuration = true,\n): string {\n\tif (!progress) return \"\";\n\tconst parts: string[] = [];\n\tif (progress.toolCount > 0) parts.push(formatToolUseStat(progress.toolCount));\n\tif (progress.tokens > 0) parts.push(formatTokenStat(progress.tokens));\n\tif (includeDuration && progress.durationMs > 0) parts.push(formatDuration(progress.durationMs));\n\treturn statJoin(theme, parts);\n}\n\nfunction firstOutputLine(text: string): string {\n\treturn (\n\t\ttext\n\t\t\t.split(\"\\n\")\n\t\t\t.find((line) => line.trim())\n\t\t\t?.trim() ?? \"\"\n\t);\n}\n\nfunction resultStatusLine(result: Details[\"results\"][number], output: string): string {\n\tif (result.detached) return result.detachedReason ? `Detached: ${result.detachedReason}` : \"Detached\";\n\tif (result.stopped) return \"Stopped\";\n\tif (result.interrupted) return \"Paused\";\n\tif (result.exitCode !== 0) return `Error: ${result.error ?? (firstOutputLine(output) || `exit ${result.exitCode}`)}`;\n\tif (result.acceptance?.status && result.acceptance.status !== \"not-required\")\n\t\treturn `Done · acceptance: ${result.acceptance.status}`;\n\tif (hasEmptyTextOutputWithoutOutputTarget(result.task, output)) return \"Done (no text output)\";\n\treturn \"Done\";\n}\n\ntype ResultPresentation = {\n\tglyph: string;\n\tlabel: \"running\" | \"detached\" | \"stopped\" | \"paused\" | \"failed\" | \"completed\";\n\ttone: \"accent\" | \"warning\" | \"error\" | \"success\";\n};\n\nfunction semanticResultPresentation(input: {\n\trunning?: boolean;\n\tdetached?: boolean;\n\tstopped?: boolean;\n\tinterrupted?: boolean;\n\tfailed?: boolean;\n\tcompletedWithoutOutput?: boolean;\n\tseed?: number;\n\tframe?: number;\n}): ResultPresentation {\n\tif (input.running) {\n\t\tconst glyph =\n\t\t\tinput.frame !== undefined ? runningGlyph((input.seed ?? 0) + input.frame) : runningGlyph(input.seed);\n\t\treturn { glyph, label: \"running\", tone: \"accent\" };\n\t}\n\tif (input.detached) return { glyph: \"■\", label: \"detached\", tone: \"warning\" };\n\tif (input.stopped) return { glyph: \"■\", label: \"stopped\", tone: \"warning\" };\n\tif (input.interrupted) return { glyph: \"■\", label: \"paused\", tone: \"warning\" };\n\tif (input.failed) return { glyph: \"✗\", label: \"failed\", tone: \"error\" };\n\treturn { glyph: \"✓\", label: \"completed\", tone: input.completedWithoutOutput ? \"warning\" : \"success\" };\n}\n\nfunction hasTerminalResultFlag(result: Details[\"results\"][number]): boolean {\n\treturn Boolean(result.detached || result.stopped || result.interrupted);\n}\n\nfunction hasTerminalResult(result: Details[\"results\"][number]): boolean {\n\tif (hasTerminalResultFlag(result)) return true;\n\tconst status = result.progress?.status;\n\tif (status === \"running\" || status === \"pending\") return false;\n\treturn result.exitCode !== undefined;\n}\n\nfunction isResultRunning(result: Details[\"results\"][number], status = result.progress?.status): boolean {\n\treturn status === \"running\" && !hasTerminalResultFlag(result);\n}\n\nfunction detailsHaveRunningResult(details: Details): boolean {\n\treturn (\n\t\tdetails.progress?.some((progress) => {\n\t\t\tif (progress.status !== \"running\") return false;\n\t\t\tconst result =\n\t\t\t\tdetails.results.find((entry) => entry.progress?.index === progress.index) ??\n\t\t\t\tdetails.results[progress.index];\n\t\t\treturn !result || !hasTerminalResultFlag(result);\n\t\t}) ||\n\t\tdetails.results.some((result) => isResultRunning(result)) ||\n\t\tworkflowGraphHasStatus(details, [\"running\"])\n\t);\n}\n\nfunction resultPresentation(\n\tresult: Details[\"results\"][number],\n\toutput: string,\n\trunning = isResultRunning(result),\n\tseed = progressRunningSeed(result.progress ?? result.progressSummary),\n\tframe?: number,\n): ResultPresentation {\n\treturn semanticResultPresentation({\n\t\trunning,\n\t\tdetached: result.detached,\n\t\tstopped: result.stopped,\n\t\tinterrupted: result.interrupted,\n\t\tfailed: result.exitCode !== 0,\n\t\tcompletedWithoutOutput: hasEmptyTextOutputWithoutOutputTarget(result.task, output),\n\t\tseed,\n\t\tframe,\n\t});\n}\n\nfunction resultGlyph(\n\tresult: Details[\"results\"][number],\n\toutput: string,\n\ttheme: Theme,\n\trunning = isResultRunning(result),\n\tseed = progressRunningSeed(result.progress ?? result.progressSummary),\n\tframe?: number,\n): string {\n\tconst presentation = resultPresentation(result, output, running, seed, frame);\n\treturn theme.fg(presentation.tone, presentation.glyph);\n}\n\nfunction styledResultPresentation(presentation: ResultPresentation, theme: Theme): { glyph: string; label: string } {\n\treturn {\n\t\tglyph: theme.fg(presentation.tone, presentation.glyph),\n\t\tlabel: theme.fg(presentation.tone, presentation.label),\n\t};\n}\n\nfunction compactCurrentActivity(progress: AgentProgress): string {\n\tconst snapshotNow = snapshotNowForProgress(progress);\n\treturn (\n\t\tformatCurrentToolLine(progress, getTermWidth() - 4, false, snapshotNow) ??\n\t\tbuildLiveStatusLine(progress, snapshotNow) ??\n\t\t\"thinking…\"\n\t);\n}\n\nexport function widgetRenderKey(job: AsyncJobState): string {\n\treturn JSON.stringify({\n\t\tasyncDir: job.asyncDir,\n\t\tstatus: job.status,\n\t\tactivityState: job.activityState,\n\t\tlastActivityAt: job.lastActivityAt,\n\t\tcurrentTool: job.currentTool,\n\t\tcurrentToolStartedAt: job.currentToolStartedAt,\n\t\tcurrentPath: job.currentPath,\n\t\tturnCount: job.turnCount,\n\t\ttoolCount: job.toolCount,\n\t\tmode: job.mode,\n\t\tagents: job.agents,\n\t\tcurrentStep: job.currentStep,\n\t\tchainStepCount: job.chainStepCount,\n\t\tparallelGroups: job.parallelGroups,\n\t\tsteps: job.steps,\n\t\tnestedChildren: job.nestedChildren,\n\t\tstepsTotal: job.stepsTotal,\n\t\trunningSteps: job.runningSteps,\n\t\tcompletedSteps: job.completedSteps,\n\t\tactiveParallelGroup: job.activeParallelGroup,\n\t\tstartedAt: job.startedAt,\n\t\tupdatedAt: job.updatedAt,\n\t\ttotalTokens: job.totalTokens,\n\t});\n}\n\nfunction formatWidgetAgents(agents: string[]): string {\n\tconst distinct = [...new Set(agents)];\n\tif (distinct.length === 1 && agents.length > 1) return `${distinct[0]} ×${agents.length}`;\n\tif (agents.length > 3) return `${agents.slice(0, 2).join(\", \")} +${agents.length - 2} more`;\n\treturn agents.join(\", \");\n}\n\nfunction widgetJobName(job: AsyncJobState): string {\n\tif (job.mode === \"parallel\") return \"parallel\";\n\tif (job.mode === \"chain\") return \"chain\";\n\tif (job.mode === \"single\" && job.agents?.length === 1) return job.agents[0]!;\n\tif (job.agents?.length) return formatWidgetAgents(job.agents);\n\treturn job.mode ?? \"subagent\";\n}\n\nfunction widgetActivity(job: AsyncJobState): string {\n\tconst facts: string[] = [];\n\tif (job.currentTool && job.currentToolStartedAt !== undefined && job.updatedAt !== undefined)\n\t\tfacts.push(`${job.currentTool} ${formatDuration(Math.max(0, job.updatedAt - job.currentToolStartedAt))}`);\n\telse if (job.currentTool) facts.push(job.currentTool);\n\tif (job.currentPath) facts.push(shortenPath(job.currentPath));\n\tif (job.turnCount !== undefined) facts.push(`${job.turnCount} turns`);\n\tif (job.toolCount !== undefined) facts.push(`${job.toolCount} tools`);\n\tconst activity = buildLiveStatusLine(job, job.updatedAt);\n\tif (activity && facts.length) return `${activity} · ${facts.join(\" · \")}`;\n\tif (activity) return activity;\n\tif (facts.length) return facts.join(\" · \");\n\tif (job.status === \"running\") return \"thinking…\";\n\tif (job.status === \"queued\") return \"queued…\";\n\tif (job.status === \"paused\") return \"Paused\";\n\tif (job.status === \"stopped\") return \"Stopped\";\n\tif (job.status === \"failed\") return \"Failed\";\n\treturn \"Done\";\n}\n\nfunction widgetStepRunningSeed(\n\tstep: NonNullable<AsyncJobState[\"steps\"]>[number],\n\tfallbackIndex?: number,\n): number | undefined {\n\treturn runningSeed(\n\t\tfallbackIndex,\n\t\tstep.index,\n\t\tstep.toolCount,\n\t\tstep.turnCount,\n\t\tstep.tokens?.total,\n\t\tstep.lastActivityAt,\n\t\tstep.currentToolStartedAt,\n\t\tstep.durationMs,\n\t);\n}\n\nfunction widgetStepsRunningSeed(\n\tsteps: Array<NonNullable<AsyncJobState[\"steps\"]>[number]> | undefined,\n): number | undefined {\n\tlet seed: number | undefined;\n\tfor (const [index, step] of (steps ?? []).entries()) seed = runningSeed(seed, widgetStepRunningSeed(step, index));\n\treturn seed;\n}\n\nfunction widgetJobRunningSeed(job: AsyncJobState): number | undefined {\n\treturn runningSeed(\n\t\tjob.updatedAt,\n\t\tjob.lastActivityAt,\n\t\tjob.toolCount,\n\t\tjob.turnCount,\n\t\tjob.totalTokens?.total,\n\t\tjob.currentStep,\n\t\tjob.runningSteps,\n\t\tjob.completedSteps,\n\t\twidgetStepsRunningSeed(job.steps),\n\t);\n}\n\nfunction widgetJobsRunningSeed(jobs: AsyncJobState[]): number | undefined {\n\tlet seed: number | undefined;\n\tfor (const job of jobs) seed = runningSeed(seed, widgetJobRunningSeed(job));\n\treturn seed;\n}\n\nfunction widgetStatusGlyph(job: AsyncJobState, theme: Theme, frame?: number): string {\n\tif (job.status === \"running\")\n\t\treturn theme.fg(\"accent\", runningGlyph(animatedSeed(widgetJobRunningSeed(job), frame)));\n\tif (job.status === \"queued\") return theme.fg(\"muted\", \"◦\");\n\tif (job.status === \"complete\") return theme.fg(\"success\", \"✓\");\n\tif (job.status === \"paused\") return theme.fg(\"warning\", \"■\");\n\tif (job.status === \"stopped\") return theme.fg(\"warning\", \"■\");\n\treturn theme.fg(\"error\", \"✗\");\n}\n\nfunction widgetStepGlyph(status: AsyncJobStep[\"status\"], theme: Theme, seed?: number, frame?: number): string {\n\tif (status === \"running\") return theme.fg(\"accent\", runningGlyph(animatedSeed(seed, frame)));\n\tif (status === \"complete\" || status === \"completed\") return theme.fg(\"success\", \"✓\");\n\tif (status === \"failed\") return theme.fg(\"error\", \"✗\");\n\tif (status === \"paused\") return theme.fg(\"warning\", \"■\");\n\tif (status === \"stopped\") return theme.fg(\"warning\", \"■\");\n\treturn theme.fg(\"muted\", \"◦\");\n}\n\nfunction widgetStepStatus(status: AsyncJobStep[\"status\"], theme: Theme): string {\n\tif (status === \"running\") return theme.fg(\"accent\", \"running\");\n\tif (status === \"complete\" || status === \"completed\") return theme.fg(\"success\", \"complete\");\n\tif (status === \"failed\") return theme.fg(\"error\", \"failed\");\n\tif (status === \"paused\") return theme.fg(\"warning\", \"paused\");\n\tif (status === \"stopped\") return theme.fg(\"warning\", \"stopped\");\n\treturn theme.fg(\"dim\", status);\n}\n\nfunction widgetStepActivity(step: NonNullable<AsyncJobState[\"steps\"]>[number], snapshotNow?: number): string {\n\tconst facts: string[] = [];\n\tif (step.currentTool && step.currentToolStartedAt !== undefined && snapshotNow !== undefined)\n\t\tfacts.push(`${step.currentTool} ${formatDuration(Math.max(0, snapshotNow - step.currentToolStartedAt))}`);\n\telse if (step.currentTool) facts.push(step.currentTool);\n\tif (step.currentPath) facts.push(shortenPath(step.currentPath));\n\tif (step.turnCount !== undefined) facts.push(`${step.turnCount} turns`);\n\tif (step.toolCount !== undefined) facts.push(`${step.toolCount} tools`);\n\tif (step.tokens?.total) facts.push(formatTokenStat(step.tokens.total));\n\tconst activity = buildLiveStatusLine(step, snapshotNow);\n\tif (activity && facts.length) return `${activity} · ${facts.join(\" · \")}`;\n\tif (activity) return activity;\n\treturn facts.join(\" · \");\n}\n\nfunction widgetChainDetails(job: AsyncJobState, theme: Theme, expanded = false, width = getTermWidth()): string[] {\n\tif (!job.steps?.length) return [];\n\tconst total = job.chainStepCount ?? job.steps.length;\n\tconst lines: string[] = [];\n\tfor (const span of buildAsyncChainStepSpans(total, job.steps.length, job.parallelGroups)) {\n\t\tconst steps = job.steps.slice(span.start, span.start + span.count);\n\t\tif (span.isParallel) {\n\t\t\tconst status = aggregateStepStatus(steps);\n\t\t\tlines.push(\n\t\t\t\t`  ${widgetStepGlyph(status, theme, widgetStepsRunningSeed(steps))} Step ${span.stepIndex + 1}/${total}: ${themeBold(theme, \"parallel group\")} ${theme.fg(\"dim\", \"·\")} ${theme.fg(\"dim\", formatParallelOutcome(steps, span.count))}`,\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\t\tconst step = steps[0];\n\t\tif (!step) {\n\t\t\tlines.push(`  ${theme.fg(\"dim\", `◦ Step ${span.stepIndex + 1}/${total}: pending`)}`);\n\t\t\tcontinue;\n\t\t}\n\t\tlines.push(\n\t\t\t...foregroundStyleWidgetStepLines(job, theme, step, \"Step\", span.stepIndex + 1, total, expanded, width),\n\t\t);\n\t}\n\treturn lines;\n}\n\nfunction widgetParallelAgentDetails(\n\tjob: AsyncJobState,\n\ttheme: Theme,\n\texpanded = false,\n\twidth = getTermWidth(),\n): string[] {\n\tif (!job.steps?.length) return [];\n\tif (job.mode !== \"parallel\" && job.mode !== \"chain\") return [];\n\tif (job.mode === \"chain\" && !job.activeParallelGroup && job.parallelGroups?.length)\n\t\treturn widgetChainDetails(job, theme, expanded, width);\n\tconst total = job.stepsTotal ?? job.steps.length;\n\tconst lines: string[] = [];\n\tfor (const [index, step] of job.steps.entries()) {\n\t\tconst marker = index === job.steps.length - 1 ? \"└\" : \"├\";\n\t\tconst activity = widgetStepActivity(step, job.updatedAt);\n\t\tconst itemTitle = job.mode === \"parallel\" || job.activeParallelGroup ? \"Agent\" : \"Step\";\n\t\tconst modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);\n\t\tlines.push(\n\t\t\t`  ${theme.fg(\"dim\", `${marker} ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index))} ${itemTitle} ${index + 1}/${total}: ${step.agent} · ${widgetStepStatus(step.status, theme)}${modelDisplay}${activity ? ` · ${activity}` : \"\"}`)}`,\n\t\t);\n\t\tfor (const nestedLine of formatNestedWidgetLines(\n\t\t\tstep.children,\n\t\t\ttheme,\n\t\t\twidth,\n\t\t\texpanded,\n\t\t\tjob.updatedAt,\n\t\t\texpanded ? 8 : 6,\n\t\t))\n\t\t\tlines.push(`    ${nestedLine}`);\n\t}\n\treturn lines;\n}\n\nfunction parseParallelGroupAgentCount(label: string | undefined): number | undefined {\n\tif (!label || !label.startsWith(\"[\") || !label.endsWith(\"]\")) return undefined;\n\tconst inner = label.slice(1, -1).trim();\n\tif (!inner) return 0;\n\treturn inner\n\t\t.split(\"+\")\n\t\t.map((part) => part.trim())\n\t\t.filter(Boolean).length;\n}\n\ninterface ChainStepSpan {\n\tstepIndex: number;\n\tstart: number;\n\tcount: number;\n\tisParallel: boolean;\n\tstatus?: WorkflowNodeStatus;\n\tlabel?: string;\n\terror?: string;\n}\n\nfunction buildChainStepSpans(details: Pick<Details, \"chainAgents\" | \"workflowGraph\">): ChainStepSpan[] {\n\tif (details.workflowGraph?.nodes?.length) {\n\t\tconst spans: ChainStepSpan[] = [];\n\t\tlet flatCursor = 0;\n\t\tfor (const node of details.workflowGraph.nodes) {\n\t\t\tif (node.stepIndex === undefined) continue;\n\t\t\tif (node.kind === \"parallel-group\" || node.kind === \"dynamic-parallel-group\") {\n\t\t\t\tconst childFlatIndexes = (node.children ?? [])\n\t\t\t\t\t.map((child) => child.flatIndex)\n\t\t\t\t\t.filter((value): value is number => typeof value === \"number\");\n\t\t\t\tconst start = childFlatIndexes.length ? Math.min(...childFlatIndexes) : flatCursor;\n\t\t\t\tconst count = node.children?.length ?? 0;\n\t\t\t\tspans.push({\n\t\t\t\t\tstepIndex: node.stepIndex,\n\t\t\t\t\tstart,\n\t\t\t\t\tcount,\n\t\t\t\t\tisParallel: true,\n\t\t\t\t\tstatus: node.status,\n\t\t\t\t\tlabel: node.label,\n\t\t\t\t\terror: node.error,\n\t\t\t\t});\n\t\t\t\tflatCursor = Math.max(flatCursor, start + count);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst start = node.flatIndex ?? flatCursor;\n\t\t\tspans.push({\n\t\t\t\tstepIndex: node.stepIndex,\n\t\t\t\tstart,\n\t\t\t\tcount: 1,\n\t\t\t\tisParallel: false,\n\t\t\t\tstatus: node.status,\n\t\t\t\tlabel: node.label,\n\t\t\t\terror: node.error,\n\t\t\t});\n\t\t\tflatCursor = Math.max(flatCursor, start + 1);\n\t\t}\n\t\tif (spans.length) return spans.sort((left, right) => left.stepIndex - right.stepIndex);\n\t}\n\n\tif (!details.chainAgents?.length) return [];\n\tconst spans: ChainStepSpan[] = [];\n\tlet start = 0;\n\tfor (let stepIndex = 0; stepIndex < details.chainAgents.length; stepIndex++) {\n\t\tconst label = details.chainAgents[stepIndex]!;\n\t\tconst parsedCount = parseParallelGroupAgentCount(label);\n\t\tconst count = parsedCount ?? 1;\n\t\tspans.push({ stepIndex, start, count, isParallel: parsedCount !== undefined });\n\t\tstart += count;\n\t}\n\treturn spans;\n}\n\nfunction buildAsyncChainStepSpans(\n\ttotal: number,\n\tstepCount: number,\n\tparallelGroups: AsyncParallelGroupStatus[] = [],\n): ChainStepSpan[] {\n\tconst groupsByStep = new Map<number, AsyncParallelGroupStatus>();\n\tfor (const group of parallelGroups) {\n\t\tif (!groupsByStep.has(group.stepIndex)) groupsByStep.set(group.stepIndex, group);\n\t}\n\tconst spans: ChainStepSpan[] = [];\n\tlet flatIndex = 0;\n\tfor (let stepIndex = 0; stepIndex < total; stepIndex++) {\n\t\tconst group = groupsByStep.get(stepIndex);\n\t\tif (group) {\n\t\t\tspans.push({ stepIndex, start: group.start, count: group.count, isParallel: true });\n\t\t\tflatIndex = Math.max(flatIndex, group.start + group.count);\n\t\t\tcontinue;\n\t\t}\n\t\tspans.push({ stepIndex, start: flatIndex, count: flatIndex < stepCount ? 1 : 0, isParallel: false });\n\t\tflatIndex++;\n\t}\n\treturn spans;\n}\n\nfunction isDoneResult(result: Details[\"results\"][number]): boolean {\n\tconst status = result.progress?.status;\n\tif (status === \"completed\") return true;\n\tif (status === \"running\" || status === \"pending\") return false;\n\tif (result.interrupted || result.detached) return false;\n\treturn result.exitCode === 0;\n}\n\nfunction workflowGraphHasStatus(details: Pick<Details, \"workflowGraph\">, statuses: WorkflowNodeStatus[]): boolean {\n\treturn details.workflowGraph?.nodes.some((node) => statuses.includes(node.status)) ?? false;\n}\n\ninterface ChainRenderResultEntry {\n\tkind: \"result\";\n\tresultIndex: number;\n\trowNumber: number;\n\trowLabel?: string;\n\tagentName: string;\n}\n\ninterface ChainRenderPlaceholderEntry {\n\tkind: \"placeholder\";\n\trowNumber: number;\n\tstepLabel: string;\n\tagentName: string;\n\tstatus: WorkflowNodeStatus;\n\terror?: string;\n}\n\ntype ChainRenderEntry = ChainRenderResultEntry | ChainRenderPlaceholderEntry;\n\nfunction buildChainRenderEntries(details: Details, label: MultiProgressLabel): ChainRenderEntry[] | undefined {\n\tif (details.mode !== \"chain\" || !label.hasParallelInChain || label.showActiveGroupOnly) return undefined;\n\tconst entries: ChainRenderEntry[] = [];\n\tfor (const span of buildChainStepSpans(details)) {\n\t\tif (span.isParallel && span.count === 0) {\n\t\t\tentries.push({\n\t\t\t\tkind: \"placeholder\",\n\t\t\t\trowNumber: span.stepIndex + 1,\n\t\t\t\tstepLabel: `Step ${span.stepIndex + 1}`,\n\t\t\t\tagentName: span.label ?? details.chainAgents?.[span.stepIndex] ?? `step-${span.stepIndex + 1}`,\n\t\t\t\tstatus: span.status ?? \"pending\",\n\t\t\t\terror: span.error,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tfor (let index = span.start; index < span.start + span.count; index++) {\n\t\t\tentries.push({\n\t\t\t\tkind: \"result\",\n\t\t\t\tresultIndex: index,\n\t\t\t\trowNumber: index + 1,\n\t\t\t\trowLabel: span.isParallel ? `Agent ${index - span.start + 1}/${span.count}` : `Step ${span.stepIndex + 1}`,\n\t\t\t\tagentName:\n\t\t\t\t\tdetails.results[index]?.agent ?? details.chainAgents?.[span.stepIndex] ?? `step-${span.stepIndex + 1}`,\n\t\t\t});\n\t\t}\n\t}\n\treturn entries;\n}\n\ninterface MultiProgressLabel {\n\theaderLabel: string;\n\titemTitle: \"Step\" | \"Agent\";\n\ttotalCount: number;\n\thasParallelInChain: boolean;\n\tactiveParallelGroup: boolean;\n\tgroupStartIndex: number;\n\tgroupEndIndex: number;\n\tshowActiveGroupOnly: boolean;\n}\n\nfunction buildMultiProgressLabel(\n\tdetails: Pick<\n\t\tDetails,\n\t\t\"mode\" | \"results\" | \"progress\" | \"totalSteps\" | \"currentStepIndex\" | \"chainAgents\" | \"workflowGraph\"\n\t>,\n\thasRunning: boolean,\n): MultiProgressLabel {\n\tconst stepSpans = buildChainStepSpans(details);\n\tconst hasParallelInChain = details.mode === \"chain\" && stepSpans.some((span) => span.isParallel);\n\tconst activeParallelGroup =\n\t\tdetails.mode === \"chain\" &&\n\t\tdetails.currentStepIndex !== undefined &&\n\t\tstepSpans.some((span) => span.stepIndex === details.currentStepIndex && span.isParallel);\n\tconst itemTitle: \"Step\" | \"Agent\" = details.mode === \"parallel\" || activeParallelGroup ? \"Agent\" : \"Step\";\n\n\tif (details.mode === \"parallel\") {\n\t\tconst totalCount = details.totalSteps ?? details.results.length;\n\t\tconst statuses = new Array(totalCount).fill(\"pending\") as Array<\n\t\t\t\"pending\" | \"running\" | \"completed\" | \"failed\" | \"stopped\" | \"detached\"\n\t\t>;\n\t\tfor (const progress of details.progress ?? []) {\n\t\t\tif (progress.index >= 0 && progress.index < totalCount) statuses[progress.index] = progress.status;\n\t\t}\n\t\tfor (let i = 0; i < details.results.length; i++) {\n\t\t\tconst result = details.results[i]!;\n\t\t\tconst progressFromArray =\n\t\t\t\tdetails.progress?.find((progress) => progress.index === i) ||\n\t\t\t\tdetails.progress?.find((progress) => progress.agent === result.agent && progress.status === \"running\");\n\t\t\tconst index = result.progress?.index ?? progressFromArray?.index ?? i;\n\t\t\tif (index < 0 || index >= totalCount) continue;\n\t\t\tconst status = result.stopped\n\t\t\t\t? \"stopped\"\n\t\t\t\t: result.interrupted || result.detached\n\t\t\t\t\t? \"detached\"\n\t\t\t\t\t: (result.progress?.status ?? (result.exitCode === 0 ? \"completed\" : \"failed\"));\n\t\t\tstatuses[index] = status;\n\t\t}\n\t\tconst running = statuses.filter((status) => status === \"running\").length;\n\t\tconst done = statuses.filter((status) => status === \"completed\").length;\n\t\tconst headerLabel = hasRunning\n\t\t\t? `${formatAgentRunningLabel(running)} · ${done}/${totalCount} done`\n\t\t\t: `${done}/${totalCount} done`;\n\t\treturn {\n\t\t\theaderLabel,\n\t\t\titemTitle,\n\t\t\ttotalCount,\n\t\t\thasParallelInChain,\n\t\t\tactiveParallelGroup,\n\t\t\tgroupStartIndex: 0,\n\t\t\tgroupEndIndex: totalCount,\n\t\t\tshowActiveGroupOnly: false,\n\t\t};\n\t}\n\n\tif (activeParallelGroup) {\n\t\tconst currentStepIndex = details.currentStepIndex!;\n\t\tconst span = stepSpans[currentStepIndex];\n\t\tconst groupSize = span?.count ?? 1;\n\t\tconst groupStart = span?.start ?? 0;\n\t\tconst groupEnd = groupStart + groupSize;\n\t\tlet running = 0;\n\t\tlet done = 0;\n\t\tfor (let index = groupStart; index < groupEnd; index++) {\n\t\t\tconst progressEntry = details.progress?.find((progress) => progress.index === index);\n\t\t\tconst resultEntry =\n\t\t\t\tdetails.results.find((result) => result.progress?.index === index) ?? details.results[index];\n\t\t\tif (progressEntry?.status === \"running\" && (!resultEntry || !hasTerminalResultFlag(resultEntry))) {\n\t\t\t\trunning++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (progressEntry?.status === \"completed\") {\n\t\t\t\tdone++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (resultEntry && isDoneResult(resultEntry)) done++;\n\t\t}\n\t\tconst totalSteps = details.totalSteps ?? details.chainAgents?.length ?? 1;\n\t\tconst headerLabel = hasRunning\n\t\t\t? `step ${currentStepIndex + 1}/${totalSteps} · parallel group: ${formatAgentRunningLabel(running)} · ${done}/${groupSize} done`\n\t\t\t: `step ${currentStepIndex + 1}/${totalSteps} · parallel group: ${done}/${groupSize} done`;\n\t\treturn {\n\t\t\theaderLabel,\n\t\t\titemTitle,\n\t\t\ttotalCount: groupSize,\n\t\t\thasParallelInChain,\n\t\t\tactiveParallelGroup,\n\t\t\tgroupStartIndex: groupStart,\n\t\t\tgroupEndIndex: groupEnd,\n\t\t\tshowActiveGroupOnly: true,\n\t\t};\n\t}\n\n\tif (details.mode === \"chain\" && details.chainAgents?.length) {\n\t\tconst totalCount = details.totalSteps ?? details.chainAgents.length;\n\t\tconst doneLogical = stepSpans.filter((span) => {\n\t\t\tif (span.status && span.status !== \"completed\") return false;\n\t\t\tif (span.count === 0) return span.status === \"completed\";\n\t\t\tfor (let index = span.start; index < span.start + span.count; index++) {\n\t\t\t\tconst progressEntry = details.progress?.find((progress) => progress.index === index);\n\t\t\t\tconst resultEntry =\n\t\t\t\t\tdetails.results.find((result) => result.progress?.index === index) ?? details.results[index];\n\t\t\t\tif (\n\t\t\t\t\tprogressEntry?.status === \"running\" ||\n\t\t\t\t\tprogressEntry?.status === \"pending\" ||\n\t\t\t\t\tprogressEntry?.status === \"failed\"\n\t\t\t\t)\n\t\t\t\t\treturn false;\n\t\t\t\tif (!resultEntry || !isDoneResult(resultEntry)) return false;\n\t\t\t}\n\t\t\treturn true;\n\t\t}).length;\n\t\tconst currentStep =\n\t\t\tdetails.currentStepIndex !== undefined\n\t\t\t\t? details.currentStepIndex + 1\n\t\t\t\t: Math.min(totalCount, doneLogical + (hasRunning ? 1 : 0));\n\t\tconst headerLabel = hasRunning ? `step ${currentStep}/${totalCount}` : `step ${doneLogical}/${totalCount}`;\n\t\treturn {\n\t\t\theaderLabel,\n\t\t\titemTitle,\n\t\t\ttotalCount,\n\t\t\thasParallelInChain,\n\t\t\tactiveParallelGroup,\n\t\t\tgroupStartIndex: 0,\n\t\t\tgroupEndIndex: details.results.length,\n\t\t\tshowActiveGroupOnly: false,\n\t\t};\n\t}\n\n\tconst totalCount = details.totalSteps ?? details.results.length;\n\tconst currentStep =\n\t\tdetails.currentStepIndex !== undefined\n\t\t\t? details.currentStepIndex + 1\n\t\t\t: Math.min(totalCount, details.results.filter(isDoneResult).length + (hasRunning ? 1 : 0));\n\tconst done = details.results.filter(isDoneResult).length;\n\tconst headerLabel = hasRunning ? `step ${currentStep}/${totalCount}` : `step ${done}/${totalCount}`;\n\treturn {\n\t\theaderLabel,\n\t\titemTitle,\n\t\ttotalCount,\n\t\thasParallelInChain,\n\t\tactiveParallelGroup,\n\t\tgroupStartIndex: 0,\n\t\tgroupEndIndex: details.results.length,\n\t\tshowActiveGroupOnly: false,\n\t};\n}\n\nfunction resultRowLabel(label: MultiProgressLabel, resultIndex: number, stepNumber: number): string {\n\tif (label.itemTitle === \"Agent\") {\n\t\tconst localStepNumber = label.activeParallelGroup ? resultIndex - label.groupStartIndex + 1 : stepNumber;\n\t\treturn `Agent ${localStepNumber}/${label.totalCount}`;\n\t}\n\treturn `Step ${stepNumber}`;\n}\n\nfunction widgetStats(job: AsyncJobState, theme: Theme): string {\n\tconst parts: string[] = [];\n\tconst stepsTotal = job.stepsTotal ?? job.agents?.length ?? 1;\n\tif (job.activeParallelGroup) {\n\t\tconst running = job.runningSteps ?? (job.status === \"running\" ? 1 : 0);\n\t\tconst done = job.completedSteps ?? (job.status === \"complete\" ? stepsTotal : 0);\n\t\tif (job.mode === \"parallel\") {\n\t\t\tif (job.status === \"running\" && running > 0) parts.push(formatAgentRunningLabel(running));\n\t\t\tif (stepsTotal > 0) parts.push(`${done}/${stepsTotal} done`);\n\t\t} else {\n\t\t\tconst activeGroup =\n\t\t\t\tjob.currentStep !== undefined\n\t\t\t\t\t? job.parallelGroups?.find(\n\t\t\t\t\t\t\t(group) => job.currentStep! >= group.start && job.currentStep! < group.start + group.count,\n\t\t\t\t\t\t)\n\t\t\t\t\t: job.parallelGroups?.find((group) => group.start === 0);\n\t\t\tconst logicalStep = activeGroup?.stepIndex ?? job.currentStep ?? 0;\n\t\t\tconst total = job.chainStepCount ?? stepsTotal;\n\t\t\tconst groupParts = [`${done}/${stepsTotal} done`];\n\t\t\tif (job.status === \"running\" && running > 0) groupParts.unshift(formatAgentRunningLabel(running));\n\t\t\tparts.push(`step ${logicalStep + 1}/${total} · parallel group: ${groupParts.join(\" · \")}`);\n\t\t}\n\t} else if (job.currentStep !== undefined) {\n\t\tif (job.mode === \"chain\" && job.parallelGroups?.length) {\n\t\t\tconst total = job.chainStepCount ?? stepsTotal;\n\t\t\tparts.push(`step ${flatToLogicalStepIndex(job.currentStep, total, job.parallelGroups) + 1}/${total}`);\n\t\t} else {\n\t\t\tparts.push(`step ${job.currentStep + 1}/${stepsTotal}`);\n\t\t}\n\t} else if (stepsTotal > 1) {\n\t\tparts.push(`steps ${stepsTotal}`);\n\t}\n\tif (job.toolCount !== undefined) parts.push(formatToolUseStat(job.toolCount));\n\tif (job.totalTokens?.total) parts.push(formatTokenStat(job.totalTokens.total));\n\tif (job.startedAt !== undefined && job.updatedAt !== undefined)\n\t\tparts.push(formatDuration(Math.max(0, job.updatedAt - job.startedAt)));\n\treturn statJoin(theme, parts);\n}\n\nfunction widgetStepStats(theme: Theme, step: NonNullable<AsyncJobState[\"steps\"]>[number]): string {\n\treturn statJoin(theme, [\n\t\tstep.turnCount !== undefined ? `${step.turnCount} turns` : \"\",\n\t\tstep.toolCount !== undefined ? formatToolUseStat(step.toolCount) : \"\",\n\t\tstep.tokens?.total ? formatTokenStat(step.tokens.total) : \"\",\n\t\tstep.durationMs !== undefined ? formatDuration(step.durationMs) : \"\",\n\t]);\n}\n\nfunction modelThinkingBadge(theme: Theme, model?: string, thinking?: string): string {\n\tconst label = formatModelThinking(model, thinking);\n\treturn label ? theme.fg(\"dim\", ` (${label})`) : \"\";\n}\n\nfunction widgetStepActivityLine(\n\tstep: NonNullable<AsyncJobState[\"steps\"]>[number],\n\twidth: number,\n\texpanded: boolean,\n\tsnapshotNow?: number,\n): string {\n\tconst toolLine = formatCurrentToolLine(step, width, expanded, snapshotNow);\n\tif (toolLine) return toolLine;\n\tconst activity = buildLiveStatusLine(step, snapshotNow);\n\tif (activity) return activity;\n\tif (step.status === \"running\") return \"thinking…\";\n\treturn \"\";\n}\n\nfunction widgetOutputPath(job: AsyncJobState, step: NonNullable<AsyncJobState[\"steps\"]>[number]): string | undefined {\n\tif (typeof step.index !== \"number\") return undefined;\n\treturn path.join(job.asyncDir, `output-${step.index}.log`);\n}\n\nfunction nestedRunName(run: NestedRunSummary): string {\n\tif (run.agent) return run.agent;\n\tif (run.agents?.length) return formatWidgetAgents(run.agents);\n\treturn run.id;\n}\n\nfunction nestedStatusGlyph(\n\tstate: NestedRunSummary[\"state\"] | NestedStepSummary[\"status\"],\n\ttheme: Theme,\n\tseed?: number,\n): string {\n\tif (state === \"running\") return theme.fg(\"accent\", runningGlyph(seed));\n\tif (state === \"complete\" || state === \"completed\") return theme.fg(\"success\", \"✓\");\n\tif (state === \"failed\") return theme.fg(\"error\", \"✗\");\n\tif (state === \"paused\") return theme.fg(\"warning\", \"■\");\n\tif (state === \"stopped\") return theme.fg(\"warning\", \"■\");\n\treturn theme.fg(\"muted\", \"◦\");\n}\n\nfunction nestedRunSeed(run: NestedRunSummary): number | undefined {\n\treturn runningSeed(\n\t\trun.lastUpdate,\n\t\trun.lastActivityAt,\n\t\trun.currentStep,\n\t\trun.toolCount,\n\t\trun.turnCount,\n\t\trun.totalTokens?.total,\n\t\trun.currentToolStartedAt,\n\t);\n}\n\nfunction formatClockTime(ms: number | undefined): string | undefined {\n\tif (ms === undefined || !Number.isFinite(ms)) return undefined;\n\tconst date = new Date(ms);\n\tconst pad = (value: number) => value.toString().padStart(2, \"0\");\n\treturn `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;\n}\n\nfunction nestedRunEventTime(run: NestedRunSummary): number | undefined {\n\treturn run.state === \"running\"\n\t\t? (run.lastActivityAt ?? run.currentToolStartedAt ?? run.lastUpdate ?? run.startedAt)\n\t\t: (run.endedAt ?? run.lastUpdate ?? run.lastActivityAt ?? run.startedAt);\n}\n\nfunction nestedStepTimestamp(step: NestedStepSummary, fallback?: number): string | undefined {\n\treturn formatClockTime(\n\t\tstep.status === \"running\"\n\t\t\t? (step.lastActivityAt ?? step.currentToolStartedAt ?? fallback ?? step.startedAt)\n\t\t\t: (step.endedAt ?? step.lastActivityAt ?? fallback ?? step.startedAt),\n\t);\n}\n\nfunction nestedTimestampPrefix(timestamp: string | undefined): string {\n\treturn timestamp ? `[${timestamp}] ` : \"\";\n}\n\nfunction nestedActivity(\n\tinput: Pick<\n\t\tNestedRunSummary | NestedStepSummary,\n\t\t| \"activityState\"\n\t\t| \"lastActivityAt\"\n\t\t| \"currentTool\"\n\t\t| \"currentToolStartedAt\"\n\t\t| \"currentPath\"\n\t\t| \"turnCount\"\n\t\t| \"toolCount\"\n\t>,\n\tstate: NestedRunSummary[\"state\"] | NestedStepSummary[\"status\"],\n\tsnapshotNow?: number,\n): string {\n\tconst facts: string[] = [];\n\tif (input.currentTool && input.currentToolStartedAt !== undefined && snapshotNow !== undefined)\n\t\tfacts.push(`${input.currentTool} ${formatDuration(Math.max(0, snapshotNow - input.currentToolStartedAt))}`);\n\telse if (input.currentTool) facts.push(input.currentTool);\n\tif (input.currentPath) facts.push(shortenPath(input.currentPath));\n\tif (input.turnCount !== undefined) facts.push(`${input.turnCount} turns`);\n\tif (input.toolCount !== undefined) facts.push(`${input.toolCount} tools`);\n\tconst activity = buildLiveStatusLine(input, snapshotNow);\n\tif (activity && facts.length) return `${activity} · ${facts.join(\" · \")}`;\n\tif (activity) return activity;\n\tif (facts.length) return facts.join(\" · \");\n\tif (state === \"running\") return \"thinking…\";\n\tif (state === \"queued\" || state === \"pending\") return \"queued…\";\n\tif (state === \"paused\") return \"Paused\";\n\tif (state === \"stopped\") return \"Stopped\";\n\tif (state === \"failed\") return \"Failed\";\n\treturn \"Done\";\n}\n\nfunction formatNestedWidgetLines(\n\tchildren: NestedRunSummary[] | undefined,\n\ttheme: Theme,\n\twidth: number,\n\texpanded: boolean,\n\tsnapshotNow?: number,\n\tlineBudget = expanded ? 12 : 1,\n): string[] {\n\tif (!children?.length || lineBudget <= 0) return [];\n\tif (!expanded) {\n\t\ttype CollapsedRow = { text: string; prefix: string };\n\t\tconst rows: CollapsedRow[] = [];\n\t\tconst maxLeaves = 4;\n\t\tconst maxLines = Math.min(6, lineBudget);\n\t\tlet leaves = 0;\n\t\tlet overflow = 0;\n\t\tconst appendLeaf = (step: NestedStepSummary | NestedRunSummary, prefix: string, fallback?: number): void => {\n\t\t\tif (leaves >= maxLeaves) {\n\t\t\t\toverflow++;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tleaves++;\n\t\t\tconst state = \"status\" in step ? step.status : step.state;\n\t\t\tconst modelThinking = formatModelThinking(step.model, step.thinking);\n\t\t\tconst activity = nestedActivity(step, state, snapshotNow ?? fallback);\n\t\t\tconst timestamp =\n\t\t\t\t\"status\" in step ? nestedStepTimestamp(step, fallback) : formatClockTime(nestedRunEventTime(step));\n\t\t\tconst error = step.error ? ` · ${step.error}` : \"\";\n\t\t\tconst name = \"status\" in step ? step.agent : nestedRunName(step);\n\t\t\trows.push({\n\t\t\t\tprefix,\n\t\t\t\ttext: `${nestedTimestampPrefix(timestamp)}${nestedStatusGlyph(state, theme)} ${name} · ${state}${modelThinking ? ` · ${modelThinking}` : \"\"}${activity ? ` · ${activity}` : \"\"}${error}`,\n\t\t\t});\n\t\t};\n\t\tfor (const child of children) {\n\t\t\tconst steps = child.mode === \"parallel\" || child.mode === \"chain\" ? (child.steps ?? []) : [];\n\t\t\tif (steps.length > 0) {\n\t\t\t\tconst ownerModelThinking = formatModelThinking(child.model, child.thinking);\n\t\t\t\tconst ownerActivity = nestedActivity(child, child.state, snapshotNow ?? child.lastUpdate);\n\t\t\t\tconst ownerError = child.error ? ` · ${child.error}` : \"\";\n\t\t\t\trows.push({\n\t\t\t\t\tprefix: \"↳ \",\n\t\t\t\t\ttext: `OWNER ${nestedStatusGlyph(child.state, theme, nestedRunSeed(child))} ${nestedRunName(child)} · ${child.state}${ownerModelThinking ? ` · ${ownerModelThinking}` : \"\"}${ownerActivity ? ` · ${ownerActivity}` : \"\"}${ownerError}`,\n\t\t\t\t});\n\t\t\t\tfor (const step of steps) appendLeaf(step, \"↳ │  \", child.lastUpdate);\n\t\t\t} else {\n\t\t\t\tappendLeaf(child, \"↳ \", child.lastUpdate);\n\t\t\t}\n\t\t}\n\t\tif (overflow > 0) rows.push({ prefix: \"↳ \", text: `… +${overflow} more nested leaves` });\n\t\tconst visibleRows =\n\t\t\trows.length <= maxLines\n\t\t\t\t? rows\n\t\t\t\t: overflow > 0\n\t\t\t\t\t? [...rows.slice(0, Math.max(0, maxLines - 1)), rows.at(-1)!]\n\t\t\t\t\t: rows.slice(0, maxLines);\n\t\treturn visibleRows.map((row, index) => {\n\t\t\tconst marker = index === visibleRows.length - 1 ? \"└─\" : \"├─\";\n\t\t\tconst prefix = row.prefix;\n\t\t\tconst text = row.text.startsWith(\"OWNER \") ? row.text.slice(\"OWNER \".length) : row.text;\n\t\t\treturn truncLine(theme.fg(\"dim\", `${prefix}${marker} ${text}`), width);\n\t\t});\n\t}\n\tconst lines: string[] = [];\n\tconst maxDepth = 2;\n\tconst append = (items: NestedRunSummary[] | undefined, depth: number, prefix: string): void => {\n\t\tif (!items?.length || lines.length >= lineBudget) return;\n\t\tif (depth > maxDepth) {\n\t\t\tconst aggregate = formatNestedAggregate(items);\n\t\t\tif (aggregate && lines.length < lineBudget) lines.push(theme.fg(\"dim\", `${prefix}↳ ${aggregate}`));\n\t\t\treturn;\n\t\t}\n\t\tfor (let index = 0; index < items.length; index++) {\n\t\t\tconst child = items[index]!;\n\t\t\tif (lines.length >= lineBudget) {\n\t\t\t\tconst aggregate = formatNestedAggregate(items.slice(index));\n\t\t\t\tif (aggregate) lines[lines.length - 1] = theme.fg(\"dim\", `${prefix}↳ ${aggregate}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst activity = nestedActivity(child, child.state, snapshotNow ?? child.lastUpdate);\n\t\t\tconst error = child.error ? ` · ${child.error}` : \"\";\n\t\t\tconst modelThinking = formatModelThinking(child.model, child.thinking);\n\t\t\tlines.push(\n\t\t\t\ttheme.fg(\n\t\t\t\t\t\"dim\",\n\t\t\t\t\t`${prefix}↳ ${nestedTimestampPrefix(formatClockTime(nestedRunEventTime(child)))}${nestedStatusGlyph(child.state, theme, nestedRunSeed(child))} ${nestedRunName(child)} · ${child.state}${modelThinking ? ` · ${modelThinking}` : \"\"} · ${activity}${error}`,\n\t\t\t\t),\n\t\t\t);\n\t\t\tif (depth === maxDepth) {\n\t\t\t\tconst aggregate = formatNestedAggregate([\n\t\t\t\t\t...(child.steps?.flatMap((step) => step.children ?? []) ?? []),\n\t\t\t\t\t...(child.children ?? []),\n\t\t\t\t]);\n\t\t\t\tif (aggregate && lines.length < lineBudget) lines.push(theme.fg(\"dim\", `${prefix}  ↳ ${aggregate}`));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor (const step of child.steps ?? []) {\n\t\t\t\tif (lines.length >= lineBudget) return;\n\t\t\t\tconst modelThinking = formatModelThinking(step.model, step.thinking);\n\t\t\t\tlines.push(\n\t\t\t\t\ttheme.fg(\n\t\t\t\t\t\t\"dim\",\n\t\t\t\t\t\t`${prefix}  ↳ ${nestedTimestampPrefix(nestedStepTimestamp(step, child.lastUpdate))}${nestedStatusGlyph(step.status, theme)} ${step.agent} · ${step.status}${modelThinking ? ` · ${modelThinking}` : \"\"} · ${nestedActivity(step, step.status, snapshotNow ?? child.lastUpdate)}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tappend(step.children, depth + 1, `${prefix}    `);\n\t\t\t}\n\t\t\tappend(child.children, depth + 1, `${prefix}  `);\n\t\t}\n\t};\n\tappend(children, 0, \"\");\n\treturn lines.map((line) => truncLine(line, width));\n}\n\nfunction foregroundStyleWidgetStepLines(\n\tjob: AsyncJobState,\n\ttheme: Theme,\n\tstep: NonNullable<AsyncJobState[\"steps\"]>[number],\n\titemTitle: \"Agent\" | \"Step\",\n\tindex: number,\n\ttotal: number,\n\texpanded: boolean,\n\twidth: number,\n\tframe?: number,\n): string[] {\n\tconst status = widgetStepStatus(step.status, theme);\n\tconst stats = widgetStepStats(theme, step);\n\tconst modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);\n\tconst lines = [\n\t\t`  ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index - 1), frame)} ${itemTitle} ${index}/${total}: ${themeBold(theme, step.agent)}${contextModeBadge(theme, step.context)} ${theme.fg(\"dim\", \"·\")} ${status}${modelDisplay}${stats ? ` ${theme.fg(\"dim\", \"·\")} ${stats}` : \"\"}`,\n\t];\n\tconst activity = widgetStepActivityLine(step, width, expanded, job.updatedAt);\n\tif (activity) lines.push(`    ${theme.fg(\"dim\", `⎿  ${activity}`)}`);\n\tfor (const nestedLine of formatNestedWidgetLines(\n\t\tstep.children,\n\t\ttheme,\n\t\twidth,\n\t\texpanded,\n\t\tjob.updatedAt,\n\t\texpanded ? 12 : 6,\n\t)) {\n\t\tlines.push(`    ${nestedLine}`);\n\t}\n\tif (step.status === \"running\") {\n\t\tif (!expanded) lines.push(`    ${theme.fg(\"accent\", liveDetailHintText())}`);\n\t\tconst output = widgetOutputPath(job, step);\n\t\tif (output) lines.push(`    ${theme.fg(\"dim\", `output: ${shortenPath(output)}`)}`);\n\t\tif (expanded) {\n\t\t\tconst liveStatus = buildLiveStatusLine(step, job.updatedAt);\n\t\t\tif (liveStatus && liveStatus !== activity) lines.push(`    ${theme.fg(\"accent\", liveStatus)}`);\n\t\t\tfor (const tool of step.recentTools?.slice(-3) ?? []) {\n\t\t\t\tconst maxArgsLen = Math.max(40, width - 30);\n\t\t\t\tconst argsPreview = tool.args.length <= maxArgsLen ? tool.args : `${tool.args.slice(0, maxArgsLen)}...`;\n\t\t\t\tlines.push(`      ${theme.fg(\"dim\", `${tool.tool}${argsPreview ? `: ${argsPreview}` : \"\"}`)}`);\n\t\t\t}\n\t\t\tfor (const line of step.recentOutput?.slice(-5) ?? []) {\n\t\t\t\tlines.push(`      ${theme.fg(\"dim\", line)}`);\n\t\t\t}\n\t\t}\n\t}\n\treturn lines;\n}\n\nfunction foregroundStyleWidgetDetails(\n\tjob: AsyncJobState,\n\ttheme: Theme,\n\texpanded: boolean,\n\twidth: number,\n\tframe?: number,\n): string[] {\n\tif (!job.steps?.length)\n\t\treturn [\n\t\t\t`  ${theme.fg(\"dim\", `⎿  ${widgetActivity(job)}`)}`,\n\t\t\t...formatNestedWidgetLines(job.nestedChildren, theme, width, expanded, job.updatedAt, expanded ? 12 : 6).map(\n\t\t\t\t(line) => `  ${line}`,\n\t\t\t),\n\t\t];\n\tif (job.mode === \"chain\" && !job.activeParallelGroup && job.parallelGroups?.length)\n\t\treturn widgetChainDetails(job, theme, expanded, width);\n\tconst total = job.stepsTotal ?? job.steps.length;\n\tconst itemTitle = job.mode === \"parallel\" || job.activeParallelGroup ? \"Agent\" : \"Step\";\n\tconst lines: string[] = [];\n\tfor (const [index, step] of job.steps.entries()) {\n\t\tlines.push(\n\t\t\t...foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index + 1, total, expanded, width, frame),\n\t\t);\n\t}\n\tconst attached = new Set(job.steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));\n\tconst unattached = job.nestedChildren?.filter((child) => !attached.has(child.id)) ?? [];\n\tfor (const nestedLine of formatNestedWidgetLines(\n\t\tunattached,\n\t\ttheme,\n\t\twidth,\n\t\texpanded,\n\t\tjob.updatedAt,\n\t\texpanded ? 12 : 6,\n\t)) {\n\t\tlines.push(`  ${nestedLine}`);\n\t}\n\treturn lines;\n}\n\nfunction buildSingleWidgetLines(\n\tjob: AsyncJobState,\n\ttheme: Theme,\n\twidth: number,\n\texpanded: boolean,\n\tframe?: number,\n): string[] {\n\tconst stats = widgetStats(job, theme);\n\tconst count =\n\t\tjob.mode === \"chain\" ? job.chainStepCount : (job.stepsTotal ?? job.agents?.length ?? job.steps?.length);\n\tconst mode = widgetJobName(job);\n\tconst title = `async subagent ${mode}${count && count > 1 ? ` (${count})` : \"\"}`;\n\treturn [\n\t\t`${theme.fg(\"toolTitle\", themeBold(theme, title))} ${theme.fg(\"dim\", \"· background\")}`,\n\t\t`${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, mode)}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg(\"dim\", \"·\")} ${stats}` : \"\"}`,\n\t\t...foregroundStyleWidgetDetails(job, theme, expanded, width, frame),\n\t].map((line) => truncLine(line, width));\n}\n\nfunction compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number, frame?: number): string[] {\n\tconst fullLines = buildSingleWidgetLines(job, theme, width, false, frame);\n\tif (fullLines.length <= 10 || !job.steps?.length || (job.mode !== \"parallel\" && !job.activeParallelGroup))\n\t\treturn fullLines;\n\n\tconst total = job.stepsTotal ?? job.steps.length;\n\tconst itemTitle = job.mode === \"parallel\" || job.activeParallelGroup ? \"Agent\" : \"Step\";\n\tconst lines = fullLines.slice(0, 2);\n\tfor (const [index, step] of job.steps.entries()) {\n\t\tconst status = widgetStepStatus(step.status, theme);\n\t\tconst activity = widgetStepActivityLine(step, width, false, job.updatedAt);\n\t\tconst stepStats = widgetStepStats(theme, step);\n\t\tconst activitySuffix = activity ? ` ${theme.fg(\"dim\", \"·\")} ${theme.fg(\"dim\", activity)}` : \"\";\n\t\tconst modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);\n\t\tlines.push(\n\t\t\t`  ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index), frame)} ${itemTitle} ${index + 1}/${total}: ${themeBold(theme, step.agent)}${contextModeBadge(theme, step.context)} ${theme.fg(\"dim\", \"·\")} ${status}${modelDisplay}${activitySuffix}${stepStats ? ` ${theme.fg(\"dim\", \"·\")} ${stepStats}` : \"\"}`,\n\t\t);\n\t\tfor (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt, 6))\n\t\t\tlines.push(`    ${nestedLine}`);\n\t}\n\tif (job.steps.some((step) => step.status === \"running\")) lines.push(theme.fg(\"accent\", `  ${liveDetailHintText()}`));\n\treturn lines.map((line) => truncLine(line, width));\n}\n\ntype WidgetRenderTier = \"full\" | \"single-line\" | \"progressive\";\n\ninterface WidgetLayoutSession {\n\texpanded: boolean;\n\trows: number;\n\tcolumns: number;\n\ttier: WidgetRenderTier;\n\tlockedRows?: number;\n\tvisibleJobKeys: string[];\n}\n\nconst RESERVED_NON_WIDGET_ROWS = 19;\n\nlet widgetLayoutSession: WidgetLayoutSession | undefined;\n\nfunction resetWidgetLayoutSession(): void {\n\twidgetLayoutSession = undefined;\n}\n\nfunction estimateAvailableWidgetRows(): number {\n\tconst rows = process.stdout.rows || 30;\n\treturn Math.max(1, rows - RESERVED_NON_WIDGET_ROWS);\n}\n\nfunction currentTerminalRows(): number {\n\treturn process.stdout.rows || 30;\n}\n\nfunction currentTerminalColumns(): number {\n\treturn process.stdout.columns || 120;\n}\n\nfunction widgetSessionMatches(expanded: boolean): boolean {\n\treturn (\n\t\twidgetLayoutSession?.expanded === expanded &&\n\t\twidgetLayoutSession.rows === currentTerminalRows() &&\n\t\twidgetLayoutSession.columns === currentTerminalColumns()\n\t);\n}\n\nfunction widgetHeaderCounts(jobs: AsyncJobState[]): {\n\trunning: AsyncJobState[];\n\tqueued: AsyncJobState[];\n\tcomplete: AsyncJobState[];\n\tfailed: AsyncJobState[];\n\tpaused: AsyncJobState[];\n\tstopped: AsyncJobState[];\n} {\n\treturn {\n\t\trunning: jobs.filter((job) => job.status === \"running\"),\n\t\tqueued: jobs.filter((job) => job.status === \"queued\"),\n\t\tcomplete: jobs.filter((job) => job.status === \"complete\"),\n\t\tfailed: jobs.filter((job) => job.status === \"failed\"),\n\t\tpaused: jobs.filter((job) => job.status === \"paused\"),\n\t\tstopped: jobs.filter((job) => job.status === \"stopped\"),\n\t};\n}\n\nfunction buildSingleLineWidgetLines(jobs: AsyncJobState[], theme: Theme, width: number, frame?: number): string[] {\n\tconst counts = widgetHeaderCounts(jobs);\n\tconst hasActive = counts.running.length > 0 || counts.queued.length > 0;\n\tconst glyph =\n\t\tcounts.running.length > 0\n\t\t\t? runningGlyph(animatedSeed(widgetJobsRunningSeed(counts.running), frame))\n\t\t\t: hasActive\n\t\t\t\t? \"●\"\n\t\t\t\t: \"○\";\n\tconst parts: string[] = [];\n\tif (counts.running.length > 0) parts.push(`${counts.running.length}/${jobs.length} running`);\n\tif (counts.queued.length > 0) parts.push(`${counts.queued.length} queued`);\n\tif (counts.failed.length > 0) parts.push(`${counts.failed.length} failed`);\n\tif (counts.stopped.length > 0) parts.push(`${counts.stopped.length} stopped`);\n\tif (counts.paused.length > 0) parts.push(`${counts.paused.length} paused`);\n\tif (!hasActive && counts.complete.length > 0) parts.push(`${counts.complete.length}/${jobs.length} done`);\n\treturn [\n\t\ttruncLine(\n\t\t\t`${theme.fg(hasActive ? \"accent\" : \"dim\", glyph)} ${theme.fg(hasActive ? \"accent\" : \"dim\", \"subagents\")} (${parts.join(\", \") || `${jobs.length} total`})`,\n\t\t\twidth,\n\t\t),\n\t];\n}\n\nfunction orderedWidgetJobs(jobs: AsyncJobState[]): AsyncJobState[] {\n\treturn [\n\t\t...jobs.filter((job) => job.status === \"running\"),\n\t\t...jobs.filter((job) => job.status === \"queued\"),\n\t\t...jobs.filter((job) => job.status !== \"running\" && job.status !== \"queued\"),\n\t];\n}\n\nfunction progressiveJobKey(job: AsyncJobState): string {\n\treturn job.asyncId;\n}\n\nfunction isProgressiveActiveJob(job: AsyncJobState | undefined): boolean {\n\treturn job?.status === \"running\" || job?.status === \"queued\";\n}\n\nfunction selectProgressiveJobKeys(jobs: AsyncJobState[], previousKeys: string[], bodyRows: number): string[] {\n\tif (bodyRows <= 0) return [];\n\tconst jobsByKey = new Map(jobs.map((job) => [progressiveJobKey(job), job]));\n\tconst selected: string[] = [];\n\tconst append = (key: string): void => {\n\t\tif (selected.includes(key) || !jobsByKey.has(key)) return;\n\t\tselected.push(key);\n\t};\n\tfor (const key of previousKeys) {\n\t\tif (!isProgressiveActiveJob(jobsByKey.get(key))) continue;\n\t\tappend(key);\n\t\tif (selected.length >= bodyRows) return selected;\n\t}\n\tfor (const job of orderedWidgetJobs(jobs)) {\n\t\tif (!isProgressiveActiveJob(job)) continue;\n\t\tconst key = progressiveJobKey(job);\n\t\tappend(key);\n\t\tif (selected.length >= bodyRows) break;\n\t}\n\tif (selected.length >= bodyRows) return selected;\n\tfor (const key of previousKeys) {\n\t\tif (isProgressiveActiveJob(jobsByKey.get(key))) continue;\n\t\tappend(key);\n\t\tif (selected.length >= bodyRows) return selected;\n\t}\n\tfor (const job of orderedWidgetJobs(jobs)) {\n\t\tconst key = progressiveJobKey(job);\n\t\tappend(key);\n\t\tif (selected.length >= bodyRows) break;\n\t}\n\treturn selected;\n}\n\nfunction progressiveHeaderLine(jobs: AsyncJobState[], theme: Theme, width: number, frame?: number): string {\n\tconst counts = widgetHeaderCounts(jobs);\n\tconst hasActive = counts.running.length > 0 || counts.queued.length > 0;\n\tconst glyph =\n\t\tcounts.running.length > 0\n\t\t\t? runningGlyph(animatedSeed(widgetJobsRunningSeed(counts.running), frame))\n\t\t\t: hasActive\n\t\t\t\t? \"●\"\n\t\t\t\t: \"○\";\n\tconst parts: string[] = [];\n\tif (counts.running.length > 0) parts.push(formatAgentRunningLabel(counts.running.length));\n\tif (counts.queued.length > 0) parts.push(`${counts.queued.length} queued`);\n\tif (!hasActive) {\n\t\tif (counts.failed.length > 0) parts.push(`${counts.failed.length} failed`);\n\t\tif (counts.stopped.length > 0) parts.push(`${counts.stopped.length} stopped`);\n\t\tif (counts.paused.length > 0) parts.push(`${counts.paused.length} paused`);\n\t\tif (counts.complete.length > 0) parts.push(`${counts.complete.length}/${jobs.length} done`);\n\t}\n\treturn truncLine(\n\t\t`${theme.fg(hasActive ? \"accent\" : \"dim\", glyph)} ${theme.fg(hasActive ? \"accent\" : \"dim\", \"Async agents\")} ${theme.fg(\"dim\", \"·\")} ${theme.fg(\"dim\", parts.join(\", \") || `${jobs.length} total`)}`,\n\t\twidth,\n\t);\n}\n\nfunction progressiveJobLine(job: AsyncJobState, theme: Theme, width: number, frame?: number): string {\n\tconst stats = widgetStats(job, theme);\n\tconst activity = widgetActivity(job);\n\tconst status = job.status === \"complete\" ? \"done\" : job.status;\n\tconst parts = [\n\t\t`${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}`,\n\t\ttheme.fg(\"dim\", status),\n\t\tstats,\n\t\tactivity && activity.toLowerCase() !== status ? theme.fg(\"dim\", activity) : \"\",\n\t].filter(Boolean);\n\treturn truncLine(`  ${widgetStatusGlyph(job, theme, frame)} ${parts.join(` ${theme.fg(\"dim\", \"·\")} `)}`, width);\n}\n\nfunction progressiveHiddenLine(hiddenJobs: AsyncJobState[], theme: Theme, width: number): string {\n\tconst counts = widgetHeaderCounts(hiddenJobs);\n\tconst parts: string[] = [];\n\tif (counts.running.length > 0) parts.push(`${counts.running.length} running`);\n\tif (counts.queued.length > 0) parts.push(`${counts.queued.length} queued`);\n\tconst finished = counts.complete.length + counts.failed.length + counts.paused.length + counts.stopped.length;\n\tif (finished > 0) parts.push(`${finished} finished`);\n\treturn truncLine(\n\t\ttheme.fg(\"dim\", `  +${hiddenJobs.length} more${parts.length ? ` (${parts.join(\", \")})` : \"\"}`),\n\t\twidth,\n\t);\n}\n\nfunction buildProgressiveWidgetLines(\n\tjobs: AsyncJobState[],\n\ttheme: Theme,\n\twidth: number,\n\tlockedRows: number,\n\tpreviousKeys: string[],\n\tframe?: number,\n): { lines: string[]; visibleJobKeys: string[] } {\n\tconst rowCount = Math.max(1, lockedRows);\n\tif (rowCount === 1) return { lines: buildSingleLineWidgetLines(jobs, theme, width, frame), visibleJobKeys: [] };\n\n\tconst bodyRows = rowCount - 1;\n\tlet visibleJobKeys = selectProgressiveJobKeys(jobs, previousKeys, bodyRows);\n\tconst jobsByKey = new Map(jobs.map((job) => [progressiveJobKey(job), job]));\n\tlet visibleJobs = visibleJobKeys\n\t\t.map((key) => jobsByKey.get(key))\n\t\t.filter((job): job is AsyncJobState => Boolean(job));\n\tlet hiddenJobs = jobs.filter((job) => !visibleJobKeys.includes(progressiveJobKey(job)));\n\tconst needsHiddenLine = hiddenJobs.length > 0;\n\n\tif (needsHiddenLine && visibleJobs.length >= bodyRows && bodyRows > 0) {\n\t\tvisibleJobs = visibleJobs.slice(0, bodyRows - 1);\n\t\tvisibleJobKeys = visibleJobs.map(progressiveJobKey);\n\t\thiddenJobs = jobs.filter((job) => !visibleJobKeys.includes(progressiveJobKey(job)));\n\t}\n\n\tconst lines = [\n\t\tprogressiveHeaderLine(jobs, theme, width, frame),\n\t\t...visibleJobs.map((job) => progressiveJobLine(job, theme, width, frame)),\n\t];\n\tif (hiddenJobs.length > 0 && lines.length < rowCount) lines.push(progressiveHiddenLine(hiddenJobs, theme, width));\n\twhile (lines.length < rowCount) lines.push(\" \");\n\treturn { lines: lines.slice(0, rowCount), visibleJobKeys };\n}\n\nfunction collapsedWidgetLineBudget(rows: number): number {\n\treturn Math.max(10, Math.min(14, Math.floor(rows * 0.35)));\n}\n\nfunction paddedWidgetLine(line: string, width: number): string {\n\tif (width <= 2) return \" \".repeat(Math.max(0, width));\n\tconst text = ` ${truncLine(line, width - 2)} `;\n\treturn `${text}${\" \".repeat(Math.max(0, width - visibleWidth(text)))}`;\n}\n\nfunction fitWidgetLineBudget(lines: string[], theme: Theme, width: number, expanded: boolean): string[] {\n\tconst rows = process.stdout.rows || 30;\n\tconst budget = expanded ? Math.max(12, Math.min(24, Math.floor(rows * 0.55))) : collapsedWidgetLineBudget(rows);\n\tif (lines.length <= budget) return lines;\n\tconst visibleLines = Math.max(1, budget - 1);\n\tconst hiddenCount = lines.length - visibleLines;\n\tconst hint = expanded\n\t\t? `… ${hiddenCount} live-detail lines hidden`\n\t\t: `… ${hiddenCount} lines hidden · ${liveDetailKeyText()} expands`;\n\treturn [...lines.slice(0, visibleLines), truncLine(theme.fg(\"dim\", hint), width)];\n}\n\nfunction fitAdaptiveWidgetLines(\n\tjobs: AsyncJobState[],\n\tlines: string[],\n\ttheme: Theme,\n\twidth: number,\n\texpanded: boolean,\n\tframe?: number,\n): string[] {\n\tif (expanded) {\n\t\tresetWidgetLayoutSession();\n\t\treturn fitWidgetLineBudget(lines, theme, width, true);\n\t}\n\n\tconst hasMatchingSession = widgetSessionMatches(expanded);\n\tconst rows = currentTerminalRows();\n\tconst columns = currentTerminalColumns();\n\tconst availableRows = estimateAvailableWidgetRows();\n\n\tif (hasMatchingSession && widgetLayoutSession?.tier === \"single-line\") {\n\t\treturn buildSingleLineWidgetLines(jobs, theme, width, frame);\n\t}\n\n\tif (\n\t\thasMatchingSession &&\n\t\twidgetLayoutSession?.tier === \"progressive\" &&\n\t\twidgetLayoutSession.lockedRows !== undefined\n\t) {\n\t\tconst rendered = buildProgressiveWidgetLines(\n\t\t\tjobs,\n\t\t\ttheme,\n\t\t\twidth,\n\t\t\twidgetLayoutSession.lockedRows,\n\t\t\twidgetLayoutSession.visibleJobKeys,\n\t\t\tframe,\n\t\t);\n\t\twidgetLayoutSession.visibleJobKeys = rendered.visibleJobKeys;\n\t\treturn rendered.lines;\n\t}\n\n\tif (lines.length <= availableRows) {\n\t\twidgetLayoutSession = { expanded, rows, columns, tier: \"full\", visibleJobKeys: [] };\n\t\treturn fitWidgetLineBudget(lines, theme, width, false);\n\t}\n\n\tif (availableRows <= 2) {\n\t\twidgetLayoutSession = { expanded, rows, columns, tier: \"single-line\", visibleJobKeys: [] };\n\t\treturn buildSingleLineWidgetLines(jobs, theme, width, frame);\n\t}\n\n\tconst lockedRows = Math.min(availableRows, collapsedWidgetLineBudget(rows));\n\tconst rendered = buildProgressiveWidgetLines(jobs, theme, width, lockedRows, [], frame);\n\twidgetLayoutSession = {\n\t\texpanded,\n\t\trows,\n\t\tcolumns,\n\t\ttier: \"progressive\",\n\t\tlockedRows,\n\t\tvisibleJobKeys: rendered.visibleJobKeys,\n\t};\n\treturn rendered.lines;\n}\n\nfunction buildWidgetComponent(jobs: AsyncJobState[], expanded: boolean): (_tui: unknown, theme: Theme) => Component {\n\treturn (_tui, theme) => {\n\t\tconst container = new Container();\n\t\tcontainer.render = (renderWidth: number): string[] => {\n\t\t\tconst width = Math.max(0, renderWidth - 2);\n\t\t\tconst lines = expanded\n\t\t\t\t? buildWidgetLines(jobs, theme, width, true)\n\t\t\t\t: jobs.length === 1\n\t\t\t\t\t? compactSingleWidgetLines(jobs[0]!, theme, width)\n\t\t\t\t\t: buildWidgetLines(jobs, theme, width, false);\n\t\t\treturn fitAdaptiveWidgetLines(jobs, lines, theme, width, expanded).map((line) =>\n\t\t\t\tpaddedWidgetLine(line, renderWidth),\n\t\t\t);\n\t\t};\n\t\treturn container;\n\t};\n}\n\nexport function buildWidgetLines(\n\tjobs: AsyncJobState[],\n\ttheme: Theme,\n\twidth = getTermWidth(),\n\texpanded = false,\n\tframe?: number,\n): string[] {\n\tif (jobs.length === 0) return [];\n\tif (jobs.length === 1) return buildSingleWidgetLines(jobs[0]!, theme, width, expanded, frame);\n\tconst running = jobs.filter((job) => job.status === \"running\");\n\tconst queued = jobs.filter((job) => job.status === \"queued\");\n\tconst finished = jobs.filter((job) => job.status !== \"running\" && job.status !== \"queued\");\n\n\tconst lines: string[] = [];\n\tconst hasActive = running.length > 0 || queued.length > 0;\n\tconst headerGlyph =\n\t\trunning.length > 0 ? runningGlyph(animatedSeed(widgetJobsRunningSeed(running), frame)) : hasActive ? \"●\" : \"○\";\n\tlines.push(\n\t\ttruncLine(\n\t\t\t`${theme.fg(hasActive ? \"accent\" : \"dim\", headerGlyph)} ${theme.fg(hasActive ? \"accent\" : \"dim\", \"Async agents\")} ${theme.fg(\"dim\", \"· background\")}`,\n\t\t\twidth,\n\t\t),\n\t);\n\n\tconst items: string[][] = [];\n\tlet hiddenRunning = 0;\n\tlet hiddenFinished = 0;\n\tlet queuedSummaryShown = false;\n\tlet slots = MAX_WIDGET_JOBS;\n\n\tfor (const job of running) {\n\t\tif (slots <= 0) {\n\t\t\thiddenRunning++;\n\t\t\tcontinue;\n\t\t}\n\t\tconst stats = widgetStats(job, theme);\n\t\titems.push([\n\t\t\t`${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg(\"dim\", \"·\")} ${stats}` : \"\"}`,\n\t\t\t`  ${theme.fg(\"dim\", `⎿  ${widgetActivity(job)}`)}`,\n\t\t\t...widgetParallelAgentDetails(job, theme, expanded, width),\n\t\t]);\n\t\tslots--;\n\t}\n\n\tif (queued.length > 0 && slots > 0) {\n\t\titems.push([`${theme.fg(\"muted\", \"◦\")} ${theme.fg(\"dim\", `${queued.length} queued`)}`]);\n\t\tqueuedSummaryShown = true;\n\t\tslots--;\n\t}\n\n\tfor (const job of finished) {\n\t\tif (slots <= 0) {\n\t\t\thiddenFinished++;\n\t\t\tcontinue;\n\t\t}\n\t\tconst stats = widgetStats(job, theme);\n\t\titems.push([\n\t\t\t`${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg(\"dim\", \"·\")} ${stats}` : \"\"}`,\n\t\t\t`  ${theme.fg(\"dim\", `⎿  ${widgetActivity(job)}`)}`,\n\t\t\t...widgetParallelAgentDetails(job, theme, expanded, width),\n\t\t]);\n\t\tslots--;\n\t}\n\n\tconst hiddenQueued = queued.length > 0 && !queuedSummaryShown ? queued.length : 0;\n\tconst hiddenTotal = hiddenRunning + hiddenFinished + hiddenQueued;\n\tif (hiddenTotal > 0) {\n\t\tconst parts: string[] = [];\n\t\tif (hiddenRunning > 0) parts.push(`${hiddenRunning} running`);\n\t\tif (hiddenQueued > 0) parts.push(`${hiddenQueued} queued`);\n\t\tif (hiddenFinished > 0) parts.push(`${hiddenFinished} finished`);\n\t\titems.push([theme.fg(\"dim\", `+${hiddenTotal} more (${parts.join(\", \")})`)]);\n\t}\n\n\tfor (let i = 0; i < items.length; i++) {\n\t\tconst item = items[i]!;\n\t\tconst last = i === items.length - 1;\n\t\tconst branch = last ? \"└─\" : \"├─\";\n\t\tconst continuation = last ? \"   \" : \"│  \";\n\t\tlines.push(truncLine(`${theme.fg(\"dim\", branch)} ${item[0]}`, width));\n\t\tfor (const detail of item.slice(1)) {\n\t\t\tlines.push(truncLine(`${theme.fg(\"dim\", continuation)} ${detail}`, width));\n\t\t}\n\t}\n\n\treturn lines;\n}\n\n/**\n * Render the async jobs widget\n */\nexport function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void {\n\tif (jobs.length === 0) {\n\t\tresetWidgetLayoutSession();\n\t\tif (ctx.hasUI) ctx.ui.setWidget(WIDGET_KEY, undefined);\n\t\treturn;\n\t}\n\tif (!ctx.hasUI) return;\n\tctx.ui.setWidget(WIDGET_KEY, buildWidgetComponent(jobs, ctx.ui.getToolsExpanded?.() ?? false));\n}\n\nfunction renderSingleCompact(d: Details, r: Details[\"results\"][number], theme: Theme, frame?: number): Component {\n\tconst output = r.truncation?.text || getSingleResultOutput(r);\n\tconst progress = r.progress || r.progressSummary;\n\tconst isRunning = isResultRunning(r);\n\tconst contextBadge = contextModeBadge(theme, r.context ?? d.context);\n\tconst stats = statJoin(theme, [r.usage?.turns ? `⟳ ${r.usage.turns}` : \"\", formatProgressStats(theme, progress)]);\n\tconst c = new Container();\n\tconst width = getTermWidth() - 4;\n\tconst modelDisplay = modelThinkingBadge(theme, r.model ?? r.progress?.model, r.thinking ?? r.progress?.thinking);\n\tc.addChild(\n\t\tnew Text(\n\t\t\ttruncLine(\n\t\t\t\t`${resultGlyph(r, output, theme, isRunning, undefined, frame)} ${theme.fg(\"toolTitle\", theme.bold(r.agent))}${modelDisplay}${contextBadge}${stats ? ` ${theme.fg(\"dim\", \"·\")} ${stats}` : \"\"}`,\n\t\t\t\twidth,\n\t\t\t),\n\t\t\t0,\n\t\t\t0,\n\t\t),\n\t);\n\n\tif (isRunning && r.progress) {\n\t\tconst progressSnapshotNow = snapshotNowForProgress(r.progress);\n\t\tconst activity = compactCurrentActivity(r.progress);\n\t\tc.addChild(new Text(truncLine(theme.fg(\"dim\", `  ⎿  ${activity}`), width), 0, 0));\n\t\tconst liveStatus = buildLiveStatusLine(r.progress, progressSnapshotNow);\n\t\tif (liveStatus && liveStatus !== activity)\n\t\t\tc.addChild(new Text(truncLine(theme.fg(\"dim\", `     ${liveStatus}`), width), 0, 0));\n\t\tfor (const nestedLine of formatNestedWidgetLines(r.children, theme, width, false, progressSnapshotNow)) {\n\t\t\tc.addChild(new Text(truncLine(`  ${nestedLine}`, width), 0, 0));\n\t\t}\n\t\tc.addChild(new Text(truncLine(theme.fg(\"accent\", `  ${liveDetailHintText()}`), width), 0, 0));\n\t\tif (r.artifactPaths)\n\t\t\tc.addChild(\n\t\t\t\tnew Text(truncLine(theme.fg(\"dim\", `  output: ${shortenPath(r.artifactPaths.outputPath)}`), width), 0, 0),\n\t\t\t);\n\t\treturn c;\n\t}\n\n\tfor (const nestedLine of formatNestedWidgetLines(r.children, theme, width, false, r.progress?.lastActivityAt)) {\n\t\tc.addChild(new Text(truncLine(`  ${nestedLine}`, width), 0, 0));\n\t}\n\tc.addChild(new Text(truncLine(theme.fg(\"dim\", `  ⎿  ${resultStatusLine(r, output)}`), width), 0, 0));\n\tconst preview = firstOutputLine(output);\n\tif (preview && r.exitCode === 0 && !hasEmptyTextOutputWithoutOutputTarget(r.task, output)) {\n\t\tc.addChild(new Text(truncLine(theme.fg(\"dim\", `     ${preview}`), width), 0, 0));\n\t}\n\tif (r.sessionFile)\n\t\tc.addChild(new Text(truncLine(theme.fg(\"dim\", `  session: ${shortenPath(r.sessionFile)}`), width), 0, 0));\n\tif (r.artifactPaths)\n\t\tc.addChild(\n\t\t\tnew Text(truncLine(theme.fg(\"dim\", `  output: ${shortenPath(r.artifactPaths.outputPath)}`), width), 0, 0),\n\t\t);\n\tif (r.truncation?.artifactPath)\n\t\tc.addChild(\n\t\t\tnew Text(truncLine(theme.fg(\"dim\", `  full output: ${shortenPath(r.truncation.artifactPath)}`), width), 0, 0),\n\t\t);\n\treturn c;\n}\n\nfunction workflowRowGlyph(row: WorkflowChatProgressRow, theme: Theme, frame?: number): string {\n\tif (row.state === \"running\") return theme.fg(\"accent\", runningGlyph(frame));\n\tif (row.state === \"complete\") return theme.fg(\"success\", \"✓\");\n\treturn theme.fg(\"error\", \"✗\");\n}\n\nfunction workflowRowStateLabel(row: WorkflowChatProgressRow, theme: Theme): string {\n\tconst label = (row.state === \"complete\" ? \"complete\" : row.state).padEnd(8);\n\tif (row.state === \"running\") return theme.fg(\"accent\", label);\n\tif (row.state === \"complete\") return theme.fg(\"success\", label);\n\treturn theme.fg(\"error\", label);\n}\n\nfunction workflowOverallState(\n\trows: WorkflowChatProgressRow[],\n\thasTerminalValue: boolean,\n\tisError?: boolean,\n): \"running\" | \"complete\" | \"failed\" {\n\tif (isError || rows.some((row) => row.state === \"failed\")) return \"failed\";\n\tif ((rows.length > 0 && rows.every((row) => row.state === \"complete\")) || hasTerminalValue) return \"complete\";\n\treturn \"running\";\n}\n\nfunction renderWorkflowChatProgress(\n\td: Details,\n\tresult: AgentToolResult<Details>,\n\ttheme: Theme,\n\tframe?: number,\n): Component {\n\tconst workflow = d.workflow;\n\tconst rows = workflow ? buildWorkflowChatProgressRows(workflow.trace) : [];\n\tconst state = workflowOverallState(rows, workflow?.value !== undefined, result.isError);\n\tconst glyph =\n\t\tstate === \"running\"\n\t\t\t? theme.fg(\"accent\", runningGlyph(frame))\n\t\t\t: state === \"complete\"\n\t\t\t\t? theme.fg(\"success\", \"✓\")\n\t\t\t\t: theme.fg(\"error\", \"✗\");\n\tconst width = getTermWidth() - 4;\n\tconst runId = d.runId ? d.runId.slice(0, 12) : \"workflow\";\n\tconst repoLabel =\n\t\td.chatProgress?.repoLabel ?? (d.chatProgress?.repoRelation === \"same\" ? \"same repo\" : \"other repo\");\n\tconst phase =\n\t\trows.find((row) => row.state === \"running\" && row.phase)?.phase ??\n\t\t[...rows].reverse().find((row) => row.phase)?.phase;\n\tconst c = new Container();\n\tc.addChild(\n\t\tnew Text(\n\t\t\ttruncLine(\n\t\t\t\t`${glyph} ${theme.fg(\"toolTitle\", theme.bold(\"workflow\"))} ${runId} ${theme.fg(\"dim\", \"·\")} ${d.chatProgress?.repoRelation === \"same\" ? \"same repo\" : \"other repo\"} ${theme.fg(\"dim\", \"·\")} ${state}`,\n\t\t\t\twidth,\n\t\t\t),\n\t\t\t0,\n\t\t\t0,\n\t\t),\n\t);\n\tc.addChild(new Text(truncLine(theme.fg(\"dim\", `  Repo   ${repoLabel}`), width), 0, 0));\n\tif (phase) c.addChild(new Text(truncLine(theme.fg(\"dim\", `  Phase  ${phase}`), width), 0, 0));\n\tif (rows.length === 0) {\n\t\tc.addChild(new Text(truncLine(theme.fg(\"dim\", \"  ◦ waiting for workflow child launches\"), width), 0, 0));\n\t\treturn c;\n\t}\n\tfor (const row of rows) {\n\t\tconst status = workflowRowStateLabel(row, theme);\n\t\tconst label = row.label && row.label !== row.key ? ` ${row.label}` : \"\";\n\t\tconst duration = row.durationMs !== undefined ? ` ${theme.fg(\"dim\", `· ${formatDuration(row.durationMs)}`)}` : \"\";\n\t\tconst run = row.runId ? ` ${theme.fg(\"dim\", `[${row.runId.slice(0, 8)}]`)}` : \"\";\n\t\tconst error = row.error ? ` ${theme.fg(\"error\", `· ${row.error}`)}` : \"\";\n\t\tc.addChild(\n\t\t\tnew Text(\n\t\t\t\ttruncLine(\n\t\t\t\t\t`  ${workflowRowGlyph(row, theme, frame)} ${status} ${theme.bold(row.key)}${label}${run}${duration}${error}`,\n\t\t\t\t\twidth,\n\t\t\t\t),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t}\n\tif (workflow?.emits.length)\n\t\tc.addChild(new Text(truncLine(theme.fg(\"dim\", `  Emits  ${workflow.emits.length}`), width), 0, 0));\n\treturn c;\n}\n\nfunction renderMultiCompact(d: Details, theme: Theme, frame?: number): Component {\n\tconst hasRunning = detailsHaveRunningResult(d);\n\tconst detached = d.results.some((r) => r.detached) || workflowGraphHasStatus(d, [\"detached\"]);\n\tconst stopped = d.results.some((r) => r.stopped) || workflowGraphHasStatus(d, [\"stopped\"]);\n\tconst failed =\n\t\td.results.some((r) => !hasTerminalResultFlag(r) && r.exitCode !== 0 && !isResultRunning(r)) ||\n\t\tworkflowGraphHasStatus(d, [\"failed\"]);\n\tconst paused = d.results.some((r) => r.interrupted) || workflowGraphHasStatus(d, [\"paused\"]);\n\tlet totalSummary = d.progressSummary;\n\tif (!totalSummary) {\n\t\tlet sawProgress = false;\n\t\tconst summary = { toolCount: 0, tokens: 0, durationMs: 0 };\n\t\tfor (const r of d.results) {\n\t\t\tconst prog = r.progress || r.progressSummary;\n\t\t\tif (!prog) continue;\n\t\t\tsawProgress = true;\n\t\t\tsummary.toolCount += prog.toolCount;\n\t\t\tsummary.tokens += prog.tokens;\n\t\t\tsummary.durationMs =\n\t\t\t\td.mode === \"chain\" ? summary.durationMs + prog.durationMs : Math.max(summary.durationMs, prog.durationMs);\n\t\t}\n\t\tif (sawProgress) totalSummary = summary;\n\t}\n\tconst multiLabel = buildMultiProgressLabel(d, hasRunning);\n\tconst itemTitle = multiLabel.itemTitle;\n\tconst stats = statJoin(theme, [\n\t\tmultiLabel.headerLabel,\n\t\tformatProgressStats(theme, totalSummary),\n\t\tformatTotalCostStat(d.totalCost),\n\t]);\n\tconst aggregatePresentation = semanticResultPresentation({\n\t\trunning: hasRunning,\n\t\tdetached,\n\t\tstopped,\n\t\tinterrupted: paused,\n\t\tfailed,\n\t\tseed: runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex),\n\t\tframe,\n\t});\n\tconst glyph = theme.fg(aggregatePresentation.tone, aggregatePresentation.glyph);\n\tconst contextBadge = contextModeBadge(theme, d.context);\n\tconst c = new Container();\n\tconst width = getTermWidth() - 4;\n\tc.addChild(\n\t\tnew Text(\n\t\t\ttruncLine(\n\t\t\t\t`${glyph} ${theme.fg(\"toolTitle\", theme.bold(d.mode))}${contextBadge}${stats ? ` ${theme.fg(\"dim\", \"·\")} ${stats}` : \"\"}`,\n\t\t\t\twidth,\n\t\t\t),\n\t\t\t0,\n\t\t\t0,\n\t\t),\n\t);\n\n\tconst useResultsDirectly = multiLabel.hasParallelInChain || !d.chainAgents?.length;\n\tconst displayStart = multiLabel.showActiveGroupOnly ? multiLabel.groupStartIndex : 0;\n\tconst displayEnd = multiLabel.showActiveGroupOnly\n\t\t? multiLabel.groupEndIndex\n\t\t: useResultsDirectly\n\t\t\t? d.results.length\n\t\t\t: d.chainAgents!.length;\n\tconst chainEntries = buildChainRenderEntries(d, multiLabel);\n\tconst renderEntries =\n\t\tchainEntries ??\n\t\tArray.from({ length: displayEnd - displayStart }, (_, offset): ChainRenderEntry => {\n\t\t\tconst i = displayStart + offset;\n\t\t\tconst r = d.results[i];\n\t\t\tconst fallbackLabel = itemTitle.toLowerCase();\n\t\t\tconst rowNumber = multiLabel.showActiveGroupOnly ? i - multiLabel.groupStartIndex + 1 : i + 1;\n\t\t\treturn {\n\t\t\t\tkind: \"result\",\n\t\t\t\tresultIndex: i,\n\t\t\t\trowNumber,\n\t\t\t\tagentName: useResultsDirectly\n\t\t\t\t\t? r?.agent || `${fallbackLabel}-${rowNumber}`\n\t\t\t\t\t: d.chainAgents![i] || r?.agent || `${fallbackLabel}-${rowNumber}`,\n\t\t\t};\n\t\t});\n\tfor (const entry of renderEntries) {\n\t\tif (entry.kind === \"placeholder\") {\n\t\t\tconst glyph = widgetStepGlyph(entry.status as AsyncJobStep[\"status\"], theme);\n\t\t\tconst statusLabel = widgetStepStatus(entry.status as AsyncJobStep[\"status\"], theme);\n\t\t\tc.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttruncLine(\n\t\t\t\t\t\t`  ${glyph} ${entry.stepLabel}: ${themeBold(theme, entry.agentName)} ${theme.fg(\"dim\", \"·\")} ${statusLabel}`,\n\t\t\t\t\t\twidth,\n\t\t\t\t\t),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t\tif (entry.error)\n\t\t\t\tc.addChild(new Text(truncLine(theme.fg(\"error\", `    ⎿  Error: ${entry.error}`), width), 0, 0));\n\t\t\tcontinue;\n\t\t}\n\t\tconst i = entry.resultIndex;\n\t\tconst r = d.results[i];\n\t\tconst rowNumber = entry.rowNumber;\n\t\tconst agentName = entry.agentName;\n\t\tif (!r) {\n\t\t\tconst pendingLabel = entry.rowLabel ?? `${itemTitle} ${rowNumber}`;\n\t\t\tc.addChild(new Text(truncLine(theme.fg(\"dim\", `  ◦ ${pendingLabel}: ${agentName} · pending`), width), 0, 0));\n\t\t\tcontinue;\n\t\t}\n\t\tconst output = getSingleResultOutput(r);\n\t\tconst progressFromArray =\n\t\t\td.progress?.find((p) => p.index === i) ||\n\t\t\td.progress?.find((p) => p.agent === r.agent && p.status === \"running\");\n\t\tconst rProg = r.progress || progressFromArray || r.progressSummary;\n\t\tconst rRunning = rProg && \"status\" in rProg && isResultRunning(r, rProg.status);\n\t\tconst rPending = rProg && \"status\" in rProg && rProg.status === \"pending\";\n\t\tconst stepNumber =\n\t\t\tr.progress?.index !== undefined\n\t\t\t\t? r.progress.index + 1\n\t\t\t\t: progressFromArray?.index !== undefined\n\t\t\t\t\t? progressFromArray.index + 1\n\t\t\t\t\t: i + 1;\n\t\tconst stepStats = formatProgressStats(theme, rProg);\n\t\tconst glyph = rPending\n\t\t\t? theme.fg(\"dim\", \"◦\")\n\t\t\t: resultGlyph(r, output, theme, rRunning, progressRunningSeed(rProg), frame);\n\t\tconst pendingLabel = rPending ? ` ${theme.fg(\"dim\", \"· pending\")}` : \"\";\n\t\tconst stepLabel = entry.rowLabel ?? resultRowLabel(multiLabel, i, stepNumber);\n\t\tconst rowProgressModel = rProg && \"status\" in rProg ? rProg : undefined;\n\t\tconst rowModelDisplay = modelThinkingBadge(\n\t\t\ttheme,\n\t\t\tr.model ?? rowProgressModel?.model,\n\t\t\tr.thinking ?? rowProgressModel?.thinking,\n\t\t);\n\t\tconst line = `${glyph} ${stepLabel}: ${themeBold(theme, agentName)}${contextModeBadge(theme, r.context)}${rowModelDisplay}${stepStats ? ` ${theme.fg(\"dim\", \"·\")} ${stepStats}` : \"\"}${pendingLabel}`;\n\t\tc.addChild(new Text(truncLine(`  ${line}`, width), 0, 0));\n\t\tif (rRunning && rProg && \"status\" in rProg) {\n\t\t\tconst liveProgress = rProg as AgentProgress;\n\t\t\tconst activity = compactCurrentActivity(liveProgress);\n\t\t\tc.addChild(new Text(truncLine(theme.fg(\"dim\", `    ⎿  ${activity}`), width), 0, 0));\n\t\t\tfor (const nestedLine of formatNestedWidgetLines(\n\t\t\t\tr.children,\n\t\t\t\ttheme,\n\t\t\t\twidth,\n\t\t\t\tfalse,\n\t\t\t\tsnapshotNowForProgress(liveProgress),\n\t\t\t)) {\n\t\t\t\tc.addChild(new Text(truncLine(`    ${nestedLine}`, width), 0, 0));\n\t\t\t}\n\t\t\tc.addChild(new Text(truncLine(theme.fg(\"accent\", `    ${liveDetailHintText()}`), width), 0, 0));\n\t\t} else if (\n\t\t\t!rPending &&\n\t\t\t(r.exitCode !== 0 || r.interrupted || r.detached || hasEmptyTextOutputWithoutOutputTarget(r.task, output))\n\t\t) {\n\t\t\tc.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttruncLine(theme.fg(r.exitCode !== 0 ? \"error\" : \"dim\", `    ⎿  ${resultStatusLine(r, output)}`), width),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (!rRunning && !rPending) {\n\t\t\tfor (const nestedLine of formatNestedWidgetLines(\n\t\t\t\tr.children,\n\t\t\t\ttheme,\n\t\t\t\twidth,\n\t\t\t\tfalse,\n\t\t\t\tr.progress?.lastActivityAt,\n\t\t\t)) {\n\t\t\t\tc.addChild(new Text(truncLine(`    ${nestedLine}`, width), 0, 0));\n\t\t\t}\n\t\t}\n\t\tconst outputTarget = extractOutputTarget(r.task);\n\t\tif (outputTarget) c.addChild(new Text(truncLine(theme.fg(\"dim\", `    output: ${outputTarget}`), width), 0, 0));\n\t\tif (r.artifactPaths)\n\t\t\tc.addChild(\n\t\t\t\tnew Text(truncLine(theme.fg(\"dim\", `    output: ${shortenPath(r.artifactPaths.outputPath)}`), width), 0, 0),\n\t\t\t);\n\t}\n\tif (d.artifacts)\n\t\tc.addChild(new Text(truncLine(theme.fg(\"dim\", `  artifacts: ${shortenPath(d.artifacts.dir)}`), width), 0, 0));\n\treturn c;\n}\n\nexport function renderSubagentSummary(\n\tresult: AgentToolResult<Details>,\n\toptions: { isPartial?: boolean },\n\ttheme: Theme,\n): Component {\n\tconst details = result.details;\n\tconst results = details?.results ?? [];\n\tconst hasSingleTerminalResult = results.length === 1 && hasTerminalResult(results[0]!);\n\tconst hasOnlyTerminalResults = results.length > 0 && results.every(hasTerminalResult);\n\tconst running =\n\t\t!hasSingleTerminalResult &&\n\t\t!hasOnlyTerminalResults &&\n\t\t(options.isPartial === true ||\n\t\t\tBoolean(details?.asyncId && details.mode !== \"management\") ||\n\t\t\tBoolean(details && detailsHaveRunningResult(details)));\n\tconst stopped =\n\t\tresults.some((entry) => entry.stopped) || Boolean(details && workflowGraphHasStatus(details, [\"stopped\"]));\n\tconst paused =\n\t\tresults.some((entry) => entry.interrupted || entry.detached) ||\n\t\tBoolean(details && workflowGraphHasStatus(details, [\"paused\", \"detached\"]));\n\tconst failed =\n\t\tresult.isError === true ||\n\t\tresults.some((entry) => !hasTerminalResultFlag(entry) && entry.exitCode !== 0 && !isResultRunning(entry)) ||\n\t\tBoolean(details && workflowGraphHasStatus(details, [\"failed\"]));\n\tconst state = running ? \"running\" : failed ? \"failed\" : stopped ? \"stopped\" : paused ? \"paused\" : \"completed\";\n\tconst glyph =\n\t\tstate === \"running\"\n\t\t\t? theme.fg(\"accent\", STATIC_RUNNING_GLYPH)\n\t\t\t: state === \"completed\"\n\t\t\t\t? theme.fg(\"success\", \"✓\")\n\t\t\t\t: state === \"failed\"\n\t\t\t\t\t? theme.fg(\"error\", \"✗\")\n\t\t\t\t\t: theme.fg(\"warning\", \"■\");\n\tconst label =\n\t\tdetails?.mode === \"single\" && results.length === 1\n\t\t\t? results[0]?.agent || \"subagent\"\n\t\t\t: details?.mode || \"subagent\";\n\treturn new Text(\n\t\ttruncLine(\n\t\t\t`${glyph} ${theme.fg(\"toolTitle\", theme.bold(label))} ${theme.fg(\"dim\", \"·\")} ${theme.fg(state === \"failed\" ? \"error\" : state === \"completed\" ? \"success\" : state === \"running\" ? \"accent\" : \"warning\", state)}`,\n\t\t\tgetTermWidth() - 4,\n\t\t),\n\t\t0,\n\t\t0,\n\t);\n}\n\n/**\n * Render a subagent result\n */\nexport function renderSubagentResult(\n\tresult: AgentToolResult<Details>,\n\toptions: { expanded: boolean },\n\ttheme: Theme,\n\tframe?: number,\n): Component {\n\tconst d = result.details;\n\tif (\n\t\td?.mode === \"workflow\" &&\n\t\td.chatProgress?.mode === \"live-card\" &&\n\t\t!result.isError &&\n\t\td.workflow?.value === undefined\n\t)\n\t\treturn renderWorkflowChatProgress(d, result, theme, frame);\n\tif (!d || !d.results.length) {\n\t\tconst t = result.content[0];\n\t\tconst text = t?.type === \"text\" ? t.text : \"(no output)\";\n\t\tconst contextPrefix = contextModePrefix(theme, d?.context);\n\t\tconst width = getTermWidth() - 4;\n\t\tif (!text.includes(\"\\n\")) return new Text(truncLine(`${contextPrefix}${text}`, width), 0, 0);\n\t\tif (d && !options.expanded && !result.isError) {\n\t\t\tconst lines = text.split(/\\r?\\n/);\n\t\t\tconst firstNonEmptyLine = lines.find((line) => line.trim())?.trim() || \"(no output)\";\n\t\t\tconst c = new Container();\n\t\t\tc.addChild(new Text(truncLine(`${contextPrefix}${firstNonEmptyLine} · ${lines.length} lines`, width), 0, 0));\n\t\t\tc.addChild(\n\t\t\t\tnew Text(truncLine(theme.fg(\"accent\", `  Press ${liveDetailKeyText()} for full output`), width), 0, 0),\n\t\t\t);\n\t\t\treturn c;\n\t\t}\n\t\tconst c = new Container();\n\t\tconst wrapped = wrapPlainText(`${contextPrefix}${text}`, width);\n\t\tfor (const line of wrapped) c.addChild(new Text(line, 0, 0));\n\t\treturn c;\n\t}\n\n\tconst expanded = options.expanded;\n\tconst mdTheme = getMarkdownTheme();\n\n\tif (d.mode === \"single\" && d.results.length === 1) {\n\t\tconst r = d.results[0];\n\t\tif (!r) return renderMultiCompact(d, theme, frame);\n\t\tif (!expanded) return renderSingleCompact(d, r, theme, frame);\n\t\tconst isRunning = isResultRunning(r);\n\t\tconst contextBadge = contextModeBadge(theme, r.context ?? d.context);\n\t\tconst output = r.truncation?.text || getSingleResultOutput(r);\n\t\tconst presentation = styledResultPresentation(resultPresentation(r, output, isRunning, undefined, frame), theme);\n\n\t\tconst progressInfo =\n\t\t\tisRunning && r.progress\n\t\t\t\t? ` | ${r.progress.toolCount} tools, ${formatTokens(r.progress.tokens)} tok, ${formatDuration(r.progress.durationMs)}`\n\t\t\t\t: r.progressSummary\n\t\t\t\t\t? ` | ${r.progressSummary.toolCount} tools, ${formatTokens(r.progressSummary.tokens)} tok, ${formatDuration(r.progressSummary.durationMs)}`\n\t\t\t\t\t: \"\";\n\n\t\tconst w = getTermWidth() - 4;\n\t\tconst fit = (text: string) => (expanded ? text : truncLine(text, w));\n\t\tconst toolCallLines = getToolCallLines(r, expanded);\n\t\tconst c = new Container();\n\t\tc.addChild(\n\t\t\tnew Text(\n\t\t\t\tfit(\n\t\t\t\t\t`${presentation.glyph} ${theme.fg(\"toolTitle\", theme.bold(r.agent))}${contextBadge}${progressInfo} ${theme.fg(\"dim\", \"·\")} ${presentation.label}`,\n\t\t\t\t),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tc.addChild(new Spacer(1));\n\t\tconst taskMaxLen = Math.max(20, w - 8);\n\t\tconst taskPreview = expanded || r.task.length <= taskMaxLen ? r.task : `${r.task.slice(0, taskMaxLen)}...`;\n\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `Task: ${taskPreview}`)), 0, 0));\n\t\tc.addChild(new Spacer(1));\n\n\t\tif (isRunning && r.progress) {\n\t\t\tconst progressSnapshotNow = snapshotNowForProgress(r.progress);\n\t\t\tfor (const nestedLine of formatNestedWidgetLines(r.children, theme, w, true, progressSnapshotNow, 12)) {\n\t\t\t\tc.addChild(new Text(fit(`  ${nestedLine}`), 0, 0));\n\t\t\t}\n\t\t\tconst toolLine = formatCurrentToolLine(r.progress, w, expanded, progressSnapshotNow);\n\t\t\tif (toolLine) {\n\t\t\t\tc.addChild(new Text(fit(theme.fg(\"warning\", `> ${toolLine}`)), 0, 0));\n\t\t\t}\n\t\t\tconst liveStatusLine = buildLiveStatusLine(r.progress, progressSnapshotNow);\n\t\t\tif (liveStatusLine) {\n\t\t\t\tc.addChild(new Text(fit(theme.fg(\"accent\", liveStatusLine)), 0, 0));\n\t\t\t}\n\t\t\tc.addChild(new Text(fit(theme.fg(\"accent\", liveDetailHintText())), 0, 0));\n\t\t\tif (r.artifactPaths) {\n\t\t\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `Artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));\n\t\t\t}\n\t\t\tif (r.progress.recentTools?.length) {\n\t\t\t\tfor (const t of r.progress.recentTools.slice(-3)) {\n\t\t\t\t\tconst maxArgsLen = Math.max(40, w - 24);\n\t\t\t\t\tconst argsPreview =\n\t\t\t\t\t\texpanded || t.args.length <= maxArgsLen ? t.args : `${t.args.slice(0, maxArgsLen)}...`;\n\t\t\t\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `${t.tool}: ${argsPreview}`)), 0, 0));\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const line of (r.progress.recentOutput ?? []).slice(-5)) {\n\t\t\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `  ${line}`)), 0, 0));\n\t\t\t}\n\t\t\tif (\n\t\t\t\ttoolLine ||\n\t\t\t\tliveStatusLine ||\n\t\t\t\tr.progress.recentTools?.length ||\n\t\t\t\tr.progress.recentOutput?.length ||\n\t\t\t\tr.artifactPaths\n\t\t\t) {\n\t\t\t\tc.addChild(new Spacer(1));\n\t\t\t}\n\t\t} else {\n\t\t\tfor (const nestedLine of formatNestedWidgetLines(r.children, theme, w, true, r.progress?.lastActivityAt, 8)) {\n\t\t\t\tc.addChild(new Text(fit(`  ${nestedLine}`), 0, 0));\n\t\t\t}\n\t\t}\n\n\t\tif (expanded) {\n\t\t\tfor (const line of toolCallLines) {\n\t\t\t\tc.addChild(new Text(fit(theme.fg(\"muted\", line)), 0, 0));\n\t\t\t}\n\t\t\tif (toolCallLines.length) c.addChild(new Spacer(1));\n\t\t}\n\n\t\tif (output) c.addChild(new Markdown(output, 0, 0, mdTheme));\n\t\tc.addChild(new Spacer(1));\n\t\tif (r.skills?.length) {\n\t\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `Skills: ${r.skills.join(\", \")}`)), 0, 0));\n\t\t}\n\t\tif (r.skillsWarning) {\n\t\t\tc.addChild(new Text(fit(theme.fg(\"warning\", `Warning: ${r.skillsWarning}`)), 0, 0));\n\t\t}\n\t\tif (r.attemptedModels && r.attemptedModels.length > 1) {\n\t\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `Fallbacks: ${r.attemptedModels.join(\" → \")}`)), 0, 0));\n\t\t}\n\t\tc.addChild(new Text(fit(theme.fg(\"dim\", formatUsage(r.usage, r.model))), 0, 0));\n\t\tif (r.sessionFile) {\n\t\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `Session: ${shortenPath(r.sessionFile)}`)), 0, 0));\n\t\t}\n\n\t\tif (!isRunning && r.artifactPaths) {\n\t\t\tc.addChild(new Spacer(1));\n\t\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `Artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));\n\t\t}\n\t\treturn c;\n\t}\n\n\tif (!expanded) return renderMultiCompact(d, theme, frame);\n\n\tconst hasRunning = detailsHaveRunningResult(d);\n\tconst detached = d.results.some((r) => r.detached) || workflowGraphHasStatus(d, [\"detached\"]);\n\tconst stopped = d.results.some((r) => r.stopped) || workflowGraphHasStatus(d, [\"stopped\"]);\n\tconst failed =\n\t\td.results.some((r) => !hasTerminalResultFlag(r) && r.exitCode !== 0 && !isResultRunning(r)) ||\n\t\tworkflowGraphHasStatus(d, [\"failed\"]);\n\tconst paused = d.results.some((r) => r.interrupted) || workflowGraphHasStatus(d, [\"paused\"]);\n\tconst completedWithoutOutput = d.results.some(\n\t\t(r) =>\n\t\t\t!hasTerminalResultFlag(r) &&\n\t\t\tr.exitCode === 0 &&\n\t\t\t!isResultRunning(r) &&\n\t\t\thasEmptyTextOutputWithoutOutputTarget(r.task, getSingleResultOutput(r)),\n\t);\n\tconst presentation = styledResultPresentation(\n\t\tsemanticResultPresentation({\n\t\t\trunning: hasRunning,\n\t\t\tdetached,\n\t\t\tstopped,\n\t\t\tinterrupted: paused,\n\t\t\tfailed,\n\t\t\tcompletedWithoutOutput,\n\t\t\tframe,\n\t\t}),\n\t\ttheme,\n\t);\n\n\tconst totalSummary =\n\t\td.progressSummary ||\n\t\td.results.reduce(\n\t\t\t(acc, r) => {\n\t\t\t\tconst prog = r.progress || r.progressSummary;\n\t\t\t\tif (prog) {\n\t\t\t\t\tacc.toolCount += prog.toolCount;\n\t\t\t\t\tacc.tokens += prog.tokens;\n\t\t\t\t\tacc.durationMs =\n\t\t\t\t\t\td.mode === \"chain\" ? acc.durationMs + prog.durationMs : Math.max(acc.durationMs, prog.durationMs);\n\t\t\t\t}\n\t\t\t\treturn acc;\n\t\t\t},\n\t\t\t{ toolCount: 0, tokens: 0, durationMs: 0 },\n\t\t);\n\n\tconst summaryParts = [\n\t\ttotalSummary.toolCount || totalSummary.tokens\n\t\t\t? `${totalSummary.toolCount} tools, ${formatTokens(totalSummary.tokens)} tok, ${formatDuration(totalSummary.durationMs)}`\n\t\t\t: \"\",\n\t\tformatTotalCostStat(d.totalCost),\n\t].filter(Boolean);\n\tconst summaryStr = summaryParts.length ? ` | ${summaryParts.join(\", \")}` : \"\";\n\n\tconst modeLabel = d.mode;\n\tconst contextBadge = contextModeBadge(theme, d.context);\n\tconst multiLabel = buildMultiProgressLabel(d, hasRunning);\n\tconst itemTitle = multiLabel.itemTitle;\n\n\tconst chainVis =\n\t\td.chainAgents?.length && !multiLabel.hasParallelInChain\n\t\t\t? d.chainAgents\n\t\t\t\t\t.map((agent, i) => {\n\t\t\t\t\t\tconst result = d.results[i];\n\t\t\t\t\t\tconst isCurrent = i === (d.currentStepIndex ?? d.results.length);\n\t\t\t\t\t\tconst stepPresentation = result\n\t\t\t\t\t\t\t? styledResultPresentation(\n\t\t\t\t\t\t\t\t\tresultPresentation(\n\t\t\t\t\t\t\t\t\t\tresult,\n\t\t\t\t\t\t\t\t\t\tgetSingleResultOutput(result),\n\t\t\t\t\t\t\t\t\t\tisCurrent && hasRunning && !hasTerminalResultFlag(result),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\ttheme,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\t\tconst stepStatus = stepPresentation\n\t\t\t\t\t\t\t? `${stepPresentation.glyph} ${stepPresentation.label}`\n\t\t\t\t\t\t\t: theme.fg(\"dim\", \"◦ pending\");\n\t\t\t\t\t\treturn `${stepStatus} ${agent}${contextModeBadge(theme, result?.context)}`;\n\t\t\t\t\t})\n\t\t\t\t\t.join(theme.fg(\"dim\", \" → \"))\n\t\t\t: null;\n\n\tconst w = getTermWidth() - 4;\n\tconst fit = (text: string) => (expanded ? text : truncLine(text, w));\n\tconst c = new Container();\n\tc.addChild(\n\t\tnew Text(\n\t\t\tfit(\n\t\t\t\t`${presentation.glyph} ${theme.fg(\"toolTitle\", theme.bold(modeLabel))}${contextBadge} · ${multiLabel.headerLabel}${summaryStr} ${theme.fg(\"dim\", \"·\")} ${presentation.label}`,\n\t\t\t),\n\t\t\t0,\n\t\t\t0,\n\t\t),\n\t);\n\tif (chainVis) {\n\t\tc.addChild(new Text(fit(`  ${chainVis}`), 0, 0));\n\t}\n\n\tconst useResultsDirectly = multiLabel.hasParallelInChain || !d.chainAgents?.length;\n\tconst displayStart = multiLabel.showActiveGroupOnly ? multiLabel.groupStartIndex : 0;\n\tconst displayEnd = multiLabel.showActiveGroupOnly\n\t\t? multiLabel.groupEndIndex\n\t\t: useResultsDirectly\n\t\t\t? d.results.length\n\t\t\t: d.chainAgents!.length;\n\tconst chainEntries = buildChainRenderEntries(d, multiLabel);\n\tconst renderEntries =\n\t\tchainEntries ??\n\t\tArray.from({ length: displayEnd - displayStart }, (_, offset): ChainRenderEntry => {\n\t\t\tconst i = displayStart + offset;\n\t\t\tconst r = d.results[i];\n\t\t\tconst rowNumber = multiLabel.showActiveGroupOnly ? i - multiLabel.groupStartIndex + 1 : i + 1;\n\t\t\treturn {\n\t\t\t\tkind: \"result\",\n\t\t\t\tresultIndex: i,\n\t\t\t\trowNumber,\n\t\t\t\tagentName: useResultsDirectly\n\t\t\t\t\t? r?.agent || `step-${rowNumber}`\n\t\t\t\t\t: d.chainAgents![i] || r?.agent || `step-${rowNumber}`,\n\t\t\t};\n\t\t});\n\n\tc.addChild(new Spacer(1));\n\n\tfor (const entry of renderEntries) {\n\t\tif (entry.kind === \"placeholder\") {\n\t\t\tconst statusLabel = widgetStepStatus(entry.status as AsyncJobStep[\"status\"], theme);\n\t\t\tc.addChild(new Text(fit(`  ${statusLabel} ${entry.stepLabel}: ${theme.bold(entry.agentName)}`), 0, 0));\n\t\t\tc.addChild(\n\t\t\t\tnew Text(theme.fg(entry.status === \"failed\" ? \"error\" : \"dim\", `    status: ${entry.status}`), 0, 0),\n\t\t\t);\n\t\t\tif (entry.error) c.addChild(new Text(theme.fg(\"error\", `    error: ${entry.error}`), 0, 0));\n\t\t\tc.addChild(new Spacer(1));\n\t\t\tcontinue;\n\t\t}\n\t\tconst i = entry.resultIndex;\n\t\tconst r = d.results[i];\n\t\tconst rowNumber = entry.rowNumber;\n\t\tconst agentName = entry.agentName;\n\n\t\tif (!r) {\n\t\t\tconst pendingLabel = entry.rowLabel ?? `${itemTitle} ${rowNumber}`;\n\t\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `  ${pendingLabel}: ${agentName}`)), 0, 0));\n\t\t\tc.addChild(new Text(theme.fg(\"dim\", `    status: pending`), 0, 0));\n\t\t\tc.addChild(new Spacer(1));\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst progressFromArray =\n\t\t\td.progress?.find((p) => p.index === i) ||\n\t\t\td.progress?.find((p) => p.agent === r.agent && p.status === \"running\");\n\t\tconst rProg = r.progress || progressFromArray || r.progressSummary;\n\t\tconst rRunning = isResultRunning(r, rProg?.status);\n\t\tconst stepNumber = typeof rProg?.index === \"number\" ? rProg.index + 1 : i + 1;\n\n\t\tconst resultOutput = getSingleResultOutput(r);\n\t\tconst rowPresentation = styledResultPresentation(\n\t\t\tresultPresentation(r, resultOutput, rRunning, progressRunningSeed(rProg), frame),\n\t\t\ttheme,\n\t\t);\n\t\tconst stats = rProg ? ` | ${rProg.toolCount} tools, ${formatDuration(rProg.durationMs)}` : \"\";\n\t\tconst modelDisplay = modelThinkingBadge(theme, r.model ?? rProg?.model, r.thinking ?? rProg?.thinking);\n\t\tconst stepLabel = entry.rowLabel ?? resultRowLabel(multiLabel, i, stepNumber);\n\t\tconst contextBadge = contextModeBadge(theme, r.context);\n\t\tconst stepHeader = rRunning\n\t\t\t? `${rowPresentation.glyph} ${stepLabel}: ${theme.bold(theme.fg(\"warning\", r.agent))}${contextBadge}${modelDisplay}${stats} ${theme.fg(\"dim\", \"·\")} ${rowPresentation.label}`\n\t\t\t: `${rowPresentation.glyph} ${stepLabel}: ${theme.bold(r.agent)}${contextBadge}${modelDisplay}${stats} ${theme.fg(\"dim\", \"·\")} ${rowPresentation.label}`;\n\t\tconst toolCallLines = getToolCallLines(r, expanded);\n\t\tc.addChild(new Text(fit(stepHeader), 0, 0));\n\n\t\tconst taskMaxLen = Math.max(20, w - 12);\n\t\tconst taskPreview = expanded || r.task.length <= taskMaxLen ? r.task : `${r.task.slice(0, taskMaxLen)}...`;\n\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `    task: ${taskPreview}`)), 0, 0));\n\n\t\tconst outputTarget = extractOutputTarget(r.task);\n\t\tif (outputTarget) {\n\t\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `    output: ${outputTarget}`)), 0, 0));\n\t\t}\n\n\t\tif (r.skills?.length) {\n\t\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `    skills: ${r.skills.join(\", \")}`)), 0, 0));\n\t\t}\n\t\tif (r.skillsWarning) {\n\t\t\tc.addChild(new Text(fit(theme.fg(\"warning\", `    Warning: ${r.skillsWarning}`)), 0, 0));\n\t\t}\n\t\tif (r.attemptedModels && r.attemptedModels.length > 1) {\n\t\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `    fallbacks: ${r.attemptedModels.join(\" → \")}`)), 0, 0));\n\t\t}\n\n\t\tif (rRunning && rProg) {\n\t\t\tif (rProg.skills?.length) {\n\t\t\t\tc.addChild(new Text(fit(theme.fg(\"accent\", `    skills: ${rProg.skills.join(\", \")}`)), 0, 0));\n\t\t\t}\n\t\t\tconst progressSnapshotNow = snapshotNowForProgress(rProg);\n\t\t\tconst toolLine = formatCurrentToolLine(rProg, w, expanded, progressSnapshotNow);\n\t\t\tif (toolLine) {\n\t\t\t\tc.addChild(new Text(fit(theme.fg(\"warning\", `    > ${toolLine}`)), 0, 0));\n\t\t\t}\n\t\t\tconst liveStatusLine = buildLiveStatusLine(rProg, progressSnapshotNow);\n\t\t\tif (liveStatusLine) {\n\t\t\t\tc.addChild(new Text(fit(theme.fg(\"accent\", `    ${liveStatusLine}`)), 0, 0));\n\t\t\t}\n\t\t\tfor (const nestedLine of formatNestedWidgetLines(r.children, theme, w, true, progressSnapshotNow, 8)) {\n\t\t\t\tc.addChild(new Text(fit(`    ${nestedLine}`), 0, 0));\n\t\t\t}\n\t\t\tc.addChild(new Text(fit(theme.fg(\"accent\", `    ${liveDetailHintText()}`)), 0, 0));\n\t\t\tif (r.artifactPaths) {\n\t\t\t\tc.addChild(\n\t\t\t\t\tnew Text(fit(theme.fg(\"dim\", `    artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0),\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (rProg.recentTools?.length) {\n\t\t\t\tfor (const t of rProg.recentTools.slice(-3)) {\n\t\t\t\t\tconst maxArgsLen = Math.max(40, w - 30);\n\t\t\t\t\tconst argsPreview =\n\t\t\t\t\t\texpanded || t.args.length <= maxArgsLen ? t.args : `${t.args.slice(0, maxArgsLen)}...`;\n\t\t\t\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `      ${t.tool}: ${argsPreview}`)), 0, 0));\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst recentLines = (rProg.recentOutput ?? []).slice(-5);\n\t\t\tfor (const line of recentLines) {\n\t\t\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `      ${line}`)), 0, 0));\n\t\t\t}\n\t\t}\n\n\t\tif (!rRunning) {\n\t\t\tfor (const nestedLine of formatNestedWidgetLines(r.children, theme, w, true, r.progress?.lastActivityAt, 8)) {\n\t\t\t\tc.addChild(new Text(fit(`    ${nestedLine}`), 0, 0));\n\t\t\t}\n\t\t}\n\n\t\tif (!rRunning && r.artifactPaths) {\n\t\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `    artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));\n\t\t}\n\n\t\tif (expanded && !rRunning) {\n\t\t\tfor (const line of toolCallLines) {\n\t\t\t\tc.addChild(new Text(fit(theme.fg(\"muted\", `      ${line}`)), 0, 0));\n\t\t\t}\n\t\t\tif (toolCallLines.length) c.addChild(new Spacer(1));\n\t\t}\n\n\t\tc.addChild(new Spacer(1));\n\t}\n\n\tif (d.artifacts) {\n\t\tc.addChild(new Spacer(1));\n\t\tc.addChild(new Text(fit(theme.fg(\"dim\", `Artifacts dir: ${shortenPath(d.artifacts.dir)}`)), 0, 0));\n\t}\n\treturn c;\n}\n"]}