@ashx-j/lunr 0.2.11 → 0.2.13

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 (342) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +211 -236
  3. package/dist/builtin-extensions/ashxj-tui.d.ts +3 -0
  4. package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
  5. package/dist/builtin-extensions/ashxj-tui.js +28 -11
  6. package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
  7. package/dist/builtin-extensions/index.d.ts.map +1 -1
  8. package/dist/builtin-extensions/index.js +0 -2
  9. package/dist/builtin-extensions/index.js.map +1 -1
  10. package/dist/builtin-extensions/lunr-cron.d.ts +1 -1
  11. package/dist/builtin-extensions/lunr-cron.d.ts.map +1 -1
  12. package/dist/builtin-extensions/lunr-cron.js +1 -1
  13. package/dist/builtin-extensions/lunr-cron.js.map +1 -1
  14. package/dist/builtin-extensions/pi-subagents/src/extension/chain-validation.d.ts.map +1 -1
  15. package/dist/builtin-extensions/pi-subagents/src/extension/chain-validation.js +3 -3
  16. package/dist/builtin-extensions/pi-subagents/src/extension/chain-validation.js.map +1 -1
  17. package/dist/builtin-extensions/pi-subagents/src/extension/doctor.d.ts +0 -2
  18. package/dist/builtin-extensions/pi-subagents/src/extension/doctor.d.ts.map +1 -1
  19. package/dist/builtin-extensions/pi-subagents/src/extension/doctor.js +1 -23
  20. package/dist/builtin-extensions/pi-subagents/src/extension/doctor.js.map +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.d.ts.map +1 -1
  22. package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js +6 -7
  23. package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js.map +1 -1
  24. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +1 -1
  25. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  26. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +7 -14
  27. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  28. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts +12 -9
  29. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
  30. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +38 -34
  31. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
  32. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +3 -3
  33. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
  34. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +32 -47
  35. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
  36. package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.d.ts +2 -2
  37. package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.d.ts.map +1 -1
  38. package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.js.map +1 -1
  39. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts +7 -10
  40. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
  41. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +90 -109
  42. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
  43. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts +3 -2
  44. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts.map +1 -1
  45. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js +37 -50
  46. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js.map +1 -1
  47. package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.d.ts.map +1 -1
  48. package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.js +1 -1
  49. package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.js.map +1 -1
  50. package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.d.ts +4 -0
  51. package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.d.ts.map +1 -1
  52. package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.js +5 -5
  53. package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.js.map +1 -1
  54. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
  55. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +31 -13
  56. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
  57. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.d.ts +2 -2
  58. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.d.ts.map +1 -1
  59. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.js.map +1 -1
  60. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts +4 -3
  61. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
  62. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +48 -20
  63. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
  64. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts +2 -2
  65. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  66. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +90 -64
  67. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  68. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts +6 -8
  69. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  70. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +262 -161
  71. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  72. package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts +2 -1
  73. package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts.map +1 -1
  74. package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js +24 -33
  75. package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js.map +1 -1
  76. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts +25 -0
  77. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts.map +1 -0
  78. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js +51 -0
  79. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js.map +1 -0
  80. package/dist/builtin-extensions/pi-subagents/src/runs/shared/dynamic-fanout.d.ts.map +1 -1
  81. package/dist/builtin-extensions/pi-subagents/src/runs/shared/dynamic-fanout.js +20 -3
  82. package/dist/builtin-extensions/pi-subagents/src/runs/shared/dynamic-fanout.js.map +1 -1
  83. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +4 -1
  84. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
  85. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
  86. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts +5 -0
  87. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts.map +1 -1
  88. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js +9 -3
  89. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js.map +1 -1
  90. package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
  91. package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js +2 -2
  92. package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
  93. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts +2 -2
  94. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts.map +1 -1
  95. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js +20 -17
  96. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js.map +1 -1
  97. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts +70 -0
  98. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts.map +1 -0
  99. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js +153 -0
  100. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -0
  101. package/dist/builtin-extensions/pi-subagents/src/shared/settings.d.ts +13 -8
  102. package/dist/builtin-extensions/pi-subagents/src/shared/settings.d.ts.map +1 -1
  103. package/dist/builtin-extensions/pi-subagents/src/shared/settings.js +20 -51
  104. package/dist/builtin-extensions/pi-subagents/src/shared/settings.js.map +1 -1
  105. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +80 -20
  106. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  107. package/dist/builtin-extensions/pi-subagents/src/shared/types.js +10 -2
  108. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  109. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts +2 -2
  110. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
  111. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +20 -32
  112. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
  113. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts +2 -1
  114. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
  115. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +23 -449
  116. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
  117. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +2 -1
  118. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  119. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +13 -11
  120. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  121. package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.d.ts +2 -1
  122. package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.d.ts.map +1 -1
  123. package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.js +18 -17
  124. package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.js.map +1 -1
  125. package/dist/builtin-extensions/simple-pi-memory.d.ts +2 -2
  126. package/dist/builtin-extensions/simple-pi-memory.d.ts.map +1 -1
  127. package/dist/builtin-extensions/simple-pi-memory.js +41 -17
  128. package/dist/builtin-extensions/simple-pi-memory.js.map +1 -1
  129. package/dist/catalog/models.json +1 -1
  130. package/dist/catalog/providers/amazon-bedrock.json +1 -1
  131. package/dist/catalog/providers/azure-openai-responses.json +1 -1
  132. package/dist/catalog/providers/cloudflare-ai-gateway.json +1 -1
  133. package/dist/catalog/providers/cloudflare-workers-ai.json +1 -1
  134. package/dist/catalog/providers/fireworks.json +1 -1
  135. package/dist/catalog/providers/github-copilot.json +1 -1
  136. package/dist/catalog/providers/huggingface.json +1 -1
  137. package/dist/catalog/providers/minimax-cn.json +1 -1
  138. package/dist/catalog/providers/minimax.json +1 -1
  139. package/dist/catalog/providers/mistral.json +1 -1
  140. package/dist/catalog/providers/nvidia.json +1 -1
  141. package/dist/catalog/providers/openai.json +1 -1
  142. package/dist/catalog/providers/opencode-go.json +1 -1
  143. package/dist/catalog/providers/opencode.json +1 -1
  144. package/dist/catalog/providers/openrouter.json +1 -1
  145. package/dist/catalog/providers/qwen-cloud-cn.json +1 -1
  146. package/dist/catalog/providers/qwen-cloud.json +1 -1
  147. package/dist/catalog/providers/qwen-token-plan-cn.json +1 -1
  148. package/dist/catalog/providers/qwen-token-plan.json +1 -1
  149. package/dist/catalog/providers/together.json +1 -1
  150. package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
  151. package/dist/catalog/providers/zai-coding-cn.json +1 -1
  152. package/dist/catalog/providers/zai.json +1 -1
  153. package/dist/catalog/publication.json +3 -3
  154. package/dist/core/agent-session.d.ts +7 -1
  155. package/dist/core/agent-session.d.ts.map +1 -1
  156. package/dist/core/agent-session.js +34 -4
  157. package/dist/core/agent-session.js.map +1 -1
  158. package/dist/core/context-breakdown.d.ts +3 -1
  159. package/dist/core/context-breakdown.d.ts.map +1 -1
  160. package/dist/core/context-breakdown.js +5 -2
  161. package/dist/core/context-breakdown.js.map +1 -1
  162. package/dist/core/customize.d.ts +2 -0
  163. package/dist/core/customize.d.ts.map +1 -1
  164. package/dist/core/customize.js +6 -0
  165. package/dist/core/customize.js.map +1 -1
  166. package/dist/core/memory-cap.d.ts +4 -8
  167. package/dist/core/memory-cap.d.ts.map +1 -1
  168. package/dist/core/memory-cap.js +8 -8
  169. package/dist/core/memory-cap.js.map +1 -1
  170. package/dist/core/permissions.d.ts +2 -0
  171. package/dist/core/permissions.d.ts.map +1 -1
  172. package/dist/core/permissions.js +30 -14
  173. package/dist/core/permissions.js.map +1 -1
  174. package/dist/core/plan-mode.d.ts.map +1 -1
  175. package/dist/core/plan-mode.js +1 -9
  176. package/dist/core/plan-mode.js.map +1 -1
  177. package/dist/core/rollback.d.ts.map +1 -1
  178. package/dist/core/rollback.js +1 -1
  179. package/dist/core/rollback.js.map +1 -1
  180. package/dist/core/settings-manager.d.ts +16 -5
  181. package/dist/core/settings-manager.d.ts.map +1 -1
  182. package/dist/core/settings-manager.js +37 -10
  183. package/dist/core/settings-manager.js.map +1 -1
  184. package/dist/core/slash-commands.d.ts.map +1 -1
  185. package/dist/core/slash-commands.js +1 -5
  186. package/dist/core/slash-commands.js.map +1 -1
  187. package/dist/core/subagent-permission-inherit.d.ts +34 -22
  188. package/dist/core/subagent-permission-inherit.d.ts.map +1 -1
  189. package/dist/core/subagent-permission-inherit.js +37 -80
  190. package/dist/core/subagent-permission-inherit.js.map +1 -1
  191. package/dist/core/swarm.d.ts.map +1 -1
  192. package/dist/core/swarm.js +13 -7
  193. package/dist/core/swarm.js.map +1 -1
  194. package/dist/core/system-prompt.d.ts +6 -4
  195. package/dist/core/system-prompt.d.ts.map +1 -1
  196. package/dist/core/system-prompt.js +38 -48
  197. package/dist/core/system-prompt.js.map +1 -1
  198. package/dist/core/usage-adapters/openai-codex.d.ts +1 -1
  199. package/dist/core/usage-adapters/openai-codex.d.ts.map +1 -1
  200. package/dist/core/usage-adapters/openai-codex.js +22 -17
  201. package/dist/core/usage-adapters/openai-codex.js.map +1 -1
  202. package/dist/core/usage-service.d.ts +10 -1
  203. package/dist/core/usage-service.d.ts.map +1 -1
  204. package/dist/core/usage-service.js +17 -6
  205. package/dist/core/usage-service.js.map +1 -1
  206. package/dist/gateway/agent-bridge.d.ts.map +1 -1
  207. package/dist/gateway/agent-bridge.js +1 -4
  208. package/dist/gateway/agent-bridge.js.map +1 -1
  209. package/dist/gateway/cron.d.ts.map +1 -1
  210. package/dist/gateway/cron.js +1 -4
  211. package/dist/gateway/cron.js.map +1 -1
  212. package/dist/index.d.ts +1 -1
  213. package/dist/index.d.ts.map +1 -1
  214. package/dist/index.js.map +1 -1
  215. package/dist/main.d.ts.map +1 -1
  216. package/dist/main.js +0 -5
  217. package/dist/main.js.map +1 -1
  218. package/dist/migrations.d.ts +2 -0
  219. package/dist/migrations.d.ts.map +1 -1
  220. package/dist/migrations.js +21 -0
  221. package/dist/migrations.js.map +1 -1
  222. package/dist/modes/interactive/components/settings-selector.d.ts +7 -4
  223. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  224. package/dist/modes/interactive/components/settings-selector.js +122 -162
  225. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  226. package/dist/modes/interactive/components/usage-view.d.ts +1 -1
  227. package/dist/modes/interactive/components/usage-view.d.ts.map +1 -1
  228. package/dist/modes/interactive/components/usage-view.js +7 -4
  229. package/dist/modes/interactive/components/usage-view.js.map +1 -1
  230. package/dist/modes/interactive/interactive-mode.d.ts +1 -2
  231. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  232. package/dist/modes/interactive/interactive-mode.js +55 -96
  233. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  234. package/dist/modes/interactive/skill-tag-autocomplete.d.ts +13 -0
  235. package/dist/modes/interactive/skill-tag-autocomplete.d.ts.map +1 -0
  236. package/dist/modes/interactive/skill-tag-autocomplete.js +72 -0
  237. package/dist/modes/interactive/skill-tag-autocomplete.js.map +1 -0
  238. package/docs/compaction.md +23 -23
  239. package/docs/containerization.md +24 -24
  240. package/docs/custom-provider.md +18 -18
  241. package/docs/development.md +16 -15
  242. package/docs/docs.json +6 -2
  243. package/docs/extensions.md +82 -80
  244. package/docs/features.md +102 -0
  245. package/docs/index.md +25 -19
  246. package/docs/json.md +7 -7
  247. package/docs/keybindings.md +4 -4
  248. package/docs/models.md +2 -2
  249. package/docs/packages.md +38 -42
  250. package/docs/prompt-templates.md +4 -4
  251. package/docs/providers.md +22 -10
  252. package/docs/quickstart.md +81 -46
  253. package/docs/rpc.md +7 -13
  254. package/docs/sdk.md +43 -41
  255. package/docs/security.md +18 -18
  256. package/docs/session-format.md +9 -9
  257. package/docs/sessions.md +13 -12
  258. package/docs/settings.md +63 -24
  259. package/docs/shell-aliases.md +2 -2
  260. package/docs/skills.md +21 -11
  261. package/docs/terminal-setup.md +5 -5
  262. package/docs/termux.md +7 -7
  263. package/docs/themes.md +12 -14
  264. package/docs/tmux.md +4 -4
  265. package/docs/tui.md +23 -23
  266. package/docs/usage.md +112 -87
  267. package/docs/windows.md +10 -2
  268. package/examples/README.md +3 -1
  269. package/examples/extensions/README.md +6 -6
  270. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  271. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  272. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  273. package/examples/extensions/doom-overlay/README.md +2 -2
  274. package/examples/extensions/gondolin/package-lock.json +2 -2
  275. package/examples/extensions/gondolin/package.json +1 -1
  276. package/examples/extensions/plan-mode/README.md +3 -1
  277. package/examples/extensions/sandbox/package-lock.json +2 -2
  278. package/examples/extensions/sandbox/package.json +1 -1
  279. package/examples/extensions/subagent/README.md +13 -11
  280. package/examples/extensions/todo.ts +2 -1
  281. package/examples/extensions/with-deps/package-lock.json +2 -2
  282. package/examples/extensions/with-deps/package.json +1 -1
  283. package/examples/sdk/README.md +9 -5
  284. package/package.json +6 -7
  285. package/dist/builtin-extensions/lunr-behavior.d.ts +0 -44
  286. package/dist/builtin-extensions/lunr-behavior.d.ts.map +0 -1
  287. package/dist/builtin-extensions/lunr-behavior.js +0 -213
  288. package/dist/builtin-extensions/lunr-behavior.js.map +0 -1
  289. package/dist/builtin-extensions/pi-subagents/agents/context-builder.md +0 -46
  290. package/dist/builtin-extensions/pi-subagents/agents/deep-researcher.md +0 -36
  291. package/dist/builtin-extensions/pi-subagents/agents/delegate.md +0 -12
  292. package/dist/builtin-extensions/pi-subagents/agents/oracle.md +0 -73
  293. package/dist/builtin-extensions/pi-subagents/agents/planner.md +0 -55
  294. package/dist/builtin-extensions/pi-subagents/agents/research-writer.md +0 -37
  295. package/dist/builtin-extensions/pi-subagents/agents/researcher.md +0 -52
  296. package/dist/builtin-extensions/pi-subagents/agents/reviewer.md +0 -79
  297. package/dist/builtin-extensions/pi-subagents/agents/scout.md +0 -50
  298. package/dist/builtin-extensions/pi-subagents/agents/worker.md +0 -55
  299. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.d.ts +0 -20
  300. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.d.ts.map +0 -1
  301. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.js +0 -1273
  302. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.js.map +0 -1
  303. package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.d.ts +0 -49
  304. package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.d.ts.map +0 -1
  305. package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.js +0 -252
  306. package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.js.map +0 -1
  307. package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.d.ts +0 -3
  308. package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.d.ts.map +0 -1
  309. package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.js +0 -6
  310. package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.js.map +0 -1
  311. package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.d.ts +0 -3
  312. package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.d.ts.map +0 -1
  313. package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.js +0 -23
  314. package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.js.map +0 -1
  315. package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.d.ts +0 -8
  316. package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.d.ts.map +0 -1
  317. package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.js +0 -143
  318. package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.js.map +0 -1
  319. package/dist/builtin-extensions/pi-subagents/src/agents/agents.d.ts +0 -176
  320. package/dist/builtin-extensions/pi-subagents/src/agents/agents.d.ts.map +0 -1
  321. package/dist/builtin-extensions/pi-subagents/src/agents/agents.js +0 -1326
  322. package/dist/builtin-extensions/pi-subagents/src/agents/agents.js.map +0 -1
  323. package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.d.ts +0 -7
  324. package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.d.ts.map +0 -1
  325. package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.js +0 -307
  326. package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.js.map +0 -1
  327. package/dist/builtin-extensions/pi-subagents/src/agents/identity.d.ts +0 -8
  328. package/dist/builtin-extensions/pi-subagents/src/agents/identity.d.ts.map +0 -1
  329. package/dist/builtin-extensions/pi-subagents/src/agents/identity.js +0 -30
  330. package/dist/builtin-extensions/pi-subagents/src/agents/identity.js.map +0 -1
  331. package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.d.ts +0 -35
  332. package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.d.ts.map +0 -1
  333. package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.js +0 -151
  334. package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.js.map +0 -1
  335. package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.d.ts +0 -131
  336. package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.d.ts.map +0 -1
  337. package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.js +0 -491
  338. package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.js.map +0 -1
  339. package/dist/core/behavior-preset.d.ts +0 -60
  340. package/dist/core/behavior-preset.d.ts.map +0 -1
  341. package/dist/core/behavior-preset.js +0 -257
  342. package/dist/core/behavior-preset.js.map +0 -1
