@ashx-j/lunr 0.2.12 → 0.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/README.md +6 -3
  3. package/dist/builtin-extensions/ashxj-tui.d.ts +16 -1
  4. package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
  5. package/dist/builtin-extensions/ashxj-tui.js +44 -20
  6. package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
  7. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +2 -0
  8. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  9. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +27 -22
  10. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  11. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
  12. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +5 -2
  13. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
  14. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts.map +1 -1
  15. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js +12 -1
  16. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js.map +1 -1
  17. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.d.ts +1 -0
  18. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.d.ts.map +1 -1
  19. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.js +3 -2
  20. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.js.map +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-append.d.ts.map +1 -1
  22. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-append.js +1 -0
  23. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-append.js.map +1 -1
  24. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.d.ts.map +1 -1
  25. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.js +3 -0
  26. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.js.map +1 -1
  27. package/dist/builtin-extensions/pi-subagents/src/runs/background/fleet-view.d.ts.map +1 -1
  28. package/dist/builtin-extensions/pi-subagents/src/runs/background/fleet-view.js +3 -3
  29. package/dist/builtin-extensions/pi-subagents/src/runs/background/fleet-view.js.map +1 -1
  30. package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.d.ts.map +1 -1
  31. package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.js +2 -2
  32. package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.js.map +1 -1
  33. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
  34. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +3 -0
  35. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
  36. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
  37. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +7 -1
  38. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
  39. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  40. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +2 -0
  41. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  42. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts +1 -0
  43. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  44. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +19 -3
  45. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  46. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts +10 -1
  47. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts.map +1 -1
  48. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js +19 -0
  49. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js.map +1 -1
  50. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +1 -0
  51. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
  52. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
  53. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts +2 -1
  54. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts.map +1 -1
  55. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js +17 -0
  56. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -1
  57. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts +3 -1
  58. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts.map +1 -1
  59. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js +14 -3
  60. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js.map +1 -1
  61. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +14 -0
  62. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  63. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  64. package/dist/builtin-extensions/pi-subagents/src/tui/fleet.d.ts.map +1 -1
  65. package/dist/builtin-extensions/pi-subagents/src/tui/fleet.js +2 -2
  66. package/dist/builtin-extensions/pi-subagents/src/tui/fleet.js.map +1 -1
  67. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  68. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +12 -10
  69. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  70. package/dist/catalog/models.json +1 -1
  71. package/dist/catalog/providers/amazon-bedrock.json +1 -1
  72. package/dist/catalog/providers/anthropic.json +1 -1
  73. package/dist/catalog/providers/azure-openai-responses.json +1 -1
  74. package/dist/catalog/providers/cloudflare-ai-gateway.json +1 -1
  75. package/dist/catalog/providers/cloudflare-workers-ai.json +1 -1
  76. package/dist/catalog/providers/fireworks.json +1 -1
  77. package/dist/catalog/providers/github-copilot.json +1 -1
  78. package/dist/catalog/providers/google-vertex.json +1 -1
  79. package/dist/catalog/providers/google.json +1 -1
  80. package/dist/catalog/providers/groq.json +1 -1
  81. package/dist/catalog/providers/huggingface.json +1 -1
  82. package/dist/catalog/providers/nvidia.json +1 -1
  83. package/dist/catalog/providers/openai-codex.json +1 -1
  84. package/dist/catalog/providers/openai.json +1 -1
  85. package/dist/catalog/providers/opencode-go.json +1 -1
  86. package/dist/catalog/providers/opencode.json +1 -1
  87. package/dist/catalog/providers/openrouter.json +1 -1
  88. package/dist/catalog/providers/together.json +1 -1
  89. package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
  90. package/dist/catalog/publication.json +131 -3
  91. package/dist/cli/list-models.d.ts.map +1 -1
  92. package/dist/cli/list-models.js +1 -0
  93. package/dist/cli/list-models.js.map +1 -1
  94. package/dist/cli.d.ts.map +1 -1
  95. package/dist/cli.js +52 -27
  96. package/dist/cli.js.map +1 -1
  97. package/dist/core/catalog-cache-scope.d.ts +3 -0
  98. package/dist/core/catalog-cache-scope.d.ts.map +1 -0
  99. package/dist/core/catalog-cache-scope.js +12 -0
  100. package/dist/core/catalog-cache-scope.js.map +1 -0
  101. package/dist/core/catalog-merge.d.ts +2 -0
  102. package/dist/core/catalog-merge.d.ts.map +1 -1
  103. package/dist/core/catalog-merge.js +29 -2
  104. package/dist/core/catalog-merge.js.map +1 -1
  105. package/dist/core/catalog-refresh-polling.d.ts +8 -0
  106. package/dist/core/catalog-refresh-polling.d.ts.map +1 -0
  107. package/dist/core/catalog-refresh-polling.js +30 -0
  108. package/dist/core/catalog-refresh-polling.js.map +1 -0
  109. package/dist/core/codex-catalog.d.ts +3 -0
  110. package/dist/core/codex-catalog.d.ts.map +1 -0
  111. package/dist/core/codex-catalog.js +15 -0
  112. package/dist/core/codex-catalog.js.map +1 -0
  113. package/dist/core/context-breakdown.d.ts +3 -1
  114. package/dist/core/context-breakdown.d.ts.map +1 -1
  115. package/dist/core/context-breakdown.js +5 -2
  116. package/dist/core/context-breakdown.js.map +1 -1
  117. package/dist/core/customize.d.ts +2 -4
  118. package/dist/core/customize.d.ts.map +1 -1
  119. package/dist/core/customize.js +2 -8
  120. package/dist/core/customize.js.map +1 -1
  121. package/dist/core/footer-data-provider.d.ts +3 -4
  122. package/dist/core/footer-data-provider.d.ts.map +1 -1
  123. package/dist/core/footer-data-provider.js +54 -82
  124. package/dist/core/footer-data-provider.js.map +1 -1
  125. package/dist/core/live-catalog.d.ts +5 -2
  126. package/dist/core/live-catalog.d.ts.map +1 -1
  127. package/dist/core/live-catalog.js +108 -27
  128. package/dist/core/live-catalog.js.map +1 -1
  129. package/dist/core/model-runtime.d.ts +8 -0
  130. package/dist/core/model-runtime.d.ts.map +1 -1
  131. package/dist/core/model-runtime.js +64 -12
  132. package/dist/core/model-runtime.js.map +1 -1
  133. package/dist/core/official-catalog.d.ts +4 -0
  134. package/dist/core/official-catalog.d.ts.map +1 -1
  135. package/dist/core/official-catalog.js +86 -21
  136. package/dist/core/official-catalog.js.map +1 -1
  137. package/dist/core/settings-manager.d.ts +7 -6
  138. package/dist/core/settings-manager.d.ts.map +1 -1
  139. package/dist/core/settings-manager.js +13 -17
  140. package/dist/core/settings-manager.js.map +1 -1
  141. package/dist/core/slash-commands.d.ts.map +1 -1
  142. package/dist/core/slash-commands.js +1 -0
  143. package/dist/core/slash-commands.js.map +1 -1
  144. package/dist/index.d.ts +1 -1
  145. package/dist/index.d.ts.map +1 -1
  146. package/dist/index.js.map +1 -1
  147. package/dist/main.d.ts +2 -0
  148. package/dist/main.d.ts.map +1 -1
  149. package/dist/main.js +106 -50
  150. package/dist/main.js.map +1 -1
  151. package/dist/migrations.d.ts +2 -0
  152. package/dist/migrations.d.ts.map +1 -1
  153. package/dist/migrations.js +14 -4
  154. package/dist/migrations.js.map +1 -1
  155. package/dist/modes/interactive/components/assistant-message.d.ts +1 -3
  156. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  157. package/dist/modes/interactive/components/assistant-message.js +4 -32
  158. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  159. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  160. package/dist/modes/interactive/components/custom-editor.js +2 -1
  161. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  162. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  163. package/dist/modes/interactive/components/model-selector.js +4 -1
  164. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  165. package/dist/modes/interactive/components/plan-message.d.ts +1 -3
  166. package/dist/modes/interactive/components/plan-message.d.ts.map +1 -1
  167. package/dist/modes/interactive/components/plan-message.js +1 -16
  168. package/dist/modes/interactive/components/plan-message.js.map +1 -1
  169. package/dist/modes/interactive/components/settings-selector.d.ts +3 -5
  170. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  171. package/dist/modes/interactive/components/settings-selector.js +113 -117
  172. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  173. package/dist/modes/interactive/components/user-message.d.ts +1 -3
  174. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  175. package/dist/modes/interactive/components/user-message.js +2 -19
  176. package/dist/modes/interactive/components/user-message.js.map +1 -1
  177. package/dist/modes/interactive/interactive-mode.d.ts +12 -1
  178. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  179. package/dist/modes/interactive/interactive-mode.js +153 -130
  180. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  181. package/dist/modes/interactive/skill-tag-autocomplete.d.ts +13 -0
  182. package/dist/modes/interactive/skill-tag-autocomplete.d.ts.map +1 -0
  183. package/dist/modes/interactive/skill-tag-autocomplete.js +72 -0
  184. package/dist/modes/interactive/skill-tag-autocomplete.js.map +1 -0
  185. package/dist/modes/interactive/theme/moon.json +0 -1
  186. package/dist/modes/interactive/theme/theme-schema.json +3 -3
  187. package/dist/modes/interactive/theme/theme.d.ts +1 -1
  188. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  189. package/dist/modes/interactive/theme/theme.js +95 -90
  190. package/dist/modes/interactive/theme/theme.js.map +1 -1
  191. package/dist/startup/interactive-view.d.ts +55 -0
  192. package/dist/startup/interactive-view.d.ts.map +1 -0
  193. package/dist/startup/interactive-view.js +332 -0
  194. package/dist/startup/interactive-view.js.map +1 -0
  195. package/dist/startup/launch-routing.d.ts +8 -0
  196. package/dist/startup/launch-routing.d.ts.map +1 -0
  197. package/dist/startup/launch-routing.js +29 -0
  198. package/dist/startup/launch-routing.js.map +1 -0
  199. package/dist/startup/startup-milestones.d.ts +4 -0
  200. package/dist/startup/startup-milestones.d.ts.map +1 -0
  201. package/dist/startup/startup-milestones.js +14 -0
  202. package/dist/startup/startup-milestones.js.map +1 -0
  203. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  204. package/dist/utils/syntax-highlight.js +10 -4
  205. package/dist/utils/syntax-highlight.js.map +1 -1
  206. package/docs/features.md +2 -2
  207. package/docs/interactive-startup.md +58 -0
  208. package/docs/keybindings.md +1 -1
  209. package/docs/model-catalog.md +29 -0
  210. package/docs/quickstart.md +1 -1
  211. package/docs/settings.md +2 -3
  212. package/docs/skills.md +10 -0
  213. package/docs/terminal-setup.md +2 -2
  214. package/docs/usage.md +2 -1
  215. package/docs/windows.md +1 -1
  216. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  217. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  218. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  219. package/examples/extensions/gondolin/package-lock.json +2 -2
  220. package/examples/extensions/gondolin/package.json +1 -1
  221. package/examples/extensions/sandbox/package-lock.json +2 -2
  222. package/examples/extensions/sandbox/package.json +1 -1
  223. package/examples/extensions/with-deps/package-lock.json +2 -2
  224. package/examples/extensions/with-deps/package.json +1 -1
  225. package/package.json +16 -16
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/tui/render.ts"],"names":[],"mappings":"AACA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnG,OAAO,EAA+B,IAAI,EAAgB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACzG,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,aAAa,EAGlB,KAAK,OAAO,EAMZ,MAAM,oBAAoB,CAAC;AAS5B,KAAK,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;AA6H7C,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;AAKD,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CAChC;AACD,eAAO,MAAM,gBAAgB,EAAE;IAAE,OAAO,EAAE,oBAAoB,EAAE,GAAG,IAAI,CAAA;CAAsB,CAAC;AAwH9F,+EAA2E;AAC3E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAOhE;AAED,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,MAAM,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAItG;AAyBD,0DAA0D;AAC1D,wBAAgB,0BAA0B,CACzC,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,WAAW,GAAG,QAAQ,GAAG,YAAY,CAAC,GAAG,SAAS,EAChF,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAiB,EACvC,MAAM,SAAU,GACd,MAAM,CAUR;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA0B1D;AAw1BD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,SAAiB,EAAE,QAAQ,UAAQ,GAAG,MAAM,EAAE,CAoExH;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,IAAI,CAQ/E;AAkID;;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,CA+UX","sourcesContent":["// @ts-nocheck\n/**\n * Rendering functions for subagent results\n */\n\nimport type { AgentToolResult } from \"@ashx-j/lunr-agent\";\nimport { getMarkdownTheme, keyText, type ExtensionContext } from \"@ashx-j/lunr\";\nimport { Container, Markdown, Spacer, Text, visibleWidth, type Component } from \"@ashx-j/lunr-tui\";\nimport {\n\ttype AgentProgress,\n\ttype AsyncJobState,\n\ttype AsyncJobStep,\n\ttype AsyncParallelGroupStatus,\n\ttype Details,\n\ttype NestedRunSummary,\n\ttype NestedStepSummary,\n\ttype WorkflowNodeStatus,\n\tMAX_WIDGET_JOBS,\n\tWIDGET_KEY,\n} from \"../shared/types.ts\";\nimport { formatTokens, formatUsage, formatDuration, formatModelThinking, formatToolCall, shortenPath } from \"../shared/formatters.ts\";\nimport { childRowLabel } from \"../shared/types.ts\";\nimport { sanitizeChildDescription } from \"../shared/child-spec.ts\";\nimport { getDisplayItems, getSingleResultOutput } from \"../shared/utils.ts\";\nimport { flatToLogicalStepIndex } from \"../runs/background/parallel-groups.ts\";\nimport { formatNestedAggregate } from \"../runs/shared/nested-render.ts\";\nimport { aggregateStepStatus, formatActivityLabel, formatAgentRunningLabel, formatParallelOutcome } from \"../shared/status-format.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\" });\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 */\nfunction truncLine(text: string, maxWidth: number): string {\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\tconst ansiMatch = text.slice(i).match(/^\\x1b\\[[0-9;]*m/);\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 = i;\n\t\twhile (end < text.length && !text.slice(end).match(/^\\x1b\\[[0-9;]*m/)) {\n\t\t\tend++;\n\t\t}\n\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 = 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 (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<Pick<AgentProgress, \"index\" | \"toolCount\" | \"tokens\" | \"durationMs\" | \"lastActivityAt\" | \"currentToolStartedAt\" | \"turnCount\">>;\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 progressRunningSeed(progress: ProgressSeedSource | undefined): number | undefined {\n\tif (!progress) return undefined;\n\t// lunr: seed only the row identity. Tokens / duration / lastActivityAt\n\t// change on every thinking token and used to advance the glyph faster than the 80ms timer.\n\treturn runningSeed(progress.index);\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\n// lunr: animation sink — while `current` is set, running-glyph lines register a\n// per-frame template so the extension timer can rewrite just those Text nodes\n// (setText + requestRender) instead of rebuilding the whole result tree per frame.\nexport interface SubagentAnimatedLine {\n\ttext: Text;\n\tline: (frame: number) => string;\n}\nexport const subagentAnimSink: { current: SubagentAnimatedLine[] | null } = { current: null };\n\nfunction animatedLine(line: (frame: number) => string, frame: number | undefined, animate: boolean): Text {\n\tconst text = new Text(line(frame ?? 0), 0, 0);\n\tif (animate && frame !== undefined && subagentAnimSink.current) {\n\t\tsubagentAnimSink.current.push({ text, line });\n\t}\n\treturn text;\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\n\nfunction snapshotNowForProgress(progress: Pick<AgentProgress, \"currentToolStartedAt\" | \"durationMs\" | \"lastActivityAt\">): number | undefined {\n\tif (progress.currentToolStartedAt !== undefined && progress.durationMs !== undefined) return 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 = progress.currentToolStartedAt !== undefined && snapshotNow !== undefined\n\t\t? ` | ${formatDuration(Math.max(0, snapshotNow - progress.currentToolStartedAt))}`\n\t\t: \"\";\n\treturn toolArgsPreview\n\t\t? `${progress.currentTool}: ${toolArgsPreview}${durationSuffix}`\n\t\t: `${progress.currentTool}${durationSuffix}`;\n}\n\nfunction buildLiveStatusLine(progress: Pick<AgentProgress, \"activityState\" | \"lastActivityAt\">, snapshotNow?: number): string | undefined {\n\tif (progress.lastActivityAt !== undefined && snapshotNow !== undefined) return 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.filter(Boolean).map((part) => theme.fg(\"dim\", part)).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)) return \"\";\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(theme: Theme, progress: Pick<AgentProgress, \"toolCount\" | \"tokens\" | \"durationMs\"> | undefined, includeDuration = true): 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 text.split(\"\\n\").find((line) => line.trim())?.trim() ?? \"\";\n}\n\n// lunr: first line of the task with whitespace collapsed, capped at ~60 chars,\n// for the simplified collapsed subagent rows.\nfunction taskSummaryText(task: string | undefined, max = 60): string {\n\tconst first = (task ?? \"\").split(\"\\n\")[0]?.replace(/\\s+/g, \" \").trim() ?? \"\";\n\tif (first.length <= max) return first;\n\treturn `${first.slice(0, max - 1)}…`;\n}\n\nconst TASK_CHROME_LINE_RE = /^\\[(?:Read from|Write to):\\s*.*\\]\\s*$/;\n\n/** Drop leading [Read from: …] / [Write to: …] lines from a child task. */\nexport function stripTaskChrome(task: string | undefined): string {\n\tif (!task) return \"\";\n\tconst lines = task.split(\"\\n\");\n\twhile (lines.length > 0 && TASK_CHROME_LINE_RE.test(lines[0]!.trim())) {\n\t\tlines.shift();\n\t}\n\treturn lines.join(\"\\n\").trim();\n}\n\n/** Compact-row lead: sanitized description, never a removed agent type or the model. */\nexport function compactRowLead(result: { description?: string; task?: string; model?: string }): string {\n\tconst fromDescription = sanitizeChildDescription(result.description ?? \"\", 80);\n\tif (fromDescription) return fromDescription;\n\treturn taskSummaryText(stripTaskChrome(result.task), 80);\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\") return `Done · acceptance: ${result.acceptance.status}`;\n\tif (hasEmptyTextOutputWithoutOutputTarget(result.task, output)) return \"Done (no text output)\";\n\treturn \"Done\";\n}\n\nfunction resultGlyph(result: Details[\"results\"][number], output: string, theme: Theme, running = result.progress?.status === \"running\", seed = progressRunningSeed(result.progress ?? result.progressSummary), frame?: number): string {\n\tif (running) {\n\t\tif (frame !== undefined) return theme.fg(\"accent\", runningGlyph((seed ?? 0) + frame));\n\t\treturn theme.fg(\"accent\", runningGlyph(seed));\n\t}\n\tif (result.detached) return theme.fg(\"warning\", \"■\");\n\tif (result.stopped) return theme.fg(\"warning\", \"■\");\n\tif (result.interrupted) return theme.fg(\"warning\", \"■\");\n\tif (result.exitCode !== 0) return theme.fg(\"error\", \"✗\");\n\tif (hasEmptyTextOutputWithoutOutputTarget(result.task, output)) return theme.fg(\"warning\", \"✓\");\n\treturn theme.fg(\"success\", \"✓\");\n}\n\n/** Compact hang row: tool count, tokens, elapsed time. */\nexport function formatCompactStatsHangLine(\n\tprogress: Pick<AgentProgress, \"toolCount\" | \"tokens\" | \"durationMs\"> | undefined,\n\twidth: number,\n\tpaint: (s: string) => string = (s) => s,\n\tprefix = \" ⎿ \",\n): string {\n\tconst toolCount = progress?.toolCount ?? 0;\n\tconst tokens = progress?.tokens ?? 0;\n\tconst durationMs = progress?.durationMs ?? 0;\n\tconst body = [\n\t\tformatToolUseStat(toolCount),\n\t\tformatTokenStat(tokens),\n\t\tdurationMs > 0 ? formatDuration(durationMs) : \"0ms\",\n\t].join(\" · \");\n\treturn truncLine(paint(`${prefix}${body}`), width);\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) facts.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(step: NonNullable<AsyncJobState[\"steps\"]>[number], fallbackIndex?: number): number | undefined {\n\treturn runningSeed(fallbackIndex, step.index);\n}\n\nfunction widgetStepsRunningSeed(steps: Array<NonNullable<AsyncJobState[\"steps\"]>[number]> | undefined): 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(job.currentStep, job.runningSteps, widgetStepsRunningSeed(job.steps));\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): string {\n\tif (job.status === \"running\") return theme.fg(\"accent\", runningGlyph(widgetJobRunningSeed(job)));\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): string {\n\tif (status === \"running\") return theme.fg(\"accent\", runningGlyph(seed));\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) facts.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\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(` ${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\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(...foregroundStyleWidgetStepLines(job, theme, step, \"Step\", span.stepIndex + 1, total, expanded, width));\n\t}\n\treturn lines;\n}\n\nfunction widgetParallelAgentDetails(job: AsyncJobState, theme: Theme, expanded = false, width = getTermWidth()): 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) return 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(` ${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\tfor (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 8 : 1)) lines.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.split(\"+\").map((part) => part.trim()).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({ stepIndex: node.stepIndex, start, count, isParallel: true, status: node.status, label: node.label, error: node.error });\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({ stepIndex: node.stepIndex, start, count: 1, isParallel: false, status: node.status, label: node.label, error: node.error });\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 isChainParallelGroupActive(details: Pick<Details, \"mode\" | \"chainAgents\" | \"currentStepIndex\" | \"workflowGraph\">): boolean {\n\tif (details.mode !== \"chain\") return false;\n\tif (details.currentStepIndex === undefined) return false;\n\treturn buildChainStepSpans(details).some((span) => span.stepIndex === details.currentStepIndex && span.isParallel);\n}\n\nfunction buildAsyncChainStepSpans(total: number, stepCount: number, parallelGroups: AsyncParallelGroupStatus[] = []): ChainStepSpan[] {\n\tconst spans: ChainStepSpan[] = [];\n\tlet flatIndex = 0;\n\tfor (let stepIndex = 0; stepIndex < total; stepIndex++) {\n\t\tconst group = parallelGroups.find((candidate) => candidate.stepIndex === 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\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\tagentName: details.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(details: Pick<Details, \"mode\" | \"results\" | \"progress\" | \"totalSteps\" | \"currentStepIndex\" | \"chainAgents\" | \"workflowGraph\">, hasRunning: boolean): MultiProgressLabel {\n\tconst stepSpans = buildChainStepSpans(details);\n\tconst hasParallelInChain = details.mode === \"chain\" && stepSpans.some((span) => span.isParallel);\n\tconst activeParallelGroup = isChainParallelGroupActive(details);\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<\"pending\" | \"running\" | \"completed\" | \"failed\" | \"stopped\" | \"detached\">;\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 = details.progress?.find((progress) => progress.index === i)\n\t\t\t\t|| details.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.progress?.status\n\t\t\t\t?? (result.stopped\n\t\t\t\t\t? \"stopped\"\n\t\t\t\t\t: result.interrupted || result.detached\n\t\t\t\t\t\t? \"detached\"\n\t\t\t\t\t\t: result.exitCode === 0\n\t\t\t\t\t\t\t? \"completed\"\n\t\t\t\t\t\t\t: \"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 { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: totalCount, showActiveGroupOnly: false };\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 = details.results.find((result) => result.progress?.index === index);\n\t\t\tif (progressEntry?.status === \"running\") {\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 { headerLabel, itemTitle, totalCount: groupSize, hasParallelInChain, activeParallelGroup, groupStartIndex: groupStart, groupEndIndex: groupEnd, showActiveGroupOnly: true };\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 = details.results.find((result) => result.progress?.index === index) ?? details.results[index];\n\t\t\t\tif (progressEntry?.status === \"running\" || progressEntry?.status === \"pending\" || progressEntry?.status === \"failed\") return 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 = details.currentStepIndex !== undefined ? details.currentStepIndex + 1 : Math.min(totalCount, doneLogical + (hasRunning ? 1 : 0));\n\t\tconst headerLabel = hasRunning ? `step ${currentStep}/${totalCount}` : `step ${doneLogical}/${totalCount}`;\n\t\treturn { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: details.results.length, showActiveGroupOnly: false };\n\t}\n\n\tconst totalCount = details.totalSteps ?? details.results.length;\n\tconst currentStep = details.currentStepIndex !== undefined ? details.currentStepIndex + 1 : 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 { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: details.results.length, showActiveGroupOnly: false };\n}\n\nfunction resultRowLabel(details: Pick<Details, \"mode\" | \"chainAgents\" | \"workflowGraph\">, label: MultiProgressLabel, resultIndex: number, stepNumber: number): string {\n\tif (details.mode === \"chain\" && label.hasParallelInChain) {\n\t\tconst span = buildChainStepSpans(details).find((candidate) => resultIndex >= candidate.start && resultIndex < candidate.start + candidate.count);\n\t\tif (span?.isParallel) return `Agent ${resultIndex - span.start + 1}/${span.count}`;\n\t\tif (span) return `Step ${span.stepIndex + 1}`;\n\t}\n\tif (label.itemTitle === \"Agent\") {\n\t\tconst localStepNumber = label.activeParallelGroup\n\t\t\t? Math.max(1, stepNumber - label.groupStartIndex)\n\t\t\t: 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 = job.currentStep !== undefined\n\t\t\t\t? job.parallelGroups?.find((group) => job.currentStep! >= group.start && job.currentStep! < group.start + group.count)\n\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) parts.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(step: NonNullable<AsyncJobState[\"steps\"]>[number], width: number, expanded: boolean, snapshotNow?: number): 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 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(state: NestedRunSummary[\"state\"] | NestedStepSummary[\"status\"], theme: Theme, seed?: number): 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(run.lastUpdate, run.lastActivityAt, run.currentStep, run.toolCount, run.turnCount, run.totalTokens?.total, run.currentToolStartedAt);\n}\n\nfunction nestedActivity(input: Pick<NestedRunSummary | NestedStepSummary, \"activityState\" | \"lastActivityAt\" | \"currentTool\" | \"currentToolStartedAt\" | \"currentPath\" | \"turnCount\" | \"toolCount\">, state: NestedRunSummary[\"state\"] | NestedStepSummary[\"status\"], snapshotNow?: number): string {\n\tconst facts: string[] = [];\n\tif (input.currentTool && input.currentToolStartedAt !== undefined && snapshotNow !== undefined) facts.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(children: NestedRunSummary[] | undefined, theme: Theme, width: number, expanded: boolean, snapshotNow?: number, lineBudget = expanded ? 12 : 1): string[] {\n\tif (!children?.length || lineBudget <= 0) return [];\n\tif (!expanded) {\n\t\tconst aggregate = formatNestedAggregate(children);\n\t\treturn aggregate ? [theme.fg(\"dim\", `↳ ${aggregate}`)] : [];\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\tlines.push(theme.fg(\"dim\", `${prefix}↳ ${nestedStatusGlyph(child.state, theme, nestedRunSeed(child))} ${nestedRunName(child)} · ${child.state} · ${activity}${error}`));\n\t\t\tif (depth === maxDepth) {\n\t\t\t\tconst aggregate = formatNestedAggregate([...(child.steps?.flatMap((step) => step.children ?? []) ?? []), ...(child.children ?? [])]);\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\tlines.push(theme.fg(\"dim\", `${prefix} ↳ ${nestedStatusGlyph(step.status, theme)} ${step.agent} · ${step.status} · ${nestedActivity(step, step.status, snapshotNow ?? child.lastUpdate)}`));\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): 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 = [` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index - 1))} ${itemTitle} ${index}/${total}: ${themeBold(theme, step.agent)} ${theme.fg(\"dim\", \"·\")} ${status}${modelDisplay}${stats ? ` ${theme.fg(\"dim\", \"·\")} ${stats}` : \"\"}`];\n\tconst activity = widgetStepActivityLine(step, width, expanded, job.updatedAt);\n\tif (activity) lines.push(` ${theme.fg(\"dim\", `⎿ ${activity}`)}`);\n\tfor (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt)) {\n\t\tlines.push(` ${nestedLine}`);\n\t}\n\tif (step.status === \"running\") {\n\t\tif (!expanded) lines.push(` ${theme.fg(\"accent\", liveDetailHintText())}`);\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(job: AsyncJobState, theme: Theme, expanded: boolean, width: number): string[] {\n\tif (!job.steps?.length) return [\n\t\t` ${theme.fg(\"dim\", `⎿ ${widgetActivity(job)}`)}`,\n\t\t...formatNestedWidgetLines(job.nestedChildren, theme, width, expanded, job.updatedAt).map((line) => ` ${line}`),\n\t];\n\tif (job.mode === \"chain\" && !job.activeParallelGroup && job.parallelGroups?.length) return 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(...foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index + 1, total, expanded, width));\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(unattached, theme, width, expanded, job.updatedAt)) {\n\t\tlines.push(` ${nestedLine}`);\n\t}\n\treturn lines;\n}\n\nfunction buildSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number, expanded: boolean): string[] {\n\tconst stats = widgetStats(job, theme);\n\tconst count = job.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)} ${themeBold(theme, mode)}${stats ? ` ${theme.fg(\"dim\", \"·\")} ${stats}` : \"\"}`,\n\t\t...foregroundStyleWidgetDetails(job, theme, expanded, width),\n\t].map((line) => truncLine(line, width));\n}\n\nfunction compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number): string[] {\n\tconst fullLines = buildSingleWidgetLines(job, theme, width, false);\n\tif (fullLines.length <= 10 || !job.steps?.length || (job.mode !== \"parallel\" && !job.activeParallelGroup)) return 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(` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index))} ${itemTitle} ${index + 1}/${total}: ${themeBold(theme, step.agent)} ${theme.fg(\"dim\", \"·\")} ${status}${modelDisplay}${activitySuffix}${stepStats ? ` ${theme.fg(\"dim\", \"·\")} ${stepStats}` : \"\"}`);\n\t\tfor (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt)) lines.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 widgetLayoutSession?.expanded === expanded\n\t\t&& widgetLayoutSession.rows === currentTerminalRows()\n\t\t&& widgetLayoutSession.columns === currentTerminalColumns();\n}\n\nfunction widgetHeaderCounts(jobs: AsyncJobState[]): { running: AsyncJobState[]; queued: AsyncJobState[]; complete: AsyncJobState[]; failed: AsyncJobState[]; paused: AsyncJobState[]; stopped: AsyncJobState[] } {\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): string[] {\n\tconst counts = widgetHeaderCounts(jobs);\n\tconst hasActive = counts.running.length > 0 || counts.queued.length > 0;\n\tconst glyph = counts.running.length > 0 ? runningGlyph(widgetJobsRunningSeed(counts.running)) : hasActive ? \"●\" : \"○\";\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 [truncLine(`${theme.fg(hasActive ? \"accent\" : \"dim\", glyph)} ${theme.fg(hasActive ? \"accent\" : \"dim\", \"subagents\")} (${parts.join(\", \") || `${jobs.length} total`})`, width)];\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): string {\n\tconst counts = widgetHeaderCounts(jobs);\n\tconst hasActive = counts.running.length > 0 || counts.queued.length > 0;\n\tconst glyph = counts.running.length > 0 ? runningGlyph(widgetJobsRunningSeed(counts.running)) : hasActive ? \"●\" : \"○\";\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(`${theme.fg(hasActive ? \"accent\" : \"dim\", glyph)} ${theme.fg(hasActive ? \"accent\" : \"dim\", \"Async agents\")} ${theme.fg(\"dim\", \"·\")} ${theme.fg(\"dim\", parts.join(\", \") || `${jobs.length} total`)}`, width);\n}\n\nfunction progressiveJobLine(job: AsyncJobState, theme: Theme, width: 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\tthemeBold(theme, widgetJobName(job)),\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)} ${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(theme.fg(\"dim\", ` +${hiddenJobs.length} more${parts.length ? ` (${parts.join(\", \")})` : \"\"}`), width);\n}\n\nfunction buildProgressiveWidgetLines(jobs: AsyncJobState[], theme: Theme, width: number, lockedRows: number, previousKeys: string[]): { lines: string[]; visibleJobKeys: string[] } {\n\tconst rowCount = Math.max(1, lockedRows);\n\tif (rowCount === 1) return { lines: buildSingleLineWidgetLines(jobs, theme, width), 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.map((key) => jobsByKey.get(key)).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),\n\t\t...visibleJobs.map((job) => progressiveJobLine(job, theme, width)),\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 fitWidgetLineBudget(lines: string[], theme: Theme, width: number, expanded: boolean): string[] {\n\tconst rows = process.stdout.rows || 30;\n\tconst budget = expanded\n\t\t? Math.max(12, Math.min(24, Math.floor(rows * 0.55)))\n\t\t: 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(jobs: AsyncJobState[], lines: string[], theme: Theme, width: number, expanded: boolean): 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);\n\t}\n\n\tif (hasMatchingSession && widgetLayoutSession?.tier === \"progressive\" && widgetLayoutSession.lockedRows !== undefined) {\n\t\tconst rendered = buildProgressiveWidgetLines(jobs, theme, width, widgetLayoutSession.lockedRows, widgetLayoutSession.visibleJobKeys);\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);\n\t}\n\n\tconst lockedRows = Math.min(availableRows, collapsedWidgetLineBudget(rows));\n\tconst rendered = buildProgressiveWidgetLines(jobs, theme, width, lockedRows, []);\n\twidgetLayoutSession = { expanded, rows, columns, tier: \"progressive\", lockedRows, visibleJobKeys: rendered.visibleJobKeys };\n\treturn rendered.lines;\n}\n\nfunction buildWidgetComponent(jobs: AsyncJobState[], expanded: boolean): (_tui: unknown, theme: Theme) => Component {\n\treturn (_tui, theme) => {\n\t\tconst width = getTermWidth();\n\t\tconst lines = expanded\n\t\t\t? buildWidgetLines(jobs, theme, width, true)\n\t\t\t: jobs.length === 1\n\t\t\t\t? compactSingleWidgetLines(jobs[0]!, theme, width)\n\t\t\t\t: buildWidgetLines(jobs, theme, width, false);\n\t\tconst container = new Container();\n\t\tfor (const line of fitAdaptiveWidgetLines(jobs, lines, theme, width, expanded)) container.addChild(new Text(line, 1, 0));\n\t\treturn container;\n\t};\n}\n\nexport function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = getTermWidth(), expanded = false): string[] {\n\tif (jobs.length === 0) return [];\n\tif (jobs.length === 1) return buildSingleWidgetLines(jobs[0]!, theme, width, expanded);\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 = running.length > 0 ? runningGlyph(widgetJobsRunningSeed(running)) : hasActive ? \"●\" : \"○\";\n\tlines.push(truncLine(`${theme.fg(hasActive ? \"accent\" : \"dim\", headerGlyph)} ${theme.fg(hasActive ? \"accent\" : \"dim\", \"Async agents\")} ${theme.fg(\"dim\", \"· background\")}`, width));\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) { hiddenRunning++; continue; }\n\t\tconst stats = widgetStats(job, theme);\n\t\titems.push([\n\t\t\t`${widgetStatusGlyph(job, theme)} ${themeBold(theme, widgetJobName(job))}${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) { hiddenFinished++; continue; }\n\t\tconst stats = widgetStats(job, theme);\n\t\titems.push([\n\t\t\t`${widgetStatusGlyph(job, theme)} ${themeBold(theme, widgetJobName(job))}${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 = r.progress?.status === \"running\";\n\tconst contextBadge = d.context === \"fork\" ? theme.fg(\"warning\", \" [fork]\") : \"\";\n\t// lunr: simplified collapsed row — glyph · task summary · agent type · runtime/tools/tokens stats.\n\tconst stats = formatProgressStats(theme, progress);\n\tconst c = new Container();\n\tconst width = getTermWidth() - 4;\n\t// lunr: row line is a per-frame template so the running glyph animates in place (see subagentAnimSink).\n\tconst lead = compactRowLead({ description: r.description ?? r.agent, task: r.task });\n\tconst modelBadge = formatModelThinking(r.model ?? progress?.model, r.thinking ?? progress?.thinking);\n\tconst permissionBadge = r.permissions ? theme.fg(\"dim\", r.permissions) : \"\";\n\tc.addChild(animatedLine((f) => truncLine(`${resultGlyph(r, output, theme, isRunning, undefined, f)} ${themeBold(theme, lead)}${modelBadge ? ` ${theme.fg(\"dim\", \"·\")} ${theme.fg(\"dim\", modelBadge)}` : \"\"}${permissionBadge ? ` ${theme.fg(\"dim\", \"·\")} ${permissionBadge}` : \"\"}${contextBadge}${stats ? ` ${theme.fg(\"dim\", \"·\")} ${stats}` : \"\"}`, width), frame, isRunning));\n\n\tif (isRunning && r.progress) {\n\t\tc.addChild(new Text(formatCompactStatsHangLine(r.progress, width, (s) => theme.fg(\"dim\", s)), 0, 0));\n\t\treturn c;\n\t}\n\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) c.addChild(new Text(truncLine(theme.fg(\"dim\", ` session: ${shortenPath(r.sessionFile)}`), width), 0, 0));\n\treturn c;\n}\n\nfunction renderMultiCompact(d: Details, theme: Theme, frame?: number): Component {\n\tconst hasRunning = d.progress?.some((p) => p.status === \"running\")\n\t\t|| d.results.some((r) => r.progress?.status === \"running\")\n\t\t|| workflowGraphHasStatus(d, [\"running\"]);\n\tconst stopped = d.results.some((r) => r.stopped && r.progress?.status !== \"running\")\n\t\t|| workflowGraphHasStatus(d, [\"stopped\"]);\n\tconst failed = d.results.some((r) => !r.stopped && r.exitCode !== 0 && r.progress?.status !== \"running\")\n\t\t|| workflowGraphHasStatus(d, [\"failed\"]);\n\tconst paused = d.results.some((r) => (r.interrupted || r.detached) && r.progress?.status !== \"running\")\n\t\t|| workflowGraphHasStatus(d, [\"paused\", \"detached\"]);\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 = d.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, [multiLabel.headerLabel, formatProgressStats(theme, totalSummary), formatTotalCostStat(d.totalCost)]);\n\tconst glyph = (f: number | undefined) => hasRunning\n\t\t? theme.fg(\"accent\", runningGlyph(f !== undefined ? (runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex) ?? 0) + f : runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex)))\n\t\t: stopped\n\t\t\t? theme.fg(\"warning\", \"■\")\n\t\t\t: failed\n\t\t\t\t? theme.fg(\"error\", \"✗\")\n\t\t\t: paused\n\t\t\t\t? theme.fg(\"warning\", \"■\")\n\t\t\t\t: theme.fg(\"success\", \"✓\");\n\tconst contextBadge = d.context === \"fork\" ? theme.fg(\"warning\", \" [fork]\") : \"\";\n\tconst c = new Container();\n\tconst width = getTermWidth() - 4;\n\t// lunr: header line is a per-frame template so the running glyph animates in place (see subagentAnimSink).\n\tc.addChild(animatedLine((f) => truncLine(`${glyph(f)} ${theme.fg(\"toolTitle\", theme.bold(d.mode))}${contextBadge}${stats ? ` ${theme.fg(\"dim\", \"·\")} ${stats}` : \"\"}`, width), frame, hasRunning));\n\n\tconst useResultsDirectly = multiLabel.hasParallelInChain || !d.chainAgents?.length;\n\tconst displayStart = multiLabel.showActiveGroupOnly ? multiLabel.groupStartIndex : 0;\n\tconst displayEnd = multiLabel.showActiveGroupOnly ? multiLabel.groupEndIndex : (useResultsDirectly ? d.results.length : d.chainAgents!.length);\n\tconst chainEntries = buildChainRenderEntries(d, multiLabel);\n\tconst renderEntries = chainEntries ?? Array.from({ length: displayEnd - displayStart }, (_, offset): ChainRenderEntry => {\n\t\tconst i = displayStart + offset;\n\t\tconst r = d.results[i];\n\t\tconst fallbackLabel = itemTitle.toLowerCase();\n\t\tconst rowNumber = multiLabel.showActiveGroupOnly ? (i - multiLabel.groupStartIndex + 1) : (i + 1);\n\t\treturn { kind: \"result\", resultIndex: i, rowNumber, agentName: useResultsDirectly ? (r?.agent || `${fallbackLabel}-${rowNumber}`) : (d.chainAgents![i] || r?.agent || `${fallbackLabel}-${rowNumber}`) };\n\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(new Text(truncLine(` ${glyph} ${entry.stepLabel}: ${themeBold(theme, entry.agentName)} ${theme.fg(\"dim\", \"·\")} ${statusLabel}`, width), 0, 0));\n\t\t\tif (entry.error) c.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 = chainEntries ? resultRowLabel(d, multiLabel, i, rowNumber) : `${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 = d.progress?.find((p) => p.index === i) || d.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 && rProg.status === \"running\";\n\t\tconst rPending = rProg && \"status\" in rProg && rProg.status === \"pending\";\n\t\tconst stepStats = formatProgressStats(theme, rProg);\n\t\tconst pendingLabel = rPending ? ` ${theme.fg(\"dim\", \"· pending\")}` : \"\";\n\t\t// lunr: simplified collapsed row — glyph · task summary · agent type · runtime/tools/tokens stats.\n\t\t// lunr: row line is a per-frame template so the running glyph animates in place (see subagentAnimSink).\n\t\tconst lead = compactRowLead({ description: r.description ?? agentName, task: r.task });\n\t\tconst permissionBadge = r.permissions ? theme.fg(\"dim\", r.permissions) : \"\";\n\t\tc.addChild(animatedLine((f) => {\n\t\t\tconst glyph = rPending ? theme.fg(\"dim\", \"◦\") : resultGlyph(r, output, theme, rRunning, progressRunningSeed(rProg), f);\n\t\t\treturn truncLine(` ${glyph} ${themeBold(theme, lead)}${permissionBadge ? ` ${theme.fg(\"dim\", \"·\")} ${permissionBadge}` : \"\"}${stepStats ? ` ${theme.fg(\"dim\", \"·\")} ${stepStats}` : \"\"}${pendingLabel}`, width);\n\t\t}, frame, !!rRunning));\n\t\tif (rRunning && rProg && \"status\" in rProg) {\n\t\t\tc.addChild(new Text(formatCompactStatsHangLine(\n\t\t\t\trProg,\n\t\t\t\twidth,\n\t\t\t\t(s) => theme.fg(\"dim\", s),\n\t\t\t\t\" ⎿ \",\n\t\t\t), 0, 0));\n\t\t} else if (!rPending && (r.exitCode !== 0 || r.interrupted || r.detached || hasEmptyTextOutputWithoutOutputTarget(r.task, output))) {\n\t\t\tc.addChild(new Text(truncLine(theme.fg(r.exitCode !== 0 ? \"error\" : \"dim\", ` ⎿ ${resultStatusLine(r, output)}`), width), 0, 0));\n\t\t}\n\t}\n\treturn c;\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 (!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 = d?.context === \"fork\" ? `${theme.fg(\"warning\", \"[fork]\")} ` : \"\";\n\t\tconst width = getTermWidth() - 4;\n\t\tif (!text.includes(\"\\n\")) return new Text(truncLine(`${contextPrefix}${text}`, width), 0, 0);\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 (!expanded) return renderSingleCompact(d, r, theme, frame);\n\t\tconst isRunning = r.progress?.status === \"running\";\n\t\tconst icon = isRunning\n\t\t\t? theme.fg(\"warning\", \"running\")\n\t\t\t: r.detached\n\t\t\t\t? theme.fg(\"warning\", \"detached\")\n\t\t\t\t: r.exitCode === 0\n\t\t\t\t\t? theme.fg(\"success\", \"ok\")\n\t\t\t\t\t: theme.fg(\"error\", \"failed\");\n\t\tconst contextBadge = d.context === \"fork\" ? theme.fg(\"warning\", \" [fork]\") : \"\";\n\t\tconst output = r.truncation?.text || getSingleResultOutput(r);\n\n\t\tconst progressInfo = isRunning && r.progress\n\t\t\t? ` | ${r.progress.toolCount} tools, ${formatTokens(r.progress.tokens)} tok, ${formatDuration(r.progress.durationMs)}`\n\t\t\t: r.progressSummary\n\t\t\t\t? ` | ${r.progressSummary.toolCount} tools, ${formatTokens(r.progressSummary.tokens)} tok, ${formatDuration(r.progressSummary.durationMs)}`\n\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(new Text(fit(`${icon} ${theme.fg(\"toolTitle\", theme.bold(r.agent))}${contextBadge}${progressInfo}`), 0, 0));\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\n\t\t\t? r.task\n\t\t\t: `${r.task.slice(0, taskMaxLen)}...`;\n\t\tc.addChild(\n\t\t\tnew Text(fit(theme.fg(\"dim\", `Task: ${taskPreview}`)), 0, 0),\n\t\t);\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\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 = expanded || t.args.length <= maxArgsLen\n\t\t\t\t\t\t? t.args\n\t\t\t\t\t\t: `${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 (toolLine || liveStatusLine || r.progress.recentTools?.length || r.progress.recentOutput?.length || r.artifactPaths) {\n\t\t\t\tc.addChild(new Spacer(1));\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 = d.progress?.some((p) => p.status === \"running\") \n\t\t|| d.results.some((r) => r.progress?.status === \"running\")\n\t\t|| workflowGraphHasStatus(d, [\"running\"]);\n\tconst ok = d.results.filter((r) => r.progress?.status === \"completed\" || (r.exitCode === 0 && r.progress?.status !== \"running\")).length;\n\tconst hasEmptyWithoutTarget = d.results.some((r) =>\n\t\tr.exitCode === 0\n\t\t&& r.progress?.status !== \"running\"\n\t\t&& hasEmptyTextOutputWithoutOutputTarget(r.task, getSingleResultOutput(r)),\n\t);\n\tconst hasWorkflowFailure = workflowGraphHasStatus(d, [\"failed\"]);\n\tconst hasWorkflowStop = d.results.some((r) => r.stopped && r.progress?.status !== \"running\") || workflowGraphHasStatus(d, [\"stopped\"]);\n\tconst hasWorkflowPause = workflowGraphHasStatus(d, [\"paused\", \"detached\"]);\n\tconst icon = hasRunning\n\t\t? theme.fg(\"warning\", \"running\")\n\t\t: hasEmptyWithoutTarget\n\t\t\t? theme.fg(\"warning\", \"warning\")\n\t\t\t: hasWorkflowFailure\n\t\t\t\t? theme.fg(\"error\", \"failed\")\n\t\t\t\t: hasWorkflowStop\n\t\t\t\t\t? theme.fg(\"warning\", \"stopped\")\n\t\t\t\t\t: hasWorkflowPause\n\t\t\t\t\t\t? theme.fg(\"warning\", \"paused\")\n\t\t\t\t\t: ok === d.results.length\n\t\t\t\t\t\t? theme.fg(\"success\", \"ok\")\n\t\t\t\t\t\t: theme.fg(\"error\", \"failed\");\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\"\n\t\t\t\t\t\t\t? acc.durationMs + prog.durationMs\n\t\t\t\t\t\t\t: 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 = d.context === \"fork\" ? theme.fg(\"warning\", \" [fork]\") : \"\";\n\tconst multiLabel = buildMultiProgressLabel(d, hasRunning);\n\tconst itemTitle = multiLabel.itemTitle;\n\t\n\tconst chainVis = d.chainAgents?.length && !multiLabel.hasParallelInChain\n\t\t? d.chainAgents\n\t\t\t\t.map((agent, i) => {\n\t\t\t\t\tconst result = d.results[i];\n\t\t\t\t\tconst isFailed = result && result.exitCode !== 0 && result.progress?.status !== \"running\";\n\t\t\t\t\tconst isComplete = result && result.exitCode === 0 && result.progress?.status !== \"running\";\n\t\t\t\t\tconst isEmptyWithoutTarget = Boolean(result)\n\t\t\t\t\t\t&& Boolean(isComplete)\n\t\t\t\t\t\t&& hasEmptyTextOutputWithoutOutputTarget(result.task, getSingleResultOutput(result));\n\t\t\t\t\tconst isCurrent = i === (d.currentStepIndex ?? d.results.length);\n\t\t\t\t\tconst stepIcon = isFailed\n\t\t\t\t\t\t? theme.fg(\"error\", \"failed\")\n\t\t\t\t\t\t: isEmptyWithoutTarget\n\t\t\t\t\t\t\t? theme.fg(\"warning\", \"warning\")\n\t\t\t\t\t\t\t: isComplete\n\t\t\t\t\t\t\t\t? theme.fg(\"success\", \"done\")\n\t\t\t\t\t\t\t\t: isCurrent && hasRunning\n\t\t\t\t\t\t\t\t\t? theme.fg(\"warning\", \"running\")\n\t\t\t\t\t\t\t\t\t: theme.fg(\"dim\", \"pending\");\n\t\t\t\t\treturn `${stepIcon} ${agent}`;\n\t\t\t\t})\n\t\t\t\t.join(theme.fg(\"dim\", \" → \"))\n\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(`${icon} ${theme.fg(\"toolTitle\", theme.bold(modeLabel))}${contextBadge} · ${multiLabel.headerLabel}${summaryStr}`),\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 ? multiLabel.groupEndIndex : (useResultsDirectly ? d.results.length : d.chainAgents!.length);\n\tconst chainEntries = buildChainRenderEntries(d, multiLabel);\n\tconst renderEntries = chainEntries ?? Array.from({ length: displayEnd - displayStart }, (_, offset): ChainRenderEntry => {\n\t\tconst i = displayStart + offset;\n\t\tconst r = d.results[i];\n\t\tconst rowNumber = multiLabel.showActiveGroupOnly ? (i - multiLabel.groupStartIndex + 1) : (i + 1);\n\t\treturn { kind: \"result\", resultIndex: i, rowNumber, agentName: useResultsDirectly ? (r?.agent || `step-${rowNumber}`) : (d.chainAgents![i] || r?.agent || `step-${rowNumber}`) };\n\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(new Text(theme.fg(entry.status === \"failed\" ? \"error\" : \"dim\", ` status: ${entry.status}`), 0, 0));\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 = chainEntries ? resultRowLabel(d, multiLabel, i, rowNumber) : `${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 = d.progress?.find((p) => p.index === i) \n\t\t\t|| d.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 === \"running\";\n\t\tconst stepNumber = typeof rProg?.index === \"number\" ? rProg.index + 1 : i + 1;\n\n\t\tconst resultOutput = getSingleResultOutput(r);\n\t\tconst statusIcon = rRunning\n\t\t\t? theme.fg(\"warning\", \"running\")\n\t\t\t: r.exitCode !== 0\n\t\t\t\t? theme.fg(\"error\", \"failed\")\n\t\t\t\t: hasEmptyTextOutputWithoutOutputTarget(r.task, resultOutput)\n\t\t\t\t\t? theme.fg(\"warning\", \"warning\")\n\t\t\t\t\t: theme.fg(\"success\", \"done\");\n\t\tconst stats = rProg ? ` | ${rProg.toolCount} tools, ${formatDuration(rProg.durationMs)}` : \"\";\n\t\tconst modelDisplay = modelThinkingBadge(theme, r.model);\n\t\tconst stepLabel = resultRowLabel(d, multiLabel, i, stepNumber);\n\t\tconst stepHeader = rRunning\n\t\t\t? `${statusIcon} ${stepLabel}: ${theme.bold(theme.fg(\"warning\", r.agent))}${modelDisplay}${stats}`\n\t\t\t: `${statusIcon} ${stepLabel}: ${theme.bold(r.agent)}${modelDisplay}${stats}`;\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\n\t\t\t? r.task\n\t\t\t: `${r.task.slice(0, taskMaxLen)}...`;\n\t\tc.addChild(new Text(fit(theme.fg(\"dim\", ` task: ${taskPreview}`)), 0, 0));\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\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 (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 = expanded || t.args.length <= maxArgsLen\n\t\t\t\t\t\t? t.args\n\t\t\t\t\t\t: `${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 && 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"]}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/tui/render.ts"],"names":[],"mappings":"AACA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnG,OAAO,EAA+B,IAAI,EAAgB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACzG,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,aAAa,EAGlB,KAAK,OAAO,EAMZ,MAAM,oBAAoB,CAAC;AAS5B,KAAK,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;AA6H7C,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;AAKD,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CAChC;AACD,eAAO,MAAM,gBAAgB,EAAE;IAAE,OAAO,EAAE,oBAAoB,EAAE,GAAG,IAAI,CAAA;CAAsB,CAAC;AAwH9F,+EAA2E;AAC3E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAOhE;AAED,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,MAAM,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAItG;AAyBD,0DAA0D;AAC1D,wBAAgB,0BAA0B,CACzC,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,WAAW,GAAG,QAAQ,GAAG,YAAY,CAAC,GAAG,SAAS,EAChF,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAiB,EACvC,MAAM,SAAU,GACd,MAAM,CAUR;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA0B1D;AAw1BD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,SAAiB,EAAE,QAAQ,UAAQ,GAAG,MAAM,EAAE,CAoExH;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,IAAI,CAQ/E;AA2ID;;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,CAgVX","sourcesContent":["// @ts-nocheck\n/**\n * Rendering functions for subagent results\n */\n\nimport type { AgentToolResult } from \"@ashx-j/lunr-agent\";\nimport { getMarkdownTheme, keyText, type ExtensionContext } from \"@ashx-j/lunr\";\nimport { Container, Markdown, Spacer, Text, visibleWidth, type Component } from \"@ashx-j/lunr-tui\";\nimport {\n\ttype AgentProgress,\n\ttype AsyncJobState,\n\ttype AsyncJobStep,\n\ttype AsyncParallelGroupStatus,\n\ttype Details,\n\ttype NestedRunSummary,\n\ttype NestedStepSummary,\n\ttype WorkflowNodeStatus,\n\tMAX_WIDGET_JOBS,\n\tWIDGET_KEY,\n} from \"../shared/types.ts\";\nimport { formatTokens, formatUsage, formatDuration, formatModelSelection, formatToolCall, shortenPath } from \"../shared/formatters.ts\";\nimport { childRowLabel } from \"../shared/types.ts\";\nimport { sanitizeChildDescription } from \"../shared/child-spec.ts\";\nimport { getDisplayItems, getSingleResultOutput } from \"../shared/utils.ts\";\nimport { flatToLogicalStepIndex } from \"../runs/background/parallel-groups.ts\";\nimport { formatNestedAggregate } from \"../runs/shared/nested-render.ts\";\nimport { aggregateStepStatus, formatActivityLabel, formatAgentRunningLabel, formatParallelOutcome } from \"../shared/status-format.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\" });\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 */\nfunction truncLine(text: string, maxWidth: number): string {\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\tconst ansiMatch = text.slice(i).match(/^\\x1b\\[[0-9;]*m/);\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 = i;\n\t\twhile (end < text.length && !text.slice(end).match(/^\\x1b\\[[0-9;]*m/)) {\n\t\t\tend++;\n\t\t}\n\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 = 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 (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<Pick<AgentProgress, \"index\" | \"toolCount\" | \"tokens\" | \"durationMs\" | \"lastActivityAt\" | \"currentToolStartedAt\" | \"turnCount\">>;\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 progressRunningSeed(progress: ProgressSeedSource | undefined): number | undefined {\n\tif (!progress) return undefined;\n\t// lunr: seed only the row identity. Tokens / duration / lastActivityAt\n\t// change on every thinking token and used to advance the glyph faster than the 80ms timer.\n\treturn runningSeed(progress.index);\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\n// lunr: animation sink — while `current` is set, running-glyph lines register a\n// per-frame template so the extension timer can rewrite just those Text nodes\n// (setText + requestRender) instead of rebuilding the whole result tree per frame.\nexport interface SubagentAnimatedLine {\n\ttext: Text;\n\tline: (frame: number) => string;\n}\nexport const subagentAnimSink: { current: SubagentAnimatedLine[] | null } = { current: null };\n\nfunction animatedLine(line: (frame: number) => string, frame: number | undefined, animate: boolean): Text {\n\tconst text = new Text(line(frame ?? 0), 0, 0);\n\tif (animate && frame !== undefined && subagentAnimSink.current) {\n\t\tsubagentAnimSink.current.push({ text, line });\n\t}\n\treturn text;\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\n\nfunction snapshotNowForProgress(progress: Pick<AgentProgress, \"currentToolStartedAt\" | \"durationMs\" | \"lastActivityAt\">): number | undefined {\n\tif (progress.currentToolStartedAt !== undefined && progress.durationMs !== undefined) return 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 = progress.currentToolStartedAt !== undefined && snapshotNow !== undefined\n\t\t? ` | ${formatDuration(Math.max(0, snapshotNow - progress.currentToolStartedAt))}`\n\t\t: \"\";\n\treturn toolArgsPreview\n\t\t? `${progress.currentTool}: ${toolArgsPreview}${durationSuffix}`\n\t\t: `${progress.currentTool}${durationSuffix}`;\n}\n\nfunction buildLiveStatusLine(progress: Pick<AgentProgress, \"activityState\" | \"lastActivityAt\">, snapshotNow?: number): string | undefined {\n\tif (progress.lastActivityAt !== undefined && snapshotNow !== undefined) return 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.filter(Boolean).map((part) => theme.fg(\"dim\", part)).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)) return \"\";\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(theme: Theme, progress: Pick<AgentProgress, \"toolCount\" | \"tokens\" | \"durationMs\"> | undefined, includeDuration = true): 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 text.split(\"\\n\").find((line) => line.trim())?.trim() ?? \"\";\n}\n\n// lunr: first line of the task with whitespace collapsed, capped at ~60 chars,\n// for the simplified collapsed subagent rows.\nfunction taskSummaryText(task: string | undefined, max = 60): string {\n\tconst first = (task ?? \"\").split(\"\\n\")[0]?.replace(/\\s+/g, \" \").trim() ?? \"\";\n\tif (first.length <= max) return first;\n\treturn `${first.slice(0, max - 1)}…`;\n}\n\nconst TASK_CHROME_LINE_RE = /^\\[(?:Read from|Write to):\\s*.*\\]\\s*$/;\n\n/** Drop leading [Read from: …] / [Write to: …] lines from a child task. */\nexport function stripTaskChrome(task: string | undefined): string {\n\tif (!task) return \"\";\n\tconst lines = task.split(\"\\n\");\n\twhile (lines.length > 0 && TASK_CHROME_LINE_RE.test(lines[0]!.trim())) {\n\t\tlines.shift();\n\t}\n\treturn lines.join(\"\\n\").trim();\n}\n\n/** Compact-row lead: sanitized description, never a removed agent type or the model. */\nexport function compactRowLead(result: { description?: string; task?: string; model?: string }): string {\n\tconst fromDescription = sanitizeChildDescription(result.description ?? \"\", 80);\n\tif (fromDescription) return fromDescription;\n\treturn taskSummaryText(stripTaskChrome(result.task), 80);\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\") return `Done · acceptance: ${result.acceptance.status}`;\n\tif (hasEmptyTextOutputWithoutOutputTarget(result.task, output)) return \"Done (no text output)\";\n\treturn \"Done\";\n}\n\nfunction resultGlyph(result: Details[\"results\"][number], output: string, theme: Theme, running = result.progress?.status === \"running\", seed = progressRunningSeed(result.progress ?? result.progressSummary), frame?: number): string {\n\tif (running) {\n\t\tif (frame !== undefined) return theme.fg(\"accent\", runningGlyph((seed ?? 0) + frame));\n\t\treturn theme.fg(\"accent\", runningGlyph(seed));\n\t}\n\tif (result.detached) return theme.fg(\"warning\", \"■\");\n\tif (result.stopped) return theme.fg(\"warning\", \"■\");\n\tif (result.interrupted) return theme.fg(\"warning\", \"■\");\n\tif (result.exitCode !== 0) return theme.fg(\"error\", \"✗\");\n\tif (hasEmptyTextOutputWithoutOutputTarget(result.task, output)) return theme.fg(\"warning\", \"✓\");\n\treturn theme.fg(\"success\", \"✓\");\n}\n\n/** Compact hang row: tool count, tokens, elapsed time. */\nexport function formatCompactStatsHangLine(\n\tprogress: Pick<AgentProgress, \"toolCount\" | \"tokens\" | \"durationMs\"> | undefined,\n\twidth: number,\n\tpaint: (s: string) => string = (s) => s,\n\tprefix = \" ⎿ \",\n): string {\n\tconst toolCount = progress?.toolCount ?? 0;\n\tconst tokens = progress?.tokens ?? 0;\n\tconst durationMs = progress?.durationMs ?? 0;\n\tconst body = [\n\t\tformatToolUseStat(toolCount),\n\t\tformatTokenStat(tokens),\n\t\tdurationMs > 0 ? formatDuration(durationMs) : \"0ms\",\n\t].join(\" · \");\n\treturn truncLine(paint(`${prefix}${body}`), width);\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) facts.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(step: NonNullable<AsyncJobState[\"steps\"]>[number], fallbackIndex?: number): number | undefined {\n\treturn runningSeed(fallbackIndex, step.index);\n}\n\nfunction widgetStepsRunningSeed(steps: Array<NonNullable<AsyncJobState[\"steps\"]>[number]> | undefined): 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(job.currentStep, job.runningSteps, widgetStepsRunningSeed(job.steps));\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): string {\n\tif (job.status === \"running\") return theme.fg(\"accent\", runningGlyph(widgetJobRunningSeed(job)));\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): string {\n\tif (status === \"running\") return theme.fg(\"accent\", runningGlyph(seed));\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) facts.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\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(` ${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\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(...foregroundStyleWidgetStepLines(job, theme, step, \"Step\", span.stepIndex + 1, total, expanded, width));\n\t}\n\treturn lines;\n}\n\nfunction widgetParallelAgentDetails(job: AsyncJobState, theme: Theme, expanded = false, width = getTermWidth()): 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) return 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, step.modelSelection);\n\t\tlines.push(` ${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\tfor (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 8 : 1)) lines.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.split(\"+\").map((part) => part.trim()).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({ stepIndex: node.stepIndex, start, count, isParallel: true, status: node.status, label: node.label, error: node.error });\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({ stepIndex: node.stepIndex, start, count: 1, isParallel: false, status: node.status, label: node.label, error: node.error });\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 isChainParallelGroupActive(details: Pick<Details, \"mode\" | \"chainAgents\" | \"currentStepIndex\" | \"workflowGraph\">): boolean {\n\tif (details.mode !== \"chain\") return false;\n\tif (details.currentStepIndex === undefined) return false;\n\treturn buildChainStepSpans(details).some((span) => span.stepIndex === details.currentStepIndex && span.isParallel);\n}\n\nfunction buildAsyncChainStepSpans(total: number, stepCount: number, parallelGroups: AsyncParallelGroupStatus[] = []): ChainStepSpan[] {\n\tconst spans: ChainStepSpan[] = [];\n\tlet flatIndex = 0;\n\tfor (let stepIndex = 0; stepIndex < total; stepIndex++) {\n\t\tconst group = parallelGroups.find((candidate) => candidate.stepIndex === 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\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\tagentName: details.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(details: Pick<Details, \"mode\" | \"results\" | \"progress\" | \"totalSteps\" | \"currentStepIndex\" | \"chainAgents\" | \"workflowGraph\">, hasRunning: boolean): MultiProgressLabel {\n\tconst stepSpans = buildChainStepSpans(details);\n\tconst hasParallelInChain = details.mode === \"chain\" && stepSpans.some((span) => span.isParallel);\n\tconst activeParallelGroup = isChainParallelGroupActive(details);\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<\"pending\" | \"running\" | \"completed\" | \"failed\" | \"stopped\" | \"detached\">;\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 = details.progress?.find((progress) => progress.index === i)\n\t\t\t\t|| details.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.progress?.status\n\t\t\t\t?? (result.stopped\n\t\t\t\t\t? \"stopped\"\n\t\t\t\t\t: result.interrupted || result.detached\n\t\t\t\t\t\t? \"detached\"\n\t\t\t\t\t\t: result.exitCode === 0\n\t\t\t\t\t\t\t? \"completed\"\n\t\t\t\t\t\t\t: \"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 { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: totalCount, showActiveGroupOnly: false };\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 = details.results.find((result) => result.progress?.index === index);\n\t\t\tif (progressEntry?.status === \"running\") {\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 { headerLabel, itemTitle, totalCount: groupSize, hasParallelInChain, activeParallelGroup, groupStartIndex: groupStart, groupEndIndex: groupEnd, showActiveGroupOnly: true };\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 = details.results.find((result) => result.progress?.index === index) ?? details.results[index];\n\t\t\t\tif (progressEntry?.status === \"running\" || progressEntry?.status === \"pending\" || progressEntry?.status === \"failed\") return 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 = details.currentStepIndex !== undefined ? details.currentStepIndex + 1 : Math.min(totalCount, doneLogical + (hasRunning ? 1 : 0));\n\t\tconst headerLabel = hasRunning ? `step ${currentStep}/${totalCount}` : `step ${doneLogical}/${totalCount}`;\n\t\treturn { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: details.results.length, showActiveGroupOnly: false };\n\t}\n\n\tconst totalCount = details.totalSteps ?? details.results.length;\n\tconst currentStep = details.currentStepIndex !== undefined ? details.currentStepIndex + 1 : 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 { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: details.results.length, showActiveGroupOnly: false };\n}\n\nfunction resultRowLabel(details: Pick<Details, \"mode\" | \"chainAgents\" | \"workflowGraph\">, label: MultiProgressLabel, resultIndex: number, stepNumber: number): string {\n\tif (details.mode === \"chain\" && label.hasParallelInChain) {\n\t\tconst span = buildChainStepSpans(details).find((candidate) => resultIndex >= candidate.start && resultIndex < candidate.start + candidate.count);\n\t\tif (span?.isParallel) return `Agent ${resultIndex - span.start + 1}/${span.count}`;\n\t\tif (span) return `Step ${span.stepIndex + 1}`;\n\t}\n\tif (label.itemTitle === \"Agent\") {\n\t\tconst localStepNumber = label.activeParallelGroup\n\t\t\t? Math.max(1, stepNumber - label.groupStartIndex)\n\t\t\t: 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 = job.currentStep !== undefined\n\t\t\t\t? job.parallelGroups?.find((group) => job.currentStep! >= group.start && job.currentStep! < group.start + group.count)\n\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) parts.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, selection?: import(\"../shared/types.ts\").ModelSelection): string {\n\tconst label = formatModelSelection(selection, model, thinking);\n\treturn label ? theme.fg(\"dim\", ` (${label})`) : \"\";\n}\n\nfunction widgetStepActivityLine(step: NonNullable<AsyncJobState[\"steps\"]>[number], width: number, expanded: boolean, snapshotNow?: number): 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 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(state: NestedRunSummary[\"state\"] | NestedStepSummary[\"status\"], theme: Theme, seed?: number): 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(run.lastUpdate, run.lastActivityAt, run.currentStep, run.toolCount, run.turnCount, run.totalTokens?.total, run.currentToolStartedAt);\n}\n\nfunction nestedActivity(input: Pick<NestedRunSummary | NestedStepSummary, \"activityState\" | \"lastActivityAt\" | \"currentTool\" | \"currentToolStartedAt\" | \"currentPath\" | \"turnCount\" | \"toolCount\">, state: NestedRunSummary[\"state\"] | NestedStepSummary[\"status\"], snapshotNow?: number): string {\n\tconst facts: string[] = [];\n\tif (input.currentTool && input.currentToolStartedAt !== undefined && snapshotNow !== undefined) facts.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(children: NestedRunSummary[] | undefined, theme: Theme, width: number, expanded: boolean, snapshotNow?: number, lineBudget = expanded ? 12 : 1): string[] {\n\tif (!children?.length || lineBudget <= 0) return [];\n\tif (!expanded) {\n\t\tconst aggregate = formatNestedAggregate(children);\n\t\treturn aggregate ? [theme.fg(\"dim\", `↳ ${aggregate}`)] : [];\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\tlines.push(theme.fg(\"dim\", `${prefix}↳ ${nestedStatusGlyph(child.state, theme, nestedRunSeed(child))} ${nestedRunName(child)} · ${child.state} · ${activity}${error}`));\n\t\t\tif (depth === maxDepth) {\n\t\t\t\tconst aggregate = formatNestedAggregate([...(child.steps?.flatMap((step) => step.children ?? []) ?? []), ...(child.children ?? [])]);\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\tlines.push(theme.fg(\"dim\", `${prefix} ↳ ${nestedStatusGlyph(step.status, theme)} ${step.agent} · ${step.status} · ${nestedActivity(step, step.status, snapshotNow ?? child.lastUpdate)}`));\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): string[] {\n\tconst status = widgetStepStatus(step.status, theme);\n\tconst stats = widgetStepStats(theme, step);\n\tconst modelDisplay = modelThinkingBadge(theme, step.model, step.thinking, step.modelSelection);\n\tconst lines = [` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index - 1))} ${itemTitle} ${index}/${total}: ${themeBold(theme, step.agent)} ${theme.fg(\"dim\", \"·\")} ${status}${modelDisplay}${stats ? ` ${theme.fg(\"dim\", \"·\")} ${stats}` : \"\"}`];\n\tconst activity = widgetStepActivityLine(step, width, expanded, job.updatedAt);\n\tif (activity) lines.push(` ${theme.fg(\"dim\", `⎿ ${activity}`)}`);\n\tfor (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt)) {\n\t\tlines.push(` ${nestedLine}`);\n\t}\n\tif (step.status === \"running\") {\n\t\tif (!expanded) lines.push(` ${theme.fg(\"accent\", liveDetailHintText())}`);\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(job: AsyncJobState, theme: Theme, expanded: boolean, width: number): string[] {\n\tif (!job.steps?.length) return [\n\t\t` ${theme.fg(\"dim\", `⎿ ${widgetActivity(job)}`)}`,\n\t\t...formatNestedWidgetLines(job.nestedChildren, theme, width, expanded, job.updatedAt).map((line) => ` ${line}`),\n\t];\n\tif (job.mode === \"chain\" && !job.activeParallelGroup && job.parallelGroups?.length) return 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(...foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index + 1, total, expanded, width));\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(unattached, theme, width, expanded, job.updatedAt)) {\n\t\tlines.push(` ${nestedLine}`);\n\t}\n\treturn lines;\n}\n\nfunction buildSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number, expanded: boolean): string[] {\n\tconst stats = widgetStats(job, theme);\n\tconst count = job.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)} ${themeBold(theme, mode)}${stats ? ` ${theme.fg(\"dim\", \"·\")} ${stats}` : \"\"}`,\n\t\t...foregroundStyleWidgetDetails(job, theme, expanded, width),\n\t].map((line) => truncLine(line, width));\n}\n\nfunction compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number): string[] {\n\tconst fullLines = buildSingleWidgetLines(job, theme, width, false);\n\tif (fullLines.length <= 10 || !job.steps?.length || (job.mode !== \"parallel\" && !job.activeParallelGroup)) return 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, step.modelSelection);\n\t\tlines.push(` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index))} ${itemTitle} ${index + 1}/${total}: ${themeBold(theme, step.agent)} ${theme.fg(\"dim\", \"·\")} ${status}${modelDisplay}${activitySuffix}${stepStats ? ` ${theme.fg(\"dim\", \"·\")} ${stepStats}` : \"\"}`);\n\t\tfor (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt)) lines.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 widgetLayoutSession?.expanded === expanded\n\t\t&& widgetLayoutSession.rows === currentTerminalRows()\n\t\t&& widgetLayoutSession.columns === currentTerminalColumns();\n}\n\nfunction widgetHeaderCounts(jobs: AsyncJobState[]): { running: AsyncJobState[]; queued: AsyncJobState[]; complete: AsyncJobState[]; failed: AsyncJobState[]; paused: AsyncJobState[]; stopped: AsyncJobState[] } {\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): string[] {\n\tconst counts = widgetHeaderCounts(jobs);\n\tconst hasActive = counts.running.length > 0 || counts.queued.length > 0;\n\tconst glyph = counts.running.length > 0 ? runningGlyph(widgetJobsRunningSeed(counts.running)) : hasActive ? \"●\" : \"○\";\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 [truncLine(`${theme.fg(hasActive ? \"accent\" : \"dim\", glyph)} ${theme.fg(hasActive ? \"accent\" : \"dim\", \"subagents\")} (${parts.join(\", \") || `${jobs.length} total`})`, width)];\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): string {\n\tconst counts = widgetHeaderCounts(jobs);\n\tconst hasActive = counts.running.length > 0 || counts.queued.length > 0;\n\tconst glyph = counts.running.length > 0 ? runningGlyph(widgetJobsRunningSeed(counts.running)) : hasActive ? \"●\" : \"○\";\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(`${theme.fg(hasActive ? \"accent\" : \"dim\", glyph)} ${theme.fg(hasActive ? \"accent\" : \"dim\", \"Async agents\")} ${theme.fg(\"dim\", \"·\")} ${theme.fg(\"dim\", parts.join(\", \") || `${jobs.length} total`)}`, width);\n}\n\nfunction progressiveJobLine(job: AsyncJobState, theme: Theme, width: 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\tthemeBold(theme, widgetJobName(job)),\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)} ${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(theme.fg(\"dim\", ` +${hiddenJobs.length} more${parts.length ? ` (${parts.join(\", \")})` : \"\"}`), width);\n}\n\nfunction buildProgressiveWidgetLines(jobs: AsyncJobState[], theme: Theme, width: number, lockedRows: number, previousKeys: string[]): { lines: string[]; visibleJobKeys: string[] } {\n\tconst rowCount = Math.max(1, lockedRows);\n\tif (rowCount === 1) return { lines: buildSingleLineWidgetLines(jobs, theme, width), 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.map((key) => jobsByKey.get(key)).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),\n\t\t...visibleJobs.map((job) => progressiveJobLine(job, theme, width)),\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 fitWidgetLineBudget(lines: string[], theme: Theme, width: number, expanded: boolean): string[] {\n\tconst rows = process.stdout.rows || 30;\n\tconst budget = expanded\n\t\t? Math.max(12, Math.min(24, Math.floor(rows * 0.55)))\n\t\t: 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(jobs: AsyncJobState[], lines: string[], theme: Theme, width: number, expanded: boolean): 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);\n\t}\n\n\tif (hasMatchingSession && widgetLayoutSession?.tier === \"progressive\" && widgetLayoutSession.lockedRows !== undefined) {\n\t\tconst rendered = buildProgressiveWidgetLines(jobs, theme, width, widgetLayoutSession.lockedRows, widgetLayoutSession.visibleJobKeys);\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);\n\t}\n\n\tconst lockedRows = Math.min(availableRows, collapsedWidgetLineBudget(rows));\n\tconst rendered = buildProgressiveWidgetLines(jobs, theme, width, lockedRows, []);\n\twidgetLayoutSession = { expanded, rows, columns, tier: \"progressive\", lockedRows, visibleJobKeys: rendered.visibleJobKeys };\n\treturn rendered.lines;\n}\n\nfunction buildWidgetComponent(jobs: AsyncJobState[], expanded: boolean): (_tui: unknown, theme: Theme) => Component {\n\treturn (_tui, theme) => {\n\t\tconst width = getTermWidth();\n\t\tconst lines = expanded\n\t\t\t? buildWidgetLines(jobs, theme, width, true)\n\t\t\t: jobs.length === 1\n\t\t\t\t? compactSingleWidgetLines(jobs[0]!, theme, width)\n\t\t\t\t: buildWidgetLines(jobs, theme, width, false);\n\t\tconst container = new Container();\n\t\tfor (const line of fitAdaptiveWidgetLines(jobs, lines, theme, width, expanded)) container.addChild(new Text(line, 1, 0));\n\t\treturn container;\n\t};\n}\n\nexport function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = getTermWidth(), expanded = false): string[] {\n\tif (jobs.length === 0) return [];\n\tif (jobs.length === 1) return buildSingleWidgetLines(jobs[0]!, theme, width, expanded);\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 = running.length > 0 ? runningGlyph(widgetJobsRunningSeed(running)) : hasActive ? \"●\" : \"○\";\n\tlines.push(truncLine(`${theme.fg(hasActive ? \"accent\" : \"dim\", headerGlyph)} ${theme.fg(hasActive ? \"accent\" : \"dim\", \"Async agents\")} ${theme.fg(\"dim\", \"· background\")}`, width));\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) { hiddenRunning++; continue; }\n\t\tconst stats = widgetStats(job, theme);\n\t\titems.push([\n\t\t\t`${widgetStatusGlyph(job, theme)} ${themeBold(theme, widgetJobName(job))}${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) { hiddenFinished++; continue; }\n\t\tconst stats = widgetStats(job, theme);\n\t\titems.push([\n\t\t\t`${widgetStatusGlyph(job, theme)} ${themeBold(theme, widgetJobName(job))}${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 = r.progress?.status === \"running\";\n\tconst contextBadge = d.context === \"fork\" ? theme.fg(\"warning\", \" [fork]\") : \"\";\n\t// lunr: simplified collapsed row — glyph · task summary · agent type · runtime/tools/tokens stats.\n\tconst stats = formatProgressStats(theme, progress);\n\tconst c = new Container();\n\tconst width = getTermWidth() - 4;\n\t// lunr: row line is a per-frame template so the running glyph animates in place (see subagentAnimSink).\n\tconst lead = compactRowLead({ description: r.description ?? r.agent, task: r.task });\n\tconst modelBadge = formatModelSelection(\n\t\tr.modelSelection ?? progress?.modelSelection,\n\t\tr.model ?? progress?.model,\n\t\tr.thinking ?? progress?.thinking,\n\t);\n\tconst permissionBadge = r.permissions ? theme.fg(\"dim\", r.permissions) : \"\";\n\tc.addChild(animatedLine((f) => truncLine(`${resultGlyph(r, output, theme, isRunning, undefined, f)} ${themeBold(theme, lead)}${modelBadge ? ` ${theme.fg(\"dim\", \"·\")} ${theme.fg(\"dim\", modelBadge)}` : \"\"}${permissionBadge ? ` ${theme.fg(\"dim\", \"·\")} ${permissionBadge}` : \"\"}${contextBadge}${stats ? ` ${theme.fg(\"dim\", \"·\")} ${stats}` : \"\"}`, width), frame, isRunning));\n\n\tif (isRunning && r.progress) {\n\t\tc.addChild(new Text(formatCompactStatsHangLine(r.progress, width, (s) => theme.fg(\"dim\", s)), 0, 0));\n\t\treturn c;\n\t}\n\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) c.addChild(new Text(truncLine(theme.fg(\"dim\", ` session: ${shortenPath(r.sessionFile)}`), width), 0, 0));\n\treturn c;\n}\n\nfunction renderMultiCompact(d: Details, theme: Theme, frame?: number): Component {\n\tconst hasRunning = d.progress?.some((p) => p.status === \"running\")\n\t\t|| d.results.some((r) => r.progress?.status === \"running\")\n\t\t|| workflowGraphHasStatus(d, [\"running\"]);\n\tconst stopped = d.results.some((r) => r.stopped && r.progress?.status !== \"running\")\n\t\t|| workflowGraphHasStatus(d, [\"stopped\"]);\n\tconst failed = d.results.some((r) => !r.stopped && r.exitCode !== 0 && r.progress?.status !== \"running\")\n\t\t|| workflowGraphHasStatus(d, [\"failed\"]);\n\tconst paused = d.results.some((r) => (r.interrupted || r.detached) && r.progress?.status !== \"running\")\n\t\t|| workflowGraphHasStatus(d, [\"paused\", \"detached\"]);\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 = d.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, [multiLabel.headerLabel, formatProgressStats(theme, totalSummary), formatTotalCostStat(d.totalCost)]);\n\tconst glyph = (f: number | undefined) => hasRunning\n\t\t? theme.fg(\"accent\", runningGlyph(f !== undefined ? (runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex) ?? 0) + f : runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex)))\n\t\t: stopped\n\t\t\t? theme.fg(\"warning\", \"■\")\n\t\t\t: failed\n\t\t\t\t? theme.fg(\"error\", \"✗\")\n\t\t\t: paused\n\t\t\t\t? theme.fg(\"warning\", \"■\")\n\t\t\t\t: theme.fg(\"success\", \"✓\");\n\tconst contextBadge = d.context === \"fork\" ? theme.fg(\"warning\", \" [fork]\") : \"\";\n\tconst c = new Container();\n\tconst width = getTermWidth() - 4;\n\t// lunr: header line is a per-frame template so the running glyph animates in place (see subagentAnimSink).\n\tc.addChild(animatedLine((f) => truncLine(`${glyph(f)} ${theme.fg(\"toolTitle\", theme.bold(d.mode))}${contextBadge}${stats ? ` ${theme.fg(\"dim\", \"·\")} ${stats}` : \"\"}`, width), frame, hasRunning));\n\n\tconst useResultsDirectly = multiLabel.hasParallelInChain || !d.chainAgents?.length;\n\tconst displayStart = multiLabel.showActiveGroupOnly ? multiLabel.groupStartIndex : 0;\n\tconst displayEnd = multiLabel.showActiveGroupOnly ? multiLabel.groupEndIndex : (useResultsDirectly ? d.results.length : d.chainAgents!.length);\n\tconst chainEntries = buildChainRenderEntries(d, multiLabel);\n\tconst renderEntries = chainEntries ?? Array.from({ length: displayEnd - displayStart }, (_, offset): ChainRenderEntry => {\n\t\tconst i = displayStart + offset;\n\t\tconst r = d.results[i];\n\t\tconst fallbackLabel = itemTitle.toLowerCase();\n\t\tconst rowNumber = multiLabel.showActiveGroupOnly ? (i - multiLabel.groupStartIndex + 1) : (i + 1);\n\t\treturn { kind: \"result\", resultIndex: i, rowNumber, agentName: useResultsDirectly ? (r?.agent || `${fallbackLabel}-${rowNumber}`) : (d.chainAgents![i] || r?.agent || `${fallbackLabel}-${rowNumber}`) };\n\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(new Text(truncLine(` ${glyph} ${entry.stepLabel}: ${themeBold(theme, entry.agentName)} ${theme.fg(\"dim\", \"·\")} ${statusLabel}`, width), 0, 0));\n\t\t\tif (entry.error) c.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 = chainEntries ? resultRowLabel(d, multiLabel, i, rowNumber) : `${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 = d.progress?.find((p) => p.index === i) || d.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 && rProg.status === \"running\";\n\t\tconst rPending = rProg && \"status\" in rProg && rProg.status === \"pending\";\n\t\tconst stepStats = formatProgressStats(theme, rProg);\n\t\tconst pendingLabel = rPending ? ` ${theme.fg(\"dim\", \"· pending\")}` : \"\";\n\t\t// lunr: simplified collapsed row — glyph · task summary · agent type · runtime/tools/tokens stats.\n\t\t// lunr: row line is a per-frame template so the running glyph animates in place (see subagentAnimSink).\n\t\tconst lead = compactRowLead({ description: r.description ?? agentName, task: r.task });\n\t\tconst permissionBadge = r.permissions ? theme.fg(\"dim\", r.permissions) : \"\";\n\t\tconst modelBadge = formatModelSelection(\n\t\t\tr.modelSelection ?? (rProg && \"modelSelection\" in rProg ? rProg.modelSelection : undefined),\n\t\t\tr.model ?? (rProg && \"model\" in rProg ? rProg.model : undefined),\n\t\t\tr.thinking ?? (rProg && \"thinking\" in rProg ? rProg.thinking : undefined),\n\t\t);\n\t\tc.addChild(animatedLine((f) => {\n\t\t\tconst glyph = rPending ? theme.fg(\"dim\", \"◦\") : resultGlyph(r, output, theme, rRunning, progressRunningSeed(rProg), f);\n\t\t\treturn truncLine(` ${glyph} ${themeBold(theme, lead)}${modelBadge ? ` ${theme.fg(\"dim\", \"·\")} ${theme.fg(\"dim\", modelBadge)}` : \"\"}${permissionBadge ? ` ${theme.fg(\"dim\", \"·\")} ${permissionBadge}` : \"\"}${stepStats ? ` ${theme.fg(\"dim\", \"·\")} ${stepStats}` : \"\"}${pendingLabel}`, width);\n\t\t}, frame, !!rRunning));\n\t\tif (rRunning && rProg && \"status\" in rProg) {\n\t\t\tc.addChild(new Text(formatCompactStatsHangLine(\n\t\t\t\trProg,\n\t\t\t\twidth,\n\t\t\t\t(s) => theme.fg(\"dim\", s),\n\t\t\t\t\" ⎿ \",\n\t\t\t), 0, 0));\n\t\t} else if (!rPending && (r.exitCode !== 0 || r.interrupted || r.detached || hasEmptyTextOutputWithoutOutputTarget(r.task, output))) {\n\t\t\tc.addChild(new Text(truncLine(theme.fg(r.exitCode !== 0 ? \"error\" : \"dim\", ` ⎿ ${resultStatusLine(r, output)}`), width), 0, 0));\n\t\t}\n\t}\n\treturn c;\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 (!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 = d?.context === \"fork\" ? `${theme.fg(\"warning\", \"[fork]\")} ` : \"\";\n\t\tconst width = getTermWidth() - 4;\n\t\tif (!text.includes(\"\\n\")) return new Text(truncLine(`${contextPrefix}${text}`, width), 0, 0);\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 (!expanded) return renderSingleCompact(d, r, theme, frame);\n\t\tconst isRunning = r.progress?.status === \"running\";\n\t\tconst icon = isRunning\n\t\t\t? theme.fg(\"warning\", \"running\")\n\t\t\t: r.detached\n\t\t\t\t? theme.fg(\"warning\", \"detached\")\n\t\t\t\t: r.exitCode === 0\n\t\t\t\t\t? theme.fg(\"success\", \"ok\")\n\t\t\t\t\t: theme.fg(\"error\", \"failed\");\n\t\tconst contextBadge = d.context === \"fork\" ? theme.fg(\"warning\", \" [fork]\") : \"\";\n\t\tconst output = r.truncation?.text || getSingleResultOutput(r);\n\n\t\tconst progressInfo = isRunning && r.progress\n\t\t\t? ` | ${r.progress.toolCount} tools, ${formatTokens(r.progress.tokens)} tok, ${formatDuration(r.progress.durationMs)}`\n\t\t\t: r.progressSummary\n\t\t\t\t? ` | ${r.progressSummary.toolCount} tools, ${formatTokens(r.progressSummary.tokens)} tok, ${formatDuration(r.progressSummary.durationMs)}`\n\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\tconst modelDisplay = modelThinkingBadge(theme, r.model, r.thinking ?? r.progress?.thinking, r.modelSelection ?? r.progress?.modelSelection);\n\t\tc.addChild(new Text(fit(`${icon} ${theme.fg(\"toolTitle\", theme.bold(r.agent))}${modelDisplay}${contextBadge}${progressInfo}`), 0, 0));\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\n\t\t\t? r.task\n\t\t\t: `${r.task.slice(0, taskMaxLen)}...`;\n\t\tc.addChild(\n\t\t\tnew Text(fit(theme.fg(\"dim\", `Task: ${taskPreview}`)), 0, 0),\n\t\t);\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\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 = expanded || t.args.length <= maxArgsLen\n\t\t\t\t\t\t? t.args\n\t\t\t\t\t\t: `${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 (toolLine || liveStatusLine || r.progress.recentTools?.length || r.progress.recentOutput?.length || r.artifactPaths) {\n\t\t\t\tc.addChild(new Spacer(1));\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 = d.progress?.some((p) => p.status === \"running\") \n\t\t|| d.results.some((r) => r.progress?.status === \"running\")\n\t\t|| workflowGraphHasStatus(d, [\"running\"]);\n\tconst ok = d.results.filter((r) => r.progress?.status === \"completed\" || (r.exitCode === 0 && r.progress?.status !== \"running\")).length;\n\tconst hasEmptyWithoutTarget = d.results.some((r) =>\n\t\tr.exitCode === 0\n\t\t&& r.progress?.status !== \"running\"\n\t\t&& hasEmptyTextOutputWithoutOutputTarget(r.task, getSingleResultOutput(r)),\n\t);\n\tconst hasWorkflowFailure = workflowGraphHasStatus(d, [\"failed\"]);\n\tconst hasWorkflowStop = d.results.some((r) => r.stopped && r.progress?.status !== \"running\") || workflowGraphHasStatus(d, [\"stopped\"]);\n\tconst hasWorkflowPause = workflowGraphHasStatus(d, [\"paused\", \"detached\"]);\n\tconst icon = hasRunning\n\t\t? theme.fg(\"warning\", \"running\")\n\t\t: hasEmptyWithoutTarget\n\t\t\t? theme.fg(\"warning\", \"warning\")\n\t\t\t: hasWorkflowFailure\n\t\t\t\t? theme.fg(\"error\", \"failed\")\n\t\t\t\t: hasWorkflowStop\n\t\t\t\t\t? theme.fg(\"warning\", \"stopped\")\n\t\t\t\t\t: hasWorkflowPause\n\t\t\t\t\t\t? theme.fg(\"warning\", \"paused\")\n\t\t\t\t\t: ok === d.results.length\n\t\t\t\t\t\t? theme.fg(\"success\", \"ok\")\n\t\t\t\t\t\t: theme.fg(\"error\", \"failed\");\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\"\n\t\t\t\t\t\t\t? acc.durationMs + prog.durationMs\n\t\t\t\t\t\t\t: 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 = d.context === \"fork\" ? theme.fg(\"warning\", \" [fork]\") : \"\";\n\tconst multiLabel = buildMultiProgressLabel(d, hasRunning);\n\tconst itemTitle = multiLabel.itemTitle;\n\t\n\tconst chainVis = d.chainAgents?.length && !multiLabel.hasParallelInChain\n\t\t? d.chainAgents\n\t\t\t\t.map((agent, i) => {\n\t\t\t\t\tconst result = d.results[i];\n\t\t\t\t\tconst isFailed = result && result.exitCode !== 0 && result.progress?.status !== \"running\";\n\t\t\t\t\tconst isComplete = result && result.exitCode === 0 && result.progress?.status !== \"running\";\n\t\t\t\t\tconst isEmptyWithoutTarget = Boolean(result)\n\t\t\t\t\t\t&& Boolean(isComplete)\n\t\t\t\t\t\t&& hasEmptyTextOutputWithoutOutputTarget(result.task, getSingleResultOutput(result));\n\t\t\t\t\tconst isCurrent = i === (d.currentStepIndex ?? d.results.length);\n\t\t\t\t\tconst stepIcon = isFailed\n\t\t\t\t\t\t? theme.fg(\"error\", \"failed\")\n\t\t\t\t\t\t: isEmptyWithoutTarget\n\t\t\t\t\t\t\t? theme.fg(\"warning\", \"warning\")\n\t\t\t\t\t\t\t: isComplete\n\t\t\t\t\t\t\t\t? theme.fg(\"success\", \"done\")\n\t\t\t\t\t\t\t\t: isCurrent && hasRunning\n\t\t\t\t\t\t\t\t\t? theme.fg(\"warning\", \"running\")\n\t\t\t\t\t\t\t\t\t: theme.fg(\"dim\", \"pending\");\n\t\t\t\t\treturn `${stepIcon} ${agent}`;\n\t\t\t\t})\n\t\t\t\t.join(theme.fg(\"dim\", \" → \"))\n\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(`${icon} ${theme.fg(\"toolTitle\", theme.bold(modeLabel))}${contextBadge} · ${multiLabel.headerLabel}${summaryStr}`),\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 ? multiLabel.groupEndIndex : (useResultsDirectly ? d.results.length : d.chainAgents!.length);\n\tconst chainEntries = buildChainRenderEntries(d, multiLabel);\n\tconst renderEntries = chainEntries ?? Array.from({ length: displayEnd - displayStart }, (_, offset): ChainRenderEntry => {\n\t\tconst i = displayStart + offset;\n\t\tconst r = d.results[i];\n\t\tconst rowNumber = multiLabel.showActiveGroupOnly ? (i - multiLabel.groupStartIndex + 1) : (i + 1);\n\t\treturn { kind: \"result\", resultIndex: i, rowNumber, agentName: useResultsDirectly ? (r?.agent || `step-${rowNumber}`) : (d.chainAgents![i] || r?.agent || `step-${rowNumber}`) };\n\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(new Text(theme.fg(entry.status === \"failed\" ? \"error\" : \"dim\", ` status: ${entry.status}`), 0, 0));\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 = chainEntries ? resultRowLabel(d, multiLabel, i, rowNumber) : `${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 = d.progress?.find((p) => p.index === i) \n\t\t\t|| d.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 === \"running\";\n\t\tconst stepNumber = typeof rProg?.index === \"number\" ? rProg.index + 1 : i + 1;\n\n\t\tconst resultOutput = getSingleResultOutput(r);\n\t\tconst statusIcon = rRunning\n\t\t\t? theme.fg(\"warning\", \"running\")\n\t\t\t: r.exitCode !== 0\n\t\t\t\t? theme.fg(\"error\", \"failed\")\n\t\t\t\t: hasEmptyTextOutputWithoutOutputTarget(r.task, resultOutput)\n\t\t\t\t\t? theme.fg(\"warning\", \"warning\")\n\t\t\t\t\t: theme.fg(\"success\", \"done\");\n\t\tconst stats = rProg ? ` | ${rProg.toolCount} tools, ${formatDuration(rProg.durationMs)}` : \"\";\n\t\tconst modelDisplay = modelThinkingBadge(theme, r.model, r.thinking ?? (rProg && \"thinking\" in rProg ? rProg.thinking : undefined), r.modelSelection ?? (rProg && \"modelSelection\" in rProg ? rProg.modelSelection : undefined));\n\t\tconst stepLabel = resultRowLabel(d, multiLabel, i, stepNumber);\n\t\tconst stepHeader = rRunning\n\t\t\t? `${statusIcon} ${stepLabel}: ${theme.bold(theme.fg(\"warning\", r.agent))}${modelDisplay}${stats}`\n\t\t\t: `${statusIcon} ${stepLabel}: ${theme.bold(r.agent)}${modelDisplay}${stats}`;\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\n\t\t\t? r.task\n\t\t\t: `${r.task.slice(0, taskMaxLen)}...`;\n\t\tc.addChild(new Text(fit(theme.fg(\"dim\", ` task: ${taskPreview}`)), 0, 0));\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\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 (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 = expanded || t.args.length <= maxArgsLen\n\t\t\t\t\t\t? t.args\n\t\t\t\t\t\t: `${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 && 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"]}
@@ -5,7 +5,7 @@
5
5
  import { getMarkdownTheme, keyText } from "@ashx-j/lunr";
