@selesai/code 0.8.3 → 0.8.5

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 (518) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +7 -1
  3. package/dist/core/slash-commands.js +1 -0
  4. package/dist/defaults/models.json +41 -55
  5. package/dist/defaults/settings.json +7 -8
  6. package/dist/extensions/handoff-new.ts +1 -1
  7. package/dist/extensions/inline-skills.test.ts +1 -0
  8. package/dist/extensions/inline-skills.ts +1 -1
  9. package/dist/extensions/pi-intercom/CHANGELOG.md +25 -3
  10. package/dist/extensions/pi-intercom/README.md +34 -15
  11. package/dist/extensions/pi-intercom/broker/broker.ts +38 -141
  12. package/dist/extensions/pi-intercom/broker/client-liveness.test.ts +173 -0
  13. package/dist/extensions/pi-intercom/broker/client.ts +78 -150
  14. package/dist/extensions/pi-intercom/broker/extension-state.ts +18 -7
  15. package/dist/extensions/pi-intercom/broker/framing.ts +1 -1
  16. package/dist/extensions/pi-intercom/broker/protocol.ts +182 -0
  17. package/dist/extensions/pi-intercom/broker/spawn.test.ts +106 -7
  18. package/dist/extensions/pi-intercom/broker/spawn.ts +37 -11
  19. package/dist/extensions/pi-intercom/config.test.ts +8 -10
  20. package/dist/extensions/pi-intercom/config.ts +3 -3
  21. package/dist/extensions/pi-intercom/index.ts +218 -147
  22. package/dist/extensions/pi-intercom/intercom.integration.test.ts +891 -103
  23. package/dist/extensions/pi-intercom/package.json +3 -2
  24. package/dist/extensions/pi-intercom/project-agent.test.ts +145 -0
  25. package/dist/extensions/pi-intercom/project-agent.ts +324 -0
  26. package/dist/extensions/pi-intercom/reply-tracker.test.ts +86 -1
  27. package/dist/extensions/pi-intercom/reply-tracker.ts +41 -9
  28. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +49 -9
  29. package/dist/extensions/pi-intercom/test/inline-message.test.ts +9 -9
  30. package/dist/extensions/pi-intercom/types.ts +3 -1
  31. package/dist/extensions/pi-intercom/ui/inline-message.ts +7 -7
  32. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +2 -2
  33. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +14 -1
  34. package/dist/extensions/pi-powerline-footer/README.md +20 -37
  35. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +40 -25
  36. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +149 -18
  37. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +1 -0
  38. package/dist/extensions/pi-powerline-footer/editor-performance.ts +99 -0
  39. package/dist/extensions/pi-powerline-footer/git-status.ts +15 -0
  40. package/dist/extensions/pi-powerline-footer/index.ts +394 -317
  41. package/dist/extensions/pi-powerline-footer/package.json +1 -1
  42. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -12
  43. package/dist/extensions/pi-powerline-footer/queue/store.ts +8 -56
  44. package/dist/extensions/pi-powerline-footer/queue/types.ts +1 -2
  45. package/dist/extensions/pi-powerline-footer/segments.ts +0 -3
  46. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +231 -1
  47. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +5 -19
  48. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +43 -0
  49. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +64 -0
  50. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +116 -0
  51. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +19 -149
  52. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +6 -0
  53. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +5 -5
  54. package/dist/extensions/pi-powerline-footer/types.ts +1 -2
  55. package/dist/extensions/pi-subagents/CHANGELOG.md +225 -14
  56. package/dist/extensions/pi-subagents/README.md +66 -1613
  57. package/dist/extensions/pi-subagents/agents/builder.md +0 -1
  58. package/dist/extensions/pi-subagents/agents/delegate.md +14 -0
  59. package/dist/extensions/pi-subagents/agents/oracle.md +74 -0
  60. package/dist/extensions/pi-subagents/agents/reviewer.md +78 -0
  61. package/dist/extensions/pi-subagents/agents/scout.md +50 -0
  62. package/dist/extensions/pi-subagents/agents/worker.md +58 -0
  63. package/dist/extensions/pi-subagents/docs/configuration.md +372 -0
  64. package/dist/extensions/pi-subagents/docs/extension-api.md +308 -0
  65. package/dist/extensions/pi-subagents/docs/missions.md +119 -0
  66. package/dist/extensions/pi-subagents/docs/models.md +192 -0
  67. package/dist/extensions/pi-subagents/docs/observability.md +174 -0
  68. package/dist/extensions/pi-subagents/docs/tool-reference.md +343 -0
  69. package/dist/extensions/pi-subagents/docs/watchdog.md +176 -0
  70. package/dist/extensions/pi-subagents/docs/workflows.md +163 -0
  71. package/dist/extensions/pi-subagents/inspector-runner.mjs +11 -0
  72. package/dist/extensions/pi-subagents/package-lock.json +152 -194
  73. package/dist/extensions/pi-subagents/package.json +9 -10
  74. package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
  75. package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
  76. package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
  77. package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
  78. package/dist/extensions/pi-subagents/prompts/review-loop.md +12 -12
  79. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +22 -17
  80. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +77 -72
  81. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +177 -69
  82. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +28 -8
  83. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +88 -66
  84. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +3 -3
  85. package/dist/extensions/pi-subagents/src/agents/agent-refinements.ts +624 -0
  86. package/dist/extensions/pi-subagents/src/agents/agents.ts +38 -19
  87. package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +7 -3
  88. package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
  89. package/dist/extensions/pi-subagents/src/agents/skills.ts +2 -9
  90. package/dist/extensions/pi-subagents/src/api/delegation.ts +1 -2
  91. package/dist/extensions/pi-subagents/src/api/project-panes.ts +30 -0
  92. package/dist/extensions/pi-subagents/src/extension/config.ts +62 -8
  93. package/dist/extensions/pi-subagents/src/extension/control-notices.ts +2 -2
  94. package/dist/extensions/pi-subagents/src/extension/doctor.ts +40 -0
  95. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +51 -26
  96. package/dist/extensions/pi-subagents/src/extension/index.ts +140 -50
  97. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +77 -0
  98. package/dist/extensions/pi-subagents/src/extension/rpc.ts +18 -24
  99. package/dist/extensions/pi-subagents/src/extension/schemas.ts +58 -21
  100. package/dist/extensions/pi-subagents/src/extension/subagent-guide.ts +39 -0
  101. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +47 -25
  102. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +3 -3
  103. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +460 -64
  104. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -1
  105. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +102 -4
  106. package/dist/extensions/pi-subagents/src/missions/actions.ts +77 -16
  107. package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +162 -0
  108. package/dist/extensions/pi-subagents/src/missions/lifecycle.ts +65 -17
  109. package/dist/extensions/pi-subagents/src/missions/store.ts +151 -9
  110. package/dist/extensions/pi-subagents/src/missions/types.ts +58 -3
  111. package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +260 -0
  112. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -8
  113. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +374 -0
  114. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +46 -0
  115. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +158 -36
  116. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +294 -134
  117. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +26 -4
  118. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +45 -6
  119. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +33 -15
  120. package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +267 -0
  121. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +145 -60
  122. package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +104 -0
  123. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +17 -3
  124. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +114 -6
  125. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +33 -0
  126. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +68 -0
  127. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +71 -0
  128. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +26 -3
  129. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +42 -14
  130. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +12 -24
  131. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +7 -5
  132. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +233 -101
  133. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +17 -4
  134. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +146 -0
  135. package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +21 -5
  136. package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +85 -0
  137. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +43 -18
  138. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +20 -19
  139. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +40 -2
  140. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +188 -46
  141. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +12 -0
  142. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +137 -0
  143. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +171 -0
  144. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1365 -252
  145. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +187 -0
  146. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +178 -26
  147. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  148. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +286 -0
  149. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -0
  150. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +32 -5
  151. package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +12 -2
  152. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +3 -0
  153. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +509 -117
  154. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +10 -10
  155. package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +280 -0
  156. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +4 -2
  157. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +189 -32
  158. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +21 -5
  159. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +20 -8
  160. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +128 -3
  161. package/dist/extensions/pi-subagents/src/shared/display-text.ts +100 -0
  162. package/dist/extensions/pi-subagents/src/shared/file-coalescer.ts +9 -0
  163. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +13 -0
  164. package/dist/extensions/pi-subagents/src/shared/formatters.ts +4 -6
  165. package/dist/extensions/pi-subagents/src/shared/prompt-resources.ts +51 -0
  166. package/dist/extensions/pi-subagents/src/shared/settings.ts +41 -11
  167. package/dist/extensions/pi-subagents/src/shared/types.ts +192 -3
  168. package/dist/extensions/pi-subagents/src/shared/utf8.ts +11 -0
  169. package/dist/extensions/pi-subagents/src/shared/utils.ts +46 -34
  170. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +1 -14
  171. package/dist/extensions/pi-subagents/src/slash/inline-subagents.ts +1 -1
  172. package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +14 -12
  173. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +176 -21
  174. package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +12 -6
  175. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +52 -18
  176. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +36 -5
  177. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +37 -21
  178. package/dist/extensions/pi-subagents/src/tui/fleet.ts +375 -42
  179. package/dist/extensions/pi-subagents/src/tui/render.ts +201 -68
  180. package/dist/extensions/pi-subagents/src/types/pi-runtime-compat.d.ts +0 -6
  181. package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +5 -4
  182. package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +0 -1
  183. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +332 -27
  184. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +15 -15
  185. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/cli.d.ts +3 -0
  186. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/register-bedrock.d.ts +2 -0
  187. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/restore-sandbox-env.d.ts +17 -0
  188. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/args.d.ts +57 -0
  189. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/config-selector.d.ts +16 -0
  190. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/file-processor.d.ts +15 -0
  191. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/initial-message.d.ts +18 -0
  192. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/list-models.d.ts +9 -0
  193. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/project-trust.d.ts +10 -0
  194. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/session-picker.d.ts +10 -0
  195. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/startup-ui.d.ts +20 -0
  196. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli.d.ts +3 -0
  197. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/config.d.ts +96 -0
  198. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-runtime.d.ts +119 -0
  199. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-services.d.ts +85 -0
  200. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session.d.ts +620 -0
  201. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-guidance.d.ts +5 -0
  202. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-storage.d.ts +56 -0
  203. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/bash-executor.d.ts +32 -0
  204. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/cache-stats.d.ts +49 -0
  205. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/branch-summarization.d.ts +93 -0
  206. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/compaction.d.ts +128 -0
  207. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/index.d.ts +7 -0
  208. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/utils.d.ts +38 -0
  209. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/defaults.d.ts +3 -0
  210. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/diagnostics.d.ts +15 -0
  211. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/event-bus.d.ts +9 -0
  212. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/exec.d.ts +29 -0
  213. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/experimental.d.ts +2 -0
  214. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/ansi-to-html.d.ts +22 -0
  215. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/index.d.ts +37 -0
  216. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/tool-renderer.d.ts +34 -0
  217. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/index.d.ts +12 -0
  218. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/loader.d.ts +23 -0
  219. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/runner.d.ts +171 -0
  220. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +1261 -0
  221. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/wrapper.d.ts +20 -0
  222. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/footer-data-provider.d.ts +54 -0
  223. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/http-dispatcher.d.ts +22 -0
  224. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/index.d.ts +13 -0
  225. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/keybindings.d.ts +358 -0
  226. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/messages.d.ts +77 -0
  227. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-config.d.ts +512 -0
  228. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-registry.d.ts +43 -0
  229. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-resolver.d.ts +121 -0
  230. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-runtime.d.ts +82 -0
  231. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/models-store.d.ts +17 -0
  232. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/output-guard.d.ts +7 -0
  233. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/package-manager.d.ts +210 -0
  234. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/project-trust.d.ts +15 -0
  235. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/prompt-templates.d.ts +54 -0
  236. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-attribution.d.ts +4 -0
  237. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-composer.d.ts +55 -0
  238. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/radius.d.ts +2 -0
  239. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/remote-catalog-provider.d.ts +5 -0
  240. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resolve-config-value.d.ts +30 -0
  241. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resource-loader.d.ts +206 -0
  242. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/runtime-credentials.d.ts +15 -0
  243. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/sdk.d.ts +106 -0
  244. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-cwd.d.ts +19 -0
  245. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-manager.d.ts +356 -0
  246. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/settings-manager.d.ts +297 -0
  247. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/skills.d.ts +60 -0
  248. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/slash-commands.d.ts +15 -0
  249. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/source-info.d.ts +18 -0
  250. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/system-prompt.d.ts +28 -0
  251. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/telemetry.d.ts +3 -0
  252. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/timings.d.ts +10 -0
  253. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/bash.d.ts +68 -0
  254. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit-diff.d.ts +106 -0
  255. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit.d.ts +51 -0
  256. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/file-mutation-queue.d.ts +6 -0
  257. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/find.d.ts +35 -0
  258. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/grep.d.ts +37 -0
  259. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/index.d.ts +40 -0
  260. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/ls.d.ts +37 -0
  261. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/output-accumulator.d.ts +52 -0
  262. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/path-utils.d.ts +10 -0
  263. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/read.d.ts +35 -0
  264. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/render-utils.d.ts +24 -0
  265. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
  266. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/truncate.d.ts +70 -0
  267. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/write.d.ts +26 -0
  268. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/trust-manager.d.ts +36 -0
  269. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/usage-totals.d.ts +19 -0
  270. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/index.d.ts +3 -0
  271. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/client.d.ts +61 -0
  272. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/huggingface.d.ts +23 -0
  273. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/index.d.ts +3 -0
  274. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/provider.d.ts +10 -0
  275. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/ui.d.ts +42 -0
  276. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/index.d.ts +35 -0
  277. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/main.d.ts +12 -0
  278. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/migrations.d.ts +33 -0
  279. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/index.d.ts +9 -0
  280. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/armin.d.ts +34 -0
  281. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/assistant-message.d.ts +22 -0
  282. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bash-execution.d.ts +34 -0
  283. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bordered-loader.d.ts +16 -0
  284. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
  285. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
  286. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/config-selector.d.ts +102 -0
  287. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
  288. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-editor.d.ts +21 -0
  289. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-entry.d.ts +19 -0
  290. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-message.d.ts +20 -0
  291. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/daxnuts.d.ts +23 -0
  292. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/diff.d.ts +12 -0
  293. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
  294. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/earendil-announcement.d.ts +5 -0
  295. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-editor.d.ts +22 -0
  296. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-input.d.ts +23 -0
  297. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-selector.d.ts +26 -0
  298. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/first-time-setup.d.ts +25 -0
  299. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/footer.d.ts +32 -0
  300. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/index.d.ts +34 -0
  301. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/keybinding-hints.d.ts +13 -0
  302. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/login-dialog.d.ts +52 -0
  303. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/model-selector.d.ts +54 -0
  304. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/oauth-selector.d.ts +33 -0
  305. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/scoped-models-selector.d.ts +42 -0
  306. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
  307. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector.d.ts +95 -0
  308. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/settings-selector.d.ts +77 -0
  309. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
  310. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
  311. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/status-indicator.d.ts +28 -0
  312. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/theme-selector.d.ts +11 -0
  313. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
  314. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tool-execution.d.ts +63 -0
  315. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tree-selector.d.ts +94 -0
  316. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/trust-selector.d.ts +23 -0
  317. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
  318. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message.d.ts +14 -0
  319. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
  320. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/interactive-mode.d.ts +394 -0
  321. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/model-search.d.ts +12 -0
  322. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme-controller.d.ts +29 -0
  323. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme.d.ts +120 -0
  324. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/print-mode.d.ts +28 -0
  325. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/jsonl.d.ts +17 -0
  326. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-client.d.ts +246 -0
  327. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-mode.d.ts +20 -0
  328. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-types.d.ts +457 -0
  329. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/package-manager-cli.d.ts +8 -0
  330. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/rpc-entry.d.ts +3 -0
  331. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/ansi.d.ts +2 -0
  332. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/changelog.d.ts +22 -0
  333. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/child-process.d.ts +18 -0
  334. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-image.d.ts +11 -0
  335. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-native.d.ts +11 -0
  336. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard.d.ts +4 -0
  337. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/deprecation.d.ts +4 -0
  338. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/exif-orientation.d.ts +5 -0
  339. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/frontmatter.d.ts +8 -0
  340. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/fs-watch.d.ts +5 -0
  341. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/git.d.ts +26 -0
  342. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/html.d.ts +7 -0
  343. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-convert.d.ts +10 -0
  344. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-process.d.ts +18 -0
  345. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-core.d.ts +30 -0
  346. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-worker.d.ts +2 -0
  347. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize.d.ts +16 -0
  348. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/json.d.ts +3 -0
  349. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/mime.d.ts +3 -0
  350. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/open-browser.d.ts +9 -0
  351. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/paths.d.ts +31 -0
  352. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/photon.d.ts +21 -0
  353. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/pi-user-agent.d.ts +2 -0
  354. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/shell.d.ts +31 -0
  355. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/sleep.d.ts +5 -0
  356. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/syntax-highlight.d.ts +12 -0
  357. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/tools-manager.d.ts +3 -0
  358. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/version-check.d.ts +15 -0
  359. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/windows-self-update.d.ts +3 -0
  360. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/index.mjs +84 -0
  361. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/package.json +14 -0
  362. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +102 -15
  363. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +507 -306
  364. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +309 -123
  365. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +124 -58
  366. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +62 -16
  367. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +167 -113
  368. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +1 -1
  369. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +4 -2
  370. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +139 -58
  371. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +324 -59
  372. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +98 -22
  373. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +106 -106
  374. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +416 -106
  375. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +287 -17
  376. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1122 -96
  377. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +107 -20
  378. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +9 -9
  379. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +26 -6
  380. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +2 -2
  381. package/dist/extensions/pi-subagents/test/support/helpers.ts +2 -0
  382. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +1 -1
  383. package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +0 -8
  384. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +194 -59
  385. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +317 -0
  386. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
  387. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
  388. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +334 -248
  389. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +52 -52
  390. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +26 -26
  391. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +118 -99
  392. package/dist/extensions/pi-subagents/test/unit/agent-refinements.test.ts +160 -0
  393. package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +9 -9
  394. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +43 -1
  395. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +36 -10
  396. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +387 -30
  397. package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +9 -9
  398. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +18 -3
  399. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +99 -40
  400. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +5 -3
  401. package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +7 -7
  402. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +19 -19
  403. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +94 -23
  404. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +9 -9
  405. package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +20 -20
  406. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +9 -9
  407. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +35 -5
  408. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +2 -2
  409. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +70 -52
  410. package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +187 -0
  411. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +125 -71
  412. package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +18 -3
  413. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +12 -12
  414. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +17 -23
  415. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +30 -2
  416. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +25 -25
  417. package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +3 -3
  418. package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +15 -0
  419. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +91 -47
  420. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +468 -38
  421. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +14 -4
  422. package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +37 -0
  423. package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +69 -0
  424. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +79 -2
  425. package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +102 -49
  426. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +98 -32
  427. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +7 -6
  428. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +171 -0
  429. package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +116 -0
  430. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +81 -7
  431. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +289 -13
  432. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +42 -1
  433. package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +2 -2
  434. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +11 -2
  435. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +84 -10
  436. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +122 -8
  437. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +8 -8
  438. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +24 -24
  439. package/dist/extensions/pi-subagents/test/unit/owned-process-tree.test.ts +69 -0
  440. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +16 -2
  441. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +7 -7
  442. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +10 -10
  443. package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +3 -3
  444. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +26 -17
  445. package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +405 -0
  446. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +440 -92
  447. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +24 -8
  448. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +6 -6
  449. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +43 -43
  450. package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +13 -13
  451. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +59 -6
  452. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +19 -19
  453. package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +243 -0
  454. package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +68 -168
  455. package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +58 -15
  456. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +51 -0
  457. package/dist/extensions/pi-subagents/test/unit/reads-resolution.test.ts +66 -0
  458. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +47 -28
  459. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +12 -12
  460. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +57 -0
  461. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +111 -31
  462. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +121 -0
  463. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +1 -1
  464. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +65 -50
  465. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +69 -28
  466. package/dist/extensions/pi-subagents/test/unit/scheduled-store-root.test.ts +67 -0
  467. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +60 -44
  468. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +424 -49
  469. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +28 -0
  470. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +23 -1
  471. package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +32 -5
  472. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +13 -13
  473. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +130 -92
  474. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +2 -2
  475. package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +85 -0
  476. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +35 -35
  477. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +24 -0
  478. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +312 -22
  479. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +150 -15
  480. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +65 -26
  481. package/dist/extensions/pi-subagents/test/unit/timeout-defaults.test.ts +138 -0
  482. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +76 -25
  483. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +5 -5
  484. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +55 -6
  485. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +13 -13
  486. package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +1 -1
  487. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +1 -1
  488. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +8 -8
  489. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +6 -6
  490. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +138 -11
  491. package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +8 -8
  492. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +113 -0
  493. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +68 -7
  494. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +6 -6
  495. package/dist/extensions/workflow/extension.ts +21 -19
  496. package/dist/extensions/workflow/modes.ts +65 -0
  497. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  498. package/dist/modes/interactive/interactive-mode.js +48 -1
  499. package/dist/skills/pi-subagents/SKILL.md +20 -15
  500. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +77 -83
  501. package/dist/skills/pi-subagents/references/execution-controls.md +215 -106
  502. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +29 -9
  503. package/dist/skills/pi-subagents/references/prompting-and-roles.md +58 -43
  504. package/docs/plans/workflow-autoloop-reference.md +192 -0
  505. package/docs/plans/workflow-subagent-consolidation-planb.md +374 -0
  506. package/docs/plans/workflow-subagent-scripted-merge.md +406 -0
  507. package/docs/quickstart.md +27 -43
  508. package/docs/settings.md +4 -0
  509. package/docs/usage.md +1 -0
  510. package/package.json +2 -1
  511. package/dist/extensions/workflow/adapter.ts +0 -949
  512. package/dist/extensions/workflow/modes/loop.ts +0 -74
  513. package/dist/extensions/workflow/modes/prototype.ts +0 -170
  514. package/dist/extensions/workflow/modes/quicktype.ts +0 -150
  515. package/dist/extensions/workflow/modes/task.ts +0 -105
  516. package/dist/extensions/workflow/run-state.ts +0 -124
  517. package/dist/extensions/workflow/state-machine.ts +0 -484
  518. package/dist/extensions/workflow/validators.ts +0 -42
