@duckmind/dm-windows-x64 0.61.4 → 0.61.9

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 (279) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +211 -67
  3. package/extensions/dm-subagents/agents/claude-code-writer.md +15 -0
  4. package/extensions/dm-subagents/agents/claude-code.md +15 -0
  5. package/extensions/dm-subagents/agents/codex-exec-writer.md +15 -0
  6. package/extensions/dm-subagents/agents/codex-exec.md +15 -0
  7. package/extensions/dm-subagents/agents/cursor-agent-writer.md +14 -0
  8. package/extensions/dm-subagents/agents/cursor-agent.md +14 -0
  9. package/extensions/dm-subagents/agents/delegate.md +3 -2
  10. package/extensions/dm-subagents/agents/oracle.md +10 -5
  11. package/extensions/dm-subagents/agents/researcher.md +2 -2
  12. package/extensions/dm-subagents/agents/reviewer.md +17 -7
  13. package/extensions/dm-subagents/agents/scout.md +5 -5
  14. package/extensions/dm-subagents/agents/worker.md +6 -2
  15. package/extensions/dm-subagents/async-retention-discovery-worker.mjs +167 -0
  16. package/extensions/dm-subagents/index.js +4 -0
  17. package/extensions/dm-subagents/inspector-runner.mjs +10 -0
  18. package/extensions/dm-subagents/install.mjs +3 -2
  19. package/extensions/dm-subagents/package.json +2 -2
  20. package/extensions/dm-subagents/prompts/council.md +60 -0
  21. package/extensions/dm-subagents/prompts/parallel-review.md +5 -1
  22. package/extensions/dm-subagents/prompts/review-loop.md +13 -7
  23. package/extensions/dm-subagents/skills/council-mode/SKILL.md +59 -0
  24. package/extensions/dm-subagents/skills/council-mode/references/pass-contracts.md +150 -0
  25. package/extensions/dm-subagents/skills/dm-subagents/SKILL.md +96 -911
  26. package/extensions/dm-subagents/skills/dm-subagents/references/constraints-and-recipes.md +70 -0
  27. package/extensions/dm-subagents/skills/dm-subagents/references/execution-controls.md +539 -0
  28. package/extensions/dm-subagents/skills/dm-subagents/references/management-authoring-rpc.md +161 -0
  29. package/extensions/dm-subagents/skills/dm-subagents/references/multi-lane-orchestration.md +51 -0
  30. package/extensions/dm-subagents/skills/dm-subagents/references/prompting-and-roles.md +295 -0
  31. package/extensions/dm-subagents/skills/dm-subagents/references/review-and-validation.md +73 -0
  32. package/extensions/dm-subagents/src/agents/agent-management.js +716 -484
  33. package/extensions/dm-subagents/src/agents/agent-refinements.js +563 -0
  34. package/extensions/dm-subagents/src/agents/agent-serializer.js +70 -5
  35. package/extensions/dm-subagents/src/agents/agents.js +1447 -299
  36. package/extensions/dm-subagents/src/agents/builtin-names.js +15 -0
  37. package/extensions/dm-subagents/src/agents/chain-serializer.js +12 -7
  38. package/extensions/dm-subagents/src/agents/frontmatter.js +64 -14
  39. package/extensions/dm-subagents/src/agents/identity.js +1 -1
  40. package/extensions/dm-subagents/src/agents/proactive-skills.js +14 -11
  41. package/extensions/dm-subagents/src/agents/runtime-agent-events.js +49 -0
  42. package/extensions/dm-subagents/src/agents/runtime-agent-registry.js +412 -0
  43. package/extensions/dm-subagents/src/agents/skills.js +52 -35
  44. package/extensions/dm-subagents/src/api/agents.js +6 -0
  45. package/extensions/dm-subagents/src/api/background-work.js +151 -0
  46. package/extensions/dm-subagents/src/api/capability-ceiling.js +12 -0
  47. package/extensions/dm-subagents/src/api/control-channel.js +3 -0
  48. package/extensions/dm-subagents/src/api/delegation.js +5 -0
  49. package/extensions/dm-subagents/src/api/dm-args.js +3 -0
  50. package/extensions/dm-subagents/src/api/external-job-provider.js +137 -0
  51. package/extensions/dm-subagents/src/api/external-runs.js +233 -0
  52. package/extensions/dm-subagents/src/api/intercom-bridge.js +3 -0
  53. package/extensions/dm-subagents/src/api/preflight.js +322 -0
  54. package/extensions/dm-subagents/src/api/project-panes.js +11 -0
  55. package/extensions/dm-subagents/src/api/shared-types.js +4 -0
  56. package/extensions/dm-subagents/src/extension/config.js +175 -0
  57. package/extensions/dm-subagents/src/extension/control-notices.js +4 -43
  58. package/extensions/dm-subagents/src/extension/doctor.js +71 -17
  59. package/extensions/dm-subagents/src/extension/fanout-child.js +49 -32
  60. package/extensions/dm-subagents/src/extension/index.js +711 -265
  61. package/extensions/dm-subagents/src/extension/public-execution.js +114 -0
  62. package/extensions/dm-subagents/src/extension/rpc.js +427 -22
  63. package/extensions/dm-subagents/src/extension/schemas.js +158 -65
  64. package/extensions/dm-subagents/src/extension/steering-notices.js +23 -0
  65. package/extensions/dm-subagents/src/extension/subagent-guide.js +31 -0
  66. package/extensions/dm-subagents/src/extension/tool-description.js +92 -74
  67. package/extensions/dm-subagents/src/extension/tool-result.js +7 -0
  68. package/extensions/dm-subagents/src/inspectors/herdr/actions.js +218 -0
  69. package/extensions/dm-subagents/src/inspectors/herdr/client.js +123 -0
  70. package/extensions/dm-subagents/src/inspectors/herdr/focus.js +47 -0
  71. package/extensions/dm-subagents/src/inspectors/herdr/inspector-runner.js +160 -0
  72. package/extensions/dm-subagents/src/inspectors/herdr/project-panes.js +618 -0
  73. package/extensions/dm-subagents/src/inspectors/herdr/session-roots-codec.js +21 -0
  74. package/extensions/dm-subagents/src/inspectors/herdr/shell-command.js +15 -0
  75. package/extensions/dm-subagents/src/integrations/herdr-status.js +377 -0
  76. package/extensions/dm-subagents/src/intercom/intercom-bridge.js +17 -13
  77. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +371 -79
  78. package/extensions/dm-subagents/src/intercom/result-intercom.js +47 -7
  79. package/extensions/dm-subagents/src/missions/actions.js +394 -0
  80. package/extensions/dm-subagents/src/missions/goal-driver.js +149 -0
  81. package/extensions/dm-subagents/src/missions/lifecycle.js +331 -0
  82. package/extensions/dm-subagents/src/missions/store.js +548 -0
  83. package/extensions/dm-subagents/src/missions/types.js +9 -0
  84. package/extensions/dm-subagents/src/missions/workflow-state.js +245 -0
  85. package/extensions/dm-subagents/src/policy/authority.js +37 -0
  86. package/extensions/dm-subagents/src/profiles/profiles.js +36 -18
  87. package/extensions/dm-subagents/src/runs/background/active-async-capacity.js +427 -0
  88. package/extensions/dm-subagents/src/runs/background/active-run-index.js +122 -0
  89. package/extensions/dm-subagents/src/runs/background/async-execution.js +925 -137
  90. package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +515 -148
  91. package/extensions/dm-subagents/src/runs/background/async-resume.js +378 -51
  92. package/extensions/dm-subagents/src/runs/background/async-retention.js +828 -0
  93. package/extensions/dm-subagents/src/runs/background/async-status-snapshot.js +31 -0
  94. package/extensions/dm-subagents/src/runs/background/async-status.js +259 -22
  95. package/extensions/dm-subagents/src/runs/background/auto-drain.js +46 -0
  96. package/extensions/dm-subagents/src/runs/background/chain-append.js +50 -15
  97. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +67 -12
  98. package/extensions/dm-subagents/src/runs/background/completion-batcher.js +5 -1
  99. package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +3 -11
  100. package/extensions/dm-subagents/src/runs/background/completion-replay.js +245 -0
  101. package/extensions/dm-subagents/src/runs/background/control-channel.js +423 -36
  102. package/extensions/dm-subagents/src/runs/background/fleet-view.js +132 -59
  103. package/extensions/dm-subagents/src/runs/background/index-segment.js +38 -0
  104. package/extensions/dm-subagents/src/runs/background/inspect-rpc.js +373 -0
  105. package/extensions/dm-subagents/src/runs/background/notify.js +357 -57
  106. package/extensions/dm-subagents/src/runs/background/owned-process-tree.js +86 -0
  107. package/extensions/dm-subagents/src/runs/background/process-terminal.js +269 -0
  108. package/extensions/dm-subagents/src/runs/background/result-delivery-ownership.js +34 -0
  109. package/extensions/dm-subagents/src/runs/background/result-files.js +469 -0
  110. package/extensions/dm-subagents/src/runs/background/result-watcher.js +540 -75
  111. package/extensions/dm-subagents/src/runs/background/resume-guidance.js +44 -0
  112. package/extensions/dm-subagents/src/runs/background/retained-children.js +119 -0
  113. package/extensions/dm-subagents/src/runs/background/run-id-query.js +5 -0
  114. package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +93 -9
  115. package/extensions/dm-subagents/src/runs/background/run-status.js +303 -32
  116. package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +784 -376
  117. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +75 -34
  118. package/extensions/dm-subagents/src/runs/background/steering.js +221 -0
  119. package/extensions/dm-subagents/src/runs/background/subagent-runner.js +3106 -844
  120. package/extensions/dm-subagents/src/runs/background/subagent-wait.js +529 -0
  121. package/extensions/dm-subagents/src/runs/background/terminal-run-index.js +106 -0
  122. package/extensions/dm-subagents/src/runs/background/top-level-async.js +1 -1
  123. package/extensions/dm-subagents/src/runs/background/wait-completions.js +155 -0
  124. package/extensions/dm-subagents/src/runs/background/wait-config.js +46 -0
  125. package/extensions/dm-subagents/src/runs/background/wait-subscriptions.js +278 -0
  126. package/extensions/dm-subagents/src/runs/background/wait-tool.js +47 -0
  127. package/extensions/dm-subagents/src/runs/foreground/async-dismiss-action.js +81 -0
  128. package/extensions/dm-subagents/src/runs/foreground/async-steering-action.js +245 -0
  129. package/extensions/dm-subagents/src/runs/foreground/async-stop-action.js +74 -0
  130. package/extensions/dm-subagents/src/runs/foreground/execution.js +1401 -342
  131. package/extensions/dm-subagents/src/runs/foreground/foreground-control.js +133 -0
  132. package/extensions/dm-subagents/src/runs/foreground/foreground-history.js +148 -0
  133. package/extensions/dm-subagents/src/runs/foreground/prompt-audit.js +139 -0
  134. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +4278 -1441
  135. package/extensions/dm-subagents/src/runs/foreground/workflow-detach-reconcile.js +278 -0
  136. package/extensions/dm-subagents/src/runs/foreground/workflow-foreground-steering.js +155 -0
  137. package/extensions/dm-subagents/src/runs/shared/abort-recovery.js +97 -0
  138. package/extensions/dm-subagents/src/runs/shared/acceptance.js +597 -148
  139. package/extensions/dm-subagents/src/runs/shared/agent-contract.js +35 -0
  140. package/extensions/dm-subagents/src/runs/shared/async-status-projection.js +472 -0
  141. package/extensions/dm-subagents/src/runs/shared/background-process-options.js +6 -0
  142. package/extensions/dm-subagents/src/runs/shared/capability-ceiling.js +175 -0
  143. package/extensions/dm-subagents/src/runs/shared/child-identity.js +32 -0
  144. package/extensions/dm-subagents/src/runs/shared/child-launch-plan.js +65 -0
  145. package/extensions/dm-subagents/src/runs/shared/child-protocol.js +447 -0
  146. package/extensions/dm-subagents/src/runs/shared/claude-code-adapter.js +120 -0
  147. package/extensions/dm-subagents/src/runs/shared/codex-exec-adapter.js +129 -0
  148. package/extensions/dm-subagents/src/runs/shared/completion-evidence.js +40 -0
  149. package/extensions/dm-subagents/src/runs/shared/completion-guard.js +140 -83
  150. package/extensions/dm-subagents/src/runs/shared/context-mode.js +38 -0
  151. package/extensions/dm-subagents/src/runs/shared/cursor-agent-adapter.js +101 -0
  152. package/extensions/dm-subagents/src/runs/shared/dm-args.js +445 -72
  153. package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +27 -16
  154. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +19 -6
  155. package/extensions/dm-subagents/src/runs/shared/extension-bindings.js +81 -0
  156. package/extensions/dm-subagents/src/runs/shared/external-cli-contract.js +134 -0
  157. package/extensions/dm-subagents/src/runs/shared/external-cli-preflight.js +98 -0
  158. package/extensions/dm-subagents/src/runs/shared/external-cli-runner.js +419 -0
  159. package/extensions/dm-subagents/src/runs/shared/external-job-bridge.js +404 -0
  160. package/extensions/dm-subagents/src/runs/shared/external-job-runner.js +334 -0
  161. package/extensions/dm-subagents/src/runs/shared/fast-mode-extension.js +8 -0
  162. package/extensions/dm-subagents/src/runs/shared/host-step-status.js +228 -0
  163. package/extensions/dm-subagents/src/runs/shared/lane-metadata.js +104 -0
  164. package/extensions/dm-subagents/src/runs/shared/launch-cwd.js +17 -0
  165. package/extensions/dm-subagents/src/runs/shared/llm-intent-arbiter.js +190 -0
  166. package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +48 -3
  167. package/extensions/dm-subagents/src/runs/shared/mcp-config-sources.js +387 -0
  168. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +212 -137
  169. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-grant.js +131 -0
  170. package/extensions/dm-subagents/src/runs/shared/model-exclusions.js +207 -0
  171. package/extensions/dm-subagents/src/runs/shared/model-fallback.js +225 -55
  172. package/extensions/dm-subagents/src/runs/shared/model-scope.js +85 -28
  173. package/extensions/dm-subagents/src/runs/shared/mutation-evidence.js +182 -0
  174. package/extensions/dm-subagents/src/runs/shared/nested-events.js +264 -102
  175. package/extensions/dm-subagents/src/runs/shared/nested-render.js +15 -5
  176. package/extensions/dm-subagents/src/runs/shared/orca-progress-tabs.js +505 -0
  177. package/extensions/dm-subagents/src/runs/shared/parallel-handoff.js +653 -0
  178. package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +29 -12
  179. package/extensions/dm-subagents/src/runs/shared/permissions.js +108 -0
  180. package/extensions/dm-subagents/src/runs/shared/process-signal.js +13 -0
  181. package/extensions/dm-subagents/src/runs/shared/run-fanout-budget.js +257 -0
  182. package/extensions/dm-subagents/src/runs/shared/run-history.js +133 -13
  183. package/extensions/dm-subagents/src/runs/shared/runtime-acknowledged-extensions.js +62 -0
  184. package/extensions/dm-subagents/src/runs/shared/session-lease.js +225 -0
  185. package/extensions/dm-subagents/src/runs/shared/single-output.js +129 -27
  186. package/extensions/dm-subagents/src/runs/shared/spawn-budget.js +95 -0
  187. package/extensions/dm-subagents/src/runs/shared/structured-output.js +129 -10
  188. package/extensions/dm-subagents/src/runs/shared/subagent-control.js +66 -11
  189. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +548 -70
  190. package/extensions/dm-subagents/src/runs/shared/subagent-startup-retry.js +50 -0
  191. package/extensions/dm-subagents/src/runs/shared/task-intent.js +130 -0
  192. package/extensions/dm-subagents/src/runs/shared/tool-availability.js +59 -0
  193. package/extensions/dm-subagents/src/runs/shared/tool-budget.js +7 -5
  194. package/extensions/dm-subagents/src/runs/shared/tool-timeout.js +64 -0
  195. package/extensions/dm-subagents/src/runs/shared/usage-budget.js +74 -0
  196. package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +22 -0
  197. package/extensions/dm-subagents/src/runs/shared/worktree-cleanup-plan.js +721 -0
  198. package/extensions/dm-subagents/src/runs/shared/worktree.js +168 -19
  199. package/extensions/dm-subagents/src/shared/accessible-dir.js +35 -0
  200. package/extensions/dm-subagents/src/shared/agent-stream-options.js +3 -0
  201. package/extensions/dm-subagents/src/shared/artifacts.js +170 -11
  202. package/extensions/dm-subagents/src/shared/atomic-json.js +36 -38
  203. package/extensions/dm-subagents/src/shared/capacity-resilient-json.js +77 -0
  204. package/extensions/dm-subagents/src/shared/child-session-name.js +15 -0
  205. package/extensions/dm-subagents/src/shared/child-transcript.js +57 -2
  206. package/extensions/dm-subagents/src/shared/completion-owner.js +7 -0
  207. package/extensions/dm-subagents/src/shared/display-text.js +142 -0
  208. package/extensions/dm-subagents/src/shared/extension-context.js +17 -0
  209. package/extensions/dm-subagents/src/shared/file-coalescer.js +9 -0
  210. package/extensions/dm-subagents/src/shared/file-system-retry.js +56 -0
  211. package/extensions/dm-subagents/src/shared/fork-context.js +96 -33
  212. package/extensions/dm-subagents/src/shared/formatters.js +21 -7
  213. package/extensions/dm-subagents/src/shared/launch-contract.js +94 -0
  214. package/extensions/dm-subagents/src/shared/model-info.js +10 -5
  215. package/extensions/dm-subagents/src/shared/node-executable.js +19 -0
  216. package/extensions/dm-subagents/src/shared/prompt-resources.js +10 -0
  217. package/extensions/dm-subagents/src/shared/pruned-fork.js +427 -0
  218. package/extensions/dm-subagents/src/shared/session-file-trust.js +19 -0
  219. package/extensions/dm-subagents/src/shared/session-tokens.js +14 -3
  220. package/extensions/dm-subagents/src/shared/settings.js +39 -47
  221. package/extensions/dm-subagents/src/shared/shortcuts.js +16 -0
  222. package/extensions/dm-subagents/src/shared/status-format.js +9 -2
  223. package/extensions/dm-subagents/src/shared/thinking-ceiling.js +41 -0
  224. package/extensions/dm-subagents/src/shared/types.js +47 -7
  225. package/extensions/dm-subagents/src/shared/utf8.js +12 -0
  226. package/extensions/dm-subagents/src/shared/utils.js +151 -131
  227. package/extensions/dm-subagents/src/shared/watch-strategy.js +3 -0
  228. package/extensions/dm-subagents/src/shared/workflow-child-permit.js +84 -0
  229. package/extensions/dm-subagents/src/slash/delegation-adapters.js +274 -0
  230. package/extensions/dm-subagents/src/slash/delegation-json.js +113 -0
  231. package/extensions/dm-subagents/src/slash/delegation-request.js +152 -0
  232. package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +294 -243
  233. package/extensions/dm-subagents/src/slash/prompt-workflows.js +35 -73
  234. package/extensions/dm-subagents/src/slash/selector.js +101 -0
  235. package/extensions/dm-subagents/src/slash/slash-bridge.js +17 -1
  236. package/extensions/dm-subagents/src/slash/slash-commands.js +722 -732
  237. package/extensions/dm-subagents/src/slash/slash-live-state.js +37 -19
  238. package/extensions/dm-subagents/src/slash/subagents-admin.js +410 -0
  239. package/extensions/dm-subagents/src/tui/fleet-status.js +824 -0
  240. package/extensions/dm-subagents/src/tui/fleet-transcript.js +479 -0
  241. package/extensions/dm-subagents/src/tui/fleet.js +1326 -0
  242. package/extensions/dm-subagents/src/tui/render-helpers.js +22 -0
  243. package/extensions/dm-subagents/src/tui/render.js +1511 -261
  244. package/extensions/dm-subagents/src/watchdog/change-signature.js +220 -0
  245. package/extensions/dm-subagents/src/watchdog/child-status.js +151 -0
  246. package/extensions/dm-subagents/src/watchdog/emission-guard.js +90 -0
  247. package/extensions/dm-subagents/src/watchdog/lsp-diagnostics.js +484 -0
  248. package/extensions/dm-subagents/src/watchdog/model-selection.js +154 -0
  249. package/extensions/dm-subagents/src/watchdog/permission-arbiter.js +138 -0
  250. package/extensions/dm-subagents/src/watchdog/register-child.js +112 -0
  251. package/extensions/dm-subagents/src/watchdog/register-main.js +419 -0
  252. package/extensions/dm-subagents/src/watchdog/render.js +54 -0
  253. package/extensions/dm-subagents/src/watchdog/review.js +251 -0
  254. package/extensions/dm-subagents/src/watchdog/runtime.js +803 -0
  255. package/extensions/dm-subagents/src/watchdog/scope.js +56 -0
  256. package/extensions/dm-subagents/src/watchdog/settings.js +515 -0
  257. package/extensions/dm-subagents/src/watchdog/tool-actions.js +151 -0
  258. package/extensions/dm-subagents/src/watchdog/turn-delta.js +169 -0
  259. package/extensions/dm-subagents/src/watchdog/types.js +29 -0
  260. package/extensions/dm-subagents/src/watchdog/warning-format.js +58 -0
  261. package/extensions/dm-subagents/src/workflows/chat-progress.js +116 -0
  262. package/extensions/dm-subagents/src/workflows/host-command.js +227 -0
  263. package/extensions/dm-subagents/src/workflows/scripted-workflow.js +2011 -0
  264. package/extensions/dm-subagents/src/workflows/workflow-child-summary.js +116 -0
  265. package/extensions/dm-subagents/src/workflows/workflow-preflight.js +243 -0
  266. package/extensions/dm-subagents/src/workflows/workflow-receipt.js +387 -0
  267. package/extensions/dm-subagents/src/workflows/workflow-settlement.js +189 -0
  268. package/package.json +4 -3
  269. package/extensions/dm-fff/package.json +0 -21
  270. package/extensions/dm-fff/src/index.js +0 -691
  271. package/extensions/dm-fff/src/query.js +0 -60
  272. package/extensions/dm-subagents/agents/context-builder.md +0 -46
  273. package/extensions/dm-subagents/agents/planner.md +0 -55
  274. package/extensions/dm-subagents/prompts/parallel-context-build.md +0 -55
  275. package/extensions/dm-subagents/prompts/parallel-handoff-plan.md +0 -61
  276. package/extensions/dm-subagents/src/runs/background/wait.js +0 -206
  277. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +0 -1013
  278. package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +0 -981
  279. package/extensions/dm-subagents/src/runs/shared/turn-budget.js +0 -50
@@ -1,26 +1,74 @@
1
1
  import * as path from "node:path";
2
+ import { createHash } from "node:crypto";
2
3
  import { getMarkdownTheme, keyText } from "@duckmind/dm-coding-agent";
3
4
  import { Container, Markdown, Spacer, Text, visibleWidth } from "@duckmind/dm-tui";
4
5
  import {
5
6
  MAX_WIDGET_JOBS,
7
+ WIDGET_ANIMATION_INTERVAL_MS,
6
8
  WIDGET_KEY
7
9
  } from "../shared/types.js";
8
- import { formatTokens, formatUsage, formatDuration, formatModelThinking, formatToolCall, shortenPath } from "../shared/formatters.js";
9
- import { getDisplayItems, getSingleResultOutput } from "../shared/utils.js";
10
+ import { previewDisplayText, sanitizeDisplayText, truncateDisplayText } from "../shared/display-text.js";
11
+ import { FLEET_OPEN_SHORTCUT, formatShortcutLabel } from "../shared/shortcuts.js";
12
+ import { formatContextUsage, formatTokens, formatUsage, formatDuration, formatModelThinking, formatToolCall, formatTokenUsage, shortenPath } from "../shared/formatters.js";
13
+ import { getDisplayItems, getSingleResultOutput, PROMPT_REDACTED } from "../shared/utils.js";
10
14
  import { flatToLogicalStepIndex } from "../runs/background/parallel-groups.js";