6
6
  import { Container, Markdown, Spacer, Text, visibleWidth } from "@ashx-j/lunr-tui";
7
7
  import { MAX_WIDGET_JOBS, WIDGET_KEY, } from "../shared/types.js";
8
- import { formatTokens, formatUsage, formatDuration, formatModelThinking, formatToolCall, shortenPath } from "../shared/formatters.js";
8
+ import { formatTokens, formatUsage, formatDuration, formatModelSelection, formatToolCall, shortenPath } from "../shared/formatters.js";
9
9
  import { sanitizeChildDescription } from "../shared/child-spec.js";
10
10
  import { getDisplayItems, getSingleResultOutput } from "../shared/utils.js";
11
11
  import { flatToLogicalStepIndex } from "../runs/background/parallel-groups.js";
@@ -491,7 +491,7 @@ function widgetParallelAgentDetails(job, theme, expanded = false, width = getTer
491
491
  const marker = index === job.steps.length - 1 ? "└" : "├";
492
492
  const activity = widgetStepActivity(step, job.updatedAt);
493
493
  const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
494
- const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
494
+ const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking, step.modelSelection);
495
495
  lines.push(` ${theme.fg("dim", `${marker} ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index))} ${itemTitle} ${index + 1}/${total}: ${step.agent} · ${widgetStepStatus(step.status, theme)}${modelDisplay}${activity ? ` · ${activity}` : ""}`)}`);