@@ -4,7 +4,7 @@ import * as path from "node:path";
4
4
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
5
5
  import type { ExtensionAPI, ExtensionContext } from "@selesai/code";
6
6
  import { resolveAgentName, type AgentConfig, type AgentScope } from "../../agents/agents.ts";
7
- import { getArtifactsDir, getChainRunsDir } from "../../shared/artifacts.ts";
7
+ import { getArtifactsDir, getChainRunsDir, getProjectArtifactPackagingWarning, getProjectSubagentsDir } from "../../shared/artifacts.ts";
8
8
  import { writeAtomicJson } from "../../shared/atomic-json.ts";
9
9
  import { ChainClarifyComponent, type ChainClarifyResult } from "./chain-clarify.ts";
10
10
  import { resolveEffectiveThinking, toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
@@ -17,13 +17,19 @@ import {
17
17
  settleForegroundSchedulingOwner,
18
18
  updateForegroundChild,
19
19
  } from "./foreground-control.ts";
20
+ import { getLivePromptAudit, rewritePromptWithGuidance, updateLiveEffectivePrompt } from "./prompt-audit.ts";
21
+ import { persistForegroundRunHistory, MAX_REMEMBERED_FOREGROUND_RUNS } from "./foreground-history.ts";
20
22
  import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
21
23
  import { handleManagementAction } from "../../agents/agent-management.ts";
24
+ import { handleRefinementAction } from "../../agents/agent-refinements.ts";
22
25
  import { buildDoctorReport } from "../../extension/doctor.ts";
26
+ import { readSubagentGuide } from "../../extension/subagent-guide.ts";
27
+ import { normalizePublicSubagentExecution } from "../../extension/public-execution.ts";
23
28
  import { runSync } from "./execution.ts";
24
29
  import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.ts";
25
30
  import type { MainWatchdogRuntime } from "../../watchdog/runtime.ts";
26
31
  import { buildModelCandidates, normalizeParentModel, resolveEffectiveSubagentModel, resolveModelCandidate, type ParentModel } from "../shared/model-fallback.ts";
32
+ import { formatRetainedChildren, listRetainedChildren } from "../background/retained-children.ts";
27
33
  import type { ModelScopeConfig } from "../shared/model-scope.ts";
28
34
  import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
29
35
  import { recordRun } from "../shared/run-history.ts";
@@ -33,6 +39,8 @@ import {
33
39
  getStepAgents,
34
40
  isParallelStep,
35
41
  isDynamicParallelStep,
42
+ resolveChainPath,
43
+ resolveExistingReadPaths,
36
44
  resolveStepBehavior,
37
45
  suppressProgressForReadOnlyTask,
38
46
  taskDisallowsFileUpdates,
@@ -45,17 +53,20 @@ import {
45
53
  type StepOverrides,
46
54
  } from "../../shared/settings.ts";
47
55
  import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
48
- import { buildAsyncRunnerSteps, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable } from "../background/async-execution.ts";
49
- import type { ScheduledRunAction } from "../background/scheduled-runs.ts";
56
+ import { buildAsyncRunnerSteps, DEFAULT_ASYNC_TIMEOUT_MS, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable, workflowAwaitedAsyncResultPath } from "../background/async-execution.ts";
57
+ import { updateActiveRunIndex } from "../background/active-run-index.ts";
58
+ import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
59
+ import { isScheduledRunAction, type ScheduledRunAction } from "../background/scheduled-runs.ts";
50
60
  import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
51
61
  import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
52
- import { validateExecutionAcceptance } from "../shared/acceptance.ts";
62
+ import { normalizeGateAcceptance, validateExecutionAcceptance } from "../shared/acceptance.ts";
53
63
  import { createForkContextResolver, forkedChildRequiresThinkingOff } from "../../shared/fork-context.ts";
54
64
  import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
55
65
  import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
56
66
  import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
57
67
  import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
58
68
  import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.ts";
69
+ import { claimRunFanoutBatch, claimRunFanoutBatchWithCommit, createRunFanoutBudget, decodeRunFanoutBudgetDescriptor, formatRunFanoutBudget, getRunFanoutBudgetSnapshot, readRunFanoutBudgetDescriptor, RunFanoutLimitError, RUN_FANOUT_BUDGET_ENV, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
59
70
  import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
60
71
  import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
61
72
  import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
@@ -63,6 +74,7 @@ import { isAgentContractV1 } from "../shared/agent-contract.ts";
63
74
  import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
64
75
  import { cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
65
76
  import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
77
+ import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
66
78
  import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
67
79
  import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
68
80
  import { summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
@@ -75,23 +87,35 @@ import {
75
87
  stripDetailsOutputsForIntercomReceipt,
76
88
  } from "../../intercom/result-intercom.ts";
77
89
  import { applySteeringRecoveryAgentConfig, buildRevivedAsyncTask, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
78
- import { deliverCheckpointDecisionRequest, deliverInterruptRequest, requestAsyncSteer } from "../background/control-channel.ts";
79
- import { waitForSteeringAction } from "../background/steering.ts";
90
+ import { deliverCheckpointDecisionRequest, deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
91
+ import { updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
80
92
  import { steerAsyncRun } from "./async-steering-action.ts";
93
+ import {
94
+ removeWorkflowForegroundSteeringRoute,
95
+ resolveWorkflowForegroundSteeringTarget,
96
+ steerWorkflowForegroundTarget,
97
+ workflowForegroundSteeringDir,
98
+ workflowForegroundSteeringLaunchOptions,
99
+ } from "./workflow-foreground-steering.ts";
81
100
  import { stopAsyncRun } from "./async-stop-action.ts";
101
+ import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
82
102
  import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
83
- import { resolveAsyncRootResultPath } from "../background/chain-root-attachment.ts";
103
+ import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.ts";
84
104
  import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
85
105
  import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
86
106
  import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
87
107
  import { inspectSubagentStatus } from "../background/run-status.ts";
88
108
  import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
89
109
  import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
90
- import { attachMissionToLaunchResult, prepareMissionLaunch, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
110
+ import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
111
+ import { updateMission } from "../../missions/store.ts";
112
+ import type { MissionWorkflowChildUpdate } from "../../missions/types.ts";
113
+ import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
91
114
  import { resolveAuthorityDecision } from "../../policy/authority.ts";
92
115
  import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
93
116
  import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
94
- import { runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult } from "../../workflows/scripted-workflow.ts";
117
+ import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult } from "../../workflows/scripted-workflow.ts";
118
+ import { renderWorkflowPrompt } from "../../shared/prompt-resources.ts";
95
119
  import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
96
120
  import {
97
121
  cleanupWorktrees,
@@ -123,6 +147,7 @@ import {
123
147
  type ResolvedControlConfig,
124
148
  type ResolvedTurnBudget,
125
149
  type ResolvedToolBudget,
150
+ type RunFanoutBudgetDescriptor,
126
151
  type SingleResult,
127
152
  type ToolBudgetConfig,
128
153
  type TurnBudgetConfig,
@@ -141,10 +166,52 @@ import {
141
166
  resolveTopLevelParallelMaxTasks,
142
167
  resolveChildMaxSubagentDepth,
143
168
  resolveCurrentMaxSubagentDepth,
169
+ resolveMaxSubagentSpawnsPerRun,
144
170
  wrapForkTask,
145
171
  } from "../../shared/types.ts";
146
172
 
147
- const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
173
+ const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "refine", "refine.rollback", "dismiss", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
174
+ const DESTRUCTIVE_MANAGEMENT_ACTIONS = new Set(["delete", "eject", "disable", "reset", "mission.close", "worktree.discard", "refine.rollback", "inspector.close", "project.close", "stop", "interrupt", "reject-checkpoint", "schedule.delete"]);
175
+
176
+ function editDistance(left: string, right: string): number {
177
+ const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
178
+ for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
179
+ let diagonal = previous[0]!;
180
+ previous[0] = leftIndex;
181
+ for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
182
+ const above = previous[rightIndex]!;
183
+ previous[rightIndex] = left[leftIndex - 1] === right[rightIndex - 1]
184
+ ? diagonal
185
+ : Math.min(diagonal, above, previous[rightIndex - 1]!) + 1;
186
+ diagonal = above;
187
+ }
188
+ }
189
+ return previous[right.length]!;
190
+ }
191
+
192
+ function hasSingleAdjacentTransposition(left: string, right: string): boolean {
193
+ if (left.length !== right.length) return false;
194
+ const mismatch = [...left].findIndex((character, index) => character !== right[index]);
195
+ return mismatch >= 0
196
+ && left[mismatch] === right[mismatch + 1]
197
+ && left[mismatch + 1] === right[mismatch]
198
+ && left.slice(mismatch + 2) === right.slice(mismatch + 2);
199
+ }
200
+
201
+ export function unknownSubagentActionMessage(action: string): string {
202
+ const requested = action.toLowerCase();
203
+ const suggestion = SUBAGENT_ACTIONS.find((candidate) => {
204
+ const distance = editDistance(requested, candidate);
205
+ const closeMatch = distance <= Math.max(1, Math.floor(candidate.length / 4)) || hasSingleAdjacentTransposition(requested, candidate);
206
+ if (DESTRUCTIVE_MANAGEMENT_ACTIONS.has(candidate)) return distance === 1 && requested.length >= candidate.length - 1;
207
+ return closeMatch;
208
+ });
209
+ const nextStep = 'Use subagent({ action: "status" }) to inspect runs or subagent({ action: "list" }) to inspect agents.';
210
+ const validActions = `Valid: ${SUBAGENT_ACTIONS.join(", ")}.`;
211
+ return suggestion
212
+ ? `Unknown action: ${action}. Did you mean ${suggestion}? ${nextStep} ${validActions}`
213
+ : `Unknown action: ${action}. ${nextStep} ${validActions}`;
214
+ }
148
215
 
149
216
  type UndefinedOmitted<T extends object> = {
150
217
  [K in keyof T as undefined extends T[K] ? never : K]: T[K];
@@ -203,21 +270,30 @@ export interface SubagentParamsLike {
203
270
  index?: number;
204
271
  view?: "fleet" | "transcript";
205
272
  lines?: number;
273
+ topic?: string;
206
274
  chainName?: string;
207
275
  config?: unknown;
208
276
  name?: string;
209
277
  type?: string;
210
278
  agent?: string;
211
279
  task?: string;
280
+ /** Retained async child run id. Valid only on workflow runs.run items. */
281
+ resume?: string;
212
282
  message?: string;
213
283
  steeringRecovery?: boolean;
284
+ mode?: SteerDeliveryMode;
214
285
  workflowScript?: string;
215
286
  chatProgress?: "auto" | "off" | "terminal" | "milestones" | "live-card";
216
287
  step?: ChainStep;
217
288
  /** Internal workflow ownership metadata; not part of the public schema. */
218
289
  workflowParentRunId?: string;
219
290
  workflowKey?: string;
291
+ workflowChildAsyncId?: string;
220
292
  suppressRoutineResultIntercom?: boolean;
293
+ /** Internal inherited cumulative run-tree budget. */
294
+ runFanoutBudget?: RunFanoutBudgetDescriptor;
295
+ /** Internal workflow host admission proof. */
296
+ runFanoutAdmitted?: boolean;
221
297
  /** Internal durable-run compatibility fields. Public callers must use workflowScript. */
222
298
  chain?: ChainStep[];
223
299
  tasks?: TaskParam[];
@@ -248,11 +324,14 @@ export interface SubagentParamsLike {
248
324
  focus?: boolean;
249
325
  skill?: string | string[] | boolean;
250
326
  output?: string | boolean;
327
+ /** Internal-only; not part of the public tool schema. Wired for single-run reads (chain steps use their own field). */
328
+ reads?: string[] | false;
251
329
  outputMode?: "inline" | "file-only";
252
330
  outputSchema?: JsonSchemaObject;
253
331
  agentScope?: unknown;
254
332
  chainDir?: string;
255
333
  acceptance?: AcceptanceInput;
334
+ gate?: string;
256
335
  agentContract?: AgentContract;
257
336
  schedule?: string;
258
337
  scheduleName?: string;
@@ -319,6 +398,7 @@ interface ExecutionContextData {
319
398
  artifactsDir: string;
320
399
  backgroundRequestedWhileClarifying: boolean;
321
400
  effectiveAsync: boolean;
401
+ asyncRunId: string;
322
402
  controlConfig: ResolvedControlConfig;
323
403
  intercomBridge: IntercomBridgeState;
324
404
  nestedRoute?: NestedRouteInfo;
@@ -335,6 +415,9 @@ interface ExecutionContextData {
335
415
  parentSessionId: string | null;
336
416
  parentPiSessionId?: string;
337
417
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
418
+ runFanoutBudget: RunFanoutBudgetDescriptor;
419
+ topLevelAsyncCapacityEligible: boolean;
420
+ activeAsyncCapacity?: ActiveAsyncCapacityHandle;
338
421
  }
339
422
 
340
423
  function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
@@ -344,6 +427,7 @@ function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefine
344
427
  function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
345
428
  const control = state.foregroundControls.get(runId);
346
429
  if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
430
+ if (control) removeWorkflowForegroundSteeringRoute(control);
347
431
  state.foregroundControls.delete(runId);
348
432
  if (state.lastForegroundControlId === runId) state.lastForegroundControlId = null;
349
433
  return true;
@@ -362,6 +446,17 @@ function getForegroundControl(state: SubagentState, runId: string | undefined) {
362
446
  return newest;
363
447
  }
364
448
 
449
+ function promptAuditRedoParams(value: unknown, rewrittenTask: string): SubagentParamsLike {
450
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Prompt redo is missing a safe live launch contract.");
451
+ const params = { ...(value as SubagentParamsLike), task: rewrittenTask, async: false };
452
+ delete params.workflowParentRunId;
453
+ delete params.workflowKey;
454
+ delete params.workflowChildAsyncId;
455
+ delete params.suppressRoutineResultIntercom;
456
+ if (params.worktree === true && Array.isArray(params.tasks)) delete params.cwd;
457
+ return params;
458
+ }
459
+
365
460
  function formatForegroundActivity(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): string | undefined {
366
461
  const facts: string[] = [];
367
462
  if (control.currentTool && control.currentToolStartedAt) facts.push(`tool ${control.currentTool} for ${Math.floor(Math.max(0, Date.now() - control.currentToolStartedAt) / 1000)}s`);
@@ -413,12 +508,18 @@ function withSpawnBudgetStatus(
413
508
  sessionId: string | null,
414
509
  ): AgentToolResult<Details> {
415
510
  const spawnBudget = getSpawnBudgetSnapshot(state, config, sessionId);
511
+ const activeAsyncCapacity = sessionId
512
+ ? getActiveAsyncCapacitySnapshot(sessionId, resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession), {
513
+ liveWorkflowRunIds: new Set(state.workflowControllers?.keys() ?? []),
514
+ })
515
+ : { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession) ?? 0 };
516
+ state.activeAsyncCapacity = activeAsyncCapacity;
416
517
  return {
417
518
  ...result,
418
519
  content: result.content.map((item, index) => index === 0 && item.type === "text"
419
- ? { ...item, text: `${formatSpawnBudget(spawnBudget)}\n${item.text}` }
520
+ ? { ...item, text: `${formatSpawnBudget(spawnBudget)}\nActive async capacity: ${activeAsyncCapacity.used}/${activeAsyncCapacity.limit || "unlimited"} used\n${item.text}` }
420
521
  : item),
421
- details: { ...result.details, spawnBudget },
522
+ details: { ...result.details, spawnBudget, activeAsyncCapacity },
422
523
  };
423
524
  }
424
525
 
@@ -439,6 +540,31 @@ function countRequestedSubagentSpawns(params: SubagentParamsLike, config: Extens
439
540
  return params.agent ? 1 : 0;
440
541
  }
441
542
 
543
+ function staticRunFanoutPaths(params: SubagentParamsLike): string[] {
544
+ if (params.tasks) return params.tasks.map((_, index) => `tasks[${index}]`);
545
+ if (params.chain) return params.chain.flatMap((step, stepIndex) => {
546
+ if (isDynamicParallelStep(step) || "checkpoint" in step) return [];
547
+ if (isParallelStep(step)) return step.parallel.map((_, itemIndex) => `chain[${stepIndex}].parallel[${itemIndex}]`);
548
+ return [`chain[${stepIndex}]`];
549
+ });
550
+ return params.agent ? ["single"] : [];
551
+ }
552
+
553
+ function runFanoutErrorResult(error: RunFanoutLimitError, mode: "single" | "parallel" | "chain"): AgentToolResult<Details> {
554
+ return { content: [{ type: "text", text: error.message }], isError: true, details: { mode, results: [], runFanoutBudget: error.snapshot, runFanoutRejection: error.rejection } };
555
+ }
556
+
557
+ function withRunFanoutBudget(result: AgentToolResult<Details>, descriptor: RunFanoutBudgetDescriptor, options: { annotateContent?: boolean } = {}): AgentToolResult<Details> {
558
+ const runFanoutBudget = getRunFanoutBudgetSnapshot(descriptor);
559
+ return {
560
+ ...result,
561
+ content: options.annotateContent === false
562
+ ? result.content
563
+ : result.content.map((item, index) => index === 0 && item.type === "text" ? { ...item, text: `${formatRunFanoutBudget(runFanoutBudget)}\n${item.text}` } : item),
564
+ details: { ...result.details, runFanoutBudget },
565
+ };
566
+ }
567
+
442
568
  function foregroundStatusResult(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): AgentToolResult<Details> {
443
569
  let nestedWarning: string | undefined;
444
570
  try {
@@ -461,7 +587,7 @@ function foregroundStatusResult(control: SubagentState["foregroundControls"] ext
461
587
 
462
588
  function trimRememberedForegroundRuns(state: SubagentState): void {
463
589
  if (!state.foregroundRuns) return;
464
- while (state.foregroundRuns.size > 50) {
590
+ while (state.foregroundRuns.size > MAX_REMEMBERED_FOREGROUND_RUNS) {
465
591
  const oldestTerminal = [...state.foregroundRuns.values()]
466
592
  .filter((run) => !run.children.some((child) => child.status === "detached"))
467
593
  .sort((left, right) => left.updatedAt - right.updatedAt)[0];
@@ -470,6 +596,14 @@ function trimRememberedForegroundRuns(state: SubagentState): void {
470
596
  }
471
597
  }
472
598
 
599
+ function persistRememberedForegroundRuns(state: SubagentState): void {
600
+ try {
601
+ persistForegroundRunHistory(state);
602
+ } catch (error) {
603
+ console.error("Failed to persist foreground run history:", error);
604
+ }
605
+ }
606
+
473
607
  function foregroundChildActivityFromProgress(progress: SingleResult["progress"] | undefined) {
474
608
  return {
475
609
  ...(progress?.activityState ? { activityState: progress.activityState } : {}),
@@ -536,6 +670,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
536
670
  }),
537
671
  });
538
672
  trimRememberedForegroundRuns(state);
673
+ persistRememberedForegroundRuns(state);
539
674
  }
540
675
 
541
676
  function applyControlEventToRememberedForegroundRun(state: SubagentState, event: ControlEvent): void {
@@ -611,6 +746,7 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
611
746
  ...(input.result.capabilityAudit ? { capabilityAudit: input.result.capabilityAudit } : {}),
612
747
  });
613
748
  trimRememberedForegroundRuns(state);
749
+ persistRememberedForegroundRuns(state);
614
750
  const output = getSingleResultOutput(input.result).trim();
615
751
  const success = terminalStatus === "completed";
616
752
  const summary = !success && input.result.error
@@ -790,6 +926,20 @@ function getAsyncInterruptTarget(
790
926
  return newest ? { asyncId: newest.asyncId, asyncDir: newest.asyncDir } : undefined;
791
927
  }
792
928
 
929
+ function isStaleExtensionContextError(error: unknown): boolean {
930
+ if (!(error instanceof Error)) return false;
931
+ return /extension ctx is stale|stale after session replacement or reload/i.test(error.message);
932
+ }
933
+
934
+ function emitAdvisoryControlEvent(pi: ExtensionAPI, channel: string, payload: unknown): void {
935
+ try {
936
+ pi.events.emit(channel, payload);
937
+ } catch (error) {
938
+ if (isStaleExtensionContextError(error)) return;
939
+ throw error;
940
+ }
941
+ }
942
+
793
943
  function emitControlNotification(input: {
794
944
  pi: ExtensionAPI;
795
945
  controlConfig: ResolvedControlConfig;
@@ -807,10 +957,10 @@ function emitControlNotification(input: {
807
957
  noticeText: formatControlNoticeMessage(input.event, childIntercomTarget),
808
958
  };
809
959
  if (input.controlConfig.notifyChannels.includes("event")) {
810
- input.pi.events.emit(SUBAGENT_CONTROL_EVENT, payload);
960
+ emitAdvisoryControlEvent(input.pi, SUBAGENT_CONTROL_EVENT, payload);
811
961
  }
812
962
  if (input.event.type !== "active_long_running" && input.controlConfig.notifyChannels.includes("intercom") && input.intercomBridge.active && input.intercomBridge.orchestratorTarget) {
813
- input.pi.events.emit(SUBAGENT_CONTROL_INTERCOM_EVENT, {
963
+ emitAdvisoryControlEvent(input.pi, SUBAGENT_CONTROL_INTERCOM_EVENT, {
814
964
  ...payload,
815
965
  to: input.intercomBridge.orchestratorTarget,
816
966
  message: formatControlIntercomMessage(input.event, childIntercomTarget),
@@ -842,8 +992,15 @@ function interruptAsyncRun(
842
992
  details: { mode: "management", results: [] },
843
993
  };
844
994
  }
995
+ if (status.mode === "workflow") {
996
+ return {
997
+ content: [{ type: "text", text: `Interrupt is unsupported for async workflow ${target.asyncId}; use stop instead.` }],
998
+ isError: true,
999
+ details: { mode: "management", results: [] },
1000
+ };
1001
+ }
845
1002
  try {
846
- deliverInterruptRequest(omitUndefinedProperties({ asyncDir: target.asyncDir, pid: status.pid, kill, source: "interrupt-action" }));
1003
+ deliverInterruptRequest({ asyncDir: target.asyncDir, source: "interrupt-action" });
847
1004
  const tracked = state.asyncJobs.get(target.asyncId);
848
1005
  if (tracked) {
849
1006
  delete tracked.activityState;
@@ -1040,18 +1197,30 @@ function appendStepToAsyncChain(input: {
1040
1197
  }
1041
1198
 
1042
1199
  try {
1200
+ const asyncDir = resolved.location.asyncDir;
1201
+ if (!asyncDir) throw new Error(`Run '${resolved.id}' is missing its async directory.`);
1202
+ const runFanoutBudget = readRunFanoutBudgetDescriptor(asyncDir);
1203
+ if (!runFanoutBudget) throw new Error(`Run '${resolved.id}' is missing its run fan-out budget identity.`);
1204
+ const startIndex = (status.chainStepCount ?? status.steps?.length ?? 0) + pendingAppendRequests.reduce((total, request) => total + request.steps.length, 0);
1205
+ const appendPaths = chain.flatMap((step, localIndex) => {
1206
+ const absoluteIndex = startIndex + localIndex;
1207
+ if (isDynamicParallelStep(step) || "checkpoint" in step) return [];
1208
+ if (isParallelStep(step)) return step.parallel.map((_, itemIndex) => `chain[${absoluteIndex}].parallel[${itemIndex}]`);
1209
+ return [`chain[${absoluteIndex}]`];
1210
+ });
1043
1211
  const result = enqueueChainAppendRequest({
1044
- asyncDir: resolved.location.asyncDir,
1212
+ asyncDir,
1045
1213
  runId: resolved.id,
1046
1214
  steps: built.steps,
1215
+ admit: (persist) => claimRunFanoutBatchWithCommit(runFanoutBudget, appendPaths, persist),
1047
1216
  });
1048
1217
  const stepText = built.steps.length === 1 ? "step" : "steps";
1049
1218
  return {
1050
1219
  content: [{
1051
1220
  type: "text",
1052
- text: `Append queued for chain run ${resolved.id}: ${built.steps.length} ${stepText}. It becomes eligible after the chain's already-queued steps finish. Pending appends: ${result.pendingCount}.`,
1221
+ text: `Append queued for chain run ${resolved.id}: ${built.steps.length} ${stepText}. It becomes eligible after the chain's already-queued steps finish. Pending appends: ${result.pendingCount}.${result.bookkeepingError ? ` Bookkeeping warning: ${result.bookkeepingError}` : ""}`,
1053
1222
  }],
1054
- details: { mode: "management", results: [], asyncId: resolved.id, asyncDir: resolved.location.asyncDir },
1223
+ details: { mode: "management", results: [], asyncId: resolved.id, asyncDir },
1055
1224
  };
1056
1225
  } catch (error) {
1057
1226
  const message = error instanceof Error ? error.message : String(error);
@@ -1152,7 +1321,7 @@ function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nes
1152
1321
  const pid = typeof status?.pid === "number" && status.pid > 0 ? status.pid : run.pid;
1153
1322
  if (!status || status.state !== "running" || typeof pid !== "number" || pid <= 0) return undefined;
1154
1323
  try {
1155
- deliverInterruptRequest({ asyncDir, pid, source: "nested-interrupt" });
1324
+ deliverInterruptRequest({ asyncDir, source: "nested-interrupt" });
1156
1325
  return { content: [{ type: "text", text: `Interrupt requested for nested async run ${run.id}.` }], details: { mode: "management", results: [] } };
1157
1326
  } catch (error) {
1158
1327
  const message = error instanceof Error ? error.message : String(error);
@@ -1160,7 +1329,7 @@ function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nes
1160
1329
  }
1161
1330
  }
1162
1331
 
1163
- async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details> | undefined> {
1332
+ async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; mode?: SteerDeliveryMode; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details> | undefined> {
1164
1333
  const run = input.target.match.run;
1165
1334
  const asyncDir = resolveNestedAsyncDir(input.target.match.rootRunId, run);
1166
1335
  if (!asyncDir) return undefined;
@@ -1182,6 +1351,7 @@ async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & {
1182
1351
  try {
1183
1352
  requestAsyncSteer(asyncDir, {
1184
1353
  message: input.message,
1354
+ mode: input.mode,
1185
1355
  ...(effectiveTargetIndex !== undefined ? { targetIndex: effectiveTargetIndex } : { targetIndexes }),
1186
1356
  source: "nested-steer",
1187
1357
  id: requestId,
@@ -1191,12 +1361,12 @@ async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & {
1191
1361
  }
1192
1362
  const targets = targetIndexes.map((index) => ({ index, state: steps[index]?.status === "pending" ? "scheduled" as const : "pending" as const }));
1193
1363
  if (targets.every((target) => target.state === "scheduled")) {
1194
- const scheduled = { requestId, state: "scheduled" as const, sourceRunId: run.id, targets };
1364
+ const scheduled = { requestId, state: "scheduled" as const, deliveryStatus: "queued" as const, sourceRunId: run.id, targets };
1195
1365
  return { content: [{ type: "text", text: `Steering scheduled for nested async run ${run.id} (request ${requestId}).` }], details: { mode: "management", results: [], steering: scheduled } };
1196
1366
  }
1197
1367
  const waited = await waitForSteeringAction(omitUndefinedProperties({ asyncDir, sourceRunId: run.id, requestId, timeoutMs: 3_000, signal: input.signal }));
1198
- const result = waited ?? { requestId, state: "pending" as const, sourceRunId: run.id, targets };
1199
- const stateText = result.state === "delivered" ? "delivered" : result.state === "failed" ? "failed" : result.state === "partial" ? "partial" : "pending";
1368
+ const result = waited ?? { requestId, state: "pending" as const, deliveryStatus: "queued" as const, sourceRunId: run.id, targets };
1369
+ const stateText = result.state === "failed" ? "failed" : result.state === "partial" ? "partial" : result.deliveryStatus === "queued" ? "queued" : result.state === "delivered" ? "delivered" : "pending";
1200
1370
  return { content: [{ type: "text", text: `Steering ${stateText} for nested async run ${run.id} (request ${requestId}).` }], ...(result.state === "failed" || result.state === "partial" ? { isError: true } : {}), details: { mode: "management", results: [], steering: result } };
1201
1371
  }
1202
1372
 
@@ -1219,7 +1389,7 @@ async function resumeLiveNestedRun(input: { target: ResolvedSubagentRunId & { ki
1219
1389
  return { content: [{ type: "text", text: `Nested run ${run.id} appears live but its owner route is not reachable. Wait for completion, then retry action='resume'.` }], isError: true, details: { mode: "management", results: [] } };
1220
1390
  }
1221
1391
 
1222
- async function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details>> {
1392
+ async function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; mode?: SteerDeliveryMode; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details>> {
1223
1393
  const run = input.target.match.run;
1224
1394
  if (run.state !== "running" && run.state !== "queued") return { content: [{ type: "text", text: `Nested run ${run.id} is ${run.state} and cannot be steered.` }], isError: true, details: { mode: "management", results: [] } };
1225
1395
  const direct = await directNestedAsyncSteer(input);
@@ -1243,6 +1413,7 @@ async function resumeAsyncRun(input: {
1243
1413
  deps: ExecutorDeps;
1244
1414
  parentModel?: ParentModel;
1245
1415
  absoluteDeadlineAt?: number;
1416
+ signal?: AbortSignal;
1246
1417
  }): Promise<AgentToolResult<Details>> {
1247
1418
  const followUp = (input.params.message ?? input.params.task ?? "").trim();
1248
1419
  const attachChain = (input.params.chain?.length ?? 0) > 0 ? input.params.chain as ChainStep[] : undefined;
@@ -1358,7 +1529,7 @@ async function resumeAsyncRun(input: {
1358
1529
  inheritProjectContext: recoveryDescriptor.inheritProjectContext,
1359
1530
  inheritSkills: recoveryDescriptor.inheritSkills,
1360
1531
  source: "project",
1361
- filePath: recoveryDescriptor.agentFilePath ?? path.join(recoveryDescriptor.cwd, ".pi-subagents-recovery-agent"),
1532
+ filePath: recoveryDescriptor.agentFilePath ?? path.join(getProjectSubagentsDir(recoveryDescriptor.cwd), "recovery-agent"),
1362
1533
  } : undefined);
1363
1534
  if (!agentConfig) {
1364
1535
  return {
@@ -1384,6 +1555,20 @@ async function resumeAsyncRun(input: {
1384
1555
  };
1385
1556
  }
1386
1557
  const runId = randomUUID().slice(0, 8);
1558
+ const topLevelResume = depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
1559
+ let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
1560
+ try {
1561
+ activeAsyncCapacity = topLevelResume ? acquireActiveAsyncCapacity({
1562
+ sessionId: input.deps.state.currentSessionId!,
1563
+ limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
1564
+ runId,
1565
+ kind: "runner",
1566
+ asyncDir: path.join(DIRS.async, runId),
1567
+ }, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
1568
+ } catch (error) {
1569
+ if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "chain", results: [], activeAsyncCapacity: error.snapshot } };
1570
+ throw error;
1571
+ }
1387
1572
  const artifactConfig: ArtifactConfig = omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
1388
1573
  const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
1389
1574
  const contextPolicy = resolveExplicitContextPolicy(input.params);
@@ -1435,9 +1620,14 @@ async function resumeAsyncRun(input: {
1435
1620
  controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
1436
1621
  childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
1437
1622
  globalConcurrencyLimit: input.deps.config.globalConcurrencyLimit,
1623
+ runFanoutBudget: createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
1438
1624
  capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
1625
+ activeAsyncCapacity,
1439
1626
  }));
1440
- if (result.isError) return result;
1627
+ if (result.isError) {
1628
+ activeAsyncCapacity?.rollback();
1629
+ return result;
1630
+ }
1441
1631
  const attachedId = result.details.asyncId ?? runId;
1442
1632
  const lines = [
1443
1633
  `Attached async subagent ${target.runId} as the first step of a new chain.`,
@@ -1449,20 +1639,49 @@ async function resumeAsyncRun(input: {
1449
1639
  return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n"), input.ctx.hasUI) }], details: result.details };
1450
1640
  }
1451
1641
 
1642
+ const sourceAsyncDir = target.source === "async" ? target.asyncDir : undefined;
1643
+ const queuedBriefs = sourceAsyncDir ? readRevivalBriefs(sourceAsyncDir) : [];
1644
+ const effectiveFollowUp = [...queuedBriefs.map(({ request }) => request.message), followUp].filter(Boolean).join("\n\n");
1452
1645
  const revivalSessionFile = target.sessionFile;
1453
1646
  if (!revivalSessionFile) {
1454
1647
  return { content: [{ type: "text", text: `Async child '${target.runId}' has no persisted session file to resume.` }], isError: true, details: { mode: "management", results: [] } };
1455
1648
  }
1649
+ if (target.source === "async" && !recoveryDescriptor) {
1650
+ return { content: [{ type: "text", text: `Async child '${target.runId}' is missing its required run fan-out recovery identity. Start a new run instead.` }], isError: true, details: { mode: "management", results: [] } };
1651
+ }
1456
1652
  const runId = randomUUID().slice(0, 8);
1653
+ const topLevelResume = depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
1654
+ let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
1655
+ try {
1656
+ activeAsyncCapacity = !topLevelResume ? undefined : target.source === "async"
1657
+ ? transferActiveAsyncCapacity({
1658
+ sessionId: input.deps.state.currentSessionId!,
1659
+ limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
1660
+ sourceRunId: target.runId,
1661
+ runId,
1662
+ asyncDir: path.join(DIRS.async, runId),
1663
+ })
1664
+ : acquireActiveAsyncCapacity({
1665
+ sessionId: input.deps.state.currentSessionId!,
1666
+ limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
1667
+ runId,
1668
+ kind: "runner",
1669
+ asyncDir: path.join(DIRS.async, runId),
1670
+ });
1671
+ } catch (error) {
1672
+ if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
1673
+ return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
1674
+ }
1457
1675
  const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(agentConfig, recoveryDescriptor) : agentConfig;
1458
1676
  const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
1459
1677
  const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
1460
1678
  const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
1461
1679
  const parentModel = input.parentModel;
1680
+ const revivalAsyncDir = path.join(DIRS.async, runId);
1462
1681
  const result = executeAsyncSingle(runId, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
1463
1682
  agent: target.agent,
1464
- task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0], followUp),
1465
- goal: followUp,
1683
+ task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0], effectiveFollowUp),
1684
+ goal: effectiveFollowUp,
1466
1685
  agentConfig: recoveryAgentConfig,
1467
1686
  ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
1468
1687
  pi: input.deps.pi,
@@ -1512,10 +1731,90 @@ async function resumeAsyncRun(input: {
1512
1731
  ...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
1513
1732
  ...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
1514
1733
  capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, recoveryDescriptor?.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
1734
+ runFanoutBudget: recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
1735
+ parentWorkflowRunId: input.params.workflowParentRunId,
1736
+ workflowKey: input.params.workflowKey,
1737
+ activeAsyncCapacity,
1515
1738
  }));
1516
- if (result.isError) return result;
1739
+ if (result.isError) {
1740
+ const startedStatus = readStatus(revivalAsyncDir);
1741
+ if (input.params.workflowParentRunId !== undefined && startedStatus?.runId === runId && startedStatus.processTerminal?.runnerProcessInstanceId) {
1742
+ return {
1743
+ ...result,
1744
+ details: {
1745
+ ...result.details,
1746
+ runId,
1747
+ asyncId: runId,
1748
+ asyncDir: revivalAsyncDir,
1749
+ ...(target.launchContractDigest ? { sourceLaunchContractDigest: target.launchContractDigest } : {}),
1750
+ },
1751
+ };
1752
+ }
1753
+ activeAsyncCapacity?.rollback();
1754
+ return result;
1755
+ }
1756
+ for (const brief of queuedBriefs) fs.rmSync(brief.path, { force: true });
1757
+ if (queuedBriefs.length > 0 && sourceAsyncDir) {
1758
+ const sourceStatus = readStatus(sourceAsyncDir);
1759
+ if (sourceStatus?.steering) {
1760
+ for (const brief of queuedBriefs) updateSteeringTarget(sourceStatus.steering, brief.request.id, target.index, "delivered", Date.now());
1761
+ writeAtomicJson(path.join(sourceAsyncDir, "status.json"), sourceStatus);
1762
+ }
1763
+ }
1517
1764
 
1518
1765
  const revivedId = result.details.asyncId ?? runId;
1766
+ if (input.params.workflowParentRunId !== undefined && result.details.asyncDir) {
1767
+ const asyncDir = result.details.asyncDir;
1768
+ const resultPath = workflowAwaitedAsyncResultPath(asyncDir);
1769
+ const stopOnAbort = () => { stopAsyncRun(input.deps.state, revivedId, input.deps.kill, { asyncDir, resolvedId: revivedId }); };
1770
+ if (input.signal?.aborted) stopOnAbort();
1771
+ else input.signal?.addEventListener("abort", stopOnAbort, { once: true });
1772
+ let completed: Awaited<ReturnType<typeof waitForImportedAsyncRoot>>;
1773
+ try {
1774
+ completed = await waitForImportedAsyncRoot({ runId: revivedId, asyncDir, resultPath, index: 0 });
1775
+ } finally {
1776
+ input.signal?.removeEventListener("abort", stopOnAbort);
1777
+ }
1778
+ fs.rmSync(resultPath, { force: true });
1779
+ const totalCost = completed.totalCost;
1780
+ const childResult: SingleResult = {
1781
+ index: 0,
1782
+ agent: completed.agent,
1783
+ task: effectiveFollowUp,
1784
+ exitCode: completed.exitCode,
1785
+ usage: {
1786
+ input: totalCost?.inputTokens ?? 0,
1787
+ output: totalCost?.outputTokens ?? 0,
1788
+ cacheRead: 0,
1789
+ cacheWrite: 0,
1790
+ cost: totalCost?.costUsd ?? 0,
1791
+ turns: 0,
1792
+ },
1793
+ finalOutput: completed.output,
1794
+ outputState: completed.output.trim() ? "present" : "absent",
1795
+ ...(completed.error ? { error: completed.error } : {}),
1796
+ ...(completed.timedOut ? { timedOut: true } : {}),
1797
+ ...(completed.stopped ? { stopped: true } : {}),
1798
+ ...(completed.sessionFile ? { sessionFile: completed.sessionFile } : {}),
1799
+ ...(completed.model ? { model: completed.model } : {}),
1800
+ ...(completed.attemptedModels ? { attemptedModels: completed.attemptedModels } : {}),
1801
+ ...(completed.modelAttempts ? { modelAttempts: completed.modelAttempts } : {}),
1802
+ ...(completed.structuredOutput !== undefined ? { structuredOutput: completed.structuredOutput } : {}),
1803
+ ...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
1804
+ ...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
1805
+ ...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
1806
+ };
1807
+ return {
1808
+ content: [{ type: "text", text: completed.output || completed.error || `Revived ${target.source} subagent ${revivedId} completed without output.` }],
1809
+ ...(completed.success ? {} : { isError: true }),
1810
+ details: {
1811
+ ...result.details,
1812
+ runId: revivedId,
1813
+ results: [childResult],
1814
+ ...(target.launchContractDigest ? { sourceLaunchContractDigest: target.launchContractDigest } : {}),
1815
+ },
1816
+ };
1817
+ }
1519
1818
  const revivedTarget = intercomBridge.active ? resolveSubagentIntercomTarget(revivedId, target.agent, 0) : undefined;
1520
1819
  const sourceLabel = target.source;
1521
1820
  const lines = [
@@ -1631,6 +1930,7 @@ async function maybeBuildForegroundIntercomReceipt(input: {
1631
1930
  mode: SubagentRunMode;
1632
1931
  details: Details;
1633
1932
  nestedChildren?: NestedRunSummary[];
1933
+ preserveDetailsOutputs?: boolean;
1634
1934
  }): Promise<{ text: string; details: Details } | null> {
1635
1935
  const payload = await emitForegroundResultIntercom({
1636
1936
  pi: input.pi,
@@ -1645,7 +1945,7 @@ async function maybeBuildForegroundIntercomReceipt(input: {
1645
1945
  if (!payload) return null;
1646
1946
  return {
1647
1947
  text: formatSubagentResultReceipt({ mode: input.mode, runId: input.runId, payload }),
1648
- details: stripDetailsOutputsForIntercomReceipt(input.details),
1948
+ details: input.preserveDetailsOutputs ? input.details : stripDetailsOutputsForIntercomReceipt(input.details),
1649
1949
  };
1650
1950
  }
1651
1951
 
@@ -1842,6 +2142,19 @@ function getRequestedModeLabel(params: SubagentParamsLike): Details["mode"] {
1842
2142
  return "single";
1843
2143
  }
1844
2144
 
2145
+ function formatStatusTargetLabel(params: Pick<SubagentParamsLike, "dir" | "index" | "view">, targetRunId: string | undefined): string {
2146
+ let target: string;
2147
+ if (targetRunId) {
2148
+ target = `run ${targetRunId}`;
2149
+ } else if (params.dir) {
2150
+ target = `dir ${params.dir}`;
2151
+ } else {
2152
+ target = params.view === "transcript" ? "active run" : "active runs";
2153
+ }
2154
+ if (params.view !== "transcript") return `Status target: ${target}`;
2155
+ return `Transcript target: ${target}${params.index !== undefined ? ` · child ${params.index}` : ""}`;
2156
+ }
2157
+
1845
2158
  interface AgentDefaultContextPolicy {
1846
2159
  params: SubagentParamsLike;
1847
2160
  contextForAgent(agentName: string): ContextMode;
@@ -1925,7 +2238,38 @@ function applySingleAgentLaunchDefaults(params: SubagentParamsLike, agents: Agen
1925
2238
 
1926
2239
  export const DEFAULT_FOREGROUND_TIMEOUT_MS = 30 * 60 * 1000;
1927
2240
 
1928
- function resolveForegroundTimeout(params: SubagentParamsLike, defaultTimeoutMs?: number): { timeoutMs?: number; error?: string } {
2241
+ // Async single-agent runs also need a wall-clock backstop: a child whose bash
2242
+ // tool blocks forever (e.g. a background process inheriting the terminal with
2243
+ // no bash `timeout` arg) would otherwise hang the parent indefinitely with
2244
+ // zero signal. Same generous default as foreground; explicit timeoutMs/
2245
+ // maxRuntimeMs and agent-level defaultTimeoutMs remain authoritative.
2246
+ //
2247
+ // Deliberately NOT applied at the workflow level: async scripted workflows
2248
+ // stay unbounded as a whole, while each runner child has its own deadline.
2249
+ export { DEFAULT_ASYNC_TIMEOUT_MS };
2250
+
2251
+ /**
2252
+ * Maximum delay a Node.js timer accepts. Values above the 32-bit signed integer
2253
+ * ceiling overflow `setTimeout`, which silently clamps the delay to ~1ms and
2254
+ * fires almost immediately — so a run configured with a larger deadline would
2255
+ * terminate right away while reporting the long duration. Any timeout destined
2256
+ * for a timer must stay within this bound.
2257
+ */
2258
+ const MAX_TIMER_DELAY_MS = 2_147_483_647;
2259
+
2260
+ /**
2261
+ * Resolve the optional global default runtime deadline from extension config
2262
+ * (`config.timeoutMs`). Returns undefined for unset or invalid values so callers
2263
+ * fall back to the built-in defaults. "Invalid" covers non-positive-integer
2264
+ * values and values above `MAX_TIMER_DELAY_MS`; the latter would overflow the
2265
+ * Node.js timer and expire the run almost immediately instead of running long.
2266
+ */
2267
+ export function resolveConfigDefaultTimeoutMs(raw: unknown): number | undefined {
2268
+ if (typeof raw !== "number" || !Number.isInteger(raw) || raw <= 0 || raw > MAX_TIMER_DELAY_MS) return undefined;
2269
+ return raw;
2270
+ }
2271
+
2272
+ export function resolveForegroundTimeout(params: SubagentParamsLike, defaultTimeoutMs?: number): { timeoutMs?: number; error?: string } {
1929
2273
  const rawTimeout = params.timeoutMs;
1930
2274
  const rawMaxRuntime = params.maxRuntimeMs;
1931
2275
  if (rawTimeout === undefined && rawMaxRuntime === undefined) {
@@ -1944,6 +2288,25 @@ function resolveForegroundTimeout(params: SubagentParamsLike, defaultTimeoutMs?:
1944
2288
  return timeoutMs === undefined ? {} : { timeoutMs };
1945
2289
  }
1946
2290
 
2291
+ /**
2292
+ * Resolve the effective launch timeout for a single-agent run, applying the
2293
+ * async/foreground default when neither the caller nor the agent set one.
2294
+ *
2295
+ * A global config default (`config.timeoutMs`, passed as `configDefaultTimeoutMs`)
2296
+ * replaces the built-in 30-minute backstop wherever a concrete default is applied.
2297
+ * The async default is deliberately applied only to plain single-agent launches.
2298
+ * Composite launches keep their top-level execution unbounded when no timeout is
2299
+ * set — even with a config default — while their runner children resolve separate
2300
+ * deadlines. Exported so the executor wiring is directly testable.
2301
+ */
2302
+ export function resolveSingleAgentLaunchTimeout(params: SubagentParamsLike, async: boolean, configDefaultTimeoutMs?: number): { timeoutMs?: number; error?: string } {
2303
+ const isComposite = (params.chain?.length ?? 0) > 0 || (params.tasks?.length ?? 0) > 0 || params.workflowScript !== undefined;
2304
+ const foregroundDefault = configDefaultTimeoutMs ?? DEFAULT_FOREGROUND_TIMEOUT_MS;
2305
+ const asyncSingleDefault = configDefaultTimeoutMs ?? DEFAULT_ASYNC_TIMEOUT_MS;
2306
+ const defaultTimeoutMs = !async ? foregroundDefault : isComposite ? undefined : asyncSingleDefault;
2307
+ return resolveForegroundTimeout(params, defaultTimeoutMs);
2308
+ }
2309
+
1947
2310
  function resolveToolBudget(
1948
2311
  raw: unknown,
1949
2312
  label = "toolBudget",
@@ -2353,7 +2716,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2353
2716
  details: { mode: "single" as const, results: [] },
2354
2717
  };
2355
2718
  }
2356
- const id = randomUUID();
2719
+ const id = data.asyncRunId;
2357
2720
  const parentModel = data.parentModel;
2358
2721
  const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
2359
2722
  pi: deps.pi,
@@ -2407,6 +2770,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2407
2770
  shareEnabled,
2408
2771
  sessionRoot,
2409
2772
  chainSkills: [],
2773
+ activeAsyncCapacity: data.activeAsyncCapacity,
2410
2774
  sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index, task.model)),
2411
2775
  thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, task.model)),
2412
2776
  contextForAgent: contextPolicy.contextForAgent,
@@ -2426,6 +2790,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2426
2790
  usageBudget: data.usageBudget,
2427
2791
  configToolBudget: data.configToolBudget,
2428
2792
  capabilityCeiling: data.capabilityCeiling,
2793
+ runFanoutBudget: data.runFanoutBudget,
2429
2794
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
2430
2795
  parentWorkflowRunId: params.workflowParentRunId,
2431
2796
  workflowKey: params.workflowKey,
@@ -2451,6 +2816,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2451
2816
  shareEnabled,
2452
2817
  sessionRoot,
2453
2818
  chainSkills,
2819
+ activeAsyncCapacity: data.activeAsyncCapacity,
2454
2820
  sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
2455
2821
  thinkingOverridesByFlatIndex: collectChainThinkingOverrides(chain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
2456
2822
  contextForAgent: contextPolicy.contextForAgent,
@@ -2471,6 +2837,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2471
2837
  usageBudget: data.usageBudget,
2472
2838
  configToolBudget: data.configToolBudget,
2473
2839
  capabilityCeiling: data.capabilityCeiling,
2840
+ runFanoutBudget: data.runFanoutBudget,
2474
2841
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
2475
2842
  parentWorkflowRunId: params.workflowParentRunId,
2476
2843
  workflowKey: params.workflowKey,
@@ -2492,7 +2859,12 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2492
2859
  const normalizedSkills = normalizeSkillInput(params.skill);
2493
2860
  const skills = normalizedSkills === false ? [] : normalizedSkills;
2494
2861
  const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
2495
- const modelOverride = resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
2862
+ const externalRunnerWithoutExplicitModel = a.runner?.type === "external-cli"
2863
+ && params.model === undefined
2864
+ && (a.model === undefined || (a.modelSource?.type === "subagents.defaultModel" && a.model === a.modelSource.model));
2865
+ const modelOverride = externalRunnerWithoutExplicitModel
2866
+ ? undefined
2867
+ : resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
2496
2868
  return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
2497
2869
  agent: params.agent!,
2498
2870
  task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
@@ -2505,15 +2877,17 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2505
2877
  artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
2506
2878
  artifactConfig,
2507
2879
  shareEnabled,
2880
+ activeAsyncCapacity: data.activeAsyncCapacity,
2508
2881
  sessionRoot,
2509
2882
  sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
2510
2883
  context: contextPolicy.contextForAgent(params.agent!),
2511
2884
  skills,
2512
2885
  output: effectiveOutput,
2513
2886
  outputMode: effectiveOutputMode,
2887
+ ...(params.reads !== undefined ? { reads: params.reads } : {}),
2514
2888
  outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
2515
2889
  modelOverride,
2516
- thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
2890
+ thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride),
2517
2891
  maxSubagentDepth,
2518
2892
  waitToolEnabled: deps.waitToolEnabled,
2519
2893
  worktreeSetupHook: deps.config.worktreeSetupHook,
@@ -2532,6 +2906,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2532
2906
  usageBudget: data.usageBudget,
2533
2907
  configToolBudget: data.configToolBudget,
2534
2908
  capabilityCeiling: data.capabilityCeiling,
2909
+ runFanoutBudget: data.runFanoutBudget,
2535
2910
  parentWorkflowRunId: params.workflowParentRunId,
2536
2911
  workflowKey: params.workflowKey,
2537
2912
  }));
@@ -2622,6 +2997,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
2622
2997
  permissions: deps.config.permissions,
2623
2998
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
2624
2999
  capabilityCeiling: data.capabilityCeiling,
3000
+ runFanoutBudget: data.runFanoutBudget,
2625
3001
  }));
2626
3002
 
2627
3003
  if (chainResult.requestedAsync) {
@@ -2632,7 +3008,9 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
2632
3008
  details: { mode: "chain" as const, results: [] },
2633
3009
  };
2634
3010
  }
2635
- const id = randomUUID();
3011
+ const id = data.asyncRunId;
3012
+ const capacity = acquireClarifiedBackgroundCapacity(data, deps, "chain", id);
3013
+ if (capacity.errorResult) return capacity.errorResult;
2636
3014
  const parentModel = data.parentModel;
2637
3015
  const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
2638
3016
  pi: deps.pi,
@@ -2648,42 +3026,48 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
2648
3026
  const rawAsyncChain = chainResult.requestedAsync.chain;
2649
3027
  const asyncChain = wrapChainTasksForFork(rawAsyncChain, contextPolicy);
2650
3028
  const firstAgent = firstChainAgent(rawAsyncChain);
2651
- return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
2652
- chain: asyncChain,
2653
- task: params.task,
2654
- goal: resolveAsyncEventGoal(params.task, rawAsyncChain, firstAgent ? shouldForkAgent(contextPolicy, firstAgent) : false),
2655
- agents,
2656
- ctx: asyncCtx,
2657
- availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
2658
- cwd: effectiveCwd,
2659
- maxOutput: params.maxOutput,
2660
- artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
2661
- artifactConfig,
2662
- shareEnabled,
2663
- sessionRoot,
2664
- chainSkills: chainResult.requestedAsync.chainSkills,
2665
- sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
2666
- thinkingOverridesByFlatIndex: collectChainThinkingOverrides(asyncChain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
2667
- contextForAgent: contextPolicy.contextForAgent,
2668
- dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
2669
- maxSubagentDepth: currentMaxSubagentDepth,
2670
- waitToolEnabled: deps.waitToolEnabled,
2671
- worktreeSetupHook: deps.config.worktreeSetupHook,
2672
- worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
2673
- worktreeBaseDir: deps.config.worktreeBaseDir,
2674
- controlConfig,
2675
- agentContract: params.agentContract,
2676
- controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
2677
- childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
2678
- nestedRoute: data.nestedRoute,
2679
- timeoutMs: data.timeoutMs,
2680
- turnBudget: data.turnBudget,
2681
- toolBudget: data.toolBudget,
2682
- usageBudget: data.usageBudget,
2683
- configToolBudget: data.configToolBudget,
2684
- capabilityCeiling: data.capabilityCeiling,
2685
- globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
2686
- }));
3029
+ try {
3030
+ return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
3031
+ chain: asyncChain,
3032
+ task: params.task,
3033
+ goal: resolveAsyncEventGoal(params.task, rawAsyncChain, firstAgent ? shouldForkAgent(contextPolicy, firstAgent) : false),
3034
+ agents,
3035
+ ctx: asyncCtx,
3036
+ availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
3037
+ cwd: effectiveCwd,
3038
+ maxOutput: params.maxOutput,
3039
+ artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
3040
+ artifactConfig,
3041
+ shareEnabled,
3042
+ sessionRoot,
3043
+ chainSkills: chainResult.requestedAsync.chainSkills,
3044
+ activeAsyncCapacity: capacity.activeAsyncCapacity,
3045
+ sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
3046
+ thinkingOverridesByFlatIndex: collectChainThinkingOverrides(asyncChain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
3047
+ contextForAgent: contextPolicy.contextForAgent,
3048
+ dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
3049
+ maxSubagentDepth: currentMaxSubagentDepth,
3050
+ waitToolEnabled: deps.waitToolEnabled,
3051
+ worktreeSetupHook: deps.config.worktreeSetupHook,
3052
+ worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
3053
+ worktreeBaseDir: deps.config.worktreeBaseDir,
3054
+ controlConfig,
3055
+ agentContract: params.agentContract,
3056
+ controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
3057
+ childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
3058
+ nestedRoute: data.nestedRoute,
3059
+ timeoutMs: data.timeoutMs,
3060
+ turnBudget: data.turnBudget,
3061
+ toolBudget: data.toolBudget,
3062
+ usageBudget: data.usageBudget,
3063
+ configToolBudget: data.configToolBudget,
3064
+ capabilityCeiling: data.capabilityCeiling,
3065
+ runFanoutBudget: data.runFanoutBudget,
3066
+ globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
3067
+ }));
3068
+ } finally {
3069
+ finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
3070
+ }
2687
3071
  }
2688
3072
 
2689
3073
  const rawChainDetails = chainResult.details ? compactOptional<Details>({ ...chainResult.details, runId, timeoutMs: data.timeoutMs }) : undefined;
@@ -2728,6 +3112,7 @@ interface ForegroundParallelRunInput {
2728
3112
  intercomEvents: IntercomEventBus;
2729
3113
  parentSessionId: string | null;
2730
3114
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
3115
+ runFanoutBudget: RunFanoutBudgetDescriptor;
2731
3116
  signal: AbortSignal;
2732
3117
  runId: string;
2733
3118
  sessionDirForIndex: (idx?: number) => string | undefined;
@@ -2777,6 +3162,48 @@ function buildParallelModeError(message: string): AgentToolResult<Details> {
2777
3162
  };
2778
3163
  }
2779
3164
 
3165
+ function acquireClarifiedBackgroundCapacity(
3166
+ data: ExecutionContextData,
3167
+ deps: ExecutorDeps,
3168
+ mode: "single" | "parallel" | "chain",
3169
+ asyncRunId: string,
3170
+ ): { activeAsyncCapacity?: ActiveAsyncCapacityHandle; errorResult?: AgentToolResult<Details> } {
3171
+ if (!data.topLevelAsyncCapacityEligible || !data.parentSessionId) return {};
3172
+ const liveWorkflowRunIds = new Set(deps.state.workflowControllers?.keys() ?? []);
3173
+ const activeLimit = resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession);
3174
+ try {
3175
+ const activeAsyncCapacity = acquireActiveAsyncCapacity({
3176
+ sessionId: data.parentSessionId,
3177
+ limit: activeLimit,
3178
+ runId: asyncRunId,
3179
+ kind: "runner",
3180
+ asyncDir: path.join(DIRS.async, asyncRunId),
3181
+ }, { liveWorkflowRunIds });
3182
+ deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(data.parentSessionId, activeLimit, { liveWorkflowRunIds });
3183
+ return { activeAsyncCapacity };
3184
+ } catch (error) {
3185
+ if (error instanceof ActiveAsyncCapacityError) {
3186
+ deps.state.activeAsyncCapacity = error.snapshot;
3187
+ return { errorResult: { content: [{ type: "text", text: error.message }], isError: true, details: { mode, results: [], activeAsyncCapacity: error.snapshot } } };
3188
+ }
3189
+ throw error;
3190
+ }
3191
+ }
3192
+
3193
+ function finalizeClarifiedBackgroundCapacity(
3194
+ data: ExecutionContextData,
3195
+ deps: ExecutorDeps,
3196
+ activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined,
3197
+ ): void {
3198
+ if (!activeAsyncCapacity || !data.parentSessionId) return;
3199
+ if (!activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
3200
+ deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(
3201
+ data.parentSessionId,
3202
+ resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession),
3203
+ { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) },
3204
+ );
3205
+ }
3206
+
2780
3207
  function createParallelWorktreeSetup(
2781
3208
  enabled: boolean | undefined,
2782
3209
  cwd: string,
@@ -2965,7 +3392,33 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
2965
3392
  beginForegroundChild(input.foregroundControl, {
2966
3393
  index,
2967
3394
  agent: task.agent,
2968
- ...(input.taskDescriptions[index] === undefined ? {} : { description: input.taskDescriptions[index] }),
3395
+ authoredTask: input.taskDescriptions[index] ?? "",
3396
+ effectivePrompt: taskText,
3397
+ cwd: taskCwd,
3398
+ ...(outputPath ? { outputPath } : {}),
3399
+ rerun: { params: compactOptional<Record<string, unknown>>({
3400
+ agent: task.agent,
3401
+ task: input.taskDescriptions[index] ?? "",
3402
+ async: false,
3403
+ cwd: input.worktreeSetup ? undefined : taskCwd,
3404
+ worktree: input.worktreeSetup ? true : false,
3405
+ context: input.contextPolicy.contextForAgent(task.agent),
3406
+ share: input.shareEnabled || undefined,
3407
+ maxOutput: input.maxOutput,
3408
+ timeoutMs: input.timeoutMs,
3409
+ turnBudget: input.turnBudget,
3410
+ ...(model ? { model } : {}),
3411
+ ...(effectiveSkills !== undefined ? { skill: effectiveSkills } : {}),
3412
+ ...(outputPath ? { output: outputPath } : {}),
3413
+ ...(behavior?.outputMode !== undefined ? { outputMode: behavior.outputMode } : {}),
3414
+ ...(behavior?.reads !== undefined ? { reads: behavior.reads } : {}),
3415
+ ...(behavior?.progress !== undefined ? { progress: behavior.progress } : {}),
3416
+ ...(task.toolBudget !== undefined ? { toolBudget: task.toolBudget } : {}),
3417
+ ...(task.outputSchema !== undefined ? { outputSchema: task.outputSchema } : {}),
3418
+ ...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
3419
+ ...(task.agentContract !== undefined || input.agentContract !== undefined ? { agentContract: task.agentContract ?? input.agentContract } : {}),
3420
+ }) },
3421
+ description: `${task.agent} child`,
2969
3422
  ...(model ? { model } : {}),
2970
3423
  ...(thinking ? { thinking } : {}),
2971
3424
  interrupt: () => {
@@ -2982,7 +3435,10 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
2982
3435
  const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, compactOptional<Parameters<typeof runSync>[4]>({
2983
3436
  permissions: input.permissions,
2984
3437
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
3438
+ llmIntentArbiter: createTaskMutationArbiter(input.ctx),
3439
+ ...workflowForegroundSteeringLaunchOptions(input.foregroundControl, index),
2985
3440
  context: input.contextPolicy.contextForAgent(task.agent),
3441
+ runFanoutBudget: { ...input.runFanoutBudget, parentPath: `${input.runFanoutBudget.parentPath ? `${input.runFanoutBudget.parentPath}/` : ""}tasks[${index}]` },
2986
3442
  cwd: taskCwd,
2987
3443
  signal: input.signal,
2988
3444
  interruptSignal: interruptController.signal,
@@ -3027,6 +3483,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
3027
3483
  agentContract: task.agentContract ?? input.agentContract,
3028
3484
  acceptance: task.acceptance,
3029
3485
  acceptanceContext: { mode: "parallel" },
3486
+ onEffectivePrompt: input.foregroundControl ? (prompt) => updateLiveEffectivePrompt(input.foregroundControl!, index, prompt) : undefined,
3030
3487
  timeoutMs: input.timeoutMs,
3031
3488
  deadlineAt: input.deadlineAt,
3032
3489
  turnBudget: input.turnBudget,
@@ -3210,7 +3667,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3210
3667
  details: { mode: "parallel" as const, results: [] },
3211
3668
  };
3212
3669
  }
3213
- const id = randomUUID();
3670
+ const id = data.asyncRunId;
3671
+ const capacity = acquireClarifiedBackgroundCapacity(data, deps, "parallel", id);
3672
+ if (capacity.errorResult) return capacity.errorResult;
3214
3673
  const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
3215
3674
  pi: deps.pi,
3216
3675
  cwd: ctx.cwd,
@@ -3241,40 +3700,46 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3241
3700
  ...(t.agentContract !== undefined ? { agentContract: t.agentContract } : {}),
3242
3701
  });
3243
3702
  });
3244
- return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
3245
- chain: [compactOptional<ParallelStep>({ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree })],
3246
- resultMode: "parallel",
3247
- goal: taskTexts[0] ?? "",
3248
- agents,
3249
- ctx: asyncCtx,
3250
- availableModels,
3251
- cwd: effectiveCwd,
3252
- maxOutput: params.maxOutput,
3253
- artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
3254
- artifactConfig,
3255
- shareEnabled,
3256
- sessionRoot,
3257
- chainSkills: [],
3258
- sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index, modelOverrides[index])),
3259
- thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, modelOverrides[index])),
3260
- contextForAgent: contextPolicy.contextForAgent,
3261
- maxSubagentDepth: currentMaxSubagentDepth,
3262
- waitToolEnabled: deps.waitToolEnabled,
3263
- worktreeSetupHook: deps.config.worktreeSetupHook,
3264
- worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
3265
- worktreeBaseDir: deps.config.worktreeBaseDir,
3266
- controlConfig,
3267
- agentContract: params.agentContract,
3268
- controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
3269
- childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
3270
- nestedRoute: data.nestedRoute,
3271
- timeoutMs: data.timeoutMs,
3272
- turnBudget: data.turnBudget,
3273
- usageBudget: data.usageBudget,
3274
- toolBudget: data.toolBudget,
3275
- configToolBudget: data.configToolBudget,
3276
- globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
3277
- }));
3703
+ try {
3704
+ return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
3705
+ chain: [compactOptional<ParallelStep>({ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree })],
3706
+ resultMode: "parallel",
3707
+ goal: taskTexts[0] ?? "",
3708
+ agents,
3709
+ ctx: asyncCtx,
3710
+ availableModels,
3711
+ cwd: effectiveCwd,
3712
+ maxOutput: params.maxOutput,
3713
+ artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
3714
+ artifactConfig,
3715
+ shareEnabled,
3716
+ sessionRoot,
3717
+ chainSkills: [],
3718
+ activeAsyncCapacity: capacity.activeAsyncCapacity,
3719
+ sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index, modelOverrides[index])),
3720
+ thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, modelOverrides[index])),
3721
+ contextForAgent: contextPolicy.contextForAgent,
3722
+ maxSubagentDepth: currentMaxSubagentDepth,
3723
+ waitToolEnabled: deps.waitToolEnabled,
3724
+ worktreeSetupHook: deps.config.worktreeSetupHook,
3725
+ worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
3726
+ worktreeBaseDir: deps.config.worktreeBaseDir,
3727
+ controlConfig,
3728
+ agentContract: params.agentContract,
3729
+ controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
3730
+ childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
3731
+ nestedRoute: data.nestedRoute,
3732
+ timeoutMs: data.timeoutMs,
3733
+ turnBudget: data.turnBudget,
3734
+ usageBudget: data.usageBudget,
3735
+ toolBudget: data.toolBudget,
3736
+ configToolBudget: data.configToolBudget,
3737
+ globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
3738
+ runFanoutBudget: data.runFanoutBudget,
3739
+ }));
3740
+ } finally {
3741
+ finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
3742
+ }
3278
3743
  }
3279
3744
  }
3280
3745
 
@@ -3300,10 +3765,11 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3300
3765
  );
3301
3766
  if (errorResult) return errorResult;
3302
3767
 
3303
- let worktreeFinalized = false;
3768
+ let worktreeCleanupHandled = false;
3769
+ let pendingHandoff: Details["parallelHandoff"];
3304
3770
  try {
3305
3771
  if (worktreeSetup) {
3306
- writePendingParallelHandoff({
3772
+ pendingHandoff = writePendingParallelHandoff({
3307
3773
  manifestPath: parallelHandoffPath(artifactsDir, runId),
3308
3774
  runId,
3309
3775
  mode: "parallel",
@@ -3352,6 +3818,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3352
3818
  intercomEvents: deps.pi.events,
3353
3819
  parentSessionId: data.parentSessionId,
3354
3820
  capabilityCeiling: data.capabilityCeiling,
3821
+ runFanoutBudget: data.runFanoutBudget,
3355
3822
  signal,
3356
3823
  runId,
3357
3824
  sessionDirForIndex,
@@ -3406,10 +3873,13 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3406
3873
  updateForegroundNestedProjection(foregroundControl);
3407
3874
  attachRootChildrenToSteps(runId, results, foregroundControl.nestedChildren);
3408
3875
  }
3876
+ const detached = results.find((result) => result.detached);
3409
3877
  let handoff: ReturnType<typeof finalizeParallelWorktreeHandoff> | undefined;
3410
3878
  if (worktreeSetup) {
3411
- worktreeFinalized = true;
3412
- handoff = finalizeParallelWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: effectiveCwd, tasks, results });
3879
+ worktreeCleanupHandled = true;
3880
+ handoff = detached
3881
+ ? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
3882
+ : finalizeParallelWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: effectiveCwd, tasks, results });
3413
3883
  }
3414
3884
  const interrupted = results.find((result) => result.interrupted);
3415
3885
  const totalCost = sumResultsCost(results);
@@ -3432,11 +3902,10 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3432
3902
  details,
3433
3903
  };
3434
3904
  }
3435
- const detachedIndex = results.findIndex((result) => result.detached);
3436
- const detached = detachedIndex >= 0 ? results[detachedIndex] : undefined;
3437
3905
  if (detached) {
3906
+ const handoffSuffix = handoff?.suffix ? `\n\n${handoff.suffix}` : "";
3438
3907
  return {
3439
- content: [{ type: "text", text: `Parallel run detached for intercom coordination (${detached.agent}). Reply to the supervisor request first, then wait with subagent_wait({ id: "${runId}" }). Use subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.` }],
3908
+ content: [{ type: "text", text: `Parallel run detached for intercom coordination (${detached.agent}). Reply to the supervisor request first, then wait with subagent_wait({ id: "${runId}" }). Use subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.${handoffSuffix}` }],
3440
3909
  details,
3441
3910
  };
3442
3911
  }
@@ -3450,6 +3919,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3450
3919
  runId,
3451
3920
  mode: "parallel",
3452
3921
  details,
3922
+ ...(params.workflowParentRunId !== undefined ? { preserveDetailsOutputs: true } : {}),
3453
3923
  ...(foregroundControl?.nestedChildren?.length ? { nestedChildren: foregroundControl.nestedChildren } : {}),
3454
3924
  });
3455
3925
  if (intercomReceipt) {
@@ -3484,7 +3954,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3484
3954
  details,
3485
3955
  };
3486
3956
  } finally {
3487
- if (worktreeSetup && !worktreeFinalized) cleanupWorktrees(worktreeSetup);
3957
+ if (worktreeSetup && !worktreeCleanupHandled) cleanupWorktrees(worktreeSetup);
3488
3958
  }