11
15
  import { formatNestedAggregate } from "../runs/shared/nested-render.js";
12
16
  import { aggregateStepStatus, formatActivityLabel, formatAgentRunningLabel, formatParallelOutcome } from "../shared/status-format.js";
17
+ import { contextModeBadge, contextModePrefix } from "../runs/shared/context-mode.js";
18
+ import { shouldSuppressSingleStep, stripRepeatedAgentPrefix, withDuplicateLabelDiscriminators } from "./render-helpers.js";
19
+ import { buildWorkflowChatProgressRows } from "../workflows/chat-progress.js";
20
+ import { formatWorkflowPreflight, formatWorkflowPreflightPlanSummary, formatWorkflowPreflightWarningSummary, formatWorkflowPreflightWarnings } from "../workflows/workflow-preflight.js";
21
+ import { encodeAsyncStatusSnapshotWidget } from "../runs/background/async-status-snapshot.js";
22
+ import { projectAsyncWorkflowRows } from "../runs/shared/async-status-projection.js";
23
+ import { hostStepReportName, hostStepVerdictLabel } from "../runs/shared/host-step-status.js";
24
+ import { workflowGraphStageNodes } from "../runs/shared/workflow-graph.js";
25
+ function resolveMainWindowRenderLayout(config) {
26
+ return {
27
+ horizontalSpacing: config?.horizontalSpacing ?? 2,
28
+ ...config?.compactResultMaxLines !== undefined ? { compactResultMaxLines: config.compactResultMaxLines } : {}
29
+ };
30
+ }
31
+ function mainWindowIndent(layout, level) {
32
+ return " ".repeat(Math.max(0, layout.horizontalSpacing * level));
33
+ }
34
+ function capCompactMainWindowResult(component, layout, theme, enabled) {
35
+ const maxLines = layout.compactResultMaxLines;
36
+ if (!enabled || maxLines === undefined)
37
+ return component;
38
+ const capped = new Container;
39
+ capped.render = (width) => {
40
+ const lines = component.render(width);
41
+ if (lines.length <= maxLines)
42
+ return lines;
43
+ const visibleRows = maxLines === 1 ? 1 : maxLines - 1;
44
+ const hiddenCount = lines.length - visibleRows;
45
+ const hint = theme.fg("accent", `… ${hiddenCount} rows hidden · ${liveDetailKeyText()} expands`);
46
+ if (maxLines === 1)
47
+ return [truncLine(`${lines[0] ?? ""} ${hint}`, width)];
48
+ return [...lines.slice(0, visibleRows), truncLine(hint, width)];
49
+ };
50
+ return capped;
51
+ }
13
52
  function liveDetailKeyText() {
14
53
  return keyText("app.tools.expand");
15
54
  }