496
496
  for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 8 : 1))
497
497
  lines.push(` ${nestedLine}`);
@@ -764,8 +764,8 @@ function widgetStepStats(theme, step) {
764
764
  step.durationMs !== undefined ? formatDuration(step.durationMs) : "",
765
765
  ]);
766
766
  }
767
- function modelThinkingBadge(theme, model, thinking) {
768
- const label = formatModelThinking(model, thinking);
767
+ function modelThinkingBadge(theme, model, thinking, selection) {
768
+ const label = formatModelSelection(selection, model, thinking);
769
769
  return label ? theme.fg("dim", ` (${label})`) : "";
770
770
  }
771
771
  function widgetStepActivityLine(step, width, expanded, snapshotNow) {
@@ -883,7 +883,7 @@ function formatNestedWidgetLines(children, theme, width, expanded, snapshotNow,
883
883
  function foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index, total, expanded, width) {
884
884
  const status = widgetStepStatus(step.status, theme);
885
885
  const stats = widgetStepStats(theme, step);
886
- const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
886
+ const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking, step.modelSelection);
887
887
  const lines = [` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index - 1))} ${itemTitle} ${index}/${total}: ${themeBold(theme, step.agent)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`];
888
888
  const activity = widgetStepActivityLine(step, width, expanded, job.updatedAt);
889
889
  if (activity)
@@ -954,7 +954,7 @@ function compactSingleWidgetLines(job, theme, width) {
954
954
  const activity = widgetStepActivityLine(step, width, false, job.updatedAt);
955
955
  const stepStats = widgetStepStats(theme, step);
956
956
  const activitySuffix = activity ? ` ${theme.fg("dim", "·")} ${theme.fg("dim", activity)}` : "";
957
- const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
957
+ const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking, step.modelSelection);
958
958
  lines.push(` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index))} ${itemTitle} ${index + 1}/${total}: ${themeBold(theme, step.agent)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${activitySuffix}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}`);
959
959
  for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt))