3489
3959
  }
3490
3960
 
@@ -3535,6 +4005,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3535
4005
  data.modelScope === undefined ? {} : { scope: data.modelScope },
3536
4006
  );
3537
4007
  let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
4008
+ let readsOverride: string[] | false | undefined = params.reads;
3538
4009
  const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
3539
4010
  let effectiveOutput = normalizeSingleOutputOverride(rawOutput, agentConfig.output);
3540
4011
  const effectiveOutputMode = params.outputMode ?? "inline";
@@ -3572,6 +4043,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3572
4043
  if (override?.model !== undefined) modelOverride = resolveEffectiveSubagentModel(override.model, agentConfig.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
3573
4044
  if (override?.output !== undefined) effectiveOutput = normalizeSingleOutputOverride(override.output, agentConfig.output);
3574
4045
  if (override?.skills !== undefined) skillOverride = override.skills;
4046
+ if (override?.reads !== undefined) readsOverride = override.reads;
3575
4047
 
3576
4048
  if (result.runInBackground) {
3577
4049
  if (!isAsyncAvailable()) {
@@ -3581,7 +4053,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3581
4053
  details: { mode: "single" as const, results: [] },
3582
4054
  };
3583
4055
  }
3584
- const id = randomUUID();
4056
+ const id = data.asyncRunId;
4057
+ const capacity = acquireClarifiedBackgroundCapacity(data, deps, "single", id);
4058
+ if (capacity.errorResult) return capacity.errorResult;
3585
4059
  const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
3586
4060
  pi: deps.pi,
3587
4061
  cwd: ctx.cwd,
@@ -3593,48 +4067,56 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3593
4067
  interactive: ctx.hasUI,
3594
4068
  permissions: deps.config.permissions,
3595
4069
  });