@@ -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;AAO5B,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,4FAA4F;AAC5F,wBAAgB,cAAc,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAMhF;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;AA+HD;;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 { 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: model name only (no thinking level), else the chrome-stripped task. */\nexport function compactRowLead(result: { task?: string; model?: string }): string {\n\tconst badge = formatModelThinking(result.model);\n\tconst cut = badge.indexOf(\" · thinking \");\n\tconst modelOnly = cut === -1 ? badge : badge.slice(0, cut);\n\tif (modelOnly) return modelOnly;\n\treturn taskSummaryText(stripTaskChrome(result.task));\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({ task: r.task, model: r.model ?? progress?.model });\n\tc.addChild(animatedLine((f) => truncLine(`${resultGlyph(r, output, theme, isRunning, undefined, f)} ${lead} ${theme.fg(\"dim\", \"·\")} ${themeBold(theme, r.agent)}${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({ task: r.task, model: r.model ?? (rProg && \"model\" in rProg ? rProg.model : undefined) });\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} ${lead} ${theme.fg(\"dim\", \"·\")} ${themeBold(theme, agentName)}${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;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"]}
@@ -6,6 +6,7 @@ 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
8
  import { formatTokens, formatUsage, formatDuration, formatModelThinking, formatToolCall, shortenPath } from "../shared/formatters.js";
9
+ import { sanitizeChildDescription } from "../shared/child-spec.js";
9
10
  import { getDisplayItems, getSingleResultOutput } from "../shared/utils.js";
10
11
  import { flatToLogicalStepIndex } from "../runs/background/parallel-groups.js";
11
12
  import { formatNestedAggregate } from "../runs/shared/nested-render.js";
@@ -249,14 +250,12 @@ export function stripTaskChrome(task) {
249
250
  }
250
251
  return lines.join("\n").trim();
251
252
  }