960
960
  lines.push(` ${nestedLine}`);
@@ -1295,7 +1295,7 @@ function renderSingleCompact(d, r, theme, frame) {
1295
1295
  const width = getTermWidth() - 4;
1296
1296
  // lunr: row line is a per-frame template so the running glyph animates in place (see subagentAnimSink).
1297
1297
  const lead = compactRowLead({ description: r.description ?? r.agent, task: r.task });
1298
- const modelBadge = formatModelThinking(r.model ?? progress?.model, r.thinking ?? progress?.thinking);
1298
+ const modelBadge = formatModelSelection(r.modelSelection ?? progress?.modelSelection, r.model ?? progress?.model, r.thinking ?? progress?.thinking);
1299
1299
  const permissionBadge = r.permissions ? theme.fg("dim", r.permissions) : "";
1300
1300
  c.addChild(animatedLine((f) => truncLine(`${resultGlyph(r, output, theme, isRunning, undefined, f)} ${themeBold(theme, lead)}${modelBadge ? ` ${theme.fg("dim", "·")} ${theme.fg("dim", modelBadge)}` : ""}${permissionBadge ? ` ${theme.fg("dim", "·")} ${permissionBadge}` : ""}${contextBadge}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`, width), frame, isRunning));
1301
1301
  if (isRunning && r.progress) {
@@ -1394,9 +1394,10 @@ function renderMultiCompact(d, theme, frame) {
1394
1394
  // lunr: row line is a per-frame template so the running glyph animates in place (see subagentAnimSink).
1395
1395
  const lead = compactRowLead({ description: r.description ?? agentName, task: r.task });
1396
1396
  const permissionBadge = r.permissions ? theme.fg("dim", r.permissions) : "";
1397
+ const modelBadge = formatModelSelection(r.modelSelection ?? (rProg && "modelSelection" in rProg ? rProg.modelSelection : undefined), r.model ?? (rProg && "model" in rProg ? rProg.model : undefined), r.thinking ?? (rProg && "thinking" in rProg ? rProg.thinking : undefined));
1397
1398
  c.addChild(animatedLine((f) => {
1398
1399
  const glyph = rPending ? theme.fg("dim", "◦") : resultGlyph(r, output, theme, rRunning, progressRunningSeed(rProg), f);
1399
- return truncLine(` ${glyph} ${themeBold(theme, lead)}${permissionBadge ? ` ${theme.fg("dim", "·")} ${permissionBadge}` : ""}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}${pendingLabel}`, width);
1400
+ return truncLine(` ${glyph} ${themeBold(theme, lead)}${modelBadge ? ` ${theme.fg("dim", "·")} ${theme.fg("dim", modelBadge)}` : ""}${permissionBadge ? ` ${theme.fg("dim", "·")} ${permissionBadge}` : ""}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}${pendingLabel}`, width);
1400
1401
  }, frame, !!rRunning));
1401
1402
  if (rRunning && rProg && "status" in rProg) {
1402
1403
  c.addChild(new Text(formatCompactStatsHangLine(rProg, width, (s) => theme.fg("dim", s), " ⎿ "), 0, 0));
@@ -1450,7 +1451,8 @@ export function renderSubagentResult(result, options, theme, frame) {
1450
1451
  const fit = (text) => expanded ? text : truncLine(text, w);
1451
1452
  const toolCallLines = getToolCallLines(r, expanded);
1452
1453
  const c = new Container();
1453
- c.addChild(new Text(fit(`${icon} ${theme.fg("toolTitle", theme.bold(r.agent))}${contextBadge}${progressInfo}`), 0, 0));
1454
+ const modelDisplay = modelThinkingBadge(theme, r.model, r.thinking ?? r.progress?.thinking, r.modelSelection ?? r.progress?.modelSelection);
1455
+ c.addChild(new Text(fit(`${icon} ${theme.fg("toolTitle", theme.bold(r.agent))}${modelDisplay}${contextBadge}${progressInfo}`), 0, 0));
1454
1456
  c.addChild(new Spacer(1));
1455
1457
  const taskMaxLen = Math.max(20, w - 8);
1456
1458
  const taskPreview = expanded || r.task.length <= taskMaxLen
@@ -1642,7 +1644,7 @@ export function renderSubagentResult(result, options, theme, frame) {
1642
1644
  ? theme.fg("warning", "warning")
1643
1645
  : theme.fg("success", "done");
1644
1646
  const stats = rProg ? ` | ${rProg.toolCount} tools, ${formatDuration(rProg.durationMs)}` : "";
1645
- const modelDisplay = modelThinkingBadge(theme, r.model);
1647
+ const modelDisplay = modelThinkingBadge(theme, r.model, r.thinking ?? (rProg && "thinking" in rProg ? rProg.thinking : undefined), r.modelSelection ?? (rProg && "modelSelection" in rProg ? rProg.modelSelection : undefined));
1646
1648
  const stepLabel = resultRowLabel(d, multiLabel, i, stepNumber);
1647
1649
  const stepHeader = rRunning
1648
1650
  ? `${statusIcon} ${stepLabel}: ${theme.bold(theme.fg("warning", r.agent))}${modelDisplay}${stats}`