3596
- return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
3597
- agent: params.agent!,
3598
- task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
3599
- goal: task,
3600
- agentConfig,
3601
- ctx: asyncCtx,
3602
- availableModels,
3603
- cwd: effectiveCwd,
3604
- maxOutput: params.maxOutput,
3605
- artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
3606
- artifactConfig,
3607
- shareEnabled,
3608
- sessionRoot,
3609
- sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
3610
- context: contextPolicy.contextForAgent(params.agent!),
3611
- skills: skillOverride === false ? [] : skillOverride,
3612
- output: effectiveOutput,
3613
- outputMode: effectiveOutputMode,
3614
- outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
3615
- modelOverride,
3616
- thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
3617
- maxSubagentDepth,
3618
- waitToolEnabled: deps.waitToolEnabled,
3619
- worktreeSetupHook: deps.config.worktreeSetupHook,
3620
- worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
3621
- worktreeBaseDir: deps.config.worktreeBaseDir,
3622
- controlConfig,
3623
- controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
3624
- childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
3625
- nestedRoute: data.nestedRoute,
3626
- agentContract: params.agentContract,
3627
- structuredOutputSchema: params.outputSchema,
3628
- acceptance: params.acceptance,
3629
- timeoutMs: data.timeoutMs,
3630
- turnBudget: data.turnBudget,
3631
- toolBudget: effectiveToolBudget.toolBudget,
3632
- usageBudget: data.usageBudget,
3633
- allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
3634
- }));
4070
+ try {
4071
+ return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
4072
+ agent: params.agent!,
4073
+ task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
4074
+ goal: task,
4075
+ agentConfig,
4076
+ ctx: asyncCtx,
4077
+ availableModels,
4078
+ cwd: effectiveCwd,
4079
+ maxOutput: params.maxOutput,
4080
+ artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
4081
+ artifactConfig,
4082
+ shareEnabled,
4083
+ activeAsyncCapacity: capacity.activeAsyncCapacity,
4084
+ sessionRoot,
4085
+ sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
4086
+ context: contextPolicy.contextForAgent(params.agent!),
4087
+ skills: skillOverride === false ? [] : skillOverride,
4088
+ output: effectiveOutput,
4089
+ outputMode: effectiveOutputMode,
4090
+ ...(readsOverride !== undefined ? { reads: readsOverride } : {}),
4091
+ outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
4092
+ modelOverride,
4093
+ thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
4094
+ maxSubagentDepth,
4095
+ waitToolEnabled: deps.waitToolEnabled,
4096
+ worktreeSetupHook: deps.config.worktreeSetupHook,
4097
+ worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
4098
+ worktreeBaseDir: deps.config.worktreeBaseDir,
4099
+ controlConfig,
4100
+ controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
4101
+ childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
4102
+ nestedRoute: data.nestedRoute,
4103
+ agentContract: params.agentContract,
4104
+ structuredOutputSchema: params.outputSchema,
4105
+ acceptance: params.acceptance,
4106
+ timeoutMs: data.timeoutMs,
4107
+ turnBudget: data.turnBudget,
4108
+ toolBudget: effectiveToolBudget.toolBudget,
4109
+ usageBudget: data.usageBudget,
4110
+ allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
4111
+ runFanoutBudget: data.runFanoutBudget,
4112
+ }));
4113
+ } finally {
4114
+ finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
4115
+ }
3635
4116
  }