252
- /** Compact-row lead: model name only (no thinking level), else the chrome-stripped task. */
253
+ /** Compact-row lead: sanitized description, never a removed agent type or the model. */
253
254
  export function compactRowLead(result) {
254
- const badge = formatModelThinking(result.model);
255
- const cut = badge.indexOf(" · thinking ");
256
- const modelOnly = cut === -1 ? badge : badge.slice(0, cut);
257
- if (modelOnly)
258
- return modelOnly;
259
- return taskSummaryText(stripTaskChrome(result.task));
255
+ const fromDescription = sanitizeChildDescription(result.description ?? "", 80);
256
+ if (fromDescription)
257
+ return fromDescription;
258
+ return taskSummaryText(stripTaskChrome(result.task), 80);
260
259
  }
261
260
  function resultStatusLine(result, output) {
262
261
  if (result.detached)
@@ -1295,8 +1294,10 @@ function renderSingleCompact(d, r, theme, frame) {
1295
1294
  const c = new Container();
1296
1295
  const width = getTermWidth() - 4;
1297
1296
  // lunr: row line is a per-frame template so the running glyph animates in place (see subagentAnimSink).
1298
- const lead = compactRowLead({ task: r.task, model: r.model ?? progress?.model });
1299
- c.addChild(animatedLine((f) => truncLine(`${resultGlyph(r, output, theme, isRunning, undefined, f)} ${lead} ${theme.fg("dim", "·")} ${themeBold(theme, r.agent)}${contextBadge}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`, width), frame, isRunning));
1297
+ const lead = compactRowLead({ description: r.description ?? r.agent, task: r.task });
1298
+ const modelBadge = formatModelThinking(r.model ?? progress?.model, r.thinking ?? progress?.thinking);
1299
+ const permissionBadge = r.permissions ? theme.fg("dim", r.permissions) : "";
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));
1300
1301
  if (isRunning && r.progress) {
1301
1302
  c.addChild(new Text(formatCompactStatsHangLine(r.progress, width, (s) => theme.fg("dim", s)), 0, 0));
1302
1303
  return c;
@@ -1391,10 +1392,11 @@ function renderMultiCompact(d, theme, frame) {
1391
1392
  const pendingLabel = rPending ? ` ${theme.fg("dim", "· pending")}` : "";
1392
1393
  // lunr: simplified collapsed row — glyph · task summary · agent type · runtime/tools/tokens stats.
1393
1394
  // lunr: row line is a per-frame template so the running glyph animates in place (see subagentAnimSink).
1394
- const lead = compactRowLead({ task: r.task, model: r.model ?? (rProg && "model" in rProg ? rProg.model : undefined) });
1395
+ const lead = compactRowLead({ description: r.description ?? agentName, task: r.task });
1396
+ const permissionBadge = r.permissions ? theme.fg("dim", r.permissions) : "";
1395
1397
  c.addChild(animatedLine((f) => {
1396
1398
  const glyph = rPending ? theme.fg("dim", "◦") : resultGlyph(r, output, theme, rRunning, progressRunningSeed(rProg), f);
1397
- return truncLine(` ${glyph} ${lead} ${theme.fg("dim", "·")} ${themeBold(theme, agentName)}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}${pendingLabel}`, width);
1399
+ return truncLine(` ${glyph} ${themeBold(theme, lead)}${permissionBadge ? ` ${theme.fg("dim", "·")} ${permissionBadge}` : ""}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}${pendingLabel}`, width);
1398
1400
  }, frame, !!rRunning));
1399
1401
  if (rRunning && rProg && "status" in rProg) {
1400
1402
  c.addChild(new Text(formatCompactStatsHangLine(rProg, width, (s) => theme.fg("dim", s), " ⎿ "), 0, 0));