16
- function liveDetailHintText() {
17
- return `Press ${liveDetailKeyText()} for live detail`;
55
+ export function liveDetailHintText() {
56
+ return `Press ${liveDetailKeyText()} for live detail · ${formatShortcutLabel(FLEET_OPEN_SHORTCUT)} Fleet`;
57
+ }
58
+ function foregroundSingleHintText(shortcut) {
59
+ if (!shortcut)
60
+ return liveDetailHintText();
61
+ const label = formatShortcutLabel(shortcut);
62
+ return `${liveDetailHintText()} · ${label} to run in background`;
18
63
  }
19
64
  function getTermWidth() {
20
65
  return process.stdout.columns || 120;
21
66
  }
22
67
  const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
23
- function truncLine(text, maxWidth) {
68
+ const ansiStylePattern = /\x1b\[[0-9;]*m/y;
69
+ export function truncLine(text, maxWidth) {
70
+ if (maxWidth <= 0)
71
+ return "";
24
72
  if (visibleWidth(text) <= maxWidth)
25
73
  return text;
26
74
  const targetWidth = maxWidth - 1;
@@ -29,7 +77,8 @@ function truncLine(text, maxWidth) {
29
77
  let activeStyles = [];
30
78
  let i = 0;
31
79
  while (i < text.length) {
32
- const ansiMatch = text.slice(i).match(/^\x1b\[[0-9;]*m/);
80
+ ansiStylePattern.lastIndex = i;
81
+ const ansiMatch = ansiStylePattern.exec(text);
33
82
  if (ansiMatch) {
34
83
  const code = ansiMatch[0];
35
84
  result += code;
@@ -41,14 +90,15 @@ function truncLine(text, maxWidth) {
41
90
  i += code.length;
42
91
  continue;
43
92
  }
44
- let end = i;
45
- while (end < text.length && !text.slice(end).match(/^\x1b\[[0-9;]*m/)) {
46
- end++;
47
- }
93
+ let end = text.indexOf("\x1B[", i);
94
+ if (end === i)
95
+ end = text.indexOf("\x1B[", i + 2);
96
+ if (end === -1)
97
+ end = text.length;
48
98
  const textPortion = text.slice(i, end);
49
99
  for (const seg of segmenter.segment(textPortion)) {
50
100
  const grapheme = seg.segment;
51
- const graphemeWidth = visibleWidth(grapheme);
101
+ const graphemeWidth = grapheme === "\x1B" ? 0 : visibleWidth(grapheme);
52
102
  if (currentWidth + graphemeWidth > targetWidth) {
53
103
  return result + activeStyles.join("") + "…";
54
104
  }
@@ -74,6 +124,8 @@ function wrapPlainText(text, maxWidth) {
74
124
  for (const seg of segmenter.segment(rawLine)) {
75
125
  const grapheme = seg.segment;
76
126
  const graphemeWidth = visibleWidth(grapheme);
127
+ if (graphemeWidth > maxWidth)
128
+ continue;
77
129
  if (currentWidth > 0 && currentWidth + graphemeWidth > maxWidth) {
78
130
  lines.push(current);
79
131
  current = grapheme;
@@ -101,7 +153,13 @@ function runningSeed(...values) {
101
153
  function runningGlyph(seed) {
102
154
  if (seed === undefined)
103
155
  return STATIC_RUNNING_GLYPH;
104
- return RUNNING_FRAMES[Math.abs(seed) % RUNNING_FRAMES.length];
156
+ const clock = Math.floor(Date.now() / 125);
157
+ return RUNNING_FRAMES[Math.abs(seed + clock) % RUNNING_FRAMES.length];
158
+ }
159
+ function animatedSeed(seed, frame) {
160
+ if (frame === undefined)
161
+ return seed;
162
+ return (seed ?? 0) + frame;
105
163
  }
106
164
  function progressRunningSeed(progress) {
107
165
  if (!progress)
@@ -138,16 +196,487 @@ function getToolCallLines(result, expanded) {
138
196
  }
139
197
  return result.toolCalls?.map((toolCall) => expanded ? toolCall.expandedText : toolCall.text) ?? [];
140
198
  }
199
+ const ansiEscapePattern = /\x1b\[[0-9;]*m/g;
200
+ const noisyStatusPatterns = [
201
+ /^(?:i|we)\s+(?:will|need|can|should|am|are)\b/i,
202
+ /^i(?:'m|’m| am)\b/i,
203
+ /\bso i (?:will|need|can)\b/i,
204
+ /^(?:checking|fetching|reading|inspecting|verifying|collecting|confirming|polling)\b/i,
205
+ /^(?:async\s+subagent\s+)?[\w.-]+\s*·\s*(?:step|agent)\s+\d+\/\d+\s*·/i,
206
+ /^(?:Step|Agent)\s+\d+\/\d+:\s+[\w.-]+\s*·\s*(?:running|queued|pending|complete|completed)\b/i,
207
+ /^(?:async\s+subagent\s+)?[\w.-]+(?:\s+\[(?:fresh|fork|mixed)\])?\s*·\s*(?:running|queued|pending|complete|completed|done)\b/i,
208
+ /^Press\s+\S+\s+for\s+live\s+detail$/i,
209
+ /^output:\s+.+\/async-subagent-runs\//i
210
+ ];
211
+ const liveOutputWordSignalPattern = /\b(?:access denied|denied|error|exception|fail(?:ed|ure)?|fatal|panic|rejected|timeout|timed out|unable|warning)\b/i;
212
+ const liveOutputCodeSignalPattern = /\bE[A-Z0-9_]{2,}\b/;
213
+ function oneLine(text) {
214
+ return text.replace(ansiEscapePattern, "").replace(/\s+/g, " ").trim();
215
+ }
216
+ const COMPACT_TASK_MAX_CHARS = 96;
217
+ function childDisplayName(result, fallback = "subagent") {
218
+ return result?.sessionName?.trim() || result?.agent || fallback;
219
+ }
220
+ export function compactTaskText(task, label) {
221
+ const taskText = task?.trim();
222
+ const labelText = label?.trim();
223
+ const normalizedTask = taskText && taskText !== PROMPT_REDACTED ? oneLine(taskText) : "";
224
+ const normalizedLabel = labelText && labelText !== PROMPT_REDACTED ? oneLine(labelText) : "";
225
+ const normalized = normalizedLabel && normalizedTask && normalizedLabel !== normalizedTask ? `${normalizedLabel} — ${normalizedTask}` : normalizedLabel || normalizedTask;
226
+ if (!normalized)
227
+ return;
228
+ return previewDisplayText(normalized, COMPACT_TASK_MAX_CHARS);
229
+ }
230
+ const LANE_VALUE_MAX_CHARS = 48;
231
+ function boundedLaneValue(value, maxChars = LANE_VALUE_MAX_CHARS) {
232
+ if (!value?.trim() || value.trim() === PROMPT_REDACTED)
233
+ return;
234
+ return previewDisplayText(oneLine(value), maxChars);
235
+ }
236
+ function workflowNodeStepStatus(status) {
237
+ switch (status) {
238
+ case "completed":
239
+ return "completed";
240
+ case "detached":
241
+ return "paused";
242
+ default:
243
+ return status;
244
+ }
245
+ }
246
+ function workflowNodeStatusLabel(status) {
247
+ switch (status) {
248
+ case "completed":
249
+ return "complete";
250
+ case "detached":
251
+ return "paused";
252
+ default:
253
+ return status;
254
+ }
255
+ }
256
+ function workflowStepPriority(step, currentNodeId) {
257
+ const isCurrent = step.workflowKey === currentNodeId;
258
+ if (step.status === "running" || isCurrent && step.status !== "complete" && step.status !== "completed")
259
+ return 0;
260
+ const gate = laneGate(step);
261
+ if (step.status === "failed" || step.status === "partial" || step.status === "paused" || step.status === "stopped" || step.status === "rejected" || step.toolBudgetBlocked === true || step.turnBudgetExceeded === true || step.activityState === "needs_attention" || step.watchdog?.phase === "stale" || gate !== undefined)
262
+ return 1;
263
+ if (step.status === "pending")
264
+ return 2;
265
+ return 3;
266
+ }
267
+ function workflowWidgetSteps(job, planned = job.mode === "workflow" ? workflowGraphStageNodes(job.workflowGraph) : []) {
268
+ const loaded = job.steps ?? [];
269
+ if (planned.length === 0)
270
+ return loaded;
271
+ const loadedIndexesByKey = new Map;
272
+ for (const [index, step] of loaded.entries()) {
273
+ if (!step.workflowKey)
274
+ continue;
275
+ const indexes = loadedIndexesByKey.get(step.workflowKey) ?? [];
276
+ indexes.push(index);
277
+ loadedIndexesByKey.set(step.workflowKey, indexes);
278
+ }
279
+ const consumed = new Set;
280
+ const entries = [];
281
+ for (const [order, node] of planned.entries()) {
282
+ const loadedIndex = loadedIndexesByKey.get(node.id)?.find((index) => !consumed.has(index));
283
+ if (loadedIndex !== undefined) {
284
+ consumed.add(loadedIndex);
285
+ const loadedStep = loaded[loadedIndex];
286
+ entries.push({
287
+ step: {
288
+ ...loadedStep,
289
+ index: node.flatIndex ?? loadedStep.index ?? order,
290
+ agent: loadedStep.agent || node.agent || job.agents?.[0] || "workflow",
291
+ phase: loadedStep.phase ?? node.phase,
292
+ label: loadedStep.label ?? node.label,
293
+ workflowKey: loadedStep.workflowKey ?? node.id,
294
+ ...loadedStep.outputName === undefined && node.outputName !== undefined ? { outputName: node.outputName } : {},
295
+ ...loadedStep.structured === undefined && node.structured !== undefined ? { structured: node.structured } : {},
296
+ ...loadedStep.error === undefined && node.error !== undefined ? { error: node.error } : {}
297
+ },
298
+ order
299
+ });
300
+ continue;
301
+ }
302
+ entries.push({
303
+ step: {
304
+ index: node.flatIndex ?? order,
305
+ agent: node.agent ?? job.agents?.[0] ?? "workflow",
306
+ status: workflowNodeStepStatus(node.status),
307
+ workflowKey: node.id,
308
+ label: node.label,
309
+ ...node.phase ? { phase: node.phase } : {},
310
+ ...node.outputName ? { outputName: node.outputName } : {},
311
+ ...node.structured !== undefined ? { structured: node.structured } : {},
312
+ ...node.error ? { error: node.error } : {}
313
+ },
314
+ order
315
+ });
316
+ }
317
+ for (const [index, step] of loaded.entries()) {
318
+ if (!consumed.has(index))
319
+ entries.push({ step, order: planned.length + index });
320
+ }
321
+ entries.sort((left, right) => workflowStepPriority(left.step, job.workflowGraph?.currentNodeId) - workflowStepPriority(right.step, job.workflowGraph?.currentNodeId) || left.order - right.order);
322
+ return entries.map(({ step }) => step);
323
+ }
324
+ function workflowStageProgress(job, stages = job.mode === "workflow" ? workflowGraphStageNodes(job.workflowGraph) : []) {
325
+ if (job.mode !== "workflow")
326
+ return;
327
+ if (stages.length === 0)
328
+ return;
329
+ const currentId = job.workflowGraph?.currentNodeId;
330
+ const currentIndex = currentId ? stages.findIndex((stage) => stage.id === currentId) : -1;
331
+ if (currentIndex >= 0 && stages[currentIndex]?.status !== "completed")
332
+ return { total: stages.length, current: currentIndex };
333
+ const runningIndex = stages.findIndex((stage) => stage.status === "running");
334
+ if (runningIndex >= 0)
335
+ return { total: stages.length, current: runningIndex };
336
+ const indexedStage = job.currentStep !== undefined && job.currentStep >= 0 ? stages[job.currentStep] : undefined;
337
+ if (indexedStage && indexedStage.status !== "completed")
338
+ return { total: stages.length, current: job.currentStep };
339
+ if (stages.every((stage) => stage.status === "completed"))
340
+ return { total: stages.length, current: stages.length - 1 };
341
+ return { total: stages.length };
342
+ }
343
+ function buildWorkflowWidgetProjection(job) {
344
+ const stages = job.mode === "workflow" ? workflowGraphStageNodes(job.workflowGraph) : [];
345
+ const stageProgress = workflowStageProgress(job, stages);
346
+ const steps = workflowWidgetSteps(job, stages);
347
+ const projection = { stages, steps };
348
+ if (stageProgress)
349
+ projection.stageProgress = stageProgress;
350
+ if (stages.length)
351
+ projection.plannedKeys = new Set(stages.map((node) => node.id));
352
+ return projection;
353
+ }
354
+ function workflowWidgetProjectionLookup() {
355
+ const projections = new WeakMap;
356
+ return (job) => {
357
+ const cached = projections.get(job);
358
+ if (cached)
359
+ return cached;
360
+ const projection = buildWorkflowWidgetProjection(job);
361
+ projections.set(job, projection);
362
+ return projection;
363
+ };
364
+ }
365
+ function laneStepForJob(job, steps = workflowWidgetSteps(job)) {
366
+ if (steps.length === 0)
367
+ return;
368
+ const graphCurrent = job.workflowGraph?.currentNodeId;
369
+ if (graphCurrent) {
370
+ const current = steps.find((step) => step.workflowKey === graphCurrent);
371
+ if (current)
372
+ return current;
373
+ }
374
+ if (job.currentStep !== undefined) {
375
+ const current = steps[job.currentStep];
376
+ if (current && (current.index === undefined || current.index === job.currentStep))
377
+ return current;
378
+ const indexedCurrent = steps.find((step) => step.index === job.currentStep);
379
+ if (indexedCurrent)
380
+ return indexedCurrent;
381
+ return steps[0];
382
+ }
383
+ return steps.find((step) => step.status === "running") ?? steps.find((step) => step.status === "pending") ?? steps.at(-1);
384
+ }
385
+ function laneTraceForJob(job) {
386
+ return Array.isArray(job.workflow?.trace) ? job.workflow.trace.at(-1) : undefined;
387
+ }
388
+ function laneGate(step) {
389
+ const review = step?.review?.status ?? step?.acceptance?.reviewResult?.status;
390
+ if (review === "blockers")
391
+ return "review blockers";
392
+ if (review === "review-required")
393
+ return "review required";
394
+ if (review === "reviewed")
395
+ return "reviewed";
396
+ const acceptance = step?.acceptance?.status;
397
+ if (acceptance === "review-required")
398
+ return "acceptance review";
399
+ if (acceptance === "checked" || acceptance === "verified" || acceptance === "accepted")
400
+ return "acceptance";
401
+ return;
402
+ }
403
+ function laneNextAction(state, step, output, gate) {
404
+ if (step?.watchdog?.phase === "stale")
405
+ return "inspect stale state";
406
+ if (step?.toolBudgetBlocked === true || step?.turnBudgetExceeded === true)
407
+ return "inspect blocked state";
408
+ if (gate === "review blockers")
409
+ return "resolve review blockers";
410
+ if (gate === "review required" || gate === "acceptance review")
411
+ return "review output";
412
+ if (step?.activityState === "needs_attention")
413
+ return "inspect attention";
414
+ if (state === "queued" || state === "pending")
415
+ return "await launch";
416
+ if (state === "failed" || state === "rejected")
417
+ return "inspect failure";
418
+ if (state === "partial")
419
+ return "inspect partial state";
420
+ if (state === "paused")
421
+ return "inspect paused state";
422
+ if (state === "stopped")
423
+ return "inspect stopped state";
424
+ if ((state === "complete" || state === "completed") && gate === "reviewed")
425
+ return "ready";
426
+ if ((state === "complete" || state === "completed") && output)
427
+ return "inspect output";
428
+ return;
429
+ }
430
+ function isTerminalLaneState(state) {
431
+ return state !== "queued" && state !== "running";
432
+ }
433
+ export function projectAsyncLane(job, ...args) {
434
+ const selectedStep = args.length === 0 ? laneStepForJob(job) : args[0];
435
+ const trace = laneTraceForJob(job);
436
+ const workspace = job.cwd ? boundedLaneValue(shortenPath(job.cwd)) : undefined;
437
+ const label = compactTaskText(selectedStep?.description, selectedStep?.label) ?? boundedLaneValue(trace?.label) ?? (workspace ? undefined : boundedLaneValue(selectedStep?.workflowKey ?? job.workflowKey));
438
+ const role = boundedLaneValue(selectedStep?.agent ?? trace?.agent ?? job.agents?.[0] ?? widgetJobName(job), 32) ?? "subagent";
439
+ const phase = boundedLaneValue(selectedStep?.phase ?? trace?.phase);
440
+ const gate = laneGate(selectedStep);
441
+ const output = boundedLaneValue(selectedStep?.outputName);
442
+ const ref = boundedLaneValue(selectedStep?.workflowKey ?? job.workflowKey ?? job.asyncId.slice(0, 8), 24) ?? job.asyncId.slice(0, 8);
443
+ const chips = [
444
+ selectedStep?.context,
445
+ selectedStep?.structured ? "structured" : undefined,
446
+ selectedStep?.activityState === "active_long_running" ? "long-running" : undefined,
447
+ selectedStep?.activityState === "needs_attention" ? "attention" : undefined,
448
+ selectedStep?.watchdog?.phase === "stale" ? "stale" : undefined,
449
+ selectedStep?.toolBudgetBlocked === true || selectedStep?.turnBudgetExceeded === true ? "blocked" : undefined
450
+ ].filter((chip) => Boolean(chip));
451
+ const state = isTerminalLaneState(job.status) ? job.status : selectedStep?.status ?? job.status;
452
+ const next = laneNextAction(state, selectedStep, output, gate);
453
+ if (!label && !phase && !gate && !output && !selectedStep?.workflowKey && !job.workflowKey && !trace?.label && !trace?.phase)
454
+ return;
455
+ return { ...label ? { label } : {}, role, ...phase ? { phase } : {}, state, ...gate ? { gate } : {}, ...next ? { next } : {}, ...output ? { output } : {}, ...workspace ? { workspace } : {}, ref, chips };
456
+ }
457
+ function laneStateLabel(state, theme) {
458
+ if (state === "running")
459
+ return theme.fg("accent", "running");
460
+ if (state === "queued" || state === "pending")
461
+ return theme.fg("muted", state);
462
+ if (state === "complete" || state === "completed")
463
+ return theme.fg("success", "complete");
464
+ if (state === "failed" || state === "rejected")
465
+ return theme.fg("error", state === "rejected" ? "rejected" : "failed");
466
+ return theme.fg("warning", state);
467
+ }
468
+ function formatLaneProjection(lane, theme) {
469
+ const label = boundedLaneValue(lane.label, 56);
470
+ const identity = [label, lane.role ? `role:${lane.role}` : undefined].filter(Boolean).join(" · ");
471
+ return `${identity ? theme.bold(identity) : theme.bold(lane.role)} · ${laneStateLabel(lane.state, theme)}`;
472
+ }
473
+ function formatLaneChip(chip, theme) {
474
+ const text = `[${chip}]`;
475
+ if (chip === "blocked")
476
+ return theme.fg("error", text);
477
+ if (chip === "stale")
478
+ return theme.fg("warning", text);
479
+ return text;
480
+ }
481
+ function formatLaneProjectionDetails(lane, theme) {
482
+ const details = [
483
+ lane.phase ? `phase:${lane.phase}` : undefined,
484
+ lane.gate ? `gate:${lane.gate}` : undefined,
485
+ lane.next ? `next:${lane.next}` : undefined,
486
+ lane.output ? `out:${lane.output}` : undefined,
487
+ lane.workspace ? `workspace:${lane.workspace}` : lane.ref ? `ref:${lane.ref}` : undefined
488
+ ].filter(Boolean);
489
+ const chips = lane.chips.map((chip) => formatLaneChip(chip, theme));
490
+ return [...details.length ? [theme.fg("dim", details.join(" · "))] : [], ...chips].join(" · ") || undefined;
491
+ }
492
+ function formatLaneProjectionLines(lane, theme, indent) {
493
+ const details = formatLaneProjectionDetails(lane, theme);
494
+ return [
495
+ `${indent}${formatLaneProjection(lane, theme)}`,
496
+ ...details ? [`${indent} ${details}`] : []
497
+ ];
498
+ }
499
+ function laneRenderKey(job, projection) {
500
+ const selectedStep = projection ? laneStepForJob(job, projection.steps) : laneStepForJob(job);
501
+ const lane = projectAsyncLane(job, selectedStep);
502
+ return lane ? [lane.label, lane.role, lane.phase, lane.state, lane.gate, lane.next, lane.output, lane.workspace, lane.ref, lane.chips] : undefined;
503
+ }
504
+ function widgetLaneDetailLines(job, theme, projection) {
505
+ if (job.steps?.length && (job.mode === "parallel" || job.mode === "chain"))
506
+ return [];
507
+ const selectedStep = projection ? laneStepForJob(job, projection.steps) : laneStepForJob(job);
508
+ const lane = projectAsyncLane(job, selectedStep);
509
+ return lane ? formatLaneProjectionLines(lane, theme, " ") : [];
510
+ }
511
+ function workflowPreflightLines(job, expanded = false) {
512
+ if (job.mode !== "workflow" || !job.preflight)
513
+ return [];
514
+ if (!expanded) {
515
+ const warning = formatWorkflowPreflightWarningSummary(job.workflow?.preflightWarnings, { indent: " ", hint: "expand for debug" });
516
+ return [
517
+ formatWorkflowPreflightPlanSummary(job.preflight, { indent: " " }),
518
+ ...warning ? [warning] : []
519
+ ];
520
+ }
521
+ return [
522
+ ...formatWorkflowPreflight(job.preflight, { indent: " " }).split(`
523
+ `),
524
+ ...job.workflow?.preflightWarnings ? formatWorkflowPreflightWarnings(job.workflow.preflightWarnings, { indent: " " }).split(`
525
+ `) : []
526
+ ];
527
+ }
528
+ function workflowLabelForResult(details, resultIndex) {
529
+ const flatIndex = foregroundResultIndex(details, resultIndex);
530
+ const visit = (nodes) => {
531
+ for (const node of nodes) {
532
+ if (node.flatIndex === flatIndex && node.label.trim())
533
+ return node.label;
534
+ const nested = node.children ? visit(node.children) : undefined;
535
+ if (nested)
536
+ return nested;
537
+ }
538
+ return;
539
+ };
540
+ return details.workflowGraph ? visit(details.workflowGraph.nodes) : undefined;
541
+ }
542
+ function foregroundProgressForResult(details, resultIndex) {
543
+ const result = details.results[resultIndex];
544
+ if (result?.progress)
545
+ return result.progress;
546
+ const index = result?.index ?? resultIndex;
547
+ const indexed = details.progress?.find((progress) => progress.index === index);
548
+ if (indexed)
549
+ return indexed;
550
+ return result?.agent ? details.progress?.find((progress) => progress.agent === result.agent && progress.status === "running") : undefined;
551
+ }
552
+ function foregroundResultIndex(details, resultIndex) {
553
+ const result = details.results[resultIndex];
554
+ if (typeof result?.index === "number")
555
+ return result.index;
556
+ return foregroundProgressForResult(details, resultIndex)?.index ?? resultIndex;
557
+ }
558
+ function foregroundResultDisplayName(details, resultIndex, result, fallback) {
559
+ const progress = foregroundProgressForResult(details, resultIndex);
560
+ const agent = normalizedParallelDisplayText(result?.agent) ?? normalizedParallelDisplayText(progress?.agent);
561
+ const workflowLabel = normalizedParallelDisplayText(workflowLabelForResult(details, resultIndex));
562
+ if (workflowLabel)
563
+ return compactTaskText(undefined, workflowLabel) ?? workflowLabel;
564
+ const sessionName = normalizedParallelDisplayText(result?.sessionName) ?? normalizedParallelDisplayText(progress?.sessionName);
565
+ if (sessionName) {
566
+ const sessionTask = stripRepeatedAgentPrefix(sessionName, agent);
567
+ if (sessionTask && sessionTask !== PROMPT_REDACTED && sessionTask.toLowerCase() !== agent?.toLowerCase()) {
568
+ return compactTaskText(sessionTask) ?? sessionTask;
569
+ }
570
+ }
571
+ return compactTaskText(result?.task) ?? compactTaskText(progress?.task) ?? agent ?? fallback;
572
+ }
573
+ function foregroundSingleDisplayName(result) {
574
+ return normalizedParallelDisplayText(result?.agent) ?? normalizedParallelDisplayText(result?.sessionName) ?? compactTaskText(result?.task) ?? "subagent";
575
+ }
576
+ function hasLiveOutputSignal(line) {
577
+ const clean = oneLine(line);
578
+ return liveOutputWordSignalPattern.test(clean) || liveOutputCodeSignalPattern.test(clean);
579
+ }
580
+ function isNoisyStatusLine(line) {
581
+ const clean = oneLine(line);
582
+ return clean.length > 0 && clean.length <= 240 && !hasLiveOutputSignal(clean) && noisyStatusPatterns.some((pattern) => pattern.test(clean));
583
+ }
584
+ function latestActivityText(line) {
585
+ return oneLine(line).replace(/^i (?:will|can|need to|am going to)\s+/i, "").replace(/^i(?:'m|’m| am)\s+/i, "");
586
+ }
587
+ function progressUpdateSummary(lines) {
588
+ const counts = new Map;
589
+ for (const line of lines)
590
+ counts.set(line.toLowerCase(), (counts.get(line.toLowerCase()) ?? 0) + 1);
591
+ const exactRepeatCount = Math.max(...counts.values());
592
+ const latest = latestActivityText(lines[lines.length - 1]);
593
+ const repeat = exactRepeatCount > 1 ? ` · repeated ${exactRepeatCount}×` : "";
594
+ return `↻ ${lines.length} progress updates${repeat} · latest: ${latest}`;
595
+ }
596
+ function compactRecentOutputLines(recentOutput) {
597
+ const lines = [];
598
+ const noisyLines = [];
599
+ const otherLines = [];
600
+ for (const rawLine of recentOutput ?? []) {
601
+ const line = oneLine(rawLine);
602
+ if (!line || line === "(running...)")
603
+ continue;
604
+ lines.push(line);
605
+ (isNoisyStatusLine(line) ? noisyLines : otherLines).push(line);
606
+ }
607
+ if (noisyLines.length >= 4 && !otherLines.some(hasLiveOutputSignal)) {
608
+ if (otherLines.length === 0) {
609
+ return [
610
+ progressUpdateSummary(noisyLines),
611
+ "pattern: repeated short status lines"
612
+ ];
613
+ }
614
+ const visibleTail = otherLines.slice(-3);
615
+ const hiddenSignals = otherLines.slice(0, -3).filter(hasLiveOutputSignal);
616
+ return [
617
+ progressUpdateSummary(noisyLines),
618
+ ...hiddenSignals.length > 0 ? [`… ${hiddenSignals.length} older signal ${hiddenSignals.length === 1 ? "line" : "lines"}: ${hiddenSignals.at(-1)}`] : [],
619
+ ...visibleTail
620
+ ].slice(0, 5);
621
+ }
622
+ if (lines.length <= 5)
623
+ return lines;
624
+ const tail = lines.slice(-5);
625
+ const hiddenSignals = lines.slice(0, -5).filter(hasLiveOutputSignal);
626
+ if (hiddenSignals.length === 0)
627
+ return tail;
628
+ return [
629
+ `… ${hiddenSignals.length} older signal ${hiddenSignals.length === 1 ? "line" : "lines"}: ${hiddenSignals.at(-1)}`,
630
+ ...lines.slice(-4)
631
+ ];
632
+ }
633
+ function compactWorkflowError(error) {
634
+ const outputMatch = error.match(/(?:^|\n)Output:\s*([\s\S]+)/);
635
+ if (!outputMatch)
636
+ return oneLine(error);
637
+ const prefix = oneLine(error.slice(0, outputMatch.index)).replace(/:$/, "") || "Failed";
638
+ const outputLines = outputMatch[1].split(/\r?\n/).map(oneLine).filter(Boolean);
639
+ const allOutputLinesAreNoisy = outputLines.length > 0 && outputLines.every(isNoisyStatusLine);
640
+ const latest = outputLines.at(-1);
641
+ return allOutputLinesAreNoisy && latest ? `${prefix} · latest: ${latestActivityText(latest)}` : `${prefix} · ${oneLine(outputMatch[1] ?? "")}`;
642
+ }
643
+ const WORKFLOW_LIVE_ROW_LIMIT = 8;
644
+ function visibleWorkflowRows(rows) {
645
+ if (rows.length <= WORKFLOW_LIVE_ROW_LIMIT)
646
+ return { rows, hiddenRows: 0 };
647
+ const selected = new Set;
648
+ const add = (row) => {
649
+ if (selected.size >= WORKFLOW_LIVE_ROW_LIMIT || selected.has(row.key))
650
+ return;
651
+ selected.add(row.key);
652
+ };
653
+ for (const row of [...rows].reverse()) {
654
+ if (row.state === "failed" || row.state === "detached")
655
+ add(row);
656
+ }
657
+ for (const row of [...rows].reverse())
658
+ add(row);
659
+ return {
660
+ rows: rows.filter((row) => selected.has(row.key)),
661
+ hiddenRows: rows.length - selected.size
662
+ };
663
+ }
141
664
  function snapshotNowForProgress(progress) {
142
665
  if (progress.currentToolStartedAt !== undefined && progress.durationMs !== undefined)
143
666
  return progress.currentToolStartedAt + progress.durationMs;
144
667
  return progress.lastActivityAt;
145
668
  }
669
+ function renderToolArgsPreview(value, maxLength, expanded) {
670
+ const normalized = sanitizeDisplayText(value);
671
+ if (expanded || normalized.length <= maxLength)
672
+ return normalized;
673
+ return `${truncateDisplayText(normalized, maxLength)}...`;
674
+ }
146
675
  function formatCurrentToolLine(progress, availableWidth, expanded, snapshotNow) {
147
676
  if (!progress.currentTool)
148
677
  return;
149
678
  const maxToolArgsLen = Math.max(50, availableWidth - 20);
150
- const toolArgsPreview = progress.currentToolArgs ? expanded || progress.currentToolArgs.length <= maxToolArgsLen ? progress.currentToolArgs : `${progress.currentToolArgs.slice(0, maxToolArgsLen)}...` : "";
679
+ const toolArgsPreview = progress.currentToolArgs ? renderToolArgsPreview(progress.currentToolArgs, maxToolArgsLen, expanded) : "";
151
680
  const durationSuffix = progress.currentToolStartedAt !== undefined && snapshotNow !== undefined ? ` | ${formatDuration(Math.max(0, snapshotNow - progress.currentToolStartedAt))}` : "";
152
681
  return toolArgsPreview ? `${progress.currentTool}: ${toolArgsPreview}${durationSuffix}` : `${progress.currentTool}${durationSuffix}`;
153
682
  }
@@ -205,6 +734,8 @@ function firstOutputLine(text) {
205
734
  function resultStatusLine(result, output) {
206
735
  if (result.detached)
207
736
  return result.detachedReason ? `Detached: ${result.detachedReason}` : "Detached";
737
+ if (result.stopped)
738
+ return "Stopped";
208
739
  if (result.interrupted)
209
740
  return "Paused";
210
741
  if (result.exitCode !== 0)
@@ -215,27 +746,171 @@ function resultStatusLine(result, output) {
215
746
  return "Done (no text output)";
216
747
  return "Done";
217
748
  }
218
- function resultGlyph(result, output, theme, running = result.progress?.status === "running", seed = progressRunningSeed(result.progress ?? result.progressSummary), frame) {
219
- if (running) {
220
- if (frame !== undefined)
221
- return theme.fg("accent", runningGlyph((seed ?? 0) + frame));
222
- return theme.fg("accent", runningGlyph(seed));
749
+ function semanticResultPresentation(input) {
750
+ if (input.running) {
751
+ const glyph = input.frame !== undefined ? runningGlyph((input.seed ?? 0) + input.frame) : runningGlyph(input.seed);
752
+ return { glyph, label: "running", tone: "accent" };
223
753
  }
224
- if (result.detached)
225
- return theme.fg("warning", "■");
226
- if (result.interrupted)
227
- return theme.fg("warning", "■");
228
- if (result.exitCode !== 0)
229
- return theme.fg("error", "✗");
230
- if (hasEmptyTextOutputWithoutOutputTarget(result.task, output))
231
- return theme.fg("warning", "✓");
232
- return theme.fg("success", "✓");
754
+ if (input.detached)
755
+ return { glyph: "■", label: "detached", tone: "warning" };
756
+ if (input.stopped)
757
+ return { glyph: "■", label: "stopped", tone: "warning" };
758
+ if (input.interrupted)
759
+ return { glyph: "■", label: "paused", tone: "warning" };
760
+ if (input.failed)
761
+ return { glyph: "✗", label: "failed", tone: "error" };
762
+ if (input.partial)
763
+ return { glyph: "■", label: "partial", tone: "warning" };
764
+ return { glyph: "✓", label: "completed", tone: input.completedWithoutOutput ? "warning" : "success" };
765
+ }
766
+ function hasTerminalResultFlag(result) {
767
+ return Boolean(result.detached || result.stopped || result.interrupted);
768
+ }
769
+ function hasTerminalResult(result) {
770
+ if (hasTerminalResultFlag(result))
771
+ return true;
772
+ const status = result.progress?.status;
773
+ if (status === "running" || status === "pending")
774
+ return false;
775
+ return result.exitCode !== undefined;
776
+ }
777
+ function isResultRunning(result, status = result.progress?.status) {
778
+ return status === "running" && !hasTerminalResultFlag(result);
779
+ }
780
+ function detailsHaveRunningResult(details) {
781
+ return details.progress?.some((progress) => {
782
+ if (progress.status !== "running")
783
+ return false;
784
+ const result = details.results.find((entry) => entry.progress?.index === progress.index) ?? details.results[progress.index];
785
+ return !result || !hasTerminalResultFlag(result);
786
+ }) || details.results.some((result) => isResultRunning(result)) || workflowGraphHasStatus(details, ["running"]);
787
+ }
788
+ function resultPresentation(result, output, running = isResultRunning(result), seed = progressRunningSeed(result.progress ?? result.progressSummary), frame) {
789
+ return semanticResultPresentation({
790
+ running,
791
+ detached: result.detached,
792
+ stopped: result.stopped,
793
+ interrupted: result.interrupted,
794
+ failed: result.exitCode !== 0,
795
+ completedWithoutOutput: hasEmptyTextOutputWithoutOutputTarget(result.task, output),
796
+ seed,
797
+ frame
798
+ });
799
+ }
800
+ function resultGlyph(result, output, theme, running = isResultRunning(result), seed = progressRunningSeed(result.progress ?? result.progressSummary), frame) {
801
+ const presentation = resultPresentation(result, output, running, seed, frame);
802
+ return theme.fg(presentation.tone, presentation.glyph);
803
+ }
804
+ function styledResultPresentation(presentation, theme) {
805
+ return {
806
+ glyph: theme.fg(presentation.tone, presentation.glyph),
807
+ label: theme.fg(presentation.tone, presentation.label)
808
+ };
233
809
  }
234
810
  function compactCurrentActivity(progress) {
235
811
  const snapshotNow = snapshotNowForProgress(progress);
236
812
  return formatCurrentToolLine(progress, getTermWidth() - 4, false, snapshotNow) ?? buildLiveStatusLine(progress, snapshotNow) ?? "thinking…";
237
813
  }
238
- export function widgetRenderKey(job) {
814
+ function textDigest(text) {
815
+ return createHash("sha256").update(text).digest("hex").slice(0, 16);
816
+ }
817
+ function displayTextRenderKey(text) {
818
+ const normalized = sanitizeDisplayText(text);
819
+ return [normalized.length, textDigest(normalized)];
820
+ }
821
+ function expandedStepActivityRenderKey(step) {
822
+ return [
823
+ step.recentTools?.slice(-3).map((tool) => [tool.tool, displayTextRenderKey(tool.args), tool.endMs]),
824
+ compactRecentOutputLines(step.recentOutput).map(displayTextRenderKey)
825
+ ];
826
+ }
827
+ function widgetStepRenderKey(step, index, expanded = false) {
828
+ return [
829
+ step.index ?? index,
830
+ step.agent,
831
+ step.sessionName,
832
+ step.workflowKey,
833
+ step.phase,
834
+ step.label,
835
+ step.status,
836
+ step.activityState,
837
+ step.lastActivityAt,
838
+ step.currentTool,
839
+ step.currentToolArgs,
840
+ step.currentToolStartedAt,
841
+ step.currentPath,
842
+ step.turnCount,
843
+ step.toolCount,
844
+ step.startedAt,
845
+ step.endedAt,
846
+ step.durationMs,
847
+ step.tokens?.total,
848
+ step.model,
849
+ step.thinking,
850
+ step.context,
851
+ step.description,
852
+ step.outputName,
853
+ step.structured,
854
+ step.acceptance?.status,
855
+ step.acceptance?.reviewResult?.status,
856
+ step.review?.status,
857
+ step.toolBudgetBlocked,
858
+ step.turnBudgetExceeded,
859
+ step.timedOut,
860
+ step.stopped,
861
+ step.execution?.status,
862
+ step.execution?.error,
863
+ step.execution?.timedOut,
864
+ step.execution?.interrupted,
865
+ step.execution?.stopped,
866
+ step.execution?.detached,
867
+ step.watchdog?.phase,
868
+ step.error,
869
+ expanded ? expandedStepActivityRenderKey(step) : undefined,
870
+ nestedRenderKey(step.children, expanded)
871
+ ];
872
+ }
873
+ function nestedRenderKey(children, expanded = false) {
874
+ return (children ?? []).map((child) => [
875
+ child.id,
876
+ child.state,
877
+ child.agent,
878
+ child.sessionName,
879
+ child.model,
880
+ child.thinking,
881
+ child.activityState,
882
+ child.lastActivityAt,
883
+ child.currentTool,
884
+ child.currentToolStartedAt,
885
+ child.currentPath,
886
+ child.turnCount,
887
+ child.toolCount,
888
+ child.startedAt,
889
+ child.endedAt,
890
+ child.lastUpdate,
891
+ child.error,
892
+ child.totalTokens?.total,
893
+ child.steps?.map((step, index) => widgetStepRenderKey({ ...step, agent: step.agent, status: step.status }, index, expanded)),
894
+ nestedRenderKey(child.children, expanded)
895
+ ]);
896
+ }
897
+ function hostStepRenderKey(row) {
898
+ return [
899
+ row.kind,
900
+ row.name,
901
+ row.state,
902
+ row.provider,
903
+ row.role,
904
+ row.verdict,
905
+ row.reasonCode,
906
+ row.detail,
907
+ row.target,
908
+ row.freshness,
909
+ row.reportPath
910
+ ];
911
+ }
912
+ export function widgetRenderKey(job, expanded = false) {
913
+ const projection = buildWorkflowWidgetProjection(job);
239
914
  return JSON.stringify({
240
915
  asyncDir: job.asyncDir,
241
916
  status: job.status,
@@ -251,14 +926,24 @@ export function widgetRenderKey(job) {
251
926
  currentStep: job.currentStep,
252
927
  chainStepCount: job.chainStepCount,
253
928
  parallelGroups: job.parallelGroups,
254
- steps: job.steps,
255
- nestedChildren: job.nestedChildren,
929
+ workflowHostSteps: projectAsyncWorkflowRows([], job.hostSteps).map(hostStepRenderKey),
930
+ workflowGraph: job.mode === "workflow" && job.workflowGraph ? {
931
+ currentNodeId: job.workflowGraph.currentNodeId,
932
+ stages: projection.stages.map((node) => [node.id, node.status, node.agent, node.phase, node.label, node.flatIndex, node.outputName, node.structured, node.error])
933
+ } : undefined,
934
+ preflight: expanded ? job.preflight : job.preflight ? formatWorkflowPreflightPlanSummary(job.preflight) : undefined,
935
+ preflightWarnings: expanded ? job.workflow?.preflightWarnings : job.workflow?.preflightWarnings?.length || undefined,
936
+ steps: job.steps?.map((step, index) => widgetStepRenderKey(step, index, expanded)),
937
+ nestedChildren: nestedRenderKey(job.nestedChildren, expanded),
938
+ lane: laneRenderKey(job, projection),
256
939
  stepsTotal: job.stepsTotal,
257
940
  runningSteps: job.runningSteps,
258
941
  completedSteps: job.completedSteps,
259
942
  activeParallelGroup: job.activeParallelGroup,
260
943
  startedAt: job.startedAt,
261
944
  updatedAt: job.updatedAt,
945
+ timedOut: job.timedOut,
946
+ stopped: job.stopped,
262
947
  totalTokens: job.totalTokens
263
948
  });
264
949
  }
@@ -281,6 +966,38 @@ function widgetJobName(job) {
281
966
  return formatWidgetAgents(job.agents);
282
967
  return job.mode ?? "subagent";
283
968
  }
969
+ function isSingleChildAsyncJob(job) {
970
+ return job.mode === "single" && job.steps?.length === 1 && shouldSuppressSingleStep(job.chainStepCount, job.stepsTotal);
971
+ }
972
+ function isCompletedWidgetStepStatus(status) {
973
+ return status === "complete" || status === "completed";
974
+ }
975
+ function singleChildAgentName(job, step) {
976
+ return job.agents?.length === 1 ? job.agents[0] : step.agent || widgetJobName(job);
977
+ }
978
+ function hasSingleChildDetailEvidence(job, step) {
979
+ return Boolean(step.error?.trim() || step.execution?.error?.trim() || step.phase?.trim() || step.label?.trim() || step.workflowKey?.trim() || step.outputName?.trim() || step.lane || job.workflowKey?.trim() || job.lane || laneTraceForJob(job)?.phase?.trim() || laneTraceForJob(job)?.label?.trim() || step.timedOut || step.stopped || step.execution?.timedOut || step.execution?.interrupted || step.execution?.stopped || step.execution?.detached || job.timedOut || job.stopped || ["failed", "partial", "paused", "stopped", "detached"].includes(step.execution?.status ?? "") || step.acceptance?.status === "rejected" || step.acceptance?.reviewResult?.status === "blockers" || step.review?.status === "blockers");
980
+ }
981
+ function shouldCollapseSingleChildDetails(job, step) {
982
+ if (!isSingleChildAsyncJob(job) || hasSingleChildDetailEvidence(job, step))
983
+ return false;
984
+ if (job.status === "running")
985
+ return step.status === "running";
986
+ if (job.status === "complete")
987
+ return isCompletedWidgetStepStatus(step.status);
988
+ return false;
989
+ }
990
+ function singleChildTask(job, step) {
991
+ const task = compactTaskText(step.description, step.label);
992
+ if (task)
993
+ return task;
994
+ const sessionName = step.sessionName?.trim();
995
+ if (!sessionName)
996
+ return;
997
+ const agentName = singleChildAgentName(job, step);
998
+ const sessionTask = stripRepeatedAgentPrefix(sessionName, agentName);
999
+ return sessionTask === agentName ? undefined : compactTaskText(sessionTask);
1000
+ }
284
1001
  function widgetActivity(job) {
285
1002
  const facts = [];
286
1003
  if (job.currentTool && job.currentToolStartedAt !== undefined && job.updatedAt !== undefined)
@@ -306,6 +1023,10 @@ function widgetActivity(job) {
306
1023
  return "queued…";
307
1024
  if (job.status === "paused")
308
1025
  return "Paused";
1026
+ if (job.status === "stopped")
1027
+ return "Stopped";
1028
+ if (job.status === "partial")
1029
+ return "Partial";
309
1030
  if (job.status === "failed")
310
1031
  return "Failed";
311
1032
  return "Done";
@@ -328,26 +1049,30 @@ function widgetJobsRunningSeed(jobs) {
328
1049
  seed = runningSeed(seed, widgetJobRunningSeed(job));
329
1050
  return seed;
330
1051
  }
331
- function widgetStatusGlyph(job, theme) {
1052
+ function widgetStatusGlyph(job, theme, frame) {
332
1053
  if (job.status === "running")
333
- return theme.fg("accent", runningGlyph(widgetJobRunningSeed(job)));
1054
+ return theme.fg("accent", runningGlyph(animatedSeed(widgetJobRunningSeed(job), frame)));
334
1055
  if (job.status === "queued")
335
1056
  return theme.fg("muted", "◦");
336
1057
  if (job.status === "complete")
337
1058
  return theme.fg("success", "✓");
338
1059
  if (job.status === "paused")
339
1060
  return theme.fg("warning", "■");
1061
+ if (job.status === "stopped")
1062
+ return theme.fg("warning", "■");
340
1063
  return theme.fg("error", "✗");
341
1064
  }
342
- function widgetStepGlyph(status, theme, seed) {
1065
+ function widgetStepGlyph(status, theme, seed, frame) {
343
1066
  if (status === "running")
344
- return theme.fg("accent", runningGlyph(seed));
1067
+ return theme.fg("accent", runningGlyph(animatedSeed(seed, frame)));
345
1068
  if (status === "complete" || status === "completed")
346
1069
  return theme.fg("success", "✓");
347
1070
  if (status === "failed")
348
1071
  return theme.fg("error", "✗");
349
1072
  if (status === "paused")
350
1073
  return theme.fg("warning", "■");
1074
+ if (status === "stopped")
1075
+ return theme.fg("warning", "■");
351
1076
  return theme.fg("muted", "◦");
352
1077
  }
353
1078
  function widgetStepStatus(status, theme) {
@@ -359,6 +1084,8 @@ function widgetStepStatus(status, theme) {
359
1084
  return theme.fg("error", "failed");
360
1085
  if (status === "paused")
361
1086
  return theme.fg("warning", "paused");
1087
+ if (status === "stopped")
1088
+ return theme.fg("warning", "stopped");
362
1089
  return theme.fg("dim", status);
363
1090
  }
364
1091
  function widgetStepActivity(step, snapshotNow) {
@@ -374,7 +1101,7 @@ function widgetStepActivity(step, snapshotNow) {
374
1101
  if (step.toolCount !== undefined)
375
1102
  facts.push(`${step.toolCount} tools`);
376
1103
  if (step.tokens?.total)
377
- facts.push(formatTokenStat(step.tokens.total));
1104
+ facts.push(formatTokenUsage(step.tokens, "token"));
378
1105
  const activity = buildLiveStatusLine(step, snapshotNow);
379
1106
  if (activity && facts.length)
380
1107
  return `${activity} · ${facts.join(" · ")}`;
@@ -382,7 +1109,7 @@ function widgetStepActivity(step, snapshotNow) {
382
1109
  return activity;
383
1110
  return facts.join(" · ");
384
1111
  }
385
- function widgetChainDetails(job, theme, expanded = false, width = getTermWidth()) {
1112
+ function widgetChainDetails(job, theme, expanded = false, width = getTermWidth(), frame) {
386
1113
  if (!job.steps?.length)
387
1114
  return [];
388
1115
  const total = job.chainStepCount ?? job.steps.length;
@@ -390,8 +1117,7 @@ function widgetChainDetails(job, theme, expanded = false, width = getTermWidth()
390
1117
  for (const span of buildAsyncChainStepSpans(total, job.steps.length, job.parallelGroups)) {
391
1118
  const steps = job.steps.slice(span.start, span.start + span.count);
392
1119
  if (span.isParallel) {
393
- const status = aggregateStepStatus(steps);
394
- lines.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))}`);
1120
+ lines.push(...parallelWidgetGroupDetails(job, theme, { steps, total: span.count, stepIndex: span.stepIndex, chainTotal: total }, expanded, width, frame, false));
395
1121
  continue;
396
1122
  }
397
1123
  const step = steps[0];
@@ -399,17 +1125,20 @@ function widgetChainDetails(job, theme, expanded = false, width = getTermWidth()
399
1125
  lines.push(` ${theme.fg("dim", `◦ Step ${span.stepIndex + 1}/${total}: pending`)}`);
400
1126
  continue;
401
1127
  }
402
- lines.push(...foregroundStyleWidgetStepLines(job, theme, step, "Step", span.stepIndex + 1, total, expanded, width));
1128
+ lines.push(...foregroundStyleWidgetStepLines(job, theme, step, "Step", span.stepIndex + 1, total, expanded, width, frame));
403
1129
  }
404
1130
  return lines;
405
1131
  }
406
- function widgetParallelAgentDetails(job, theme, expanded = false, width = getTermWidth()) {
1132
+ function widgetParallelAgentDetails(job, theme, expanded = false, width = getTermWidth(), frame) {
407
1133
  if (!job.steps?.length)
408
1134
  return [];
409
1135
  if (job.mode !== "parallel" && job.mode !== "chain")
410
1136
  return [];
411
1137
  if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length)
412
- return widgetChainDetails(job, theme, expanded, width);
1138
+ return widgetChainDetails(job, theme, expanded, width, frame);
1139
+ const group = activeParallelWidgetGroup(job);
1140
+ if (group)
1141
+ return parallelWidgetGroupDetails(job, theme, group, expanded, width, frame, Boolean(job.activeParallelGroup));
413
1142
  const total = job.stepsTotal ?? job.steps.length;
414
1143
  const lines = [];
415
1144
  for (const [index, step] of job.steps.entries()) {
@@ -417,8 +1146,13 @@ function widgetParallelAgentDetails(job, theme, expanded = false, width = getTer
417
1146
  const activity = widgetStepActivity(step, job.updatedAt);
418
1147
  const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
419
1148
  const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
420
- lines.push(` ${theme.fg("dim", `${marker} ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index))} ${itemTitle} ${index + 1}/${total}: ${step.agent} · ${widgetStepStatus(step.status, theme)}${modelDisplay}${activity ? ` · ${activity}` : ""}`)}`);
421
- for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 8 : 1))
1149
+ const label = compactTaskText(step.description, step.label);
1150
+ const display = step.sessionName?.trim() || (label ? `${label} (${step.agent})` : step.agent);
1151
+ lines.push(` ${theme.fg("dim", `${marker} ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index), frame)} ${itemTitle} ${index + 1}/${total}: ${display} · ${widgetStepStatus(step.status, theme)}${modelDisplay}${activity ? ` · ${activity}` : ""}`)}`);
1152
+ const lane = projectAsyncLane(job, step);
1153
+ if (lane)
1154
+ lines.push(...formatLaneProjectionLines(lane, theme, " "));
1155
+ for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 8 : 6))
422
1156
  lines.push(` ${nestedLine}`);
423
1157
  }
424
1158
  return lines;
@@ -466,18 +1200,16 @@ function buildChainStepSpans(details) {
466
1200
  }
467
1201
  return spans;
468
1202
  }
469
- function isChainParallelGroupActive(details) {
470
- if (details.mode !== "chain")
471
- return false;
472
- if (details.currentStepIndex === undefined)
473
- return false;
474
- return buildChainStepSpans(details).some((span) => span.stepIndex === details.currentStepIndex && span.isParallel);
475
- }
476
1203
  function buildAsyncChainStepSpans(total, stepCount, parallelGroups = []) {
1204
+ const groupsByStep = new Map;
1205
+ for (const group of parallelGroups) {
1206
+ if (!groupsByStep.has(group.stepIndex))
1207
+ groupsByStep.set(group.stepIndex, group);
1208
+ }
477
1209
  const spans = [];
478
1210
  let flatIndex = 0;
479
1211
  for (let stepIndex = 0;stepIndex < total; stepIndex++) {
480
- const group = parallelGroups.find((candidate) => candidate.stepIndex === stepIndex);
1212
+ const group = groupsByStep.get(stepIndex);
481
1213
  if (group) {
482
1214
  spans.push({ stepIndex, start: group.start, count: group.count, isParallel: true });
483
1215
  flatIndex = Math.max(flatIndex, group.start + group.count);
@@ -488,6 +1220,93 @@ function buildAsyncChainStepSpans(total, stepCount, parallelGroups = []) {
488
1220
  }
489
1221
  return spans;
490
1222
  }
1223
+ function normalizedParallelDisplayText(value) {
1224
+ if (!value?.trim())
1225
+ return;
1226
+ const normalized = oneLine(value);
1227
+ return normalized && normalized !== PROMPT_REDACTED ? normalized : undefined;
1228
+ }
1229
+ function parallelWidgetStepDisplayName(step) {
1230
+ const agent = normalizedParallelDisplayText(step.agent);
1231
+ const explicitLabel = normalizedParallelDisplayText(step.label);
1232
+ if (explicitLabel) {
1233
+ const label = compactTaskText(step.description, explicitLabel) ?? explicitLabel;
1234
+ return agent ? `${label} (${agent})` : label;
1235
+ }
1236
+ const sessionName = normalizedParallelDisplayText(step.sessionName);
1237
+ if (sessionName) {
1238
+ const sessionTask = stripRepeatedAgentPrefix(sessionName, agent);
1239
+ if (sessionTask && sessionTask !== PROMPT_REDACTED && sessionTask.toLowerCase() !== agent?.toLowerCase())
1240
+ return sessionTask;
1241
+ }
1242
+ return compactTaskText(step.description) ?? agent ?? "subagent";
1243
+ }
1244
+ function parallelWidgetStepPriority(step) {
1245
+ if (step.status === "running" || step.activityState === "needs_attention")
1246
+ return 0;
1247
+ switch (step.status) {
1248
+ case "failed":
1249
+ case "rejected":
1250
+ case "partial":
1251
+ case "stopped":
1252
+ case "paused":
1253
+ return 1;
1254
+ case "complete":
1255
+ case "completed":
1256
+ return 2;
1257
+ default:
1258
+ return 3;
1259
+ }
1260
+ }
1261
+ function parallelWidgetStepRows(steps, total, prioritizeActive) {
1262
+ const indexed = steps.map((step, index) => ({ step, index, displayName: parallelWidgetStepDisplayName(step) }));
1263
+ if (prioritizeActive)
1264
+ indexed.sort((left, right) => parallelWidgetStepPriority(left.step) - parallelWidgetStepPriority(right.step) || left.index - right.index);
1265
+ return withDuplicateLabelDiscriminators(indexed, total).map(({ displayName, ...row }) => row);
1266
+ }
1267
+ function activeParallelWidgetGroup(job) {
1268
+ const steps = job.steps ?? [];
1269
+ if (!steps.length)
1270
+ return;
1271
+ if (job.mode === "parallel")
1272
+ return { steps, total: job.stepsTotal ?? steps.length };
1273
+ if (job.mode !== "chain" || !job.activeParallelGroup)
1274
+ return;
1275
+ const chainTotal = job.chainStepCount ?? job.stepsTotal ?? steps.length;
1276
+ const spans = buildAsyncChainStepSpans(chainTotal, steps.length, job.parallelGroups);
1277
+ const currentStep = job.currentStep;
1278
+ const activeSpan = currentStep === undefined ? spans.find((span) => span.isParallel) : spans.find((span) => span.isParallel && currentStep >= span.start && currentStep < span.start + span.count);
1279
+ return {
1280
+ steps,
1281
+ total: activeSpan?.count ?? job.stepsTotal ?? steps.length,
1282
+ stepIndex: activeSpan?.stepIndex,
1283
+ chainTotal
1284
+ };
1285
+ }
1286
+ function parallelWidgetGroupHeader(group, theme, frame) {
1287
+ const status = aggregateStepStatus(group.steps);
1288
+ const label = group.stepIndex !== undefined && group.chainTotal !== undefined ? `Step ${group.stepIndex + 1}/${group.chainTotal}: parallel group` : "parallel group";
1289
+ return ` ${widgetStepGlyph(status, theme, widgetStepsRunningSeed(group.steps), frame)} ${themeBold(theme, label)} ${theme.fg("dim", "·")} ${theme.fg("dim", formatParallelOutcome(group.steps, group.total))}`;
1290
+ }
1291
+ function parallelWidgetGroupDetails(job, theme, group, expanded, width, frame, prioritizeActive) {
1292
+ const lines = [parallelWidgetGroupHeader(group, theme, frame)];
1293
+ const rows = parallelWidgetStepRows(group.steps, group.total, prioritizeActive);
1294
+ for (const [rowIndex, row] of rows.entries()) {
1295
+ const marker = rowIndex === rows.length - 1 && rows.length >= group.total ? "└─" : "├─";
1296
+ lines.push(...foregroundStyleWidgetStepLines(job, theme, row.step, "Agent", row.index + 1, group.total, expanded, width, frame, {
1297
+ rowLabel: row.rowLabel,
1298
+ rowIndent: " ",
1299
+ detailIndent: " ",
1300
+ rowMarker: marker,
1301
+ includeCurrentPath: true
1302
+ }));
1303
+ }
1304
+ for (let index = rows.length;index < group.total; index++) {
1305
+ const marker = index === group.total - 1 ? "└─" : "├─";
1306
+ lines.push(` ${marker} ${theme.fg("muted", "◦")} ${theme.fg("dim", "pending")}`);
1307
+ }
1308
+ return lines;
1309
+ }
491
1310
  function isDoneResult(result) {
492
1311
  const status = result.progress?.status;
493
1312
  if (status === "completed")
@@ -501,28 +1320,71 @@ function isDoneResult(result) {
501
1320
  function workflowGraphHasStatus(details, statuses) {
502
1321
  return details.workflowGraph?.nodes.some((node) => statuses.includes(node.status)) ?? false;
503
1322
  }
1323
+ function chainSpanStatus(details, span) {
1324
+ if (span.status)
1325
+ return span.status;
1326
+ const results = details.results.slice(span.start, span.start + span.count);
1327
+ if (results.some((result) => isResultRunning(result)))
1328
+ return "running";
1329
+ if (results.some((result) => !hasTerminalResultFlag(result) && (result.progress?.status === "failed" || result.exitCode !== 0) && !isResultRunning(result)))
1330
+ return "failed";
1331
+ if (results.some((result) => result.stopped))
1332
+ return "stopped";
1333
+ if (results.some((result) => result.interrupted))
1334
+ return "paused";
1335
+ if (results.some((result) => result.detached || result.progress?.status === "detached"))
1336
+ return "detached";
1337
+ if (results.length < span.count)
1338
+ return "pending";
1339
+ if (results.length > 0 && results.every(isDoneResult))
1340
+ return "completed";
1341
+ return "pending";
1342
+ }
1343
+ function withDuplicateForegroundLabels(entries, total) {
1344
+ return withDuplicateLabelDiscriminators(entries.map((entry) => ({ ...entry, index: entry.displayIndex, displayName: entry.agentName })), total).map(({ displayName, rowLabel, ...entry }) => ({
1345
+ ...entry,
1346
+ rowLabel: rowLabel === displayName ? undefined : rowLabel.slice(0, -(displayName.length + 2))
1347
+ }));
1348
+ }
504
1349
  function buildChainRenderEntries(details, label) {
505
1350
  if (details.mode !== "chain" || !label.hasParallelInChain || label.showActiveGroupOnly)
506
1351
  return;
507
1352
  const entries = [];
508
1353
  for (const span of buildChainStepSpans(details)) {
509
- if (span.isParallel && span.count === 0) {
1354
+ if (span.isParallel) {
510
1355
  entries.push({
511
- kind: "placeholder",
512
- rowNumber: span.stepIndex + 1,
513
- stepLabel: `Step ${span.stepIndex + 1}`,
514
- agentName: span.label ?? details.chainAgents?.[span.stepIndex] ?? `step-${span.stepIndex + 1}`,
515
- status: span.status ?? "pending",
1356
+ kind: "group",
1357
+ stepLabel: `Step ${span.stepIndex + 1}/${label.logicalStepCount}: parallel group`,
1358
+ groupLabel: span.label?.trim() || undefined,
1359
+ status: chainSpanStatus(details, span),
516
1360
  error: span.error
517
1361
  });
1362
+ const groupEntries = [];
1363
+ for (let index = span.start;index < span.start + span.count; index++) {
1364
+ const result = details.results[index];
1365
+ const localIndex = foregroundResultIndex(details, index);
1366
+ const displayIndex = localIndex >= span.start && localIndex < span.start + span.count ? localIndex - span.start : index - span.start;
1367
+ groupEntries.push({
1368
+ kind: "result",
1369
+ resultIndex: index,
1370
+ rowNumber: index - span.start + 1,
1371
+ agentName: foregroundResultDisplayName(details, index, result, `agent-${displayIndex + 1}`),
1372
+ displayIndex,
1373
+ isParallel: true
1374
+ });
1375
+ }
1376
+ entries.push(...withDuplicateForegroundLabels(groupEntries, span.count));
518
1377
  continue;
519
1378
  }
520
1379
  for (let index = span.start;index < span.start + span.count; index++) {
1380
+ const result = details.results[index];
521
1381
  entries.push({
522
1382
  kind: "result",
523
1383
  resultIndex: index,
524
- rowNumber: index + 1,
525
- agentName: details.results[index]?.agent ?? details.chainAgents?.[span.stepIndex] ?? `step-${span.stepIndex + 1}`
1384
+ rowNumber: span.stepIndex + 1,
1385
+ rowLabel: resultRowLabel(label, span.stepIndex + 1),
1386
+ agentName: foregroundResultDisplayName(details, index, result, details.chainAgents?.[span.stepIndex] ?? `step-${span.stepIndex + 1}`),
1387
+ displayIndex: foregroundResultIndex(details, index)
526
1388
  });
527
1389
  }
528
1390
  }
@@ -531,7 +1393,7 @@ function buildChainRenderEntries(details, label) {
531
1393
  function buildMultiProgressLabel(details, hasRunning) {
532
1394
  const stepSpans = buildChainStepSpans(details);
533
1395
  const hasParallelInChain = details.mode === "chain" && stepSpans.some((span) => span.isParallel);
534
- const activeParallelGroup = isChainParallelGroupActive(details);
1396
+ const activeParallelGroup = details.mode === "chain" && details.currentStepIndex !== undefined && stepSpans.some((span) => span.stepIndex === details.currentStepIndex && span.isParallel);
535
1397
  const itemTitle = details.mode === "parallel" || activeParallelGroup ? "Agent" : "Step";
536
1398
  if (details.mode === "parallel") {
537
1399
  const totalCount = details.totalSteps ?? details.results.length;
@@ -546,13 +1408,13 @@ function buildMultiProgressLabel(details, hasRunning) {
546
1408
  const index = result.progress?.index ?? progressFromArray?.index ?? i;
547
1409
  if (index < 0 || index >= totalCount)
548
1410
  continue;
549
- const status = result.progress?.status ?? (result.interrupted || result.detached ? "detached" : result.exitCode === 0 ? "completed" : "failed");
1411
+ const status = result.stopped ? "stopped" : result.interrupted || result.detached ? "detached" : result.progress?.status ?? (result.exitCode === 0 ? "completed" : "failed");
550
1412
  statuses[index] = status;
551
1413
  }
552
1414
  const running = statuses.filter((status) => status === "running").length;
553
1415
  const done = statuses.filter((status) => status === "completed").length;
554
1416
  const headerLabel = hasRunning ? `${formatAgentRunningLabel(running)} · ${done}/${totalCount} done` : `${done}/${totalCount} done`;
555
- return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: totalCount, showActiveGroupOnly: false };
1417
+ return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: totalCount, showActiveGroupOnly: false, logicalStepCount: totalCount };
556
1418
  }
557
1419
  if (activeParallelGroup) {
558
1420
  const currentStepIndex = details.currentStepIndex;
@@ -564,8 +1426,8 @@ function buildMultiProgressLabel(details, hasRunning) {
564
1426
  let done = 0;
565
1427
  for (let index = groupStart;index < groupEnd; index++) {
566
1428
  const progressEntry = details.progress?.find((progress) => progress.index === index);
567
- const resultEntry = details.results.find((result) => result.progress?.index === index);
568
- if (progressEntry?.status === "running") {
1429
+ const resultEntry = details.results.find((result) => result.progress?.index === index) ?? details.results[index];
1430
+ if (progressEntry?.status === "running" && (!resultEntry || !hasTerminalResultFlag(resultEntry))) {
569
1431
  running++;
570
1432
  continue;
571
1433
  }
@@ -578,7 +1440,7 @@ function buildMultiProgressLabel(details, hasRunning) {
578
1440
  }
579
1441
  const totalSteps = details.totalSteps ?? details.chainAgents?.length ?? 1;
580
1442
  const headerLabel = hasRunning ? `step ${currentStepIndex + 1}/${totalSteps} · parallel group: ${formatAgentRunningLabel(running)} · ${done}/${groupSize} done` : `step ${currentStepIndex + 1}/${totalSteps} · parallel group: ${done}/${groupSize} done`;
581
- return { headerLabel, itemTitle, totalCount: groupSize, hasParallelInChain, activeParallelGroup, groupStartIndex: groupStart, groupEndIndex: groupEnd, showActiveGroupOnly: true };
1443
+ return { headerLabel, itemTitle, totalCount: groupSize, hasParallelInChain, activeParallelGroup, groupStartIndex: groupStart, groupEndIndex: groupEnd, showActiveGroupOnly: true, logicalStepCount: totalSteps };
582
1444
  }
583
1445
  if (details.mode === "chain" && details.chainAgents?.length) {
584
1446
  const totalCount = details.totalSteps ?? details.chainAgents.length;
@@ -599,32 +1461,51 @@ function buildMultiProgressLabel(details, hasRunning) {
599
1461
  }).length;
600
1462
  const currentStep = details.currentStepIndex !== undefined ? details.currentStepIndex + 1 : Math.min(totalCount, doneLogical + (hasRunning ? 1 : 0));
601
1463
  const headerLabel = hasRunning ? `step ${currentStep}/${totalCount}` : `step ${doneLogical}/${totalCount}`;
602
- return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: details.results.length, showActiveGroupOnly: false };
1464
+ return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: details.results.length, showActiveGroupOnly: false, logicalStepCount: totalCount };
603
1465
  }
604
1466
  const totalCount = details.totalSteps ?? details.results.length;
605
1467
  const currentStep = details.currentStepIndex !== undefined ? details.currentStepIndex + 1 : Math.min(totalCount, details.results.filter(isDoneResult).length + (hasRunning ? 1 : 0));
606
1468
  const done = details.results.filter(isDoneResult).length;
607
1469
  const headerLabel = hasRunning ? `step ${currentStep}/${totalCount}` : `step ${done}/${totalCount}`;
608
- return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: details.results.length, showActiveGroupOnly: false };
1470
+ return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: details.results.length, showActiveGroupOnly: false, logicalStepCount: totalCount };
609
1471
  }
610
- function resultRowLabel(details, label, resultIndex, stepNumber) {
611
- if (details.mode === "chain" && label.hasParallelInChain) {
612
- const span = buildChainStepSpans(details).find((candidate) => resultIndex >= candidate.start && resultIndex < candidate.start + candidate.count);
613
- if (span?.isParallel)
614
- return `Agent ${resultIndex - span.start + 1}/${span.count}`;
615
- if (span)
616
- return `Step ${span.stepIndex + 1}`;
617
- }
618
- if (label.itemTitle === "Agent") {
619
- const localStepNumber = label.activeParallelGroup ? Math.max(1, stepNumber - label.groupStartIndex) : stepNumber;
620
- return `Agent ${localStepNumber}/${label.totalCount}`;
621
- }
622
- return `Step ${stepNumber}`;
1472
+ function resultRowLabel(label, stepNumber) {
1473
+ if (label.itemTitle === "Agent")
1474
+ return;
1475
+ if (shouldSuppressSingleStep(label.logicalStepCount))
1476
+ return;
1477
+ return `Step ${stepNumber}/${label.logicalStepCount}`;
1478
+ }
1479
+ function buildForegroundResultEntries(details, label, displayStart, displayEnd, useResultsDirectly) {
1480
+ const fallbackLabel = label.itemTitle.toLowerCase();
1481
+ const entries = Array.from({ length: displayEnd - displayStart }, (_, offset) => {
1482
+ const index = displayStart + offset;
1483
+ const result = details.results[index];
1484
+ const stableIndex = foregroundResultIndex(details, index);
1485
+ const rowNumber = label.showActiveGroupOnly ? index - label.groupStartIndex + 1 : stableIndex + 1;
1486
+ const fallbackAgent = useResultsDirectly ? result?.agent || `${fallbackLabel}-${rowNumber}` : details.chainAgents[index] || result?.agent || `${fallbackLabel}-${rowNumber}`;
1487
+ const displayIndex = label.activeParallelGroup && stableIndex >= label.groupStartIndex && stableIndex < label.groupEndIndex ? stableIndex - label.groupStartIndex : label.activeParallelGroup ? index - label.groupStartIndex : stableIndex;
1488
+ return {
1489
+ kind: "result",
1490
+ resultIndex: index,
1491
+ rowNumber,
1492
+ rowLabel: resultRowLabel(label, rowNumber),
1493
+ agentName: foregroundResultDisplayName(details, index, result, fallbackAgent),
1494
+ displayIndex
1495
+ };
1496
+ });
1497
+ return label.itemTitle === "Agent" ? withDuplicateForegroundLabels(entries, label.totalCount) : entries;
623
1498
  }
624
- function widgetStats(job, theme) {
1499
+ function widgetStats(job, theme, projection = buildWorkflowWidgetProjection(job)) {
625
1500
  const parts = [];
626
- const stepsTotal = job.stepsTotal ?? (job.agents?.length ?? 1);
627
- if (job.activeParallelGroup) {
1501
+ const { stageProgress } = projection;
1502
+ const stepsTotal = stageProgress?.total ?? job.stepsTotal ?? (job.agents?.length ?? 1);
1503
+ const isSingleChild = isSingleChildAsyncJob(job);
1504
+ if (stageProgress) {
1505
+ const currentStage = stageProgress.current !== undefined ? projection.stages[stageProgress.current] : undefined;
1506
+ const focus = currentStage ? [compactTaskText(undefined, currentStage.label) ?? boundedLaneValue(currentStage.id), currentStage.agent ? boundedLaneValue(currentStage.agent) : "", workflowNodeStatusLabel(currentStage.status)].filter(Boolean) : [];
1507
+ parts.push(["staged lane", stageProgress.current !== undefined ? `stage ${stageProgress.current + 1}/${stageProgress.total}` : `${stageProgress.total} stages`, ...focus].join(" · "));
1508
+ } else if (job.activeParallelGroup) {
628
1509
  const running = job.runningSteps ?? (job.status === "running" ? 1 : 0);
629
1510
  const done = job.completedSteps ?? (job.status === "complete" ? stepsTotal : 0);
630
1511
  if (job.mode === "parallel") {
@@ -642,10 +1523,10 @@ function widgetStats(job, theme) {
642
1523
  parts.push(`step ${logicalStep + 1}/${total} · parallel group: ${groupParts.join(" · ")}`);
643
1524
  }
644
1525
  } else if (job.currentStep !== undefined) {
645
- if (job.mode === "chain" && job.parallelGroups?.length) {
1526
+ if (job.mode === "chain") {
646
1527
  const total = job.chainStepCount ?? stepsTotal;
647
- parts.push(`step ${flatToLogicalStepIndex(job.currentStep, total, job.parallelGroups) + 1}/${total}`);
648
- } else {
1528
+ parts.push(`step ${flatToLogicalStepIndex(job.currentStep, total, job.parallelGroups ?? []) + 1}/${total}`);
1529
+ } else if (!isSingleChild) {
649
1530
  parts.push(`step ${job.currentStep + 1}/${stepsTotal}`);
650
1531
  }
651
1532
  } else if (stepsTotal > 1) {
@@ -654,7 +1535,7 @@ function widgetStats(job, theme) {
654
1535
  if (job.toolCount !== undefined)
655
1536
  parts.push(formatToolUseStat(job.toolCount));
656
1537
  if (job.totalTokens?.total)
657
- parts.push(formatTokenStat(job.totalTokens.total));
1538
+ parts.push(formatTokenUsage(job.totalTokens, "token"));
658
1539
  if (job.startedAt !== undefined && job.updatedAt !== undefined)
659
1540
  parts.push(formatDuration(Math.max(0, job.updatedAt - job.startedAt)));
660
1541
  return statJoin(theme, parts);
@@ -663,7 +1544,7 @@ function widgetStepStats(theme, step) {
663
1544
  return statJoin(theme, [
664
1545
  step.turnCount !== undefined ? `${step.turnCount} turns` : "",
665
1546
  step.toolCount !== undefined ? formatToolUseStat(step.toolCount) : "",
666
- step.tokens?.total ? formatTokenStat(step.tokens.total) : "",
1547
+ step.tokens ? step.contextLimit !== undefined ? formatContextUsage(step.tokens, step.contextLimit) ?? formatTokenUsage(step.tokens, "token") : step.tokens.total ? formatTokenUsage(step.tokens, "token") : "" : "",
667
1548
  step.durationMs !== undefined ? formatDuration(step.durationMs) : ""
668
1549
  ]);
669
1550
  }
@@ -688,6 +1569,8 @@ function widgetOutputPath(job, step) {
688
1569
  return path.join(job.asyncDir, `output-${step.index}.log`);
689
1570
  }
690
1571
  function nestedRunName(run) {
1572
+ if (run.sessionName?.trim())
1573
+ return run.sessionName.trim();
691
1574
  if (run.agent)
692
1575
  return run.agent;
693
1576
  if (run.agents?.length)
@@ -701,13 +1584,33 @@ function nestedStatusGlyph(state, theme, seed) {
701
1584
  return theme.fg("success", "✓");
702
1585
  if (state === "failed")
703
1586
  return theme.fg("error", "✗");
1587
+ if (state === "partial")
1588
+ return theme.fg("warning", "■");
704
1589
  if (state === "paused")
705
1590
  return theme.fg("warning", "■");
1591
+ if (state === "stopped")
1592
+ return theme.fg("warning", "■");
706
1593
  return theme.fg("muted", "◦");
707
1594
  }
708
1595
  function nestedRunSeed(run) {
709
1596
  return runningSeed(run.lastUpdate, run.lastActivityAt, run.currentStep, run.toolCount, run.turnCount, run.totalTokens?.total, run.currentToolStartedAt);
710
1597
  }
1598
+ function formatClockTime(ms) {
1599
+ if (ms === undefined || !Number.isFinite(ms))
1600
+ return;
1601
+ const date = new Date(ms);
1602
+ const pad = (value) => value.toString().padStart(2, "0");
1603
+ return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
1604
+ }
1605
+ function nestedRunEventTime(run) {
1606
+ return run.state === "running" ? run.lastActivityAt ?? run.currentToolStartedAt ?? run.lastUpdate ?? run.startedAt : run.endedAt ?? run.lastUpdate ?? run.lastActivityAt ?? run.startedAt;
1607
+ }
1608
+ function nestedStepTimestamp(step, fallback) {
1609
+ return formatClockTime(step.status === "running" ? step.lastActivityAt ?? step.currentToolStartedAt ?? fallback ?? step.startedAt : step.endedAt ?? step.lastActivityAt ?? fallback ?? step.startedAt);
1610
+ }
1611
+ function nestedTimestampPrefix(timestamp) {
1612
+ return timestamp ? `[${timestamp}] ` : "";
1613
+ }
711
1614
  function nestedActivity(input, state, snapshotNow) {
712
1615
  const facts = [];
713
1616
  if (input.currentTool && input.currentToolStartedAt !== undefined && snapshotNow !== undefined)
@@ -733,6 +1636,10 @@ function nestedActivity(input, state, snapshotNow) {
733
1636
  return "queued…";
734
1637
  if (state === "paused")
735
1638
  return "Paused";
1639
+ if (state === "stopped")
1640
+ return "Stopped";
1641
+ if (state === "partial")
1642
+ return "Partial";
736
1643
  if (state === "failed")
737
1644
  return "Failed";
738
1645
  return "Done";
@@ -741,8 +1648,53 @@ function formatNestedWidgetLines(children, theme, width, expanded, snapshotNow,
741
1648
  if (!children?.length || lineBudget <= 0)
742
1649
  return [];
743
1650
  if (!expanded) {
744
- const aggregate = formatNestedAggregate(children);
745
- return aggregate ? [theme.fg("dim", `↳ ${aggregate}`)] : [];
1651
+ const rows = [];
1652
+ const maxLeaves = 4;
1653
+ const maxLines = Math.min(6, lineBudget);
1654
+ let leaves = 0;
1655
+ let overflow = 0;
1656
+ const appendLeaf = (step, prefix, fallback) => {
1657
+ if (leaves >= maxLeaves) {
1658
+ overflow++;
1659
+ return;
1660
+ }
1661
+ leaves++;
1662
+ const state = "status" in step ? step.status : step.state;
1663
+ const modelThinking = formatModelThinking(step.model, step.thinking);
1664
+ const activity = nestedActivity(step, state, snapshotNow ?? fallback);
1665
+ const timestamp = "status" in step ? nestedStepTimestamp(step, fallback) : formatClockTime(nestedRunEventTime(step));
1666
+ const error = step.error ? ` · ${step.error}` : "";
1667
+ const name = "status" in step ? childDisplayName(step) : nestedRunName(step);
1668
+ rows.push({
1669
+ prefix,
1670
+ text: `${nestedTimestampPrefix(timestamp)}${nestedStatusGlyph(state, theme)} ${name} · ${state}${modelThinking ? ` · ${modelThinking}` : ""}${activity ? ` · ${activity}` : ""}${error}`
1671
+ });
1672
+ };
1673
+ for (const child of children) {
1674
+ const steps = child.mode === "parallel" || child.mode === "chain" ? child.steps ?? [] : [];
1675
+ if (steps.length > 0) {
1676
+ const ownerModelThinking = formatModelThinking(child.model, child.thinking);
1677
+ const ownerActivity = nestedActivity(child, child.state, snapshotNow ?? child.lastUpdate);
1678
+ const ownerError = child.error ? ` · ${child.error}` : "";
1679
+ rows.push({
1680
+ prefix: "↳ ",
1681
+ text: `OWNER ${nestedStatusGlyph(child.state, theme, nestedRunSeed(child))} ${nestedRunName(child)} · ${child.state}${ownerModelThinking ? ` · ${ownerModelThinking}` : ""}${ownerActivity ? ` · ${ownerActivity}` : ""}${ownerError}`
1682
+ });
1683
+ for (const step of steps)
1684
+ appendLeaf(step, "↳ │ ", child.lastUpdate);
1685
+ } else {
1686
+ appendLeaf(child, "↳ ", child.lastUpdate);
1687
+ }
1688
+ }
1689
+ if (overflow > 0)
1690
+ rows.push({ prefix: "↳ ", text: `… +${overflow} more nested leaves` });
1691
+ const visibleRows = rows.length <= maxLines ? rows : overflow > 0 ? [...rows.slice(0, Math.max(0, maxLines - 1)), rows.at(-1)] : rows.slice(0, maxLines);
1692
+ return visibleRows.map((row, index) => {
1693
+ const marker = index === visibleRows.length - 1 ? "└─" : "├─";
1694
+ const prefix = row.prefix;
1695
+ const text = row.text.startsWith("OWNER ") ? row.text.slice("OWNER ".length) : row.text;
1696
+ return truncLine(theme.fg("dim", `${prefix}${marker} ${text}`), width);
1697
+ });
746
1698
  }
747
1699
  const lines = [];
748
1700
  const maxDepth = 2;
@@ -765,7 +1717,8 @@ function formatNestedWidgetLines(children, theme, width, expanded, snapshotNow,
765
1717
  }
766
1718
  const activity = nestedActivity(child, child.state, snapshotNow ?? child.lastUpdate);
767
1719
  const error = child.error ? ` · ${child.error}` : "";
768
- lines.push(theme.fg("dim", `${prefix}↳ ${nestedStatusGlyph(child.state, theme, nestedRunSeed(child))} ${nestedRunName(child)} · ${child.state} · ${activity}${error}`));
1720
+ const modelThinking = formatModelThinking(child.model, child.thinking);
1721
+ lines.push(theme.fg("dim", `${prefix}↳ ${nestedTimestampPrefix(formatClockTime(nestedRunEventTime(child)))}${nestedStatusGlyph(child.state, theme, nestedRunSeed(child))} ${nestedRunName(child)} · ${child.state}${modelThinking ? ` · ${modelThinking}` : ""} · ${activity}${error}`));
769
1722
  if (depth === maxDepth) {
770
1723
  const aggregate = formatNestedAggregate([...child.steps?.flatMap((step) => step.children ?? []) ?? [], ...child.children ?? []]);
771
1724
  if (aggregate && lines.length < lineBudget)
@@ -775,7 +1728,8 @@ function formatNestedWidgetLines(children, theme, width, expanded, snapshotNow,
775
1728
  for (const step of child.steps ?? []) {
776
1729
  if (lines.length >= lineBudget)
777
1730
  return;
778
- lines.push(theme.fg("dim", `${prefix} ↳ ${nestedStatusGlyph(step.status, theme)} ${step.agent} · ${step.status} · ${nestedActivity(step, step.status, snapshotNow ?? child.lastUpdate)}`));
1731
+ const modelThinking = formatModelThinking(step.model, step.thinking);
1732
+ lines.push(theme.fg("dim", `${prefix} ↳ ${nestedTimestampPrefix(nestedStepTimestamp(step, child.lastUpdate))}${nestedStatusGlyph(step.status, theme)} ${childDisplayName(step)} · ${step.status}${modelThinking ? ` · ${modelThinking}` : ""} · ${nestedActivity(step, step.status, snapshotNow ?? child.lastUpdate)}`));
779
1733
  append(step.children, depth + 1, `${prefix} `);
780
1734
  }
781
1735
  append(child.children, depth + 1, `${prefix} `);
@@ -784,88 +1738,160 @@ function formatNestedWidgetLines(children, theme, width, expanded, snapshotNow,
784
1738
  append(children, 0, "");
785
1739
  return lines.map((line) => truncLine(line, width));
786
1740
  }
787
- function foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index, total, expanded, width) {
1741
+ function foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index, total, expanded, width, frame, options) {
1742
+ const rowIndent = options?.rowIndent ?? " ";
1743
+ const detailIndent = options?.detailIndent ?? " ";
788
1744
  const status = widgetStepStatus(step.status, theme);
789
1745
  const stats = widgetStepStats(theme, step);
790
1746
  const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
791
- const lines = [` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index - 1))} ${itemTitle} ${index}/${total}: ${themeBold(theme, step.agent)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`];
1747
+ const collapseDetails = shouldCollapseSingleChildDetails(job, step);
1748
+ const displayName = collapseDetails ? singleChildAgentName(job, step) : childDisplayName(step);
1749
+ const stageName = itemTitle === "Stage" ? compactTaskText(undefined, step.label) ?? boundedLaneValue(step.workflowKey) ?? displayName : undefined;
1750
+ const stageIdentity = stageName && stageName !== displayName ? `${stageName} (${displayName})` : stageName ?? displayName;
1751
+ const rowLabel = collapseDetails ? displayName : options?.rowLabel ?? `${itemTitle} ${index}/${total}: ${itemTitle === "Stage" ? stageIdentity : displayName}`;
1752
+ const rowMarker = options?.rowMarker ? `${options.rowMarker} ` : "";
1753
+ const lines = [`${rowIndent}${rowMarker}${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index - 1), frame)} ${themeBold(theme, rowLabel)}${contextModeBadge(theme, step.context)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`];
1754
+ const lane = projectAsyncLane(job, step);
1755
+ if (lane)
1756
+ lines.push(...formatLaneProjectionLines(lane, theme, detailIndent));
1757
+ const task = collapseDetails ? singleChildTask(job, step) : compactTaskText(step.description, step.label);
1758
+ if (task)
1759
+ lines.push(`${detailIndent}${theme.fg("dim", `task: ${task}`)}`);
792
1760
  const activity = widgetStepActivityLine(step, width, expanded, job.updatedAt);
793
- if (activity)
794
- lines.push(` ${theme.fg("dim", `⎿ ${activity}`)}`);
795
- for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt)) {
796
- lines.push(` ${nestedLine}`);
1761
+ const currentPath = options?.includeCurrentPath && step.currentPath ? shortenPath(step.currentPath) : undefined;
1762
+ const activityWithPath = currentPath && activity && !activity.includes(currentPath) ? `${activity} · ${currentPath}` : activity ?? currentPath;
1763
+ if (activityWithPath)
1764
+ lines.push(`${detailIndent}${theme.fg("dim", `⎿ ${activityWithPath}`)}`);
1765
+ for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 12 : 6)) {
1766
+ lines.push(`${detailIndent}${nestedLine}`);
797
1767
  }
1768
+ const error = step.error?.trim() || step.execution?.error?.trim();
1769
+ if (error)
1770
+ lines.push(`${detailIndent}${theme.fg("error", `error: ${oneLine(error)}`)}`);
798
1771
  if (step.status === "running") {
799
1772
  if (!expanded)
800
- lines.push(` ${theme.fg("accent", liveDetailHintText())}`);
1773
+ lines.push(`${detailIndent}${theme.fg("accent", liveDetailHintText())}`);
801
1774
  const output = widgetOutputPath(job, step);
802
1775
  if (output)
803
- lines.push(` ${theme.fg("dim", `output: ${shortenPath(output)}`)}`);
1776
+ lines.push(`${detailIndent}${theme.fg("dim", `output: ${shortenPath(output)}`)}`);
804
1777
  if (expanded) {
805
1778
  const liveStatus = buildLiveStatusLine(step, job.updatedAt);
806
1779
  if (liveStatus && liveStatus !== activity)
807
- lines.push(` ${theme.fg("accent", liveStatus)}`);
1780
+ lines.push(`${detailIndent}${theme.fg("accent", liveStatus)}`);
808
1781
  for (const tool of step.recentTools?.slice(-3) ?? []) {
809
1782
  const maxArgsLen = Math.max(40, width - 30);
810
- const argsPreview = tool.args.length <= maxArgsLen ? tool.args : `${tool.args.slice(0, maxArgsLen)}...`;
811
- lines.push(` ${theme.fg("dim", `${tool.tool}${argsPreview ? `: ${argsPreview}` : ""}`)}`);
1783
+ const argsPreview = renderToolArgsPreview(tool.args, maxArgsLen, expanded);
1784
+ lines.push(`${detailIndent} ${theme.fg("dim", `${tool.tool}${argsPreview ? `: ${argsPreview}` : ""}`)}`);
812
1785
  }
813
- for (const line of step.recentOutput?.slice(-5) ?? []) {
814
- lines.push(` ${theme.fg("dim", line)}`);
1786
+ for (const line of compactRecentOutputLines(step.recentOutput)) {
1787
+ lines.push(`${detailIndent} ${theme.fg("dim", line)}`);
815
1788
  }
816
1789
  }
817
1790
  }
818
1791
  return lines;
819
1792
  }
820
- function foregroundStyleWidgetDetails(job, theme, expanded, width) {
821
- if (!job.steps?.length)
1793
+ function hostStepWidgetLines(job, theme, indent) {
1794
+ const rows = projectAsyncWorkflowRows([], job.hostSteps).filter((row) => row.kind);
1795
+ const visible = rows.slice(0, 8);
1796
+ const lines = visible.map((row) => {
1797
+ const state = hostStepVerdictLabel(row.state, row.verdict);
1798
+ const glyph = state === "running" ? theme.fg("accent", "●") : state === "pending" ? theme.fg("muted", "◦") : state === "pass" ? theme.fg("success", "✓") : state === "fail" || state === "error" ? theme.fg("error", "✗") : theme.fg("warning", "■");
1799
+ const details = [
1800
+ row.provider ? `provider:${row.provider}` : undefined,
1801
+ row.role ? `role:${row.role}` : undefined,
1802
+ row.target,
1803
+ row.detail,
1804
+ row.reasonCode ? `reason:${row.reasonCode}` : undefined,
1805
+ row.freshness?.stale ? "stale" : row.freshness?.observedRef ? `ref:${row.freshness.observedRef}` : undefined,
1806
+ row.reportPath ? `out:${hostStepReportName(row.reportPath)}` : undefined
1807
+ ].filter(Boolean).join(" · ");
1808
+ return `${indent}${glyph} ${row.kind}: ${row.name} · ${state}${details ? ` · ${details}` : ""}`;
1809
+ });
1810
+ if (rows.length > visible.length)
1811
+ lines.push(`${indent}${theme.fg("dim", `… +${rows.length - visible.length} host steps hidden`)}`);
1812
+ return lines;
1813
+ }
1814
+ function foregroundStyleWidgetDetails(job, theme, expanded, width, frame, projection = buildWorkflowWidgetProjection(job)) {
1815
+ const { steps } = projection;
1816
+ if (!steps.length) {
1817
+ const lane = projectAsyncLane(job, laneStepForJob(job, steps));
822
1818
  return [
1819
+ ...workflowPreflightLines(job, expanded),
1820
+ ...lane ? formatLaneProjectionLines(lane, theme, " ") : [],
1821
+ ...hostStepWidgetLines(job, theme, " "),
823
1822
  ` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
824
- ...formatNestedWidgetLines(job.nestedChildren, theme, width, expanded, job.updatedAt).map((line) => ` ${line}`)
1823
+ ...formatNestedWidgetLines(job.nestedChildren, theme, width, expanded, job.updatedAt, expanded ? 12 : 6).map((line) => ` ${line}`)
825
1824
  ];
1825
+ }
826
1826
  if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length)
827
- return widgetChainDetails(job, theme, expanded, width);
828
- const total = job.stepsTotal ?? job.steps.length;
829
- const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
830
- const lines = [];
831
- for (const [index, step] of job.steps.entries()) {
832
- lines.push(...foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index + 1, total, expanded, width));
1827
+ return widgetChainDetails(job, theme, expanded, width, frame);
1828
+ const lines = workflowPreflightLines(job, expanded);
1829
+ const group = activeParallelWidgetGroup(job);
1830
+ if (group) {
1831
+ lines.push(...parallelWidgetGroupDetails(job, theme, group, expanded, width, frame, Boolean(job.activeParallelGroup)));
1832
+ } else {
1833
+ const { stageProgress, plannedKeys } = projection;
1834
+ const total = job.mode === "chain" ? job.chainStepCount ?? job.stepsTotal ?? steps.length : stageProgress?.total ?? job.stepsTotal ?? steps.length;
1835
+ const stageTotal = stageProgress?.total ?? total;
1836
+ const extraStepCount = plannedKeys ? steps.filter((step) => step.workflowKey === undefined || !plannedKeys.has(step.workflowKey)).length : total;
1837
+ let extraStepIndex = 0;
1838
+ for (const [index, step] of steps.entries()) {
1839
+ const isPlannedStage = plannedKeys !== undefined && step.workflowKey !== undefined && plannedKeys.has(step.workflowKey);
1840
+ const itemTitle = isPlannedStage ? "Stage" : "Step";
1841
+ const displayIndex = isPlannedStage ? (step.index ?? index) + 1 : plannedKeys ? ++extraStepIndex : index + 1;
1842
+ const displayTotal = isPlannedStage ? stageTotal : extraStepCount;
1843
+ lines.push(...foregroundStyleWidgetStepLines(job, theme, step, itemTitle, displayIndex, displayTotal, expanded, width, frame));
1844
+ }
833
1845
  }
834
- const attached = new Set(job.steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));
1846
+ lines.push(...hostStepWidgetLines(job, theme, " "));
1847
+ const attached = new Set(steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));
835
1848
  const unattached = job.nestedChildren?.filter((child) => !attached.has(child.id)) ?? [];
836
- for (const nestedLine of formatNestedWidgetLines(unattached, theme, width, expanded, job.updatedAt)) {
1849
+ for (const nestedLine of formatNestedWidgetLines(unattached, theme, width, expanded, job.updatedAt, expanded ? 12 : 6)) {
837
1850
  lines.push(` ${nestedLine}`);
838
1851
  }
839
1852
  return lines;
840
1853
  }
841
- function buildSingleWidgetLines(job, theme, width, expanded) {
842
- const stats = widgetStats(job, theme);
843
- const count = job.mode === "chain" ? job.chainStepCount : job.stepsTotal ?? job.agents?.length ?? job.steps?.length;
1854
+ function buildSingleWidgetLines(job, theme, width, expanded, frame, projection = buildWorkflowWidgetProjection(job)) {
1855
+ const stats = widgetStats(job, theme, projection);
1856
+ const count = job.mode === "chain" ? job.chainStepCount : projection.stageProgress?.total ?? job.stepsTotal ?? job.agents?.length ?? job.steps?.length;
844
1857
  const mode = widgetJobName(job);
845
- const title = `async subagent ${mode}${count && count > 1 ? ` (${count})` : ""}`;
1858
+ const title = isSingleChildAsyncJob(job) ? "async subagent" : `async subagent ${mode}${count && count > 1 ? ` (${count})` : ""}`;
1859
+ const collapseDetails = job.steps?.length === 1 && shouldCollapseSingleChildDetails(job, job.steps[0]);
1860
+ const summary = `${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, mode)}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`;
846
1861
  return [
847
1862
  `${theme.fg("toolTitle", themeBold(theme, title))} ${theme.fg("dim", "· background")}`,
848
- `${widgetStatusGlyph(job, theme)} ${themeBold(theme, mode)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
849
- ...foregroundStyleWidgetDetails(job, theme, expanded, width)
1863
+ ...collapseDetails ? [] : [summary],
1864
+ ...foregroundStyleWidgetDetails(job, theme, expanded, width, frame, projection)
850
1865
  ].map((line) => truncLine(line, width));
851
1866
  }
852
- function compactSingleWidgetLines(job, theme, width) {
853
- const fullLines = buildSingleWidgetLines(job, theme, width, false);
1867
+ function compactSingleWidgetLines(job, theme, width, frame, projection = buildWorkflowWidgetProjection(job)) {
1868
+ const fullLines = buildSingleWidgetLines(job, theme, width, false, frame, projection);
854
1869
  if (fullLines.length <= 10 || !job.steps?.length || job.mode !== "parallel" && !job.activeParallelGroup)
855
1870
  return fullLines;
856
- const total = job.stepsTotal ?? job.steps.length;
857
- const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
1871
+ const group = activeParallelWidgetGroup(job);
1872
+ if (!group)
1873
+ return fullLines;
1874
+ const rows = parallelWidgetStepRows(group.steps, group.total, Boolean(job.activeParallelGroup));
858
1875
  const lines = fullLines.slice(0, 2);
859
- for (const [index, step] of job.steps.entries()) {
1876
+ lines.push(parallelWidgetGroupHeader(group, theme, frame));
1877
+ for (const [rowIndex, row] of rows.entries()) {
1878
+ const step = row.step;
860
1879
  const status = widgetStepStatus(step.status, theme);
861
1880
  const activity = widgetStepActivityLine(step, width, false, job.updatedAt);
862
1881
  const stepStats = widgetStepStats(theme, step);
863
1882
  const activitySuffix = activity ? ` ${theme.fg("dim", "·")} ${theme.fg("dim", activity)}` : "";
864
1883
  const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
865
- lines.push(` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index))} ${itemTitle} ${index + 1}/${total}: ${themeBold(theme, step.agent)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${activitySuffix}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}`);
866
- for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt))
867
- lines.push(` ${nestedLine}`);
1884
+ const task = compactTaskText(step.description, step.label);
1885
+ const taskSuffix = task ? ` ${theme.fg("dim", "·")} ${theme.fg("dim", `task: ${task}`)}` : "";
1886
+ const marker = rowIndex === rows.length - 1 && rowIndex >= group.total - 1 ? "└─" : "├─";
1887
+ lines.push(` ${marker} ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, row.index), frame)} ${themeBold(theme, row.rowLabel)}${contextModeBadge(theme, step.context)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${taskSuffix}${activitySuffix}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}`);
1888
+ const lane = projectAsyncLane(job, step);
1889
+ if (lane)
1890
+ lines.push(...formatLaneProjectionLines(lane, theme, " "));
1891
+ for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt, 6))
1892
+ lines.push(` ${nestedLine}`);
868
1893
  }
1894
+ lines.push(...hostStepWidgetLines(job, theme, " "));
869
1895
  if (job.steps.some((step) => step.status === "running"))
870
1896
  lines.push(theme.fg("accent", ` ${liveDetailHintText()}`));
871
1897
  return lines.map((line) => truncLine(line, width));
@@ -894,13 +1920,14 @@ function widgetHeaderCounts(jobs) {
894
1920
  queued: jobs.filter((job) => job.status === "queued"),
895
1921
  complete: jobs.filter((job) => job.status === "complete"),
896
1922
  failed: jobs.filter((job) => job.status === "failed"),
897
- paused: jobs.filter((job) => job.status === "paused")
1923
+ paused: jobs.filter((job) => job.status === "paused"),
1924
+ stopped: jobs.filter((job) => job.status === "stopped")
898
1925
  };
899
1926
  }
900
- function buildSingleLineWidgetLines(jobs, theme, width) {
1927
+ function buildSingleLineWidgetLines(jobs, theme, width, frame) {
901
1928
  const counts = widgetHeaderCounts(jobs);
902
1929
  const hasActive = counts.running.length > 0 || counts.queued.length > 0;
903
- const glyph = counts.running.length > 0 ? runningGlyph(widgetJobsRunningSeed(counts.running)) : hasActive ? "●" : "○";
1930
+ const glyph = counts.running.length > 0 ? runningGlyph(animatedSeed(widgetJobsRunningSeed(counts.running), frame)) : hasActive ? "●" : "○";
904
1931
  const parts = [];
905
1932
  if (counts.running.length > 0)
906
1933
  parts.push(`${counts.running.length}/${jobs.length} running`);
@@ -908,6 +1935,8 @@ function buildSingleLineWidgetLines(jobs, theme, width) {
908
1935
  parts.push(`${counts.queued.length} queued`);
909
1936
  if (counts.failed.length > 0)
910
1937
  parts.push(`${counts.failed.length} failed`);
1938
+ if (counts.stopped.length > 0)
1939
+ parts.push(`${counts.stopped.length} stopped`);
911
1940
  if (counts.paused.length > 0)
912
1941
  parts.push(`${counts.paused.length} paused`);
913
1942
  if (!hasActive && counts.complete.length > 0)
@@ -969,10 +1998,10 @@ function selectProgressiveJobKeys(jobs, previousKeys, bodyRows) {
969
1998
  }
970
1999
  return selected;
971
2000
  }
972
- function progressiveHeaderLine(jobs, theme, width) {
2001
+ function progressiveHeaderLine(jobs, theme, width, frame) {
973
2002
  const counts = widgetHeaderCounts(jobs);
974
2003
  const hasActive = counts.running.length > 0 || counts.queued.length > 0;
975
- const glyph = counts.running.length > 0 ? runningGlyph(widgetJobsRunningSeed(counts.running)) : hasActive ? "●" : "○";
2004
+ const glyph = counts.running.length > 0 ? runningGlyph(animatedSeed(widgetJobsRunningSeed(counts.running), frame)) : hasActive ? "●" : "○";
976
2005
  const parts = [];
977
2006
  if (counts.running.length > 0)
978
2007
  parts.push(formatAgentRunningLabel(counts.running.length));
@@ -981,6 +2010,8 @@ function progressiveHeaderLine(jobs, theme, width) {
981
2010
  if (!hasActive) {
982
2011
  if (counts.failed.length > 0)
983
2012
  parts.push(`${counts.failed.length} failed`);
2013
+ if (counts.stopped.length > 0)
2014
+ parts.push(`${counts.stopped.length} stopped`);
984
2015
  if (counts.paused.length > 0)
985
2016
  parts.push(`${counts.paused.length} paused`);
986
2017
  if (counts.complete.length > 0)
@@ -988,17 +2019,22 @@ function progressiveHeaderLine(jobs, theme, width) {
988
2019
  }
989
2020
  return 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);
990
2021
  }
991
- function progressiveJobLine(job, theme, width) {
992
- const stats = widgetStats(job, theme);
2022
+ function progressiveJobLine(job, theme, width, frame, projection = buildWorkflowWidgetProjection(job)) {
2023
+ const stats = widgetStats(job, theme, projection);
993
2024
  const activity = widgetActivity(job);
994
2025
  const status = job.status === "complete" ? "done" : job.status;
2026
+ const lane = projectAsyncLane(job, laneStepForJob(job, projection.steps));
2027
+ const laneSummary = lane ? [lane.label ?? lane.role, lane.phase ? `phase:${lane.phase}` : undefined, lane.output ? `out:${lane.output}` : undefined, lane.workspace ? `workspace:${lane.workspace}` : `ref:${lane.ref}`].filter(Boolean).join(" · ") : "";
2028
+ const laneSignals = lane ? [lane.next ? `next:${lane.next}` : undefined, ...lane.chips.map((chip) => formatLaneChip(chip, theme))].filter(Boolean).join(" · ") : "";
995
2029
  const parts = [
996
- themeBold(theme, widgetJobName(job)),
2030
+ `${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}`,
997
2031
  theme.fg("dim", status),
2032
+ laneSignals ? laneSignals : "",
2033
+ laneSummary ? theme.fg("dim", laneSummary) : "",
998
2034
  stats,
999
2035
  activity && activity.toLowerCase() !== status ? theme.fg("dim", activity) : ""
1000
2036
  ].filter(Boolean);
1001
- return truncLine(` ${widgetStatusGlyph(job, theme)} ${parts.join(` ${theme.fg("dim", "·")} `)}`, width);
2037
+ return truncLine(` ${widgetStatusGlyph(job, theme, frame)} ${parts.join(` ${theme.fg("dim", "·")} `)}`, width);
1002
2038
  }
1003
2039
  function progressiveHiddenLine(hiddenJobs, theme, width) {
1004
2040
  const counts = widgetHeaderCounts(hiddenJobs);
@@ -1007,15 +2043,15 @@ function progressiveHiddenLine(hiddenJobs, theme, width) {
1007
2043
  parts.push(`${counts.running.length} running`);
1008
2044
  if (counts.queued.length > 0)
1009
2045
  parts.push(`${counts.queued.length} queued`);
1010
- const finished = counts.complete.length + counts.failed.length + counts.paused.length;
2046
+ const finished = counts.complete.length + counts.failed.length + counts.paused.length + counts.stopped.length;
1011
2047
  if (finished > 0)
1012
2048
  parts.push(`${finished} finished`);
1013
2049
  return truncLine(theme.fg("dim", ` +${hiddenJobs.length} more${parts.length ? ` (${parts.join(", ")})` : ""}`), width);
1014
2050
  }
1015
- function buildProgressiveWidgetLines(jobs, theme, width, lockedRows, previousKeys) {
2051
+ function buildProgressiveWidgetLines(jobs, theme, width, lockedRows, previousKeys, frame, projectionFor = workflowWidgetProjectionLookup()) {
1016
2052
  const rowCount = Math.max(1, lockedRows);
1017
2053
  if (rowCount === 1)
1018
- return { lines: buildSingleLineWidgetLines(jobs, theme, width), visibleJobKeys: [] };
2054
+ return { lines: buildSingleLineWidgetLines(jobs, theme, width, frame), visibleJobKeys: [] };
1019
2055
  const bodyRows = rowCount - 1;
1020
2056
  let visibleJobKeys = selectProgressiveJobKeys(jobs, previousKeys, bodyRows);
1021
2057
  const jobsByKey = new Map(jobs.map((job) => [progressiveJobKey(job), job]));
@@ -1028,8 +2064,8 @@ function buildProgressiveWidgetLines(jobs, theme, width, lockedRows, previousKey
1028
2064
  hiddenJobs = jobs.filter((job) => !visibleJobKeys.includes(progressiveJobKey(job)));
1029
2065
  }
1030
2066
  const lines = [
1031
- progressiveHeaderLine(jobs, theme, width),
1032
- ...visibleJobs.map((job) => progressiveJobLine(job, theme, width))
2067
+ progressiveHeaderLine(jobs, theme, width, frame),
2068
+ ...visibleJobs.map((job) => progressiveJobLine(job, theme, width, frame, projectionFor(job)))
1033
2069
  ];
1034
2070
  if (hiddenJobs.length > 0 && lines.length < rowCount)
1035
2071
  lines.push(progressiveHiddenLine(hiddenJobs, theme, width));
@@ -1040,6 +2076,12 @@ function buildProgressiveWidgetLines(jobs, theme, width, lockedRows, previousKey
1040
2076
  function collapsedWidgetLineBudget(rows) {
1041
2077
  return Math.max(10, Math.min(14, Math.floor(rows * 0.35)));
1042
2078
  }
2079
+ function paddedWidgetLine(line, width) {
2080
+ if (width <= 2)
2081
+ return " ".repeat(Math.max(0, width));
2082
+ const text = ` ${truncLine(line, width - 2)} `;
2083
+ return `${text}${" ".repeat(Math.max(0, width - visibleWidth(text)))}`;
2084
+ }
1043
2085
  function fitWidgetLineBudget(lines, theme, width, expanded) {
1044
2086
  const rows = process.stdout.rows || 30;
1045
2087
  const budget = expanded ? Math.max(12, Math.min(24, Math.floor(rows * 0.55))) : collapsedWidgetLineBudget(rows);
@@ -1050,74 +2092,98 @@ function fitWidgetLineBudget(lines, theme, width, expanded) {
1050
2092
  const hint = expanded ? `… ${hiddenCount} live-detail lines hidden` : `… ${hiddenCount} lines hidden · ${liveDetailKeyText()} expands`;
1051
2093
  return [...lines.slice(0, visibleLines), truncLine(theme.fg("dim", hint), width)];
1052
2094
  }
1053
- function fitAdaptiveWidgetLines(jobs, lines, theme, width, expanded) {
2095
+ function fitAdaptiveWidgetLines(jobs, buildLines, theme, width, expanded, frame, projectionFor) {
1054
2096
  if (expanded) {
1055
2097
  resetWidgetLayoutSession();
1056
- return fitWidgetLineBudget(lines, theme, width, true);
2098
+ return fitWidgetLineBudget(buildLines(), theme, width, true);
1057
2099
  }
1058
2100
  const hasMatchingSession = widgetSessionMatches(expanded);
1059
2101
  const rows = currentTerminalRows();
1060
2102
  const columns = currentTerminalColumns();
1061
2103
  const availableRows = estimateAvailableWidgetRows();
1062
2104
  if (hasMatchingSession && widgetLayoutSession?.tier === "single-line") {
1063
- return buildSingleLineWidgetLines(jobs, theme, width);
2105
+ return buildSingleLineWidgetLines(jobs, theme, width, frame);
1064
2106
  }
1065
2107
  if (hasMatchingSession && widgetLayoutSession?.tier === "progressive" && widgetLayoutSession.lockedRows !== undefined) {
1066
- const rendered = buildProgressiveWidgetLines(jobs, theme, width, widgetLayoutSession.lockedRows, widgetLayoutSession.visibleJobKeys);
2108
+ const rendered = buildProgressiveWidgetLines(jobs, theme, width, widgetLayoutSession.lockedRows, widgetLayoutSession.visibleJobKeys, frame, projectionFor);
1067
2109
  widgetLayoutSession.visibleJobKeys = rendered.visibleJobKeys;
1068
2110
  return rendered.lines;
1069
2111
  }
2112
+ const lines = buildLines();
1070
2113
  if (lines.length <= availableRows) {
1071
2114
  widgetLayoutSession = { expanded, rows, columns, tier: "full", visibleJobKeys: [] };
1072
2115
  return fitWidgetLineBudget(lines, theme, width, false);
1073
2116
  }
2117
+ if (availableRows > 2 && jobs.length === 1 && projectionFor?.(jobs[0]).stageProgress) {
2118
+ widgetLayoutSession = { expanded, rows, columns, tier: "full", visibleJobKeys: [] };
2119
+ return fitWidgetLineBudget(lines, theme, width, false);
2120
+ }
1074
2121
  if (availableRows <= 2) {
1075
2122
  widgetLayoutSession = { expanded, rows, columns, tier: "single-line", visibleJobKeys: [] };
1076
- return buildSingleLineWidgetLines(jobs, theme, width);
2123
+ return buildSingleLineWidgetLines(jobs, theme, width, frame);
1077
2124
  }
1078
2125
  const lockedRows = Math.min(availableRows, collapsedWidgetLineBudget(rows));
1079
- const rendered = buildProgressiveWidgetLines(jobs, theme, width, lockedRows, []);
2126
+ const rendered = buildProgressiveWidgetLines(jobs, theme, width, lockedRows, [], frame, projectionFor);
1080
2127
  widgetLayoutSession = { expanded, rows, columns, tier: "progressive", lockedRows, visibleJobKeys: rendered.visibleJobKeys };
1081
2128
  return rendered.lines;
1082
2129
  }
1083
- function buildWidgetComponent(jobs, expanded) {
2130
+ function buildWidgetComponent(jobs, isExpanded) {
1084
2131
  return (_tui, theme) => {
1085
- const width = getTermWidth();
1086
- const lines = expanded ? buildWidgetLines(jobs, theme, width, true) : jobs.length === 1 ? compactSingleWidgetLines(jobs[0], theme, width) : buildWidgetLines(jobs, theme, width, false);
1087
2132
  const container = new Container;
1088
- for (const line of fitAdaptiveWidgetLines(jobs, lines, theme, width, expanded))
1089
- container.addChild(new Text(line, 1, 0));
2133
+ let cachedRenderWidth;
2134
+ let cachedFrame;
2135
+ let cachedExpanded;
2136
+ let cachedLines;
2137
+ container.render = (renderWidth) => {
2138
+ const frame = Math.floor(Date.now() / WIDGET_ANIMATION_INTERVAL_MS);
2139
+ const expanded = isExpanded();
2140
+ if (cachedLines && cachedRenderWidth === renderWidth && cachedFrame === frame && cachedExpanded === expanded)
2141
+ return cachedLines;
2142
+ const width = Math.max(0, renderWidth - 2);
2143
+ const projectionFor = workflowWidgetProjectionLookup();
2144
+ const buildLines = () => expanded ? buildWidgetLinesWithProjection(jobs, theme, width, true, frame, projectionFor) : jobs.length === 1 ? compactSingleWidgetLines(jobs[0], theme, width, frame, projectionFor(jobs[0])) : buildWidgetLinesWithProjection(jobs, theme, width, false, frame, projectionFor);
2145
+ cachedRenderWidth = renderWidth;
2146
+ cachedFrame = frame;
2147
+ cachedExpanded = expanded;
2148
+ cachedLines = fitAdaptiveWidgetLines(jobs, buildLines, theme, width, expanded, frame, projectionFor).map((line) => paddedWidgetLine(line, renderWidth));
2149
+ return cachedLines;
2150
+ };
1090
2151
  return container;
1091
2152
  };
1092
2153
  }
1093
- export function buildWidgetLines(jobs, theme, width = getTermWidth(), expanded = false) {
2154
+ function buildWidgetLinesWithProjection(jobs, theme, width = getTermWidth(), expanded = false, frame, projectionFor = workflowWidgetProjectionLookup()) {
1094
2155
  if (jobs.length === 0)
1095
2156
  return [];
1096
2157
  if (jobs.length === 1)
1097
- return buildSingleWidgetLines(jobs[0], theme, width, expanded);
2158
+ return buildSingleWidgetLines(jobs[0], theme, width, expanded, frame, projectionFor(jobs[0]));
1098
2159
  const running = jobs.filter((job) => job.status === "running");
1099
2160
  const queued = jobs.filter((job) => job.status === "queued");
1100
2161
  const finished = jobs.filter((job) => job.status !== "running" && job.status !== "queued");
1101
2162
  const lines = [];
1102
2163
  const hasActive = running.length > 0 || queued.length > 0;
1103
- const headerGlyph = running.length > 0 ? runningGlyph(widgetJobsRunningSeed(running)) : hasActive ? "●" : "○";
2164
+ const headerGlyph = running.length > 0 ? runningGlyph(animatedSeed(widgetJobsRunningSeed(running), frame)) : hasActive ? "●" : "○";
1104
2165
  lines.push(truncLine(`${theme.fg(hasActive ? "accent" : "dim", headerGlyph)} ${theme.fg(hasActive ? "accent" : "dim", "Async agents")} ${theme.fg("dim", "· background")}`, width));
1105
2166
  const items = [];
1106
2167
  let hiddenRunning = 0;
1107
2168
  let hiddenFinished = 0;
1108
2169
  let queuedSummaryShown = false;
1109
2170
  let slots = MAX_WIDGET_JOBS;
2171
+ const appendJob = (job) => {
2172
+ const projection = projectionFor(job);
2173
+ const stats = widgetStats(job, theme, projection);
2174
+ items.push([
2175
+ `${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
2176
+ ` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
2177
+ ...widgetLaneDetailLines(job, theme, projection),
2178
+ ...widgetParallelAgentDetails(job, theme, expanded, width, frame)
2179
+ ]);
2180
+ };
1110
2181
  for (const job of running) {
1111
2182
  if (slots <= 0) {
1112
2183
  hiddenRunning++;
1113
2184
  continue;
1114
2185
  }
1115
- const stats = widgetStats(job, theme);
1116
- items.push([
1117
- `${widgetStatusGlyph(job, theme)} ${themeBold(theme, widgetJobName(job))}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
1118
- ` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
1119
- ...widgetParallelAgentDetails(job, theme, expanded, width)
1120
- ]);
2186
+ appendJob(job);
1121
2187
  slots--;
1122
2188
  }
1123
2189
  if (queued.length > 0 && slots > 0) {
@@ -1130,12 +2196,7 @@ export function buildWidgetLines(jobs, theme, width = getTermWidth(), expanded =
1130
2196
  hiddenFinished++;
1131
2197
  continue;
1132
2198
  }
1133
- const stats = widgetStats(job, theme);
1134
- items.push([
1135
- `${widgetStatusGlyph(job, theme)} ${themeBold(theme, widgetJobName(job))}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
1136
- ` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
1137
- ...widgetParallelAgentDetails(job, theme, expanded, width)
1138
- ]);
2199
+ appendJob(job);
1139
2200
  slots--;
1140
2201
  }
1141
2202
  const hiddenQueued = queued.length > 0 && !queuedSummaryShown ? queued.length : 0;
@@ -1162,6 +2223,9 @@ export function buildWidgetLines(jobs, theme, width = getTermWidth(), expanded =
1162
2223
  }
1163
2224
  return lines;
1164
2225
  }
2226
+ export function buildWidgetLines(jobs, theme, width = getTermWidth(), expanded = false, frame) {
2227
+ return buildWidgetLinesWithProjection(jobs, theme, width, expanded, frame);
2228
+ }
1165
2229
  export function renderWidget(ctx, jobs) {
1166
2230
  if (jobs.length === 0) {
1167
2231
  resetWidgetLayoutSession();
@@ -1171,50 +2235,151 @@ export function renderWidget(ctx, jobs) {
1171
2235
  }
1172
2236
  if (!ctx.hasUI)
1173
2237
  return;
1174
- ctx.ui.setWidget(WIDGET_KEY, buildWidgetComponent(jobs, ctx.ui.getToolsExpanded?.() ?? false));
2238
+ if (ctx.mode === "rpc") {
2239
+ ctx.ui.setWidget(WIDGET_KEY, encodeAsyncStatusSnapshotWidget(jobs));
2240
+ return;
2241
+ }
2242
+ ctx.ui.setWidget(WIDGET_KEY, buildWidgetComponent(jobs, () => ctx.ui.getToolsExpanded?.() ?? false));
1175
2243
  }
1176
- function renderSingleCompact(d, r, theme, frame) {
2244
+ function renderSingleCompact(d, r, theme, layout, frame, foregroundDetachShortcut) {
1177
2245
  const output = r.truncation?.text || getSingleResultOutput(r);
1178
2246
  const progress = r.progress || r.progressSummary;
1179
- const isRunning = r.progress?.status === "running";
1180
- const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
2247
+ const isRunning = isResultRunning(r);
2248
+ const contextBadge = contextModeBadge(theme, r.context ?? d.context);
1181
2249
  const stats = statJoin(theme, [
1182
2250
  r.usage?.turns ? `⟳ ${r.usage.turns}` : "",
1183
2251
  formatProgressStats(theme, progress)
1184
2252
  ]);
1185
2253
  const c = new Container;
1186
2254
  const width = getTermWidth() - 4;
1187
- const modelDisplay = modelThinkingBadge(theme, r.model);
1188
- c.addChild(new Text(truncLine(`${resultGlyph(r, output, theme, isRunning, undefined, frame)} ${theme.fg("toolTitle", theme.bold(r.agent))}${modelDisplay}${contextBadge}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`, width), 0, 0));
2255
+ const detailIndent = mainWindowIndent(layout, 1);
2256
+ const continuationIndent = mainWindowIndent(layout, 2) + (layout.horizontalSpacing > 0 ? " " : "");
2257
+ const modelDisplay = modelThinkingBadge(theme, r.model ?? r.progress?.model, r.thinking ?? r.progress?.thinking);
2258
+ c.addChild(new Text(truncLine(`${resultGlyph(r, output, theme, isRunning, undefined, frame)} ${theme.fg("toolTitle", theme.bold(foregroundSingleDisplayName(r)))}${modelDisplay}${contextBadge}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`, width), 0, 0));
1189
2259
  if (isRunning && r.progress) {
2260
+ const task = compactTaskText(r.task);
2261
+ if (task)
2262
+ c.addChild(new Text(truncLine(theme.fg("dim", `${detailIndent}task: ${task}`), width), 0, 0));
1190
2263
  const progressSnapshotNow = snapshotNowForProgress(r.progress);
1191
2264
  const activity = compactCurrentActivity(r.progress);
1192
- c.addChild(new Text(truncLine(theme.fg("dim", ` ⎿ ${activity}`), width), 0, 0));
2265
+ c.addChild(new Text(truncLine(theme.fg("dim", `${detailIndent}⎿ ${activity}`), width), 0, 0));
1193
2266
  const liveStatus = buildLiveStatusLine(r.progress, progressSnapshotNow);
1194
2267
  if (liveStatus && liveStatus !== activity)
1195
- c.addChild(new Text(truncLine(theme.fg("dim", ` ${liveStatus}`), width), 0, 0));
1196
- c.addChild(new Text(truncLine(theme.fg("accent", ` ${liveDetailHintText()}`), width), 0, 0));
2268
+ c.addChild(new Text(truncLine(theme.fg("dim", `${continuationIndent}${liveStatus}`), width), 0, 0));
2269
+ for (const nestedLine of formatNestedWidgetLines(r.children, theme, width, false, progressSnapshotNow)) {
2270
+ c.addChild(new Text(truncLine(`${detailIndent}${nestedLine}`, width), 0, 0));
2271
+ }
2272
+ c.addChild(new Text(truncLine(theme.fg("accent", `${detailIndent}${foregroundSingleHintText(foregroundDetachShortcut)}`), width), 0, 0));
1197
2273
  if (r.artifactPaths)
1198
- c.addChild(new Text(truncLine(theme.fg("dim", ` output: ${shortenPath(r.artifactPaths.outputPath)}`), width), 0, 0));
2274
+ c.addChild(new Text(truncLine(theme.fg("dim", `${detailIndent}output: ${shortenPath(r.artifactPaths.outputPath)}`), width), 0, 0));
1199
2275
  return c;
1200
2276
  }
1201
- c.addChild(new Text(truncLine(theme.fg("dim", ` ⎿ ${resultStatusLine(r, output)}`), width), 0, 0));
2277
+ for (const nestedLine of formatNestedWidgetLines(r.children, theme, width, false, r.progress?.lastActivityAt)) {
2278
+ c.addChild(new Text(truncLine(`${detailIndent}${nestedLine}`, width), 0, 0));
2279
+ }
2280
+ c.addChild(new Text(truncLine(theme.fg("dim", `${detailIndent}⎿ ${resultStatusLine(r, output)}`), width), 0, 0));
1202
2281
  const preview = firstOutputLine(output);
1203
2282
  if (preview && r.exitCode === 0 && !hasEmptyTextOutputWithoutOutputTarget(r.task, output)) {
1204
- c.addChild(new Text(truncLine(theme.fg("dim", ` ${preview}`), width), 0, 0));
2283
+ c.addChild(new Text(truncLine(theme.fg("dim", `${continuationIndent}${preview}`), width), 0, 0));
1205
2284
  }
1206
2285
  if (r.sessionFile)
1207
- c.addChild(new Text(truncLine(theme.fg("dim", ` session: ${shortenPath(r.sessionFile)}`), width), 0, 0));
2286
+ c.addChild(new Text(truncLine(theme.fg("dim", `${detailIndent}session: ${shortenPath(r.sessionFile)}`), width), 0, 0));
1208
2287
  if (r.artifactPaths)
1209
- c.addChild(new Text(truncLine(theme.fg("dim", ` output: ${shortenPath(r.artifactPaths.outputPath)}`), width), 0, 0));
2288
+ c.addChild(new Text(truncLine(theme.fg("dim", `${detailIndent}output: ${shortenPath(r.artifactPaths.outputPath)}`), width), 0, 0));
1210
2289
  if (r.truncation?.artifactPath)
1211
- c.addChild(new Text(truncLine(theme.fg("dim", ` full output: ${shortenPath(r.truncation.artifactPath)}`), width), 0, 0));
2290
+ c.addChild(new Text(truncLine(theme.fg("dim", `${detailIndent}full output: ${shortenPath(r.truncation.artifactPath)}`), width), 0, 0));
2291
+ return c;
2292
+ }
2293
+ function workflowRowGlyph(row, theme, frame) {
2294
+ if (row.state === "planned")
2295
+ return theme.fg("muted", "◦");
2296
+ if (row.state === "running")
2297
+ return theme.fg("accent", runningGlyph(frame));
2298
+ if (row.state === "complete")
2299
+ return theme.fg("success", "✓");
2300
+ if (row.state === "detached" || row.state === "stopped")
2301
+ return theme.fg("warning", "■");
2302
+ return theme.fg("error", "✗");
2303
+ }
2304
+ function workflowRowStateLabel(row, theme) {
2305
+ const label = (row.state === "complete" ? "complete" : row.state).padEnd(8);
2306
+ if (row.state === "planned")
2307
+ return theme.fg("dim", label);
2308
+ if (row.state === "running")
2309
+ return theme.fg("accent", label);
2310
+ if (row.state === "complete")
2311
+ return theme.fg("success", label);
2312
+ if (row.state === "detached" || row.state === "stopped")
2313
+ return theme.fg("warning", label);
2314
+ return theme.fg("error", label);
2315
+ }
2316
+ function workflowOverallState(rows, hasTerminalValue, isError) {
2317
+ if (rows.some((row) => row.state === "failed"))
2318
+ return "failed";
2319
+ if (rows.some((row) => row.state === "detached"))
2320
+ return "paused";
2321
+ if (isError)
2322
+ return "failed";
2323
+ if (rows.length > 0 && rows.every((row) => row.state === "complete") || hasTerminalValue)
2324
+ return "complete";
2325
+ return "running";
2326
+ }
2327
+ function renderWorkflowChatProgress(d, result, theme, layout, frame, expanded = false) {
2328
+ const workflow = d.workflow;
2329
+ const rows = workflow ? buildWorkflowChatProgressRows(workflow.trace, d.preflight) : d.preflight ? buildWorkflowChatProgressRows([], d.preflight) : [];
2330
+ const state = workflowOverallState(rows, workflow?.value !== undefined, result.isError);
2331
+ const glyph = state === "running" ? theme.fg("accent", runningGlyph(frame)) : state === "complete" ? theme.fg("success", "✓") : state === "paused" ? theme.fg("warning", "■") : theme.fg("error", "✗");
2332
+ const width = getTermWidth() - 4;
2333
+ const runId = d.runId ? d.runId.slice(0, 12) : "workflow";
2334
+ const repoLabel = d.chatProgress?.repoLabel ?? (d.chatProgress?.repoRelation === "same" ? "same repo" : "other repo");
2335
+ const phase = rows.find((row) => row.state === "running" && row.phase)?.phase ?? [...rows].reverse().find((row) => row.phase)?.phase;
2336
+ const c = new Container;
2337
+ const rowIndent = mainWindowIndent(layout, 1);
2338
+ c.addChild(new Text(truncLine(`${glyph} ${theme.fg("toolTitle", theme.bold("workflow"))} ${runId} ${theme.fg("dim", "·")} ${d.chatProgress?.repoRelation === "same" ? "same repo" : "other repo"} ${theme.fg("dim", "·")} ${state}`, width), 0, 0));
2339
+ c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}Repo ${repoLabel}`), width), 0, 0));
2340
+ if (d.preflight)
2341
+ c.addChild(new Text(truncLine(theme.fg("dim", formatWorkflowPreflightPlanSummary(d.preflight, { indent: rowIndent })), width), 0, 0));
2342
+ if (phase)
2343
+ c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}Phase ${phase}`), width), 0, 0));
2344
+ if (rows.length === 0) {
2345
+ c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}◦ waiting for workflow child launches`), width), 0, 0));
2346
+ return c;
2347
+ }
2348
+ const visible = visibleWorkflowRows(rows);
2349
+ if (visible.hiddenRows > 0)
2350
+ c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}… ${visible.hiddenRows} older workflow rows hidden`), width), 0, 0));
2351
+ for (const row of visible.rows) {
2352
+ const status = workflowRowStateLabel(row, theme);
2353
+ const label = row.label && row.label !== row.key ? ` ${oneLine(row.label)}` : "";
2354
+ const duration = row.durationMs !== undefined ? ` ${theme.fg("dim", `· ${formatDuration(row.durationMs)}`)}` : "";
2355
+ const run = row.runId ? ` ${theme.fg("dim", `[${row.runId.slice(0, 8)}]`)}` : "";
2356
+ const error = row.error ? ` ${theme.fg(row.state === "detached" ? "warning" : "error", `· ${compactWorkflowError(row.error)}`)}` : "";
2357
+ const hints = expanded && row.preflight ? [
2358
+ row.preflight.mode ? `mode:${row.preflight.mode}` : undefined,
2359
+ row.preflight.decision ? `decision:${row.preflight.decision}` : undefined,
2360
+ row.preflight.claims?.length ? `claims:${row.preflight.claims.join(",")}` : undefined,
2361
+ row.preflight.expectedOutput ? `expected:${row.preflight.expectedOutput}` : undefined,
2362
+ row.preflight.independence ? `independence:${row.preflight.independence}` : undefined
2363
+ ].filter((value) => Boolean(value)).join(" · ") : "";
2364
+ c.addChild(new Text(truncLine(`${rowIndent}${workflowRowGlyph(row, theme, frame)} ${status} ${theme.bold(row.key)}${label}${run}${duration}${error}${hints ? ` ${theme.fg("dim", `· ${hints}`)}` : ""}`, width), 0, 0));
2365
+ }
2366
+ if (workflow?.preflightWarnings?.length) {
2367
+ const warningLines = expanded ? formatWorkflowPreflightWarnings(workflow.preflightWarnings, { indent: rowIndent }).split(`
2368
+ `) : [formatWorkflowPreflightWarningSummary(workflow.preflightWarnings, { indent: rowIndent, hint: "expand for debug" })];
2369
+ for (const warningLine of warningLines)
2370
+ c.addChild(new Text(truncLine(theme.fg("warning", warningLine), width), 0, 0));
2371
+ }
2372
+ if (workflow?.emits.length)
2373
+ c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}Emits ${workflow.emits.length}`), width), 0, 0));
1212
2374
  return c;
1213
2375
  }
1214
- function renderMultiCompact(d, theme, frame) {
1215
- const hasRunning = d.progress?.some((p) => p.status === "running") || d.results.some((r) => r.progress?.status === "running") || workflowGraphHasStatus(d, ["running"]);
1216
- const failed = d.results.some((r) => r.exitCode !== 0 && r.progress?.status !== "running") || workflowGraphHasStatus(d, ["failed"]);
1217
- const paused = d.results.some((r) => (r.interrupted || r.detached) && r.progress?.status !== "running") || workflowGraphHasStatus(d, ["paused", "detached"]);
2376
+ function renderMultiCompact(d, theme, layout, frame) {
2377
+ const hasRunning = detailsHaveRunningResult(d);
2378
+ const detached = d.results.some((r) => r.detached) || workflowGraphHasStatus(d, ["detached"]);
2379
+ const stopped = d.results.some((r) => r.stopped) || workflowGraphHasStatus(d, ["stopped"]);
2380
+ const failed = d.results.some((r) => !hasTerminalResultFlag(r) && r.exitCode !== 0 && !isResultRunning(r)) || workflowGraphHasStatus(d, ["failed"]);
2381
+ const paused = d.results.some((r) => r.interrupted) || workflowGraphHasStatus(d, ["paused"]);
2382
+ const partial = workflowGraphHasStatus(d, ["partial"]);
1218
2383
  let totalSummary = d.progressSummary;
1219
2384
  if (!totalSummary) {
1220
2385
  let sawProgress = false;
@@ -1234,29 +2399,36 @@ function renderMultiCompact(d, theme, frame) {
1234
2399
  const multiLabel = buildMultiProgressLabel(d, hasRunning);
1235
2400
  const itemTitle = multiLabel.itemTitle;
1236
2401
  const stats = statJoin(theme, [multiLabel.headerLabel, formatProgressStats(theme, totalSummary), formatTotalCostStat(d.totalCost)]);
1237
- const glyph = hasRunning ? theme.fg("accent", runningGlyph(frame !== undefined ? (runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex) ?? 0) + frame : runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex))) : failed ? theme.fg("error", "✗") : paused ? theme.fg("warning", "■") : theme.fg("success", "✓");
1238
- const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
2402
+ const aggregatePresentation = semanticResultPresentation({
2403
+ running: hasRunning,
2404
+ detached,
2405
+ stopped,
2406
+ interrupted: paused,
2407
+ failed,
2408
+ partial,
2409
+ seed: runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex),
2410
+ frame
2411
+ });
2412
+ const glyph = theme.fg(aggregatePresentation.tone, aggregatePresentation.glyph);
2413
+ const contextBadge = contextModeBadge(theme, d.context);
1239
2414
  const c = new Container;
1240
2415
  const width = getTermWidth() - 4;
2416
+ const rowIndent = mainWindowIndent(layout, 1);
2417
+ const detailIndent = mainWindowIndent(layout, 2);
1241
2418
  c.addChild(new Text(truncLine(`${glyph} ${theme.fg("toolTitle", theme.bold(d.mode))}${contextBadge}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`, width), 0, 0));
1242
2419
  const useResultsDirectly = multiLabel.hasParallelInChain || !d.chainAgents?.length;
1243
2420
  const displayStart = multiLabel.showActiveGroupOnly ? multiLabel.groupStartIndex : 0;
1244
2421
  const displayEnd = multiLabel.showActiveGroupOnly ? multiLabel.groupEndIndex : useResultsDirectly ? d.results.length : d.chainAgents.length;
1245
2422
  const chainEntries = buildChainRenderEntries(d, multiLabel);
1246
- const renderEntries = chainEntries ?? Array.from({ length: displayEnd - displayStart }, (_, offset) => {
1247
- const i = displayStart + offset;
1248
- const r = d.results[i];
1249
- const fallbackLabel = itemTitle.toLowerCase();
1250
- const rowNumber = multiLabel.showActiveGroupOnly ? i - multiLabel.groupStartIndex + 1 : i + 1;
1251
- return { kind: "result", resultIndex: i, rowNumber, agentName: useResultsDirectly ? r?.agent || `${fallbackLabel}-${rowNumber}` : d.chainAgents[i] || r?.agent || `${fallbackLabel}-${rowNumber}` };
1252
- });
2423
+ const renderEntries = chainEntries ?? buildForegroundResultEntries(d, multiLabel, displayStart, displayEnd, useResultsDirectly);
1253
2424
  for (const entry of renderEntries) {
1254
- if (entry.kind === "placeholder") {
2425
+ if (entry.kind === "group") {
1255
2426
  const glyph = widgetStepGlyph(entry.status, theme);
1256
2427
  const statusLabel = widgetStepStatus(entry.status, theme);
1257
- c.addChild(new Text(truncLine(` ${glyph} ${entry.stepLabel}: ${themeBold(theme, entry.agentName)} ${theme.fg("dim", "·")} ${statusLabel}`, width), 0, 0));
2428
+ const groupLabel = entry.groupLabel ? ` (${compactTaskText(undefined, entry.groupLabel) ?? entry.groupLabel})` : "";
2429
+ c.addChild(new Text(truncLine(`${rowIndent}${glyph} ${entry.stepLabel}${groupLabel} ${theme.fg("dim", "·")} ${statusLabel}`, width), 0, 0));
1258
2430
  if (entry.error)
1259
- c.addChild(new Text(truncLine(theme.fg("error", ` ⎿ Error: ${entry.error}`), width), 0, 0));
2431
+ c.addChild(new Text(truncLine(theme.fg("error", `${detailIndent}⎿ Error: ${entry.error}`), width), 0, 0));
1260
2432
  continue;
1261
2433
  }
1262
2434
  const i = entry.resultIndex;
@@ -1264,49 +2436,96 @@ function renderMultiCompact(d, theme, frame) {
1264
2436
  const rowNumber = entry.rowNumber;
1265
2437
  const agentName = entry.agentName;
1266
2438
  if (!r) {
1267
- const pendingLabel = chainEntries ? resultRowLabel(d, multiLabel, i, rowNumber) : `${itemTitle} ${rowNumber}`;
1268
- c.addChild(new Text(truncLine(theme.fg("dim", ` ◦ ${pendingLabel}: ${agentName} · pending`), width), 0, 0));
2439
+ const pendingLabel = entry.rowLabel ?? (entry.isParallel ? "" : `${itemTitle} ${rowNumber}`);
2440
+ const labelPrefix = pendingLabel ? `${pendingLabel}: ` : "";
2441
+ c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}◦ ${labelPrefix}${agentName} · pending`), width), 0, 0));
1269
2442
  continue;
1270
2443
  }
1271
2444
  const output = getSingleResultOutput(r);
1272
- const progressFromArray = d.progress?.find((p) => p.index === i) || d.progress?.find((p) => p.agent === r.agent && p.status === "running");
2445
+ const progressFromArray = foregroundProgressForResult(d, i);
1273
2446
  const rProg = r.progress || progressFromArray || r.progressSummary;
1274
- const rRunning = rProg && "status" in rProg && rProg.status === "running";
2447
+ const rRunning = rProg && "status" in rProg && isResultRunning(r, rProg.status);
1275
2448
  const rPending = rProg && "status" in rProg && rProg.status === "pending";
1276
- const stepNumber = r.progress?.index !== undefined ? r.progress.index + 1 : progressFromArray?.index !== undefined ? progressFromArray.index + 1 : i + 1;
1277
2449
  const stepStats = formatProgressStats(theme, rProg);
1278
2450
  const glyph = rPending ? theme.fg("dim", "◦") : resultGlyph(r, output, theme, rRunning, progressRunningSeed(rProg), frame);
1279
2451
  const pendingLabel = rPending ? ` ${theme.fg("dim", "· pending")}` : "";
1280
- const stepLabel = resultRowLabel(d, multiLabel, i, stepNumber);
1281
- const line = `${glyph} ${stepLabel}: ${themeBold(theme, agentName)}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}${pendingLabel}`;
1282
- c.addChild(new Text(truncLine(` ${line}`, width), 0, 0));
2452
+ const stepLabel = entry.rowLabel;
2453
+ const rowProgressModel = rProg && "status" in rProg ? rProg : undefined;
2454
+ const rowModelDisplay = modelThinkingBadge(theme, r.model ?? rowProgressModel?.model, r.thinking ?? rowProgressModel?.thinking);
2455
+ const labelPrefix = stepLabel ? `${stepLabel}: ` : "";
2456
+ const line = `${glyph} ${labelPrefix}${themeBold(theme, agentName)}${contextModeBadge(theme, r.context)}${rowModelDisplay}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}${pendingLabel}`;
2457
+ c.addChild(new Text(truncLine(`${rowIndent}${line}`, width), 0, 0));
2458
+ if (rRunning || rPending) {
2459
+ const task = compactTaskText(r.task, workflowLabelForResult(d, i));
2460
+ if (task)
2461
+ c.addChild(new Text(truncLine(theme.fg("dim", `${detailIndent}task: ${task}`), width), 0, 0));
2462
+ }
1283
2463
  if (rRunning && rProg && "status" in rProg) {
1284
- const activity = compactCurrentActivity(rProg);
1285
- c.addChild(new Text(truncLine(theme.fg("dim", ` ⎿ ${activity}`), width), 0, 0));
1286
- c.addChild(new Text(truncLine(theme.fg("accent", ` ${liveDetailHintText()}`), width), 0, 0));
2464
+ const liveProgress = rProg;
2465
+ const activity = compactCurrentActivity(liveProgress);
2466
+ c.addChild(new Text(truncLine(theme.fg("dim", `${detailIndent}⎿ ${activity}`), width), 0, 0));
2467
+ for (const nestedLine of formatNestedWidgetLines(r.children, theme, width, false, snapshotNowForProgress(liveProgress))) {
2468
+ c.addChild(new Text(truncLine(`${detailIndent}${nestedLine}`, width), 0, 0));
2469
+ }
2470
+ c.addChild(new Text(truncLine(theme.fg("accent", `${detailIndent}${liveDetailHintText()}`), width), 0, 0));
1287
2471
  } else if (!rPending && (r.exitCode !== 0 || r.interrupted || r.detached || hasEmptyTextOutputWithoutOutputTarget(r.task, output))) {
1288
- c.addChild(new Text(truncLine(theme.fg(r.exitCode !== 0 ? "error" : "dim", ` ⎿ ${resultStatusLine(r, output)}`), width), 0, 0));
2472
+ c.addChild(new Text(truncLine(theme.fg(r.exitCode !== 0 ? "error" : "dim", `${detailIndent}⎿ ${resultStatusLine(r, output)}`), width), 0, 0));
2473
+ }
2474
+ if (!rRunning && !rPending) {
2475
+ for (const nestedLine of formatNestedWidgetLines(r.children, theme, width, false, r.progress?.lastActivityAt)) {
2476
+ c.addChild(new Text(truncLine(`${detailIndent}${nestedLine}`, width), 0, 0));
2477
+ }
1289
2478
  }
1290
2479
  const outputTarget = extractOutputTarget(r.task);
1291
2480
  if (outputTarget)
1292
- c.addChild(new Text(truncLine(theme.fg("dim", ` output: ${outputTarget}`), width), 0, 0));
2481
+ c.addChild(new Text(truncLine(theme.fg("dim", `${detailIndent}output: ${outputTarget}`), width), 0, 0));
1293
2482
  if (r.artifactPaths)
1294
- c.addChild(new Text(truncLine(theme.fg("dim", ` output: ${shortenPath(r.artifactPaths.outputPath)}`), width), 0, 0));
2483
+ c.addChild(new Text(truncLine(theme.fg("dim", `${detailIndent}output: ${shortenPath(r.artifactPaths.outputPath)}`), width), 0, 0));
1295
2484
  }
1296
2485
  if (d.artifacts)
1297
- c.addChild(new Text(truncLine(theme.fg("dim", ` artifacts: ${shortenPath(d.artifacts.dir)}`), width), 0, 0));
2486
+ c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}artifacts: ${shortenPath(d.artifacts.dir)}`), width), 0, 0));
1298
2487
  return c;
1299
2488
  }
1300
- export function renderSubagentResult(result, options, theme, frame) {
2489
+ export function renderSubagentSummary(result, options, theme) {
2490
+ const details = result.details;
2491
+ const results = details?.results ?? [];
2492
+ const hasSingleTerminalResult = results.length === 1 && hasTerminalResult(results[0]);
2493
+ const hasOnlyTerminalResults = results.length > 0 && results.every(hasTerminalResult);
2494
+ const running = !hasSingleTerminalResult && !hasOnlyTerminalResults && (options.isPartial === true || Boolean(details?.asyncId && details.mode !== "management") || Boolean(details && detailsHaveRunningResult(details)));
2495
+ const stopped = results.some((entry) => entry.stopped) || Boolean(details && workflowGraphHasStatus(details, ["stopped"]));
2496
+ const paused = results.some((entry) => entry.interrupted || entry.detached) || Boolean(details && workflowGraphHasStatus(details, ["paused", "detached"]));
2497
+ const failed = result.isError === true || results.some((entry) => !hasTerminalResultFlag(entry) && entry.exitCode !== 0 && !isResultRunning(entry)) || Boolean(details && workflowGraphHasStatus(details, ["failed"]));
2498
+ const partial = Boolean(details && workflowGraphHasStatus(details, ["partial"]));
2499
+ const state = running ? "running" : failed ? "failed" : stopped ? "stopped" : paused ? "paused" : partial ? "partial" : "completed";
2500
+ const glyph = state === "running" ? theme.fg("accent", STATIC_RUNNING_GLYPH) : state === "completed" ? theme.fg("success", "✓") : state === "failed" ? theme.fg("error", "✗") : theme.fg("warning", "■");
2501
+ const label = details?.mode === "single" && results.length === 1 ? foregroundSingleDisplayName(results[0]) : details?.mode || "subagent";
2502
+ return new Text(truncLine(`${glyph} ${theme.fg("toolTitle", theme.bold(label))} ${theme.fg("dim", "·")} ${theme.fg(state === "failed" ? "error" : state === "completed" ? "success" : state === "running" ? "accent" : "warning", state)}`, getTermWidth() - 4), 0, 0);
2503
+ }
2504
+ export function renderSubagentResult(result, options, theme, frame, rendererConfig, foregroundDetachShortcut) {
2505
+ const layout = resolveMainWindowRenderLayout(rendererConfig);
2506
+ const compact = (component) => capCompactMainWindowResult(component, layout, theme, !options.expanded);
1301
2507
  const d = result.details;
2508
+ if (d?.mode === "workflow" && d.chatProgress?.mode === "live-card" && d.workflow?.value === undefined && (!result.isError || (d.workflow?.trace.length ?? 0) > 0)) {
2509
+ return compact(renderWorkflowChatProgress(d, result, theme, options.expanded ? resolveMainWindowRenderLayout() : layout, frame, options.expanded));
2510
+ }
1302
2511
  if (!d || !d.results.length) {
1303
2512
  const t = result.content[0];
1304
2513
  const text = t?.type === "text" ? t.text : "(no output)";
1305
- const contextPrefix = d?.context === "fork" ? `${theme.fg("warning", "[fork]")} ` : "";
2514
+ const contextPrefix = contextModePrefix(theme, d?.context);
1306
2515
  const width = getTermWidth() - 4;
1307
2516
  if (!text.includes(`
1308
2517
  `))
1309
- return new Text(truncLine(`${contextPrefix}${text}`, width), 0, 0);
2518
+ return compact(new Text(truncLine(`${contextPrefix}${text}`, width), 0, 0));
2519
+ if (d && !options.expanded && !result.isError) {
2520
+ const lines = text.split(/\r?\n/);
2521
+ const firstNonEmptyLine = lines.find((line) => line.trim())?.trim() || "(no output)";
2522
+ const compactLine = d.mode === "workflow" && d.preflight ? formatWorkflowPreflightPlanSummary(d.preflight) : firstNonEmptyLine;
2523
+ const c = new Container;
2524
+ const detailIndent = mainWindowIndent(layout, 1);
2525
+ c.addChild(new Text(truncLine(`${contextPrefix}${compactLine} · ${lines.length} lines`, width), 0, 0));
2526
+ c.addChild(new Text(truncLine(theme.fg("accent", `${detailIndent}Press ${liveDetailKeyText()} for full output`), width), 0, 0));
2527
+ return compact(c);
2528
+ }
1310
2529
  const c = new Container;
1311
2530
  const wrapped = wrapPlainText(`${contextPrefix}${text}`, width);
1312
2531
  for (const line of wrapped)
@@ -1317,25 +2536,34 @@ export function renderSubagentResult(result, options, theme, frame) {
1317
2536
  const mdTheme = getMarkdownTheme();
1318
2537
  if (d.mode === "single" && d.results.length === 1) {
1319
2538
  const r = d.results[0];
2539
+ const detachableShortcut = d.asyncId || d.background ? undefined : foregroundDetachShortcut;
2540
+ if (!r)
2541
+ return compact(renderMultiCompact(d, theme, layout, frame));
1320
2542
  if (!expanded)
1321
- return renderSingleCompact(d, r, theme, frame);
1322
- const isRunning = r.progress?.status === "running";
1323
- const icon = isRunning ? theme.fg("warning", "running") : r.detached ? theme.fg("warning", "detached") : r.exitCode === 0 ? theme.fg("success", "ok") : theme.fg("error", "failed");
1324
- const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
2543
+ return compact(renderSingleCompact(d, r, theme, layout, frame, detachableShortcut));
2544
+ const isRunning = isResultRunning(r);
2545
+ const contextBadge = contextModeBadge(theme, r.context ?? d.context);
1325
2546
  const output = r.truncation?.text || getSingleResultOutput(r);
2547
+ const presentation = styledResultPresentation(resultPresentation(r, output, isRunning, undefined, frame), theme);
1326
2548
  const progressInfo = isRunning && r.progress ? ` | ${r.progress.toolCount} tools, ${formatTokens(r.progress.tokens)} tok, ${formatDuration(r.progress.durationMs)}` : r.progressSummary ? ` | ${r.progressSummary.toolCount} tools, ${formatTokens(r.progressSummary.tokens)} tok, ${formatDuration(r.progressSummary.durationMs)}` : "";
1327
2549
  const w = getTermWidth() - 4;
1328
2550
  const fit = (text) => expanded ? text : truncLine(text, w);
1329
2551
  const toolCallLines = getToolCallLines(r, expanded);
1330
2552
  const c = new Container;
1331
- c.addChild(new Text(fit(`${icon} ${theme.fg("toolTitle", theme.bold(r.agent))}${contextBadge}${progressInfo}`), 0, 0));
2553
+ c.addChild(new Text(fit(`${presentation.glyph} ${theme.fg("toolTitle", theme.bold(foregroundSingleDisplayName(r)))}${contextBadge}${progressInfo} ${theme.fg("dim", "·")} ${presentation.label}`), 0, 0));
1332
2554
  c.addChild(new Spacer(1));
1333
2555
  const taskMaxLen = Math.max(20, w - 8);
1334
2556
  const taskPreview = expanded || r.task.length <= taskMaxLen ? r.task : `${r.task.slice(0, taskMaxLen)}...`;
1335
2557
  c.addChild(new Text(fit(theme.fg("dim", `Task: ${taskPreview}`)), 0, 0));
1336
2558
  c.addChild(new Spacer(1));
2559
+ if (!isRunning && (r.exitCode !== 0 || r.interrupted || r.detached || r.stopped)) {
2560
+ c.addChild(new Text(fit(theme.fg(r.exitCode !== 0 ? "error" : "dim", ` ⎿ ${resultStatusLine(r, output)}`)), 0, 0));
2561
+ }
1337
2562
  if (isRunning && r.progress) {
1338
2563
  const progressSnapshotNow = snapshotNowForProgress(r.progress);
2564
+ for (const nestedLine of formatNestedWidgetLines(r.children, theme, w, true, progressSnapshotNow, 12)) {
2565
+ c.addChild(new Text(fit(` ${nestedLine}`), 0, 0));
2566
+ }
1339
2567
  const toolLine = formatCurrentToolLine(r.progress, w, expanded, progressSnapshotNow);
1340
2568
  if (toolLine) {
1341
2569
  c.addChild(new Text(fit(theme.fg("warning", `> ${toolLine}`)), 0, 0));
@@ -1344,23 +2572,27 @@ export function renderSubagentResult(result, options, theme, frame) {
1344
2572
  if (liveStatusLine) {
1345
2573
  c.addChild(new Text(fit(theme.fg("accent", liveStatusLine)), 0, 0));
1346
2574
  }
1347
- c.addChild(new Text(fit(theme.fg("accent", liveDetailHintText())), 0, 0));
2575
+ c.addChild(new Text(fit(theme.fg("accent", foregroundSingleHintText(detachableShortcut))), 0, 0));
1348
2576
  if (r.artifactPaths) {
1349
2577
  c.addChild(new Text(fit(theme.fg("dim", `Artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
1350
2578
  }
1351
2579
  if (r.progress.recentTools?.length) {
1352
2580
  for (const t of r.progress.recentTools.slice(-3)) {
1353
2581
  const maxArgsLen = Math.max(40, w - 24);
1354
- const argsPreview = expanded || t.args.length <= maxArgsLen ? t.args : `${t.args.slice(0, maxArgsLen)}...`;
2582
+ const argsPreview = renderToolArgsPreview(t.args, maxArgsLen, expanded);
1355
2583
  c.addChild(new Text(fit(theme.fg("dim", `${t.tool}: ${argsPreview}`)), 0, 0));
1356
2584
  }
1357
2585
  }
1358
- for (const line of (r.progress.recentOutput ?? []).slice(-5)) {
2586
+ for (const line of compactRecentOutputLines(r.progress.recentOutput)) {
1359
2587
  c.addChild(new Text(fit(theme.fg("dim", ` ${line}`)), 0, 0));
1360
2588
  }
1361
2589
  if (toolLine || liveStatusLine || r.progress.recentTools?.length || r.progress.recentOutput?.length || r.artifactPaths) {
1362
2590
  c.addChild(new Spacer(1));
1363
2591
  }
2592
+ } else {
2593
+ for (const nestedLine of formatNestedWidgetLines(r.children, theme, w, true, r.progress?.lastActivityAt, 8)) {
2594
+ c.addChild(new Text(fit(` ${nestedLine}`), 0, 0));
2595
+ }
1364
2596
  }
1365
2597
  if (expanded) {
1366
2598
  for (const line of toolCallLines) {
@@ -1392,13 +2624,24 @@ export function renderSubagentResult(result, options, theme, frame) {
1392
2624
  return c;
1393
2625
  }
1394
2626
  if (!expanded)
1395
- return renderMultiCompact(d, theme, frame);
1396
- const hasRunning = d.progress?.some((p) => p.status === "running") || d.results.some((r) => r.progress?.status === "running") || workflowGraphHasStatus(d, ["running"]);
1397
- const ok = d.results.filter((r) => r.progress?.status === "completed" || r.exitCode === 0 && r.progress?.status !== "running").length;
1398
- const hasEmptyWithoutTarget = d.results.some((r) => r.exitCode === 0 && r.progress?.status !== "running" && hasEmptyTextOutputWithoutOutputTarget(r.task, getSingleResultOutput(r)));
1399
- const hasWorkflowFailure = workflowGraphHasStatus(d, ["failed"]);
1400
- const hasWorkflowPause = workflowGraphHasStatus(d, ["paused", "detached"]);
1401
- const icon = hasRunning ? theme.fg("warning", "running") : hasEmptyWithoutTarget ? theme.fg("warning", "warning") : hasWorkflowFailure ? theme.fg("error", "failed") : hasWorkflowPause ? theme.fg("warning", "paused") : ok === d.results.length ? theme.fg("success", "ok") : theme.fg("error", "failed");
2627
+ return compact(renderMultiCompact(d, theme, layout, frame));
2628
+ const hasRunning = detailsHaveRunningResult(d);
2629
+ const detached = d.results.some((r) => r.detached) || workflowGraphHasStatus(d, ["detached"]);
2630
+ const stopped = d.results.some((r) => r.stopped) || workflowGraphHasStatus(d, ["stopped"]);
2631
+ const failed = d.results.some((r) => !hasTerminalResultFlag(r) && r.exitCode !== 0 && !isResultRunning(r)) || workflowGraphHasStatus(d, ["failed"]);
2632
+ const paused = d.results.some((r) => r.interrupted) || workflowGraphHasStatus(d, ["paused"]);
2633
+ const partial = workflowGraphHasStatus(d, ["partial"]);
2634
+ const completedWithoutOutput = d.results.some((r) => !hasTerminalResultFlag(r) && r.exitCode === 0 && !isResultRunning(r) && hasEmptyTextOutputWithoutOutputTarget(r.task, getSingleResultOutput(r)));
2635
+ const presentation = styledResultPresentation(semanticResultPresentation({
2636
+ running: hasRunning,
2637
+ detached,
2638
+ stopped,
2639
+ interrupted: paused,
2640
+ failed,
2641
+ partial,
2642
+ completedWithoutOutput,
2643
+ frame
2644
+ }), theme);
1402
2645
  const totalSummary = d.progressSummary || d.results.reduce((acc, r) => {
1403
2646
  const prog = r.progress || r.progressSummary;
1404
2647
  if (prog) {
@@ -1414,22 +2657,21 @@ export function renderSubagentResult(result, options, theme, frame) {
1414
2657
  ].filter(Boolean);
1415
2658
  const summaryStr = summaryParts.length ? ` | ${summaryParts.join(", ")}` : "";
1416
2659
  const modeLabel = d.mode;
1417
- const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
2660
+ const contextBadge = contextModeBadge(theme, d.context);
1418
2661
  const multiLabel = buildMultiProgressLabel(d, hasRunning);
1419
2662
  const itemTitle = multiLabel.itemTitle;
1420
2663
  const chainVis = d.chainAgents?.length && !multiLabel.hasParallelInChain ? d.chainAgents.map((agent, i) => {
1421
2664
  const result = d.results[i];
1422
- const isFailed = result && result.exitCode !== 0 && result.progress?.status !== "running";
1423
- const isComplete = result && result.exitCode === 0 && result.progress?.status !== "running";
1424
- const isEmptyWithoutTarget = Boolean(result) && Boolean(isComplete) && hasEmptyTextOutputWithoutOutputTarget(result.task, getSingleResultOutput(result));
2665
+ const displayName = foregroundResultDisplayName(d, i, result, agent);
1425
2666
  const isCurrent = i === (d.currentStepIndex ?? d.results.length);
1426
- const stepIcon = isFailed ? theme.fg("error", "failed") : isEmptyWithoutTarget ? theme.fg("warning", "warning") : isComplete ? theme.fg("success", "done") : isCurrent && hasRunning ? theme.fg("warning", "running") : theme.fg("dim", "pending");
1427
- return `${stepIcon} ${agent}`;
2667
+ const stepPresentation = result ? styledResultPresentation(resultPresentation(result, getSingleResultOutput(result), isCurrent && hasRunning && !hasTerminalResultFlag(result)), theme) : undefined;
2668
+ const stepStatus = stepPresentation ? `${stepPresentation.glyph} ${stepPresentation.label}` : theme.fg("dim", "◦ pending");
2669
+ return `${stepStatus} ${displayName}${contextModeBadge(theme, result?.context)}`;
1428
2670
  }).join(theme.fg("dim", " → ")) : null;
1429
2671
  const w = getTermWidth() - 4;
1430
2672
  const fit = (text) => expanded ? text : truncLine(text, w);
1431
2673
  const c = new Container;
1432
- c.addChild(new Text(fit(`${icon} ${theme.fg("toolTitle", theme.bold(modeLabel))}${contextBadge} · ${multiLabel.headerLabel}${summaryStr}`), 0, 0));
2674
+ c.addChild(new Text(fit(`${presentation.glyph} ${theme.fg("toolTitle", theme.bold(modeLabel))}${contextBadge} · ${multiLabel.headerLabel}${summaryStr} ${theme.fg("dim", "·")} ${presentation.label}`), 0, 0));
1433
2675
  if (chainVis) {
1434
2676
  c.addChild(new Text(fit(` ${chainVis}`), 0, 0));
1435
2677
  }
@@ -1437,17 +2679,13 @@ export function renderSubagentResult(result, options, theme, frame) {
1437
2679
  const displayStart = multiLabel.showActiveGroupOnly ? multiLabel.groupStartIndex : 0;
1438
2680
  const displayEnd = multiLabel.showActiveGroupOnly ? multiLabel.groupEndIndex : useResultsDirectly ? d.results.length : d.chainAgents.length;
1439
2681
  const chainEntries = buildChainRenderEntries(d, multiLabel);
1440
- const renderEntries = chainEntries ?? Array.from({ length: displayEnd - displayStart }, (_, offset) => {
1441
- const i = displayStart + offset;
1442
- const r = d.results[i];
1443
- const rowNumber = multiLabel.showActiveGroupOnly ? i - multiLabel.groupStartIndex + 1 : i + 1;
1444
- return { kind: "result", resultIndex: i, rowNumber, agentName: useResultsDirectly ? r?.agent || `step-${rowNumber}` : d.chainAgents[i] || r?.agent || `step-${rowNumber}` };
1445
- });
2682
+ const renderEntries = chainEntries ?? buildForegroundResultEntries(d, multiLabel, displayStart, displayEnd, useResultsDirectly);
1446
2683
  c.addChild(new Spacer(1));
1447
2684
  for (const entry of renderEntries) {
1448
- if (entry.kind === "placeholder") {
2685
+ if (entry.kind === "group") {
1449
2686
  const statusLabel = widgetStepStatus(entry.status, theme);
1450
- c.addChild(new Text(fit(` ${statusLabel} ${entry.stepLabel}: ${theme.bold(entry.agentName)}`), 0, 0));
2687
+ const groupLabel = entry.groupLabel ? ` (${compactTaskText(undefined, entry.groupLabel) ?? entry.groupLabel})` : "";
2688
+ c.addChild(new Text(fit(` ${statusLabel} ${entry.stepLabel}${groupLabel}`), 0, 0));
1451
2689
  c.addChild(new Text(theme.fg(entry.status === "failed" ? "error" : "dim", ` status: ${entry.status}`), 0, 0));
1452
2690
  if (entry.error)
1453
2691
  c.addChild(new Text(theme.fg("error", ` error: ${entry.error}`), 0, 0));
@@ -1459,22 +2697,24 @@ export function renderSubagentResult(result, options, theme, frame) {
1459
2697
  const rowNumber = entry.rowNumber;
1460
2698
  const agentName = entry.agentName;
1461
2699
  if (!r) {
1462
- const pendingLabel = chainEntries ? resultRowLabel(d, multiLabel, i, rowNumber) : `${itemTitle} ${rowNumber}`;
1463
- c.addChild(new Text(fit(theme.fg("dim", ` ${pendingLabel}: ${agentName}`)), 0, 0));
2700
+ const pendingLabel = entry.rowLabel ?? (entry.isParallel ? "" : `${itemTitle} ${rowNumber}`);
2701
+ const labelPrefix = pendingLabel ? `${pendingLabel}: ` : "";
2702
+ c.addChild(new Text(fit(theme.fg("dim", ` ${labelPrefix}${agentName}`)), 0, 0));
1464
2703
  c.addChild(new Text(theme.fg("dim", ` status: pending`), 0, 0));
1465
2704
  c.addChild(new Spacer(1));
1466
2705
  continue;
1467
2706
  }
1468
- const progressFromArray = d.progress?.find((p) => p.index === i) || d.progress?.find((p) => p.agent === r.agent && p.status === "running");
2707
+ const progressFromArray = foregroundProgressForResult(d, i);
1469
2708
  const rProg = r.progress || progressFromArray || r.progressSummary;
1470
- const rRunning = rProg?.status === "running";
1471
- const stepNumber = typeof rProg?.index === "number" ? rProg.index + 1 : i + 1;
2709
+ const rRunning = isResultRunning(r, rProg?.status);
1472
2710
  const resultOutput = getSingleResultOutput(r);
1473
- const statusIcon = rRunning ? theme.fg("warning", "running") : r.exitCode !== 0 ? theme.fg("error", "failed") : hasEmptyTextOutputWithoutOutputTarget(r.task, resultOutput) ? theme.fg("warning", "warning") : theme.fg("success", "done");
2711
+ const rowPresentation = styledResultPresentation(resultPresentation(r, resultOutput, rRunning, progressRunningSeed(rProg), frame), theme);
1474
2712
  const stats = rProg ? ` | ${rProg.toolCount} tools, ${formatDuration(rProg.durationMs)}` : "";
1475
- const modelDisplay = modelThinkingBadge(theme, r.model);
1476
- const stepLabel = resultRowLabel(d, multiLabel, i, stepNumber);
1477
- const stepHeader = rRunning ? `${statusIcon} ${stepLabel}: ${theme.bold(theme.fg("warning", r.agent))}${modelDisplay}${stats}` : `${statusIcon} ${stepLabel}: ${theme.bold(r.agent)}${modelDisplay}${stats}`;
2713
+ const modelDisplay = modelThinkingBadge(theme, r.model ?? rProg?.model, r.thinking ?? rProg?.thinking);
2714
+ const stepLabel = entry.rowLabel;
2715
+ const contextBadge = contextModeBadge(theme, r.context);
2716
+ const labelPrefix = stepLabel ? `${stepLabel}: ` : "";
2717
+ const stepHeader = rRunning ? `${rowPresentation.glyph} ${labelPrefix}${theme.bold(theme.fg("warning", agentName))}${contextBadge}${modelDisplay}${stats} ${theme.fg("dim", "·")} ${rowPresentation.label}` : `${rowPresentation.glyph} ${labelPrefix}${theme.bold(agentName)}${contextBadge}${modelDisplay}${stats} ${theme.fg("dim", "·")} ${rowPresentation.label}`;
1478
2718
  const toolCallLines = getToolCallLines(r, expanded);
1479
2719
  c.addChild(new Text(fit(stepHeader), 0, 0));
1480
2720
  const taskMaxLen = Math.max(20, w - 12);
@@ -1484,6 +2724,9 @@ export function renderSubagentResult(result, options, theme, frame) {
1484
2724
  if (outputTarget) {
1485
2725
  c.addChild(new Text(fit(theme.fg("dim", ` output: ${outputTarget}`)), 0, 0));
1486
2726
  }
2727
+ if (!rRunning && (r.exitCode !== 0 || r.interrupted || r.detached || r.stopped)) {
2728
+ c.addChild(new Text(fit(theme.fg(r.exitCode !== 0 ? "error" : "dim", ` ⎿ ${resultStatusLine(r, resultOutput)}`)), 0, 0));
2729
+ }
1487
2730
  if (r.skills?.length) {
1488
2731
  c.addChild(new Text(fit(theme.fg("dim", ` skills: ${r.skills.join(", ")}`)), 0, 0));
1489
2732
  }
@@ -1506,6 +2749,9 @@ export function renderSubagentResult(result, options, theme, frame) {
1506
2749
  if (liveStatusLine) {
1507
2750
  c.addChild(new Text(fit(theme.fg("accent", ` ${liveStatusLine}`)), 0, 0));
1508
2751
  }
2752
+ for (const nestedLine of formatNestedWidgetLines(r.children, theme, w, true, progressSnapshotNow, 8)) {
2753
+ c.addChild(new Text(fit(` ${nestedLine}`), 0, 0));
2754
+ }
1509
2755
  c.addChild(new Text(fit(theme.fg("accent", ` ${liveDetailHintText()}`)), 0, 0));
1510
2756
  if (r.artifactPaths) {
1511
2757
  c.addChild(new Text(fit(theme.fg("dim", ` artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
@@ -1513,15 +2759,19 @@ export function renderSubagentResult(result, options, theme, frame) {
1513
2759
  if (rProg.recentTools?.length) {
1514
2760
  for (const t of rProg.recentTools.slice(-3)) {
1515
2761
  const maxArgsLen = Math.max(40, w - 30);
1516
- const argsPreview = expanded || t.args.length <= maxArgsLen ? t.args : `${t.args.slice(0, maxArgsLen)}...`;
2762
+ const argsPreview = renderToolArgsPreview(t.args, maxArgsLen, expanded);
1517
2763
  c.addChild(new Text(fit(theme.fg("dim", ` ${t.tool}: ${argsPreview}`)), 0, 0));
1518
2764
  }
1519
2765
  }
1520
- const recentLines = (rProg.recentOutput ?? []).slice(-5);
1521
- for (const line of recentLines) {
2766
+ for (const line of compactRecentOutputLines(rProg.recentOutput)) {
1522
2767
  c.addChild(new Text(fit(theme.fg("dim", ` ${line}`)), 0, 0));
1523
2768
  }
1524
2769
  }
2770
+ if (!rRunning) {
2771
+ for (const nestedLine of formatNestedWidgetLines(r.children, theme, w, true, r.progress?.lastActivityAt, 8)) {
2772
+ c.addChild(new Text(fit(` ${nestedLine}`), 0, 0));
2773
+ }
2774
+ }
1525
2775
  if (!rRunning && r.artifactPaths) {
1526
2776
  c.addChild(new Text(fit(theme.fg("dim", ` artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
1527
2777
  }