3636
4117
  }
3637
4118
 
4119
+ const authoredTask = task;
3638
4120
  if (shouldForkAgent(contextPolicy, params.agent!)) {
3639
4121
  task = wrapForkTask(task);
3640
4122
  }
@@ -3647,6 +4129,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3647
4129
  const structuredRuntime = params.outputSchema
3648
4130
  ? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined)
3649
4131
  : undefined;
4132
+ // Reads: caller override > agent defaultReads > none. `~`/`~/` expand to home;
4133
+ // absolute paths pass through; relative paths resolve against the child cwd.
4134
+ const reads = readsOverride !== undefined ? readsOverride : agentConfig.defaultReads ?? false;
4135
+ const readPaths = Array.isArray(reads) ? resolveExistingReadPaths(reads, effectiveCwd) : [];
4136
+ const readsInstruction = readPaths.length > 0
4137
+ ? `[Read from: ${readPaths.join(", ")}]\n\n`
4138
+ : "";
4139
+ task = readsInstruction + task;
3650
4140
  task = injectSingleOutputInstruction(task, outputPath, agentConfig);
3651
4141
 
3652
4142
  let effectiveSkills: string[] | undefined;
@@ -3663,6 +4153,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3663
4153
  beginForegroundChild(foregroundControl, omitUndefinedProperties({
3664
4154
  index: 0,
3665
4155
  agent: params.agent!,
4156
+ authoredTask,
4157
+ effectivePrompt: task,
4158
+ cwd: effectiveCwd,
4159
+ outputPath,
4160
+ rerun: { params: { ...params, task: authoredTask, async: params.async ?? false } },
3666
4161
  description: foregroundControl.description,
3667
4162
  ...(modelOverride ? { model: modelOverride } : {}),
3668
4163
  ...(thinking ? { thinking } : {}),
@@ -3688,7 +4183,10 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3688
4183
  r = await runSync(ctx.cwd, agents, params.agent!, task, compactOptional<Parameters<typeof runSync>[4]>({
3689
4184
  permissions: deps.config.permissions,
3690
4185
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
4186
+ llmIntentArbiter: createTaskMutationArbiter(ctx),
4187
+ ...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
3691
4188
  context: data.contextPolicy.contextForAgent(params.agent!),
4189
+ runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
3692
4190
  cwd: effectiveCwd,
3693
4191
  signal,
3694
4192
  interruptSignal: interruptController.signal,
@@ -3722,6 +4220,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3722
4220
  agentContract: params.agentContract,
3723
4221
  acceptance: params.acceptance,
3724
4222
  acceptanceContext: { mode: "single" },
4223
+ onEffectivePrompt: foregroundControl ? (prompt) => updateLiveEffectivePrompt(foregroundControl, 0, prompt) : undefined,
3725
4224
  onDetachReady: (detach) => {
3726
4225
  detachForeground = detach;
3727
4226
  },
@@ -3770,12 +4269,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3770
4269
  if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
3771
4270
 
3772
4271
  const fullOutput = getSingleResultOutput(r);
4272
+ const preserveRejectedSavedOutput = r.acceptance?.explicit && r.acceptance.status === "rejected" && r.savedOutputPath !== undefined;
3773
4273
  const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
3774
4274
  fullOutput,
3775
4275
  truncatedOutput: r.truncation?.text,
3776
4276
  outputPath,
3777
4277
  outputMode: r.outputMode,
3778
4278
  exitCode: r.exitCode,
4279
+ preserveSavedOutput: preserveRejectedSavedOutput,
3779
4280
  savedPath: r.savedOutputPath,
3780
4281
  outputReference: r.outputReference,
3781
4282
  saveError: r.outputSaveError,
@@ -3810,6 +4311,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3810
4311
  runId,
3811
4312
  mode: "single",
3812
4313
  details,
4314
+ ...(params.workflowParentRunId !== undefined ? { preserveDetailsOutputs: true } : {}),
3813
4315
  ...(foregroundControl?.nestedChildren?.length ? { nestedChildren: foregroundControl.nestedChildren } : {}),
3814
4316
  });
3815
4317
  if (intercomReceipt) {
@@ -3865,8 +4367,71 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
3865
4367
  };
3866
4368
  }
3867
4369
 
4370
+ const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string; async: boolean; runId?: string }) => void>();
4371
+
4372
+ function recordMissionWorkflowChild(
4373
+ binding: MissionLaunchBinding | undefined,
4374
+ workflowRunId: string,
4375
+ key: string,
4376
+ update: Omit<MissionWorkflowChildUpdate, "workflowRunId" | "key">,
4377
+ ): void {
4378
+ if (!binding) return;
4379
+ const { task: _task, ...durableUpdate } = update;
4380
+ try {
4381
+ updateMission(binding.location, binding.missionId, { upsertWorkflowChildren: [{ workflowRunId, key, ...durableUpdate }] });
4382
+ } catch (error) {
4383
+ console.warn(`[pi-subagents] Failed to record mission workflow child '${key}': ${error instanceof Error ? error.message : String(error)}`);
4384
+ }
4385
+ }
4386
+
4387
+ export function missionWorkflowChildStatus(result: AgentToolResult<Details>): string {
4388
+ const childResults = result.details.results;
4389
+ if (childResults.some((child) => child.detached || child.interrupted)) return "paused";
4390
+ if (result.isError === true || childResults.some((child) => child.exitCode !== 0)) return "failed";
4391
+ if (childResults.length === 0 && (result.details.asyncId || result.details.asyncDir)) return "running";
4392
+ return "completed";
4393
+ }
4394
+
4395
+ export async function runMissionWorkflowChild(
4396
+ binding: MissionLaunchBinding | undefined,
4397
+ workflowRunId: string,
4398
+ key: string,
4399
+ phase: string | undefined,
4400
+ run: () => Promise<AgentToolResult<Details>>,
4401
+ ): Promise<AgentToolResult<Details>> {
4402
+ try {
4403
+ return await run();
4404
+ } catch (error) {
4405
+ recordMissionWorkflowChild(binding, workflowRunId, key, {
4406
+ status: "failed",
4407
+ completedAt: new Date().toISOString(),
4408
+ heartbeat: { status: "failed", ...(phase ? { phase } : {}), message: error instanceof Error ? error.message : String(error) },
4409
+ });
4410
+ throw error;
4411
+ }
4412
+ }
4413
+
4414
+ export function bindMissionWorkflowChildAsyncLaunch(
4415
+ params: SubagentParamsLike,
4416
+ binding: MissionLaunchBinding | undefined,
4417
+ asyncByDefault: boolean,
4418
+ asyncId: string = randomUUID(),
4419
+ ): SubagentParamsLike {
4420
+ const requestedAsync = params.async ?? asyncByDefault;
4421
+ if (!binding || !requestedAsync || params.clarify === true) return params;
4422
+ const id = asyncId.trim();
4423
+ if (!id || path.basename(id) !== id) throw new Error("workflow child async id must be a single path segment");
4424
+ writeMissionAsyncBinding(path.join(DIRS.async, id), binding);
4425
+ return { ...params, workflowChildAsyncId: id };
4426
+ }
4427
+
3868
4428
  function workflowChildResult(key: string, result: AgentToolResult<Details>): WorkflowScriptChildResult {
3869
- const output = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n");
4429
+ const receiptOutput = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n");
4430
+ const output = result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
4431
+ ? result.details.results[0].finalOutput
4432
+ : receiptOutput;
4433
+ const detached = result.details.results.some((child) => child.detached);
4434
+ const ok = result.isError !== true && !detached;
3870
4435
  const artifactPaths = new Set<string>();
3871
4436
  if (result.details.asyncDir) artifactPaths.add(result.details.asyncDir);
3872
4437
  if (result.details.parallelHandoff?.path) artifactPaths.add(result.details.parallelHandoff.path);
@@ -3876,18 +4441,76 @@ function workflowChildResult(key: string, result: AgentToolResult<Details>): Wor
3876
4441
  if (child.sessionFile) artifactPaths.add(child.sessionFile);
3877
4442
  }
3878
4443
  const structured = result.details.results.map((child) => child.structuredOutput).filter((value) => value !== undefined);
4444
+ const resolvedAgents = [...new Set(result.details.results.map((child) => child.agent).filter((agent): agent is string => Boolean(agent)))];
3879
4445
  return {
3880
4446
  key,
3881
- ok: result.isError !== true,
4447
+ ok,
4448
+ ...(resolvedAgents.length === 1 ? { agent: resolvedAgents[0] } : {}),
3882
4449
  ...(result.details.runId || result.details.asyncId ? { runId: result.details.runId ?? result.details.asyncId } : {}),
3883
4450
  output,
3884
- ...(result.isError === true ? { error: output || "Child run failed." } : {}),
4451
+ ...(!ok ? { error: receiptOutput || output || "Child run failed." } : {}),
3885
4452
  ...(structured.length === 1 ? { structuredOutput: structured[0] } : structured.length > 1 ? { structuredOutput: structured } : {}),
3886
4453
  artifactPaths: [...artifactPaths],
3887
4454
  results: result.details.results,
3888
4455
  };
3889
4456
  }
3890
4457
 
4458
+ export function prepareWorkflowLaunchParams(
4459
+ workflowDefaults: SubagentParamsLike,
4460
+ childParams: Record<string, unknown>,
4461
+ parentWorkflowRunId: string,
4462
+ workflowKey: string,
4463
+ options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor } = {},
4464
+ ): SubagentParamsLike {
4465
+ if (typeof childParams.resume === "string") {
4466
+ if (childParams.gate !== undefined || workflowDefaults.gate !== undefined) {
4467
+ throw new Error("gate is not supported with retained resume; resume uses the retained child contract.");
4468
+ }
4469
+ const timeoutMs = childParams.timeoutMs ?? childParams.maxRuntimeMs ?? workflowDefaults.timeoutMs ?? workflowDefaults.maxRuntimeMs;
4470
+ const turnBudget = childParams.turnBudget ?? workflowDefaults.turnBudget;
4471
+ const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
4472
+ return {
4473
+ action: "resume",
4474
+ id: childParams.resume.trim(),
4475
+ message: typeof childParams.task === "string" ? childParams.task.trim() : "",
4476
+ workflowParentRunId: parentWorkflowRunId,
4477
+ workflowKey,
4478
+ ...(options.missionDetached ? { mission: false } : {}),
4479
+ ...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
4480
+ ...(turnBudget !== undefined ? { turnBudget: turnBudget as TurnBudgetConfig } : {}),
4481
+ ...(toolBudget !== undefined ? { toolBudget: toolBudget as ToolBudgetConfig } : {}),
4482
+ };
4483
+ }
4484
+ const launchParams = {
4485
+ ...workflowDefaults,
4486
+ async: false,
4487
+ ...childParams,
4488
+ ...(options.missionDetached ? { mission: false } : {}),
4489
+ workflowParentRunId: parentWorkflowRunId,
4490
+ workflowKey,
4491
+ ...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
4492
+ ...(options.suppressRoutineResultIntercom ? { suppressRoutineResultIntercom: true } : {}),
4493
+ } as SubagentParamsLike;
4494
+ const normalizedGate = normalizeGateParams(launchParams);
4495
+ if (!normalizedGate.ok) throw new Error(normalizedGate.error);
4496
+ return prepareWorkflowChildParams(normalizedGate.params);
4497
+ }
4498
+
4499
+ type GateParamsNormalizationResult =
4500
+ | { ok: true; params: SubagentParamsLike }
4501
+ | { ok: false; error: string };
4502
+
4503
+ function normalizeGateParams(params: SubagentParamsLike): GateParamsNormalizationResult {
4504
+ if (params.gate !== undefined && params.action === "resume") {
4505
+ return { ok: false, error: "gate is not supported with action='resume'; resume uses the retained child contract." };
4506
+ }
4507
+ const normalized = normalizeGateAcceptance(params.gate, params.acceptance);
4508
+ if (!normalized.ok) return { ok: false, error: normalized.error };
4509
+ if (params.gate === undefined) return { ok: true, params };
4510
+ const { gate: _gate, ...rest } = params;
4511
+ return { ok: true, params: { ...rest, ...(normalized.acceptance !== undefined ? { acceptance: normalized.acceptance } : {}) } };
4512
+ }
4513
+
3891
4514
  function prepareWorkflowChildParams(params: SubagentParamsLike): SubagentParamsLike {
3892
4515
  if (params.worktree !== true || !params.agent) return params;
3893
4516
  const {
@@ -3901,6 +4524,7 @@ function prepareWorkflowChildParams(params: SubagentParamsLike): SubagentParamsL
3901
4524
  acceptance,
3902
4525
  agentContract,
3903
4526
  toolBudget,
4527
+ reads,
3904
4528
  ...runParams
3905
4529
  } = params;
3906
4530
  return {
@@ -3912,6 +4536,7 @@ function prepareWorkflowChildParams(params: SubagentParamsLike): SubagentParamsL
3912
4536
  ...(model !== undefined ? { model } : {}),
3913
4537
  ...(skill !== undefined ? { skill } : {}),
3914
4538
  ...(output !== undefined ? { output } : {}),
4539
+ ...(reads !== undefined ? { reads } : {}),
3915
4540
  ...(outputMode !== undefined ? { outputMode } : {}),
3916
4541
  ...(outputSchema !== undefined ? { outputSchema } : {}),
3917
4542
  ...(acceptance !== undefined ? { acceptance } : {}),
@@ -3943,21 +4568,6 @@ function workflowChatProgressUpdate(
3943
4568
  };
3944
4569
  }
3945
4570
 
3946
- function omitExecutionModeActionAlias(params: SubagentParamsLike): SubagentParamsLike {
3947
- const action = params.action?.toLowerCase();
3948
- if (action === "single" && (params.agent !== undefined || params.task !== undefined)) {
3949
- const rest = { ...params };
3950
- delete rest.action;
3951
- return rest;
3952
- }
3953
- if ((action === "parallel" || action === "tasks") && (params.tasks?.length ?? 0) > 0) {
3954
- const rest = { ...params };
3955
- delete rest.action;
3956
- return rest;
3957
- }
3958
- return params;
3959
- }
3960
-
3961
4571
  function createScheduledOwnerState(source: SubagentState, ownerSessionId: string, ctx: ExtensionContext): SubagentState {
3962
4572
  const ownerSpawns = source.subagentSpawns?.sessionId === ownerSessionId
3963
4573
  ? {
@@ -3996,6 +4606,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3996
4606
  onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
3997
4607
  ctx: ExtensionContext,
3998
4608
  ) => Promise<AgentToolResult<Details>>;
4609
+ /** Public/model-facing execution boundary. Internal direct launch primitives use execute or executeDelegated. */
4610
+ executePublic: (
4611
+ id: string,
4612
+ params: SubagentParamsLike,
4613
+ signal: AbortSignal,
4614
+ onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
4615
+ ctx: ExtensionContext,
4616
+ ) => Promise<AgentToolResult<Details>>;
3999
4617
  /**
4000
4618
  * Correlated extension-to-extension delegation owns its request IDs and
4001
4619
  * cancellation controllers, so independent requests may execute concurrently.
@@ -4018,6 +4636,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4018
4636
  } {
4019
4637
  const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
4020
4638
  const delegatedZeroToolBudgets = new WeakSet<object>();
4639
+ const delegatedExecutions = new WeakSet<object>();
4640
+ const warnedArtifactPackageDirs = new Set<string>();
4021
4641
  const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
4022
4642
  const execute = async (
4023
4643
  _id: string,
@@ -4027,63 +4647,156 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4027
4647
  ctx: ExtensionContext,
4028
4648
  preserveActiveSession = false,
4029
4649
  ): Promise<AgentToolResult<Details>> => {
4650
+ const workflowLaunchObserver = workflowLaunchObservers.get(params);
4030
4651
  const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
4031
4652
  const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
4653
+ const delegatedExecution = delegatedExecutions.has(params);
4032
4654
  if (!preserveActiveSession) deps.state.baseCwd = ctx.cwd;
4033
4655
  deps.state.foregroundRuns ??= new Map();
4034
4656
  deps.state.foregroundControls ??= new Map();
4035
4657
  deps.state.lastForegroundControlId ??= null;
4036
- const requestParams = omitExecutionModeActionAlias(params);
4037
- if (requestParams.workflowScript !== undefined) {
4038
- const invalidMode = requestParams.action !== undefined || requestParams.agent !== undefined || requestParams.step !== undefined || requestParams.tasks !== undefined || requestParams.chain !== undefined;
4039
- if (invalidMode) {
4040
- return { content: [{ type: "text", text: "workflowScript is its own execution mode; do not combine it with action, agent, step, tasks, or chain." }], isError: true, details: { mode: "workflow", results: [] } };
4041
- }
4042
- if (requestParams.clarify === true) {
4043
- return { content: [{ type: "text", text: "workflowScript does not support clarify UI." }], isError: true, details: { mode: "workflow", results: [] } };
4044
- }
4045
- const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? DEFAULT_FOREGROUND_TIMEOUT_MS;
4658
+ const normalizedGate = normalizeGateParams(params);
4659
+ if (!normalizedGate.ok) return buildRequestedModeError(params, normalizedGate.error);
4660
+ const requestParams = normalizedGate.params;
4661
+ const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
4662
+ if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
4663
+ const parentCwd = ctx.cwd;
4664
+ const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? (requestParams.async === false ? resolveConfigDefaultTimeoutMs(deps.config.timeoutMs) ?? DEFAULT_FOREGROUND_TIMEOUT_MS : undefined);
4046
4665
  const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
4047
4666
  if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
4048
- const workflowCwd = resolveRequestedCwd(ctx.cwd, requestParams.cwd);
4049
- const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd: ctx.cwd, workflowCwd, background: requestParams.async !== false });
4667
+ const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
4668
+ const workflowPrompts = { render: (ref: string, vars?: unknown) => renderWorkflowPrompt(ref, vars, workflowCwd) };
4669
+ const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd, workflowCwd, background: requestParams.async !== false });
4050
4670
  if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
4051
4671
  const chatProgress = chatProgressResult.projection!;
4052
- if (requestParams.async !== false) {
4053
- const workflowRunId = _id;
4672
+ const explicitMission = requestParams.missionId !== undefined || requestParams.mission !== undefined;
4673
+ const autoMission = !explicitMission;
4674
+ const workflowPreview = autoMission ? previewSimpleWorkflowRun(requestParams.workflowScript) : undefined;
4675
+ const previewAgent = workflowPreview?.agent?.trim() || undefined;
4676
+ const scriptFirstLine = requestParams.workflowScript.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || "Workflow";
4677
+ const boundedScriptPreview = scriptFirstLine.length > 100 ? `${scriptFirstLine.slice(0, 97)}...` : scriptFirstLine;
4678
+ const derivedObjective = previewAgent ? `Workflow: ${previewAgent}` : boundedScriptPreview;
4679
+ const workflowDepth = checkSubagentDepth(deps.config.maxSubagentDepth).depth;
4680
+ const asyncWorkflow = requestParams.async !== false;
4681
+ const topLevelAsyncWorkflow = asyncWorkflow
4682
+ && workflowDepth === 0
4683
+ && !resolveInheritedNestedRouteFromEnv()
4684
+ && !requestParams.workflowParentRunId;
4685
+ const workflowRunId = asyncWorkflow ? randomUUID() : undefined;
4686
+ let workflowCapacity: ActiveAsyncCapacityHandle | undefined;
4687
+ if (workflowRunId && topLevelAsyncWorkflow) {
4688
+ const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
4689
+ try {
4690
+ workflowCapacity = acquireActiveAsyncCapacity({
4691
+ sessionId: currentSessionId,
4692
+ limit: resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession),
4693
+ runId: workflowRunId,
4694
+ kind: "workflow",
4695
+ asyncDir: path.join(DIRS.async, workflowRunId),
4696
+ }, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
4697
+ } catch (error) {
4698
+ if (error instanceof ActiveAsyncCapacityError) {
4699
+ deps.state.activeAsyncCapacity = error.snapshot;
4700
+ return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "workflow", results: [], activeAsyncCapacity: error.snapshot } };
4701
+ }
4702
+ throw error;
4703
+ }
4704
+ }
4705
+ let workflowFanoutBudget: RunFanoutBudgetDescriptor;
4706
+ try {
4707
+ workflowFanoutBudget = requestParams.runFanoutBudget
4708
+ ?? decodeRunFanoutBudgetDescriptor(process.env[RUN_FANOUT_BUDGET_ENV])
4709
+ ?? createRunFanoutBudget(_id, resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
4710
+ } catch (error) {
4711
+ workflowCapacity?.rollback();
4712
+ return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
4713
+ }
4714
+ let missionBinding: MissionLaunchBinding | undefined;
4715
+ let missionWarning: string | undefined;
4716
+ try {
4717
+ missionBinding = prepareMissionLaunch({
4718
+ params: autoMission ? { ...requestParams, task: derivedObjective } : requestParams,
4719
+ projectRoot: workflowCwd,
4720
+ ...(deps.config.missions ? { config: deps.config.missions } : {}),
4721
+ ownerSessionId: resolveCurrentSessionId(ctx.sessionManager),
4722
+ });
4723
+ } catch (error) {
4724
+ if (explicitMission) {
4725
+ workflowCapacity?.rollback();
4726
+ return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
4727
+ }
4728
+ missionWarning = `Mission tracking unavailable: ${error instanceof Error ? error.message : String(error)}`;
4729
+ }
4730
+ const detachWorkflowChildMissions = autoMission || missionBinding !== undefined || requestParams.mission === false;
4731
+ const workflowState = missionBinding ? createMissionWorkflowState(missionBinding.location, missionBinding.missionId) : undefined;
4732
+ const attachWorkflowMission = (result: AgentToolResult<Details>): AgentToolResult<Details> => {
4733
+ if (!missionBinding) return missionWarning ? { ...result, details: { ...result.details, missionWarning } } : result;
4734
+ try {
4735
+ return attachMissionToLaunchResult({ binding: missionBinding, result });
4736
+ } catch (error) {
4737
+ const warning = `Mission tracking unavailable after launch: ${error instanceof Error ? error.message : String(error)}`;
4738
+ return explicitMission
4739
+ ? { ...result, isError: true, content: [...result.content, { type: "text", text: warning }], details: { ...result.details, missionWarning: warning } }
4740
+ : { ...result, details: { ...result.details, missionWarning: warning } };
4741
+ }
4742
+ };
4743
+ if (workflowRunId) {
4744
+ const toolCallId = _id;
4054
4745
  const asyncDir = path.join(DIRS.async, workflowRunId);
4055
4746
  const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
4056
4747
  const statusPath = path.join(asyncDir, "status.json");
4057
4748
  const eventsPath = path.join(asyncDir, "events.jsonl");
4058
4749
  const startedAt = Date.now();
4059
4750
  const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
4060
- fs.mkdirSync(asyncDir, { recursive: true });
4061
- fs.mkdirSync(DIRS.results, { recursive: true });
4751
+ try {
4752
+ fs.mkdirSync(asyncDir, { recursive: true });
4753
+ writeRunFanoutBudgetDescriptor(asyncDir, workflowFanoutBudget);
4754
+ fs.mkdirSync(DIRS.results, { recursive: true });
4755
+ } catch (error) {
4756
+ workflowCapacity?.rollback();
4757
+ return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
4758
+ }
4062
4759
  const controller = new AbortController();
4063
4760
  deps.state.workflowControllers ??= new Map();
4064
4761
  deps.state.workflowControllers.set(workflowRunId, controller);
4762
+ workflowCapacity?.markWorkflowStarted();
4763
+ if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()) });
4065
4764
  let status: AsyncStatus = {
4066
4765
  runId: workflowRunId,
4766
+ toolCallId,
4067
4767
  sessionId: currentSessionId ?? undefined,
4068
4768
  mode: "workflow",
4069
4769
  state: "running",
4070
4770
  startedAt,
4071
4771
  lastUpdate: startedAt,
4072
- deadlineAt: startedAt + timeout,
4073
- timeoutMs: timeout,
4074
- cwd: ctx.cwd,
4772
+ ...(timeout !== undefined ? { deadlineAt: startedAt + timeout, timeoutMs: timeout } : {}),
4773
+ cwd: workflowCwd,
4075
4774
  pid: process.pid,
4076
4775
  steps: [],
4077
4776
  workflow: { trace: [], emits: [], console: [] },
4777
+ runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
4078
4778
  };
4079
4779
  const appendWorkflowEvent = (event: Record<string, unknown>) => fs.appendFileSync(eventsPath, `${JSON.stringify({ ts: Date.now(), runId: workflowRunId, ...event })}\n`, "utf-8");
4780
+ let indexedState: AsyncStatus["state"] | undefined;
4080
4781
  const persist = () => {
4081
4782
  status.lastUpdate = Date.now();
4082
4783
  writeAtomicJson(statusPath, status);
4784
+ if (indexedState !== status.state) {
4785
+ updateActiveRunIndex(asyncDir, status.state);
4786
+ indexedState = status.state;
4787
+ }
4083
4788
  const job = deps.state.asyncJobs.get(workflowRunId);
4084
4789
  if (job) {
4085
4790
  job.status = status.state;
4086
4791
  job.updatedAt = status.lastUpdate;
4792
+ job.activityState = status.activityState;
4793
+ job.lastActivityAt = status.lastActivityAt;
4794
+ job.currentTool = status.currentTool;
4795
+ job.currentToolStartedAt = status.currentToolStartedAt;
4796
+ job.currentPath = status.currentPath;
4797
+ job.turnCount = status.turnCount;
4798
+ job.toolCount = status.toolCount;
4799
+ job.currentStep = status.currentStep;
4087
4800
  if (status.steps) {
4088
4801
  job.steps = status.steps.map((step, index) => ({ ...step, index }));
4089
4802
  job.agents = status.steps.map((step) => step.agent);
@@ -4094,7 +4807,28 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4094
4807
  job.workflow = status.workflow;
4095
4808
  }
4096
4809
  };
4097
- const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, cwd: ctx.cwd, status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, timeoutMs: timeout, deadlineAt: startedAt + timeout, workflow: status.workflow };
4810
+ const projectWorkflowActivity = () => {
4811
+ const steps = status.steps ?? [];
4812
+ const runningSteps = steps.filter((step) => step.status === "running");
4813
+ const lastActivityAt = runningSteps.reduce<number | undefined>((latest, step) => step.lastActivityAt === undefined ? latest : Math.max(latest ?? step.lastActivityAt, step.lastActivityAt), undefined);
4814
+ const activeToolStep = runningSteps
4815
+ .filter((step) => step.currentTool)
4816
+ .sort((left, right) => (left.lastActivityAt ?? 0) - (right.lastActivityAt ?? 0))
4817
+ .at(-1);
4818
+ status.activityState = runningSteps.some((step) => step.activityState === "needs_attention")
4819
+ ? "needs_attention"
4820
+ : runningSteps.some((step) => step.activityState === "active_long_running") ? "active_long_running" : undefined;
4821
+ status.lastActivityAt = lastActivityAt;
4822
+ status.currentTool = activeToolStep?.currentTool;
4823
+ status.currentToolStartedAt = activeToolStep?.currentToolStartedAt;
4824
+ status.currentPath = activeToolStep?.currentPath;
4825
+ const turnCounts = steps.flatMap((step) => step.turnCount === undefined ? [] : [step.turnCount]);
4826
+ const toolCounts = steps.flatMap((step) => step.toolCount === undefined ? [] : [step.toolCount]);
4827
+ status.turnCount = turnCounts.length > 0 ? turnCounts.reduce((total, count) => total + count, 0) : undefined;
4828
+ status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
4829
+ status.currentStep = runningSteps.length === 1 ? steps.indexOf(runningSteps[0]!) : undefined;
4830
+ };
4831
+ const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, cwd: workflowCwd, status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow };
4098
4832
  deps.state.asyncJobs.set(workflowRunId, workflowJob);
4099
4833
  deps.state.fleetJobs ??= new Map();
4100
4834
  deps.state.fleetJobs.set(workflowRunId, workflowJob);
@@ -4103,23 +4837,54 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4103
4837
  const { workflowScript, async: _workflowAsync, chatProgress: _chatProgress, ...workflowRequest } = requestParams;
4104
4838
  void Promise.resolve().then(async () => {
4105
4839
  const workflowResults: SingleResult[] = [];
4106
- const { action: _action, agent: _agent, task: _task, tasks: _tasks, chain: _chain, concurrency: _concurrency, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, ...workflowChildDefaults } = workflowRequest;
4840
+ const { action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = workflowRequest;
4841
+ const workflowSteps = new Map<string, NonNullable<AsyncStatus["steps"]>[number]>();
4842
+ let projectedTraceLength = 0;
4843
+ let projectedTraceTail: NonNullable<Details["workflow"]>["trace"][number] | undefined;
4107
4844
  const updateTrace = (trace: NonNullable<Details["workflow"]>["trace"]) => {
4108
4845
  status.workflow = { ...(status.workflow ?? { emits: [], console: [] }), trace };
4109
- for (const entry of trace.filter((candidate) => candidate.operation === "run")) {
4110
- const existing = status.steps?.find((step) => step.workflowKey === entry.key);
4846
+ const rebuild = trace.length < projectedTraceLength
4847
+ || (projectedTraceLength > 0 && trace[projectedTraceLength - 1] !== projectedTraceTail);
4848
+ if (rebuild) {
4849
+ workflowSteps.clear();
4850
+ for (const step of status.steps ?? []) {
4851
+ if (step.workflowKey) workflowSteps.set(step.workflowKey, step);
4852
+ }
4853
+ projectedTraceLength = 0;
4854
+ }
4855
+ for (let index = projectedTraceLength; index < trace.length; index += 1) {
4856
+ const entry = trace[index]!;
4857
+ if (entry.operation !== "run") continue;
4858
+ const existing = workflowSteps.get(entry.key);
4111
4859
  if (entry.state === "reused" && existing) continue;
4112
4860
  const mapped = entry.state === "started" || entry.state === "reused" ? "running" : entry.state === "completed" ? "completed" : "failed";
4113
4861
  if (existing) {
4114
4862
  existing.status = mapped;
4863
+ if (entry.agent) existing.agent = entry.agent;
4864
+ if (entry.runId) existing.runId = entry.runId;
4865
+ if (entry.state === "failed" && !entry.runId && existing.async === undefined) existing.async = false;
4115
4866
  if (entry.error === undefined) delete existing.error;
4116
4867
  else existing.error = entry.error;
4117
4868
  if (entry.durationMs === undefined) delete existing.durationMs;
4118
4869
  else existing.durationMs = entry.durationMs;
4119
4870
  } else {
4120
- status.steps?.push({ agent: entry.key, label: entry.key, workflowKey: entry.key, parentWorkflowRunId: workflowRunId, status: mapped });
4871
+ const step: NonNullable<AsyncStatus["steps"]>[number] = {
4872
+ agent: entry.agent ?? entry.key,
4873
+ label: entry.key,
4874
+ workflowKey: entry.key,
4875
+ parentWorkflowRunId: workflowRunId,
4876
+ status: mapped,
4877
+ startedAt: Date.now(),
4878
+ ...(entry.runId ? { runId: entry.runId } : {}),
4879
+ ...(entry.state === "failed" && !entry.runId ? { async: false } : {}),
4880
+ };
4881
+ status.steps?.push(step);
4882
+ workflowSteps.set(entry.key, step);
4121
4883
  }
4122
4884
  }
4885
+ projectedTraceLength = trace.length;
4886
+ projectedTraceTail = trace.at(-1);
4887
+ projectWorkflowActivity();
4123
4888
  persist();
4124
4889
  appendWorkflowEvent({ type: "subagent.workflow.trace", trace });
4125
4890
  };
@@ -4128,21 +4893,93 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4128
4893
  script: workflowScript,
4129
4894
  timeoutMs: timeout,
4130
4895
  signal: controller.signal,
4896
+ prompts: workflowPrompts,
4897
+ ...(workflowState ? { state: workflowState } : {}),
4131
4898
  onTrace: updateTrace,
4899
+ admit: (calls) => {
4900
+ status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
4901
+ persist();
4902
+ },
4132
4903
  onEmit: (emits) => {
4133
4904
  // Each emit is validated at the host boundary in runWorkflowScript before onEmit fires.
4134
4905
  status.workflow = { ...(status.workflow ?? { trace: [], console: [] }), emits };
4135
4906
  persist();
4136
4907
  appendWorkflowEvent({ type: "subagent.workflow.emit", value: emits.at(-1) });
4137
4908
  },
4138
- launch: async (key, childParams, workflowSignal) => {
4909
+ launch: async (key, childParams, workflowSignal, admission) => {
4139
4910
  if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
4140
4911
  const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
4141
4912
  if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
4142
- const childRequest = prepareWorkflowChildParams({ ...workflowChildDefaults, ...childParams, workflowParentRunId: workflowRunId, workflowKey: key } as SubagentParamsLike);
4143
- const result = await execute(randomUUID(), childRequest, workflowSignal, undefined, ctx, preserveActiveSession);
4913
+ const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
4914
+ const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
4915
+ recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
4916
+ status: "running",
4917
+ ...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
4918
+ ...(childLabel ? { label: childLabel } : {}),
4919
+ ...(childPhase ? { phase: childPhase } : {}),
4920
+ heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
4921
+ });
4922
+ const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
4923
+ const childRequest = bindMissionWorkflowChildAsyncLaunch(
4924
+ { ...prepareWorkflowLaunchParams(workflowChildDefaults, childParams, workflowRunId, key, { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget }), runFanoutAdmitted: admission.admitted },
4925
+ missionBinding,
4926
+ deps.asyncByDefault,
4927
+ );
4928
+ workflowLaunchObservers.set(childRequest, (launch) => {
4929
+ const step = status.steps?.find((candidate) => candidate.workflowKey === key);
4930
+ if (step) {
4931
+ step.agent = launch.agent;
4932
+ step.sessionFile = launch.sessionFile;
4933
+ step.async = launch.async;
4934
+ if (launch.runId) step.runId = launch.runId;
4935
+ persist();
4936
+ }
4937
+ recordMissionWorkflowChild(missionBinding, workflowRunId, key, { status: "running", agent: launch.agent, ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}) });
4938
+ });
4939
+ return execute(randomUUID(), childRequest, workflowSignal, (update) => {
4940
+ const progress = update.details.progress?.[0];
4941
+ const step = status.steps?.find((candidate) => candidate.workflowKey === key);
4942
+ if (!progress || !step) return;
4943
+ step.status = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
4944
+ step.activityState = progress.activityState;
4945
+ step.lastActivityAt = progress.lastActivityAt;
4946
+ step.currentTool = progress.currentTool;
4947
+ step.currentToolArgs = progress.currentToolArgs;
4948
+ step.currentToolStartedAt = progress.currentToolStartedAt;
4949
+ step.currentPath = progress.currentPath;
4950
+ step.recentTools = progress.recentTools.map((tool) => ({ ...tool }));
4951
+ step.recentOutput = [...progress.recentOutput];
4952
+ step.turnCount = progress.turnCount;
4953
+ step.toolCount = progress.toolCount;
4954
+ step.model = progress.model;
4955
+ step.thinking = progress.thinking;
4956
+ step.error = progress.error;
4957
+ projectWorkflowActivity();
4958
+ persist();
4959
+ recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
4960
+ status: step.status,
4961
+ heartbeat: { status: step.status, ...(childPhase ? { phase: childPhase } : {}) },
4962
+ });
4963
+ }, ctx, preserveActiveSession);
4964
+ });
4144
4965
  workflowResults.push(...result.details.results);
4145
4966
  const child = workflowChildResult(key, result);
4967
+ const step = status.steps?.find((candidate) => candidate.workflowKey === key);
4968
+ if (step) {
4969
+ step.async = Boolean(result.details.asyncId || result.details.asyncDir);
4970
+ if (child.runId) step.runId = child.runId;
4971
+ }
4972
+ if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
4973
+ const childStatus = missionWorkflowChildStatus(result);
4974
+ recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
4975
+ status: childStatus,
4976
+ ...(child.runId ? { runId: child.runId } : {}),
4977
+ ...(result.details.results[0]?.agent ? { agent: result.details.results[0].agent } : {}),
4978
+ ...(result.details.results[0]?.sessionFile ? { sessionPath: result.details.results[0].sessionFile } : {}),
4979
+ artifactPaths: child.artifactPaths,
4980
+ ...(["completed", "failed"].includes(childStatus) ? { completedAt: new Date().toISOString() } : {}),
4981
+ heartbeat: { status: childStatus, ...(childPhase ? { phase: childPhase } : {}) },
4982
+ });
4146
4983
  if (result.details.asyncId) {
4147
4984
  const childJob = deps.state.asyncJobs.get(result.details.asyncId);
4148
4985
  if (childJob) { childJob.parentWorkflowRunId = workflowRunId; childJob.workflowKey = key; }
@@ -4156,26 +4993,28 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4156
4993
  const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
4157
4994
  const workflowUsage = sumResultsUsage(workflowResults);
4158
4995
  status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
4996
+ writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary, output: summary, results: workflow.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
4159
4997
  persist();
4160
4998
  appendWorkflowEvent({ type: "subagent.workflow.completed", state: "complete" });
4161
- writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary, output: summary, results: workflow.children.map((child) => ({ agent: child.key, output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: ctx.cwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
4162
4999
  } catch (error) {
4163
5000
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
4164
5001
  const stopped = controller.signal.aborted;
4165
5002
  status = compactOptional<AsyncStatus>({ ...status, state: stopped ? "stopped" : "failed", stopped: stopped || undefined, error: error instanceof Error ? error.message : String(error), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console } });
5003
+ writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: status.error, error: status.error, stopped: status.stopped, results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
4166
5004
  persist();
4167
5005
  appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
4168
- writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: status.error, error: status.error, stopped: status.stopped, results: partial.children.map((child) => ({ agent: child.key, output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: ctx.cwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
4169
5006
  } finally {
4170
5007
  deps.state.workflowControllers?.delete(workflowRunId);
5008
+ deps.state.activeAsyncCapacity = workflowCapacity?.reconcile(new Set(deps.state.workflowControllers?.keys() ?? []))
5009
+ ?? deps.state.activeAsyncCapacity;
4171
5010
  }
4172
5011
  });
4173
- return {
5012
+ return attachWorkflowMission(withRunFanoutBudget({
4174
5013
  content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
4175
- details: { mode: "workflow", runId: workflowRunId, asyncId: workflowRunId, asyncDir, results: [], chatProgress },
4176
- };
5014
+ details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
5015
+ }, workflowFanoutBudget));
4177
5016
  }
4178
- const { workflowScript: _workflowScript, action: _action, agent: _agent, task: _task, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, ...workflowChildDefaults } = requestParams;
5017
+ const { workflowScript: _workflowScript, action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = requestParams;
4179
5018
  const workflowResults: SingleResult[] = [];
4180
5019
  let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
4181
5020
  const sendWorkflowProgress = () => {
@@ -4187,22 +5026,67 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4187
5026
  script: requestParams.workflowScript,
4188
5027
  timeoutMs: timeout,
4189
5028
  signal,
5029
+ prompts: workflowPrompts,
5030
+ ...(workflowState ? { state: workflowState } : {}),
4190
5031
  onTrace: (trace) => {
4191
5032
  liveWorkflow = { ...liveWorkflow, trace };
4192
5033
  sendWorkflowProgress();
4193
5034
  },
5035
+ admit: (calls) => {
5036
+ claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
5037
+ },
4194
5038
  onEmit: (emits) => {
4195
5039
  liveWorkflow = { ...liveWorkflow, emits };
4196
5040
  sendWorkflowProgress();
4197
5041
  },
4198
- launch: async (key, childParams, workflowSignal) => {
5042
+ launch: async (key, childParams, workflowSignal, admission) => {
4199
5043
  if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
4200
5044
  const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
4201
5045
  if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
4202
- const childRequest = prepareWorkflowChildParams({ ...workflowChildDefaults, ...childParams, workflowParentRunId: _id, workflowKey: key, suppressRoutineResultIntercom: chatProgress.mode === "live-card" } as SubagentParamsLike);
4203
- const result = await execute(randomUUID(), childRequest, workflowSignal, undefined, ctx, preserveActiveSession);
5046
+ const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
5047
+ const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
5048
+ recordMissionWorkflowChild(missionBinding, _id, key, {
5049
+ status: "running",
5050
+ ...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
5051
+ ...(childLabel ? { label: childLabel } : {}),
5052
+ ...(childPhase ? { phase: childPhase } : {}),
5053
+ heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
5054
+ });
5055
+ const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
5056
+ const childRequest = bindMissionWorkflowChildAsyncLaunch(
5057
+ { ...prepareWorkflowLaunchParams(workflowChildDefaults, childParams, _id, key, { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget }), runFanoutAdmitted: admission.admitted },
5058
+ missionBinding,
5059
+ deps.asyncByDefault,
5060
+ );
5061
+ workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding, _id, key, {
5062
+ status: "running",
5063
+ agent: launch.agent,
5064
+ ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}),
5065
+ }));
5066
+ return execute(randomUUID(), childRequest, workflowSignal, (update) => {
5067
+ const progress = update.details.progress?.[0];
5068
+ if (!progress) return;
5069
+ const progressStatus = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
5070
+ recordMissionWorkflowChild(missionBinding, _id, key, {
5071
+ status: progressStatus,
5072
+ heartbeat: { status: progressStatus, ...(childPhase ? { phase: childPhase } : {}) },
5073
+ });
5074
+ }, ctx, preserveActiveSession);
5075
+ });
4204
5076
  workflowResults.push(...result.details.results);
4205
- return workflowChildResult(key, result);
5077
+ if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
5078
+ const child = workflowChildResult(key, result);
5079
+ const childStatus = missionWorkflowChildStatus(result);
5080
+ recordMissionWorkflowChild(missionBinding, _id, key, {
5081
+ status: childStatus,
5082
+ ...(child.runId ? { runId: child.runId } : {}),
5083
+ ...(result.details.results[0]?.agent ? { agent: result.details.results[0].agent } : {}),
5084
+ ...(result.details.results[0]?.sessionFile ? { sessionPath: result.details.results[0].sessionFile } : {}),
5085
+ artifactPaths: child.artifactPaths,
5086
+ ...(["completed", "failed"].includes(childStatus) ? { completedAt: new Date().toISOString() } : {}),
5087
+ heartbeat: { status: childStatus, ...(childPhase ? { phase: childPhase } : {}) },
5088
+ });
5089
+ return child;
4206
5090
  },
4207
5091
  status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
4208
5092
  });
@@ -4211,10 +5095,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4211
5095
  if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
4212
5096
  if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
4213
5097
  if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
4214
- return {
5098
+ return attachWorkflowMission(withRunFanoutBudget({
4215
5099
  content: [{ type: "text", text: sections.join("\n\n") }],
4216
5100
  details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, chatProgress }),
4217
- };
5101
+ }, workflowFanoutBudget));
4218
5102
  } catch (error) {
4219
5103
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
4220
5104
  const text = error instanceof Error ? error.message : String(error);
@@ -4223,11 +5107,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4223
5107
  if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
4224
5108
  if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
4225
5109
  if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
4226
- return {
5110
+ return attachWorkflowMission(withRunFanoutBudget({
4227
5111
  content: [{ type: "text", text: sections.join("\n\n") }],
4228
5112
  isError: true,
4229
5113
  details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console }, chatProgress }),
4230
- };
5114
+ }, workflowFanoutBudget));
4231
5115
  }
4232
5116
  }
4233
5117
  const directParams = prepareWorkflowChildParams(requestParams);
@@ -4244,7 +5128,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4244
5128
  ? normalizeParentModel(ctx.model)
4245
5129
  : rememberParentModel(deps.state, requestSessionId, ctx.model);
4246
5130
  } catch (error) {
4247
- if (action?.toLowerCase() !== "doctor") throw error;
5131
+ if (action?.toLowerCase() !== "doctor" && action?.toLowerCase() !== "guide") throw error;
4248
5132
  requestParentModel = normalizeParentModel(ctx.model);
4249
5133
  }
4250
5134
  if (action) {
@@ -4331,6 +5215,29 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4331
5215
  }
4332
5216
  return handleWatchdogToolAction(action, paramsWithResolvedCwd, ctx, deps.watchdog);
4333
5217
  }
5218
+ if (action === "refine" || action === "refine.show" || action === "refine.rollback") {
5219
+ if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
5220
+ return {
5221
+ content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
5222
+ isError: true,
5223
+ details: { mode: "management", results: [] },
5224
+ };
5225
+ }
5226
+ return handleRefinementAction(action, paramsWithResolvedCwd, {
5227
+ cwd: requestCwd,
5228
+ state: deps.state,
5229
+ signal,
5230
+ launchProposalChild: (task, outputSchema, proposalSignal) => execute(randomUUID(), {
5231
+ agent: "reviewer",
5232
+ task,
5233
+ context: "fresh",
5234
+ async: false,
5235
+ artifacts: false,
5236
+ outputSchema,
5237
+ toolBudget: { hard: 1, block: ["write", "edit", "bash"] },
5238
+ }, proposalSignal, undefined, ctx, true),
5239
+ });
5240
+ }
4334
5241
  if (action === "grant-spawn-budget") {
4335
5242
  if (deps.allowMutatingManagementActions === false || !ctx.hasUI) {
4336
5243
  return {
@@ -4403,6 +5310,28 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4403
5310
  details: { mode: "management", results: [], spawnBudget: granted.snapshot },
4404
5311
  };
4405
5312
  }
5313
+ if (action === "guide") {
5314
+ try {
5315
+ return {
5316
+ content: [{ type: "text", text: readSubagentGuide(paramsWithResolvedCwd.topic) }],
5317
+ details: { mode: "management", results: [] },
5318
+ };
5319
+ } catch (error) {
5320
+ return {
5321
+ content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
5322
+ isError: true,
5323
+ details: { mode: "management", results: [] },
5324
+ };
5325
+ }
5326
+ }
5327
+ if (action === "children.list") {
5328
+ deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
5329
+ const children = listRetainedChildren(DIRS.async, deps.state.currentSessionId);
5330
+ return {
5331
+ content: [{ type: "text", text: formatRetainedChildren(children) }],
5332
+ details: { mode: "management", results: [] },
5333
+ };
5334
+ }
4406
5335
  if (action === "doctor") {
4407
5336
  let currentSessionFile: string | null = null;
4408
5337
  let currentSessionId = deps.state.currentSessionId;
@@ -4420,6 +5349,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4420
5349
  if (!sessionError) sessionError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
4421
5350
  }
4422
5351
  const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, currentSessionId);
5352
+ const activeAsyncCapacity = currentSessionId
5353
+ ? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) })
5354
+ : { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession) ?? 0 };
5355
+ deps.state.activeAsyncCapacity = activeAsyncCapacity;
4423
5356
  return {
4424
5357
  content: [{
4425
5358
  type: "text",
@@ -4436,18 +5369,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4436
5369
  expandTilde: deps.expandTilde,
4437
5370
  })),
4438
5371
  }],
4439
- details: { mode: "management", results: [], spawnBudget },
5372
+ details: { mode: "management", results: [], spawnBudget, activeAsyncCapacity },
4440
5373
  };
4441
5374
  }
4442
5375
  if (action === "status") {
4443
5376
  if (!preserveActiveSession) deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
4444
- const withBudget = (result: AgentToolResult<Details>) => withSpawnBudgetStatus(
4445
- result,
4446
- deps.state,
4447
- deps.config,
4448
- deps.state.currentSessionId,
4449
- );
4450
5377
  const targetRunId = paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId;
5378
+ const hasDirectoryTarget = Boolean(paramsWithResolvedCwd.dir);
5379
+ const targetLabel = formatStatusTargetLabel(paramsWithResolvedCwd, targetRunId);
5380
+ const withBudget = (result: AgentToolResult<Details>) => {
5381
+ const budgeted = withSpawnBudgetStatus(result, deps.state, deps.config, deps.state.currentSessionId);
5382
+ return {
5383
+ ...budgeted,
5384
+ content: budgeted.content.map((item, index) => index === 0 && item.type === "text"
5385
+ ? { ...item, text: `${targetLabel}\n${item.text}` }
5386
+ : item),
5387
+ };
5388
+ };
4451
5389
  const nestedScope = nestedResolutionScopeForExecutor(deps);
4452
5390
  const sessionRoots = trustedSessionRootsForStatus(ctx, deps);
4453
5391
  if (paramsWithResolvedCwd.view === "fleet") {
@@ -4472,7 +5410,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4472
5410
  const message = error instanceof Error ? error.message : String(error);
4473
5411
  return withBudget({ content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } });
4474
5412
  }
4475
- } else {
5413
+ } else if (!hasDirectoryTarget) {
4476
5414
  const foreground = getForegroundControl(deps.state, undefined);
4477
5415
  if (foreground && paramsWithResolvedCwd.view !== "transcript") return withBudget(foregroundStatusResult(foreground));
4478
5416
  if (foreground && paramsWithResolvedCwd.view === "transcript") {
@@ -4540,7 +5478,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4540
5478
  }
4541
5479
  }
4542
5480
  if (action === "resume") {
4543
- return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel }));
5481
+ return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel, signal }));
4544
5482
  }
4545
5483
  if (action === "steer") {
4546
5484
  deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
@@ -4551,6 +5489,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4551
5489
  try {
4552
5490
  const location = resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results);
4553
5491
  const runId = location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir);
5492
+ const directoryStatus = location.asyncDir ? readStatus(location.asyncDir) : null;
5493
+ if (directoryStatus?.mode === "workflow") {
5494
+ const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: directoryStatus.runId || runId, asyncDirRoot: DIRS.async });
5495
+ if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
5496
+ return steerWorkflowForegroundTarget({ target: route.target, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal });
5497
+ }
4554
5498
  if (location.asyncDir) {
4555
5499
  const unsupported = externalRunnerControlError(location.asyncDir, "steer");
4556
5500
  if (unsupported) return unsupported;
@@ -4559,6 +5503,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4559
5503
  state: deps.state,
4560
5504
  runId,
4561
5505
  message,
5506
+ mode: paramsWithResolvedCwd.mode,
4562
5507
  index: paramsWithResolvedCwd.index,
4563
5508
  kill: deps.kill,
4564
5509
  location,
@@ -4584,9 +5529,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4584
5529
  const text = error instanceof Error ? error.message : String(error);
4585
5530
  return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
4586
5531
  }
4587
- if (resolved?.kind === "nested") return steerNestedRun(omitUndefinedProperties({ target: resolved, message, index: paramsWithResolvedCwd.index, signal }));
4588
- if (resolved?.kind === "foreground") return { content: [{ type: "text", text: "action='steer' currently supports live async Pi child sessions only; use action='interrupt' or action='resume' for foreground runs." }], isError: true, details: { mode: "management", results: [] } };
5532
+ if (resolved?.kind === "nested") return steerNestedRun(omitUndefinedProperties({ target: resolved, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal }));
5533
+ if (resolved?.kind === "foreground") {
5534
+ const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, childRunId: resolved.id, asyncDirRoot: DIRS.async });
5535
+ if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
5536
+ return steerWorkflowForegroundTarget({ target: route.target, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal });
5537
+ }
4589
5538
  if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
5539
+ const resolvedStatus = resolved.location.asyncDir ? readStatus(resolved.location.asyncDir) : null;
5540
+ if (resolvedStatus?.mode === "workflow") {
5541
+ const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: resolvedStatus.runId || resolved.id, asyncDirRoot: DIRS.async });
5542
+ if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
5543
+ return steerWorkflowForegroundTarget({ target: route.target, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal });
5544
+ }
4590
5545
  if (resolved.location.asyncDir) {
4591
5546
  const unsupported = externalRunnerControlError(resolved.location.asyncDir, "steer");
4592
5547
  if (unsupported) return unsupported;
@@ -4595,6 +5550,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4595
5550
  state: deps.state,
4596
5551
  runId: resolved.id,
4597
5552
  message,
5553
+ mode: paramsWithResolvedCwd.mode,
4598
5554
  index: paramsWithResolvedCwd.index,
4599
5555
  kill: deps.kill,
4600
5556
  location: resolved.location,
@@ -4619,6 +5575,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4619
5575
  return appendStepToAsyncChain(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel }));
4620
5576
  }
4621
5577
  if (action.startsWith("schedule.")) {
5578
+ if (!isScheduledRunAction(action)) {
5579
+ return { content: [{ type: "text", text: unknownSubagentActionMessage(action) }], isError: true, details: { mode: "management", results: [] } };
5580
+ }
4622
5581
  if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
4623
5582
  return {
4624
5583
  content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
@@ -4635,6 +5594,28 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4635
5594
  }
4636
5595
  return deps.handleScheduledRunAction(paramsWithResolvedCwd, ctx);
4637
5596
  }
5597
+ if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
5598
+ return {
5599
+ content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
5600
+ isError: true,
5601
+ details: { mode: "management" as const, results: [] },
5602
+ };
5603
+ }
5604
+ if (action === "dismiss") {
5605
+ const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
5606
+ if (!targetRunId) return { content: [{ type: "text", text: "action='dismiss' requires id." }], isError: true, details: { mode: "management", results: [] } };
5607
+ let resolved: ResolvedSubagentRunId | undefined;
5608
+ try {
5609
+ resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }));
5610
+ } catch (error) {
5611
+ const message = error instanceof Error ? error.message : String(error);
5612
+ return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
5613
+ }
5614
+ if (resolved?.kind !== "async") {
5615
+ return { content: [{ type: "text", text: `Run '${targetRunId}' is not a recovered workflow.` }], isError: true, details: { mode: "management", results: [] } };
5616
+ }
5617
+ return dismissRecoveredWorkflow(deps.state, resolved.location);
5618
+ }
4638
5619
  if (action === "stop") {
4639
5620
  const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
4640
5621
  const workflowController = targetRunId ? deps.state.workflowControllers?.get(targetRunId) : undefined;
@@ -4646,6 +5627,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4646
5627
  if (paramsWithResolvedCwd.dir) {
4647
5628
  try {
4648
5629
  const location = resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results);
5630
+ const existingStatus = readStatus(location.asyncDir ?? "");
5631
+ if (existingStatus?.mode === "workflow" && existingStatus.state === "running") {
5632
+ return { content: [{ type: "text", text: `Workflow ${existingStatus.runId} is not controlled by this extension runtime; reload recovery cannot stop it safely.` }], isError: true, details: { mode: "management", results: [] } };
5633
+ }
4649
5634
  const stopResult = stopAsyncRun(deps.state, location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir), deps.kill, location);
4650
5635
  return stopResult ?? { content: [{ type: "text", text: `No running or queued async run was found for '${targetRunId ?? paramsWithResolvedCwd.dir}'.` }], isError: true, details: { mode: "management", results: [] } };
4651
5636
  } catch (error) {
@@ -4725,14 +5710,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4725
5710
  }
4726
5711
  if (!(SUBAGENT_ACTIONS as readonly string[]).includes(action)) {
4727
5712
  return {
4728
- content: [{ type: "text", text: `Unknown action: ${action}. Valid: ${SUBAGENT_ACTIONS.join(", ")}` }],
4729
- isError: true,
4730
- details: { mode: "management" as const, results: [] },
4731
- };
4732
- }
4733
- if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
4734
- return {
4735
- content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
5713
+ content: [{ type: "text", text: unknownSubagentActionMessage(action) }],
4736
5714
  isError: true,
4737
5715
  details: { mode: "management" as const, results: [] },
4738
5716
  };
@@ -4808,7 +5786,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4808
5786
  const runId = randomUUID().slice(0, 8);
4809
5787
  const inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
4810
5788
  const nestedParentAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
4811
- const nestedRoute = inheritedNestedRoute ?? createNestedRoute(runId);
4812
5789
  const shareEnabled = effectiveParams.share === true;
4813
5790
  const hasChain = (effectiveParams.chain?.length ?? 0) > 0;
4814
5791
  const hasTasks = (effectiveParams.tasks?.length ?? 0) > 0;
@@ -4889,12 +5866,53 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4889
5866
  if (externalAgent && (!effectiveAsync || effectiveParams.foregroundOnly === true)) {
4890
5867
  return buildRequestedModeError(effectiveParams, `Agent '${externalAgent.name}' uses runner.type='external-cli', which currently supports async/background execution only. Omit async or pass async:true; clarify and foregroundOnly are unsupported.`);
4891
5868
  }
4892
- const foregroundTimeout = resolveForegroundTimeout(
5869
+ const foregroundTimeout = resolveSingleAgentLaunchTimeout(
4893
5870
  effectiveParams,
4894
- effectiveAsync ? undefined : DEFAULT_FOREGROUND_TIMEOUT_MS,
5871
+ effectiveAsync,
5872
+ resolveConfigDefaultTimeoutMs(deps.config.timeoutMs),
4895
5873
  );
4896
5874
  if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
4897
5875
  const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
5876
+ const requestedWorkflowChildAsyncId = typeof effectiveParams.workflowChildAsyncId === "string" ? effectiveParams.workflowChildAsyncId.trim() : "";
5877
+ const asyncRunId = requestedWorkflowChildAsyncId && path.basename(requestedWorkflowChildAsyncId) === requestedWorkflowChildAsyncId
5878
+ ? requestedWorkflowChildAsyncId
5879
+ : randomUUID();
5880
+ const topLevelAsyncCapacityEligible = depth === 0 && !inheritedNestedRoute && !effectiveParams.workflowParentRunId;
5881
+ const topLevelAsync = effectiveAsync && topLevelAsyncCapacityEligible;
5882
+ let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
5883
+ if (topLevelAsync) {
5884
+ try {
5885
+ const activeLimit = resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession);
5886
+ activeAsyncCapacity = acquireActiveAsyncCapacity({
5887
+ sessionId: requestSessionId,
5888
+ limit: activeLimit,
5889
+ runId: asyncRunId,
5890
+ kind: "runner",
5891
+ asyncDir: path.join(DIRS.async, asyncRunId),
5892
+ }, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
5893
+ deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(requestSessionId, activeLimit, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
5894
+ } catch (error) {
5895
+ if (error instanceof ActiveAsyncCapacityError) {
5896
+ deps.state.activeAsyncCapacity = error.snapshot;
5897
+ return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: foregroundMode, results: [], activeAsyncCapacity: error.snapshot } };
5898
+ }
5899
+ throw error;
5900
+ }
5901
+ }
5902
+
5903
+ let runFanoutBudget: RunFanoutBudgetDescriptor;
5904
+ try {
5905
+ const inheritedRunFanoutBudget = effectiveParams.runFanoutBudget ? undefined : decodeRunFanoutBudgetDescriptor(process.env[RUN_FANOUT_BUDGET_ENV]);
5906
+ runFanoutBudget = effectiveParams.runFanoutBudget
5907
+ ?? (inheritedRunFanoutBudget ? { ...inheritedRunFanoutBudget, parentPath: `${inheritedRunFanoutBudget.parentPath ? `${inheritedRunFanoutBudget.parentPath}/` : ""}${runId}` } : undefined)
5908
+ ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
5909
+ if (!effectiveParams.runFanoutAdmitted) claimRunFanoutBatch(runFanoutBudget, staticRunFanoutPaths(effectiveParams));
5910
+ } catch (error) {
5911
+ activeAsyncCapacity?.rollback();
5912
+ if (error instanceof RunFanoutLimitError) return runFanoutErrorResult(error, foregroundMode);
5913
+ return buildRequestedModeError(effectiveParams, error instanceof Error ? error.message : String(error));
5914
+ }
5915
+ const nestedRoute = inheritedNestedRoute ?? createNestedRoute(runId);
4898
5916
 
4899
5917
  const artifactConfig: ArtifactConfig = omitUndefinedProperties({
4900
5918
  ...DEFAULT_ARTIFACT_CONFIG,
@@ -4902,6 +5920,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4902
5920
  dir: deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir,
4903
5921
  });
4904
5922
  const artifactsDir = getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
5923
+ if (artifactConfig.dir === "project" && !warnedArtifactPackageDirs.has(effectiveCwd)) {
5924
+ warnedArtifactPackageDirs.add(effectiveCwd);
5925
+ const warning = getProjectArtifactPackagingWarning(effectiveCwd);
5926
+ if (warning) console.warn(`[pi-subagents] ${warning}`);
5927
+ }
4905
5928
 
4906
5929
  let sessionRoot: string;
4907
5930
  if (effectiveParams.sessionDir) {
@@ -4915,6 +5938,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4915
5938
  try {
4916
5939
  fs.mkdirSync(sessionRoot, { recursive: true });
4917
5940
  } catch (error) {
5941
+ activeAsyncCapacity?.rollback();
4918
5942
  const message = error instanceof Error ? error.message : String(error);
4919
5943
  return toExecutionErrorResult(
4920
5944
  effectiveParams,
@@ -4945,10 +5969,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4945
5969
  preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems);
4946
5970
  }
4947
5971
  } catch (error) {
5972
+ activeAsyncCapacity?.rollback();
4948
5973
  return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
4949
5974
  }
4950
5975
  const chainBindingsError = validateExecutionChainBindings(effectiveParams, deps.config.chain?.dynamicFanout?.maxItems);
4951
- if (chainBindingsError) return withResolvedContext(chainBindingsError, contextPolicy.contextSummary);
5976
+ if (chainBindingsError) {
5977
+ activeAsyncCapacity?.rollback();
5978
+ return withResolvedContext(chainBindingsError, contextPolicy.contextSummary);
5979
+ }
4952
5980
 
4953
5981
  const onUpdateWithContext = onUpdate
4954
5982
  ? (r: AgentToolResult<Details>) => onUpdate(withResolvedContext({
@@ -4968,14 +5996,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4968
5996
  ownerSessionId: requestSessionId,
4969
5997
  });
4970
5998
  } catch (error) {
4971
- if (explicitMission) return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
5999
+ if (explicitMission) {
6000
+ activeAsyncCapacity?.rollback();
6001
+ return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
6002
+ }
4972
6003
  missionWarning = `Mission tracking unavailable: ${error instanceof Error ? error.message : String(error)}`;
4973
6004
  }
4974
6005
 
4975
6006
  const attachMission = (result: AgentToolResult<Details>): AgentToolResult<Details> => {
4976
6007
  if (!missionBinding) return missionWarning ? { ...result, details: { ...result.details, missionWarning } } : result;
4977
6008
  try {
4978
- return attachMissionToLaunchResult({ binding: missionBinding, result });
6009
+ return attachMissionToLaunchResult({ binding: delegatedExecution ? { ...missionBinding, announceInContent: false } : missionBinding, result });
4979
6010
  } catch (error) {
4980
6011
  const warning = `Mission tracking unavailable after launch: ${error instanceof Error ? error.message : String(error)}`;
4981
6012
  if (explicitMission) {
@@ -4996,7 +6027,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4996
6027
  requestSessionId,
4997
6028
  requestedSpawns,
4998
6029
  );
4999
- if (reservation.error) return attachMission(spawnBudgetErrorResult(reservation.error, foregroundMode));
6030
+ if (reservation.error) {
6031
+ activeAsyncCapacity?.rollback();
6032
+ return attachMission(spawnBudgetErrorResult(reservation.error, foregroundMode));
6033
+ }
5000
6034
 
5001
6035
  const execData: ExecutionContextData = omitUndefinedProperties({
5002
6036
  params: effectiveParams,
@@ -5016,6 +6050,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5016
6050
  artifactsDir,
5017
6051
  backgroundRequestedWhileClarifying,
5018
6052
  effectiveAsync,
6053
+ asyncRunId,
5019
6054
  controlConfig,
5020
6055
  intercomBridge,
5021
6056
  nestedRoute,
@@ -5031,17 +6066,34 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5031
6066
  parentSessionId: requestSessionId,
5032
6067
  parentPiSessionId: requestPiSessionId,
5033
6068
  capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(requestSessionId),
6069
+ runFanoutBudget,
6070
+ topLevelAsyncCapacityEligible,
6071
+ activeAsyncCapacity,
5034
6072
  });
5035
6073
 
5036
- const foregroundDescription = effectiveParams.task?.trim()
5037
- || effectiveParams.tasks?.[0]?.task?.trim()
5038
- || (effectiveParams.chain ? firstRawChainTask(effectiveParams.chain)?.trim() : undefined);
6074
+ const foregroundDescription = selectedAgentNames.length === 1
6075
+ ? `${selectedAgentNames[0]} child`
6076
+ : `${selectedAgentNames.length} live children`;
6077
+ const parentWorkflowStatus = effectiveParams.workflowParentRunId
6078
+ ? readStatus(path.join(DIRS.async, effectiveParams.workflowParentRunId))
6079
+ : null;
6080
+ const workflowSteeringDir = effectiveParams.workflowParentRunId
6081
+ && requestSessionId
6082
+ && deps.state.workflowControllers?.has(effectiveParams.workflowParentRunId)
6083
+ && parentWorkflowStatus?.mode === "workflow"
6084
+ && (parentWorkflowStatus.state === "running" || parentWorkflowStatus.state === "queued")
6085
+ && parentWorkflowStatus.sessionId === requestSessionId
6086
+ ? workflowForegroundSteeringDir(DIRS.async, effectiveParams.workflowParentRunId, runId)
6087
+ : undefined;
5039
6088
  const foregroundControl: ForegroundRunControl | undefined = effectiveAsync
5040
6089
  ? undefined
5041
6090
  : compactOptional<ForegroundRunControl>({
5042
6091
  runId,
5043
6092
  sessionId: requestSessionId,
5044
6093
  mode: foregroundMode,
6094
+ ...(effectiveParams.workflowParentRunId ? { parentWorkflowRunId: effectiveParams.workflowParentRunId } : {}),
6095
+ ...(effectiveParams.workflowKey ? { workflowKey: effectiveParams.workflowKey } : {}),
6096
+ workflowSteeringDir,
5045
6097
  startedAt: Date.now(),
5046
6098
  updatedAt: Date.now(),
5047
6099
  cwd: effectiveCwd,
@@ -5056,6 +6108,37 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5056
6108
  interrupt: undefined,
5057
6109
  });
5058
6110
  if (foregroundControl) {
6111
+ foregroundControl.promptAuditRedo = async (index, guidance) => {
6112
+ const audit = getLivePromptAudit(foregroundControl, index);
6113
+ if (!audit?.rerun) return { text: "Redo is not safe for this prompt in this slice.", isError: true };
6114
+ const rewrittenTask = await rewritePromptWithGuidance({
6115
+ ctx,
6116
+ authoredTask: audit.authoredTask,
6117
+ runtimeAdditions: audit.runtimeAdditions,
6118
+ finalEffectivePrompt: audit.finalEffectivePrompt,
6119
+ guidance,
6120
+ signal,
6121
+ });
6122
+ const redoParams = promptAuditRedoParams(audit.rerun.params, rewrittenTask);
6123
+ const previousForegroundId = deps.state.lastForegroundControlId;
6124
+ const launch = execute(randomUUID(), redoParams, signal, undefined, ctx, true);
6125
+ const newRunId = deps.state.lastForegroundControlId && deps.state.lastForegroundControlId !== previousForegroundId
6126
+ ? deps.state.lastForegroundControlId
6127
+ : undefined;
6128
+ if (!newRunId) {
6129
+ const result = await launch;
6130
+ return { text: result.content.find((item) => item.type === "text")?.text ?? "Prompt redo could not start.", isError: true };
6131
+ }
6132
+ foregroundControl.supersededByRunId = newRunId;
6133
+ const replacement = deps.state.foregroundControls.get(newRunId);
6134
+ if (replacement) replacement.sourceRunId = foregroundControl.runId;
6135
+ void launch.then((result) => {
6136
+ if (result.isError) console.warn(`[pi-subagents] Prompt redo ${newRunId} failed: ${result.content.find((item) => item.type === "text")?.text ?? "unknown error"}`);
6137
+ }).catch((error) => {
6138
+ console.warn(`[pi-subagents] Prompt redo ${newRunId} failed: ${error instanceof Error ? error.message : String(error)}`);
6139
+ });
6140
+ return { text: `Prompt redo started ${newRunId}.` };
6141
+ };
5059
6142
  deps.state.foregroundControls.set(runId, foregroundControl);
5060
6143
  deps.state.lastForegroundControlId = runId;
5061
6144
  }
@@ -5147,32 +6230,46 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5147
6230
 
5148
6231
  let nestedForegroundStarted = false;
5149
6232
  try {
6233
+ if (workflowLaunchObserver) {
6234
+ const singleTask = hasTasks && effectiveParams.tasks?.length === 1 ? effectiveParams.tasks[0] : undefined;
6235
+ const launch = hasSingle
6236
+ ? { agent: effectiveParams.agent!, sessionFile: childSessionFileForTask(effectiveParams.agent!, 0, effectiveParams.model), async: effectiveAsync, ...(effectiveAsync ? { runId: asyncRunId } : {}) }
6237
+ : singleTask
6238
+ ? { agent: singleTask.agent, sessionFile: childSessionFileForTask(singleTask.agent, 0, singleTask.model), async: effectiveAsync, ...(effectiveAsync ? { runId: asyncRunId } : {}) }
6239
+ : undefined;
6240
+ if (launch) {
6241
+ workflowLaunchObservers.delete(params);
6242
+ workflowLaunchObserver(launch);
6243
+ }
6244
+ }
5150
6245
  const asyncResult = runAsyncPath(execData, deps);
5151
- if (asyncResult) return attachMission(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary));
6246
+ if (asyncResult) return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget));
5152
6247
  if (foregroundControl) {
5153
6248
  writeNestedForegroundEvent("subagent.nested.started");
5154
6249
  nestedForegroundStarted = true;
5155
6250
  }
6251
+ const runFanoutAnnotateContent = !delegatedExecution;
5156
6252
  if (hasChain && effectiveParams.chain) {
5157
6253
  const result = await runChainPath(execData, deps);
5158
6254
  writeNestedForegroundEvent("subagent.nested.completed", result);
5159
- return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
6255
+ return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
5160
6256
  }
5161
6257
  if (hasTasks && effectiveParams.tasks) {
5162
6258
  const result = await runParallelPath(execData, deps);
5163
6259
  writeNestedForegroundEvent("subagent.nested.completed", result);
5164
- return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
6260
+ return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
5165
6261
  }
5166
6262
  if (hasSingle) {
5167
6263
  const result = await runSinglePath(execData, deps);
5168
6264
  writeNestedForegroundEvent("subagent.nested.completed", result);
5169
- return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
6265
+ return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
5170
6266
  }
5171
6267
  } catch (error) {
5172
6268
  const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary), forkThinkingDowngrades);
5173
6269
  if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
5174
6270
  return attachMission(errorResult);
5175
6271
  } finally {
6272
+ if (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
5176
6273
  if (foregroundControl) {
5177
6274
  settleForegroundSchedulingOwner(foregroundControl);
5178
6275
  removeForegroundControlIfIdle(deps.state, runId);
@@ -5193,8 +6290,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5193
6290
  onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
5194
6291
  ctx: ExtensionContext,
5195
6292
  ): Promise<AgentToolResult<Details>> => {
5196
- const requestParams = omitExecutionModeActionAlias(params);
5197
- if (requestParams.action) return execute(id, requestParams, signal, onUpdate, ctx);
6293
+ const normalizedAction = typeof params.action === "string" ? params.action.trim() : params.action;
6294
+ const requestParams = normalizedAction ? { ...params, action: normalizedAction } : params;
6295
+ if (normalizedAction) return execute(id, requestParams, signal, onUpdate, ctx);
5198
6296
  const { depth } = checkSubagentDepth(deps.config.maxSubagentDepth);
5199
6297
  const dispatchParams = applyForceTopLevelAsyncOverride(requestParams, depth, deps.config.forceTopLevelAsync === true);
5200
6298
  const runsForeground = dispatchParams.clarify === true || (dispatchParams.async ?? deps.asyncByDefault) !== true;
@@ -5208,6 +6306,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5208
6306
  }
5209
6307
  };
5210
6308
 
6309
+ const executePublic = (
6310
+ id: string,
6311
+ params: SubagentParamsLike,
6312
+ signal: AbortSignal,
6313
+ onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
6314
+ ctx: ExtensionContext,
6315
+ ): Promise<AgentToolResult<Details>> => {
6316
+ const normalized = normalizePublicSubagentExecution(params);
6317
+ if (!normalized.ok) {
6318
+ return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
6319
+ }
6320
+ return executeWithSingleDispatchGuard(id, normalized.params, signal, onUpdate, ctx);
6321
+ };
6322
+
5211
6323
  const executeDelegated = async (
5212
6324
  id: string,
5213
6325
  params: SubagentParamsLike,
@@ -5226,6 +6338,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5226
6338
  delete privateParams.delegatedAllowZeroToolBudget;
5227
6339
  if (thinkingOverride !== undefined) delegatedThinkingOverrides.set(delegatedParams, thinkingOverride);
5228
6340
  if (allowZeroToolBudget) delegatedZeroToolBudgets.add(delegatedParams);
6341
+ delegatedExecutions.add(delegatedParams);
5229
6342
  return execute(id, delegatedParams, signal, onUpdate, ctx);
5230
6343
  };
5231
6344
 
@@ -5247,5 +6360,5 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5247
6360
  return ownerExecutor.execute(id, params, signal, undefined, ctx);
5248
6361
  };
5249
6362
 
5250
- return { execute: executeWithSingleDispatchGuard, executeDelegated, executeScheduled };
6363
+ return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated, executeScheduled };
5251
6364
  }