@selesai/code 0.9.11 → 0.9.12

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 (580) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/config.d.ts +4 -6
  3. package/dist/config.js +10 -14
  4. package/dist/core/agent-session.d.ts +0 -1
  5. package/dist/core/agent-session.js +3 -49
  6. package/dist/core/extensions/types.d.ts +0 -2
  7. package/dist/core/settings-manager.d.ts +0 -2
  8. package/dist/core/settings-manager.js +0 -3
  9. package/dist/core/tools/index.d.ts +0 -1
  10. package/dist/core/tools/index.js +0 -1
  11. package/dist/defaults/settings.json +0 -1
  12. package/dist/extensions/model-prompt-injector/index.test.ts +0 -10
  13. package/dist/extensions/model-prompt-injector/index.ts +0 -3
  14. package/dist/extensions/pi-intercom/CHANGELOG.md +53 -16
  15. package/dist/extensions/pi-intercom/README.md +41 -8
  16. package/dist/extensions/pi-intercom/banner.png +0 -0
  17. package/dist/extensions/pi-intercom/broker/broker.ts +498 -215
  18. package/dist/extensions/pi-intercom/broker/client.ts +39 -12
  19. package/dist/extensions/pi-intercom/broker/extension.test.ts +102 -3
  20. package/dist/extensions/pi-intercom/broker/paths.test.ts +2 -2
  21. package/dist/extensions/pi-intercom/broker/protocol.ts +26 -0
  22. package/dist/extensions/pi-intercom/broker/spawn.test.ts +5 -20
  23. package/dist/extensions/pi-intercom/config.test.ts +41 -0
  24. package/dist/extensions/pi-intercom/config.ts +21 -0
  25. package/dist/extensions/pi-intercom/extension-api.ts +40 -0
  26. package/dist/extensions/pi-intercom/index.ts +362 -26
  27. package/dist/extensions/pi-intercom/intercom.integration.test.ts +819 -13
  28. package/dist/extensions/pi-intercom/package.json +1 -1
  29. package/dist/extensions/pi-intercom/project-agent.ts +2 -2
  30. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +8 -110
  31. package/dist/extensions/pi-intercom/types.ts +32 -5
  32. package/dist/extensions/pi-intercom/ui/inline-message.ts +6 -0
  33. package/dist/extensions/pi-subagents/.github/assets/subagent-fleet-inspector.png +0 -0
  34. package/dist/extensions/pi-subagents/.github/workflows/release.yml +28 -0
  35. package/dist/extensions/pi-subagents/.github/workflows/test.yml +51 -0
  36. package/dist/extensions/pi-subagents/.greptile/config.json +33 -0
  37. package/dist/extensions/pi-subagents/.greptile/files.json +8 -0
  38. package/dist/extensions/pi-subagents/.greptile/rules.md +16 -0
  39. package/dist/extensions/pi-subagents/CHANGELOG.md +309 -87
  40. package/dist/extensions/pi-subagents/README.md +15 -17
  41. package/dist/extensions/pi-subagents/agents/oracle.md +3 -1
  42. package/dist/extensions/pi-subagents/agents/researcher.md +32 -13
  43. package/dist/extensions/pi-subagents/agents/reviewer.md +13 -2
  44. package/dist/extensions/pi-subagents/agents/scout.md +2 -2
  45. package/dist/extensions/pi-subagents/agents/worker.md +2 -1
  46. package/dist/extensions/pi-subagents/async-retention-discovery-worker.mjs +180 -0
  47. package/dist/extensions/pi-subagents/docs/configuration.md +106 -27
  48. package/dist/extensions/pi-subagents/docs/extension-api.md +85 -21
  49. package/dist/extensions/pi-subagents/docs/missions.md +41 -4
  50. package/dist/extensions/pi-subagents/docs/models.md +52 -9
  51. package/dist/extensions/pi-subagents/docs/observability.md +103 -16
  52. package/dist/extensions/pi-subagents/docs/tool-reference.md +60 -45
  53. package/dist/extensions/pi-subagents/docs/watchdog.md +60 -3
  54. package/dist/extensions/pi-subagents/docs/workflows.md +181 -7
  55. package/dist/extensions/pi-subagents/index.ts +10 -1
  56. package/dist/extensions/pi-subagents/install.mjs +4 -4
  57. package/dist/extensions/pi-subagents/package-lock.json +1483 -1470
  58. package/dist/extensions/pi-subagents/package.json +6 -3
  59. package/dist/extensions/pi-subagents/prompts/council.md +60 -0
  60. package/dist/extensions/pi-subagents/prompts/parallel-review.md +5 -1
  61. package/dist/extensions/pi-subagents/prompts/review-loop.md +9 -5
  62. package/dist/extensions/pi-subagents/skills/council-mode/SKILL.md +254 -0
  63. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +18 -17
  64. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +31 -9
  65. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +107 -140
  66. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +9 -12
  67. package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +39 -0
  68. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +44 -92
  69. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +261 -368
  70. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +6 -2
  71. package/dist/extensions/pi-subagents/src/agents/agents.ts +388 -85
  72. package/dist/extensions/pi-subagents/src/agents/builtin-names.ts +14 -0
  73. package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +418 -0
  74. package/dist/extensions/pi-subagents/src/agents/skills.ts +1 -1
  75. package/dist/extensions/pi-subagents/src/api/agents.ts +7 -0
  76. package/dist/extensions/pi-subagents/src/api/external-job-provider.ts +195 -0
  77. package/dist/extensions/pi-subagents/src/api/preflight.ts +88 -27
  78. package/dist/extensions/pi-subagents/src/api/project-panes.ts +2 -0
  79. package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -0
  80. package/dist/extensions/pi-subagents/src/extension/config.ts +20 -11
  81. package/dist/extensions/pi-subagents/src/extension/doctor.ts +14 -6
  82. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +5 -4
  83. package/dist/extensions/pi-subagents/src/extension/index.ts +270 -187
  84. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +65 -28
  85. package/dist/extensions/pi-subagents/src/extension/rpc.ts +120 -11
  86. package/dist/extensions/pi-subagents/src/extension/schemas.ts +46 -64
  87. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +51 -37
  88. package/dist/extensions/pi-subagents/src/extension/tool-result.ts +19 -0
  89. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +2 -2
  90. package/dist/extensions/pi-subagents/src/inspectors/herdr/client.ts +3 -3
  91. package/dist/extensions/pi-subagents/src/inspectors/herdr/focus.ts +55 -0
  92. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +2 -1
  93. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +230 -46
  94. package/dist/extensions/pi-subagents/src/integrations/herdr-status.ts +26 -4
  95. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +32 -10
  96. package/dist/extensions/pi-subagents/src/missions/lifecycle.ts +6 -1
  97. package/dist/extensions/pi-subagents/src/missions/store.ts +4 -9
  98. package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +2 -2
  99. package/dist/extensions/pi-subagents/src/policy/authority.ts +2 -0
  100. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +3 -1
  101. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +31 -8
  102. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +210 -52
  103. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +63 -33
  104. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +71 -17
  105. package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +886 -0
  106. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +14 -5
  107. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +49 -56
  108. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +1 -0
  109. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +3 -33
  110. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +5 -0
  111. package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +5 -1
  112. package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +22 -12
  113. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +105 -71
  114. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +68 -19
  115. package/dist/extensions/pi-subagents/src/runs/background/index-segment.ts +59 -0
  116. package/dist/extensions/pi-subagents/src/runs/background/inspect-rpc.ts +443 -0
  117. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +94 -10
  118. package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +158 -90
  119. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +141 -53
  120. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +8 -5
  121. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +14 -4
  122. package/dist/extensions/pi-subagents/src/runs/background/run-id-query.ts +7 -0
  123. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +13 -14
  124. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +46 -25
  125. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +823 -0
  126. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +9 -3
  127. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +11 -1
  128. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +594 -216
  129. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +17 -13
  130. package/dist/extensions/pi-subagents/src/runs/background/terminal-run-index.ts +138 -0
  131. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +23 -4
  132. package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +82 -4
  133. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +5 -3
  134. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +21 -14
  135. package/dist/extensions/pi-subagents/src/runs/foreground/async-stop-action.ts +23 -2
  136. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +153 -58
  137. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +7 -9
  138. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1283 -1657
  139. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +274 -0
  140. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +6 -5
  141. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +59 -10
  142. package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +1 -1
  143. package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +1 -3
  144. package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +36 -0
  145. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +52 -2
  146. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  147. package/dist/extensions/pi-subagents/src/runs/shared/extension-bindings.ts +78 -0
  148. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +2 -0
  149. package/dist/extensions/pi-subagents/src/runs/shared/external-job-bridge.ts +460 -0
  150. package/dist/extensions/pi-subagents/src/runs/shared/external-job-runner.ts +389 -0
  151. package/dist/extensions/pi-subagents/src/runs/shared/fast-mode-extension.ts +10 -0
  152. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
  153. package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +243 -0
  154. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +201 -85
  155. package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +106 -39
  156. package/dist/extensions/pi-subagents/src/runs/shared/mutation-evidence.ts +145 -0
  157. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +150 -27
  158. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +46 -4
  159. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +19 -15
  160. package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +5 -1
  161. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +86 -2
  162. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +9 -9
  163. package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +0 -6
  164. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +18 -4
  165. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +51 -7
  166. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +62 -21
  167. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +1 -3
  168. package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +1 -3
  169. package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +1 -23
  170. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +14 -2
  171. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +2 -2
  172. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +22 -2
  173. package/dist/extensions/pi-subagents/src/shared/capacity-resilient-json.ts +102 -0
  174. package/dist/extensions/pi-subagents/src/shared/completion-owner.ts +14 -0
  175. package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +49 -1
  176. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +59 -1
  177. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +12 -5
  178. package/dist/extensions/pi-subagents/src/shared/model-info.ts +20 -0
  179. package/dist/extensions/pi-subagents/src/shared/prompt-resources.ts +0 -40
  180. package/dist/extensions/pi-subagents/src/shared/settings.ts +11 -30
  181. package/dist/extensions/pi-subagents/src/shared/thinking-ceiling.ts +52 -0
  182. package/dist/extensions/pi-subagents/src/shared/types.ts +228 -49
  183. package/dist/extensions/pi-subagents/src/shared/utils.ts +11 -3
  184. package/dist/extensions/pi-subagents/src/shared/watch-strategy.ts +10 -0
  185. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +0 -162
  186. package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +2 -5
  187. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +105 -13
  188. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +6 -36
  189. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +829 -0
  190. package/dist/extensions/pi-subagents/src/tui/fleet.ts +1415 -0
  191. package/dist/extensions/pi-subagents/src/tui/render.ts +78 -7
  192. package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +1 -1
  193. package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +1 -0
  194. package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +59 -51
  195. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +8 -2
  196. package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +2 -1
  197. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +4 -4
  198. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +471 -86
  199. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +140 -0
  200. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +2 -2
  201. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/cli.d.ts +3 -0
  202. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/register-bedrock.d.ts +2 -0
  203. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/restore-sandbox-env.d.ts +17 -0
  204. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/args.d.ts +57 -0
  205. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/config-selector.d.ts +16 -0
  206. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/file-processor.d.ts +15 -0
  207. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/initial-message.d.ts +18 -0
  208. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/list-models.d.ts +9 -0
  209. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/project-trust.d.ts +10 -0
  210. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/session-picker.d.ts +10 -0
  211. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/startup-ui.d.ts +20 -0
  212. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli.d.ts +3 -0
  213. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/config.d.ts +96 -0
  214. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-runtime.d.ts +119 -0
  215. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-services.d.ts +85 -0
  216. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session.d.ts +620 -0
  217. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-guidance.d.ts +5 -0
  218. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-storage.d.ts +56 -0
  219. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/bash-executor.d.ts +32 -0
  220. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/cache-stats.d.ts +49 -0
  221. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/branch-summarization.d.ts +93 -0
  222. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/compaction.d.ts +128 -0
  223. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/index.d.ts +7 -0
  224. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/utils.d.ts +38 -0
  225. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/defaults.d.ts +3 -0
  226. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/diagnostics.d.ts +15 -0
  227. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/event-bus.d.ts +9 -0
  228. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/exec.d.ts +29 -0
  229. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/experimental.d.ts +2 -0
  230. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/ansi-to-html.d.ts +22 -0
  231. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/index.d.ts +37 -0
  232. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/tool-renderer.d.ts +34 -0
  233. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/index.d.ts +12 -0
  234. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/loader.d.ts +23 -0
  235. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/runner.d.ts +171 -0
  236. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +1261 -0
  237. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/wrapper.d.ts +20 -0
  238. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/footer-data-provider.d.ts +54 -0
  239. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/http-dispatcher.d.ts +22 -0
  240. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/index.d.ts +13 -0
  241. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/keybindings.d.ts +358 -0
  242. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/messages.d.ts +77 -0
  243. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-config.d.ts +512 -0
  244. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-registry.d.ts +43 -0
  245. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-resolver.d.ts +121 -0
  246. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-runtime.d.ts +82 -0
  247. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/models-store.d.ts +17 -0
  248. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/output-guard.d.ts +7 -0
  249. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/package-manager.d.ts +210 -0
  250. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/project-trust.d.ts +15 -0
  251. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/prompt-templates.d.ts +54 -0
  252. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-attribution.d.ts +4 -0
  253. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-composer.d.ts +55 -0
  254. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/radius.d.ts +2 -0
  255. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/remote-catalog-provider.d.ts +5 -0
  256. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resolve-config-value.d.ts +30 -0
  257. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resource-loader.d.ts +206 -0
  258. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/runtime-credentials.d.ts +15 -0
  259. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/sdk.d.ts +106 -0
  260. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-cwd.d.ts +19 -0
  261. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-manager.d.ts +356 -0
  262. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/settings-manager.d.ts +297 -0
  263. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/skills.d.ts +60 -0
  264. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/slash-commands.d.ts +15 -0
  265. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/source-info.d.ts +18 -0
  266. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/system-prompt.d.ts +28 -0
  267. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/telemetry.d.ts +3 -0
  268. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/timings.d.ts +10 -0
  269. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/bash.d.ts +68 -0
  270. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit-diff.d.ts +106 -0
  271. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit.d.ts +51 -0
  272. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/file-mutation-queue.d.ts +6 -0
  273. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/find.d.ts +35 -0
  274. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/grep.d.ts +37 -0
  275. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/index.d.ts +40 -0
  276. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/ls.d.ts +37 -0
  277. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/output-accumulator.d.ts +52 -0
  278. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/path-utils.d.ts +10 -0
  279. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/read.d.ts +35 -0
  280. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/render-utils.d.ts +24 -0
  281. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
  282. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/truncate.d.ts +70 -0
  283. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/write.d.ts +26 -0
  284. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/trust-manager.d.ts +36 -0
  285. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/usage-totals.d.ts +19 -0
  286. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/index.d.ts +3 -0
  287. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/client.d.ts +61 -0
  288. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/huggingface.d.ts +23 -0
  289. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/index.d.ts +3 -0
  290. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/provider.d.ts +10 -0
  291. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/ui.d.ts +42 -0
  292. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/index.d.ts +35 -0
  293. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/main.d.ts +12 -0
  294. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/migrations.d.ts +33 -0
  295. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/index.d.ts +9 -0
  296. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/armin.d.ts +34 -0
  297. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/assistant-message.d.ts +22 -0
  298. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bash-execution.d.ts +34 -0
  299. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bordered-loader.d.ts +16 -0
  300. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
  301. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
  302. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/config-selector.d.ts +102 -0
  303. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
  304. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-editor.d.ts +21 -0
  305. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-entry.d.ts +19 -0
  306. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-message.d.ts +20 -0
  307. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/daxnuts.d.ts +23 -0
  308. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/diff.d.ts +12 -0
  309. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
  310. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/earendil-announcement.d.ts +5 -0
  311. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-editor.d.ts +22 -0
  312. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-input.d.ts +23 -0
  313. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-selector.d.ts +26 -0
  314. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/first-time-setup.d.ts +25 -0
  315. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/footer.d.ts +32 -0
  316. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/index.d.ts +34 -0
  317. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/keybinding-hints.d.ts +13 -0
  318. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/login-dialog.d.ts +52 -0
  319. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/model-selector.d.ts +54 -0
  320. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/oauth-selector.d.ts +33 -0
  321. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/scoped-models-selector.d.ts +42 -0
  322. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
  323. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector.d.ts +95 -0
  324. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/settings-selector.d.ts +77 -0
  325. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
  326. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
  327. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/status-indicator.d.ts +28 -0
  328. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/theme-selector.d.ts +11 -0
  329. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
  330. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tool-execution.d.ts +63 -0
  331. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tree-selector.d.ts +94 -0
  332. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/trust-selector.d.ts +23 -0
  333. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
  334. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message.d.ts +14 -0
  335. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
  336. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/interactive-mode.d.ts +394 -0
  337. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/model-search.d.ts +12 -0
  338. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme-controller.d.ts +29 -0
  339. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme.d.ts +120 -0
  340. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/print-mode.d.ts +28 -0
  341. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/jsonl.d.ts +17 -0
  342. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-client.d.ts +246 -0
  343. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-mode.d.ts +20 -0
  344. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-types.d.ts +457 -0
  345. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/package-manager-cli.d.ts +8 -0
  346. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/rpc-entry.d.ts +3 -0
  347. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/ansi.d.ts +2 -0
  348. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/changelog.d.ts +22 -0
  349. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/child-process.d.ts +18 -0
  350. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-image.d.ts +11 -0
  351. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-native.d.ts +11 -0
  352. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard.d.ts +4 -0
  353. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/deprecation.d.ts +4 -0
  354. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/exif-orientation.d.ts +5 -0
  355. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/frontmatter.d.ts +8 -0
  356. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/fs-watch.d.ts +5 -0
  357. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/git.d.ts +26 -0
  358. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/html.d.ts +7 -0
  359. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-convert.d.ts +10 -0
  360. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-process.d.ts +18 -0
  361. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-core.d.ts +30 -0
  362. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-worker.d.ts +2 -0
  363. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize.d.ts +16 -0
  364. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/json.d.ts +3 -0
  365. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/mime.d.ts +3 -0
  366. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/open-browser.d.ts +9 -0
  367. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/paths.d.ts +31 -0
  368. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/photon.d.ts +21 -0
  369. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/pi-user-agent.d.ts +2 -0
  370. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/shell.d.ts +31 -0
  371. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/sleep.d.ts +5 -0
  372. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/syntax-highlight.d.ts +12 -0
  373. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/tools-manager.d.ts +3 -0
  374. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/version-check.d.ts +15 -0
  375. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/windows-self-update.d.ts +3 -0
  376. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/index.mjs +1 -1
  377. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +0 -54
  378. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1053 -414
  379. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +194 -4
  380. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +59 -4
  381. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +0 -77
  382. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +107 -6
  383. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +211 -897
  384. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +265 -375
  385. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +77 -7
  386. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +1 -1
  387. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +10 -10
  388. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +547 -30
  389. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +2112 -310
  390. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +274 -34
  391. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +1 -1
  392. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +20 -3
  393. package/dist/extensions/pi-subagents/test/support/isolated-temp-root.mjs +9 -0
  394. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +30 -5
  395. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +2 -0
  396. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +4 -2
  397. package/dist/extensions/pi-subagents/test/support/register-loader.mjs +1 -0
  398. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +197 -0
  399. package/dist/extensions/pi-subagents/test/unit/agent-contract.test.ts +14 -0
  400. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +1 -1
  401. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +204 -27
  402. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +444 -145
  403. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +256 -4
  404. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +9 -7
  405. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +152 -22
  406. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +29 -0
  407. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +205 -23
  408. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +579 -0
  409. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +24 -0
  410. package/dist/extensions/pi-subagents/test/unit/authority-policy.test.ts +1 -0
  411. package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +3 -3
  412. package/dist/extensions/pi-subagents/test/unit/capacity-resilient-json.test.ts +155 -0
  413. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +2 -2
  414. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +7 -0
  415. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +123 -0
  416. package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +81 -3
  417. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +109 -7
  418. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +2 -2
  419. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +5 -2
  420. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -2
  421. package/dist/extensions/pi-subagents/test/unit/extension-bindings.test.ts +79 -0
  422. package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +22 -0
  423. package/dist/extensions/pi-subagents/test/unit/external-job-runner.test.ts +961 -0
  424. package/dist/extensions/pi-subagents/test/unit/file-system-retry.test.ts +64 -0
  425. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +1110 -0
  426. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +1664 -0
  427. package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +81 -1
  428. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +138 -11
  429. package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +41 -2
  430. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +210 -79
  431. package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +95 -0
  432. package/dist/extensions/pi-subagents/test/unit/inspect-rpc.test.ts +596 -0
  433. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +10 -1
  434. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +3 -5
  435. package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +167 -0
  436. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +319 -18
  437. package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +78 -0
  438. package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +19 -5
  439. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +44 -0
  440. package/dist/extensions/pi-subagents/test/unit/mutation-evidence.test.ts +129 -0
  441. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +231 -2
  442. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +28 -1
  443. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +106 -2
  444. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +204 -7
  445. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +59 -4
  446. package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +2 -6
  447. package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +34 -2
  448. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +116 -6
  449. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +12 -3
  450. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +56 -61
  451. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +416 -1
  452. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +9 -2
  453. package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +42 -0
  454. package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +21 -30
  455. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +86 -11
  456. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +49 -1
  457. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +233 -5
  458. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +28 -5
  459. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +323 -20
  460. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +66 -10
  461. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +68 -5
  462. package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +174 -0
  463. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +679 -0
  464. package/dist/extensions/pi-subagents/test/unit/scheduled-store-root.test.ts +67 -0
  465. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +55 -44
  466. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +399 -84
  467. package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +13 -0
  468. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
  469. package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +15 -10
  470. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +11 -7
  471. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -0
  472. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +40 -1
  473. package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +10 -2
  474. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +38 -3
  475. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +110 -12
  476. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +204 -19
  477. package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +25 -0
  478. package/dist/extensions/pi-subagents/test/unit/thinking-ceiling.test.ts +73 -0
  479. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +90 -50
  480. package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +1 -2
  481. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +234 -7
  482. package/dist/extensions/pi-subagents/test/unit/watch-strategy.test.ts +24 -0
  483. package/dist/extensions/pi-subagents/test/unit/watchdog-permission-arbiter.test.ts +36 -0
  484. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +37 -9
  485. package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +302 -0
  486. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +167 -7
  487. package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +113 -0
  488. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +6 -6
  489. package/dist/extensions/ponytail/index.js +31 -25
  490. package/dist/extensions/ponytail/ponytail-config.cjs +34 -7
  491. package/dist/extensions/ponytail/ponytail-instructions.cjs +7 -9
  492. package/dist/extensions/ponytail/test/extension.test.js +7 -8
  493. package/dist/extensions/ponytail/test/helpers.test.js +1 -1
  494. package/dist/skills/brandkit/SKILL.md +799 -0
  495. package/dist/skills/design-taste-frontend/SKILL.md +1207 -0
  496. package/dist/skills/design-taste-frontend-v1/SKILL.md +227 -0
  497. package/dist/skills/full-output-enforcement/SKILL.md +50 -0
  498. package/dist/skills/gpt-taste/SKILL.md +75 -0
  499. package/dist/skills/high-end-visual-design/SKILL.md +99 -0
  500. package/dist/skills/image-to-code/SKILL.md +1229 -0
  501. package/dist/skills/imagegen-frontend-mobile/SKILL.md +1466 -0
  502. package/dist/skills/imagegen-frontend-web/SKILL.md +988 -0
  503. package/dist/skills/industrial-brutalist-ui/SKILL.md +93 -0
  504. package/dist/skills/minimalist-ui/SKILL.md +86 -0
  505. package/dist/skills/pi-subagents/SKILL.md +18 -17
  506. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +31 -9
  507. package/dist/skills/pi-subagents/references/execution-controls.md +95 -143
  508. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +9 -12
  509. package/dist/skills/pi-subagents/references/multi-lane-orchestration.md +39 -0
  510. package/dist/skills/pi-subagents/references/prompting-and-roles.md +45 -94
  511. package/dist/skills/ponytail/SKILL.md +12 -8
  512. package/dist/skills/ponytail-audit/SKILL.md +0 -1
  513. package/dist/skills/ponytail-debt/SKILL.md +0 -1
  514. package/dist/skills/ponytail-gain/SKILL.md +0 -1
  515. package/dist/skills/ponytail-help/SKILL.md +4 -3
  516. package/dist/skills/ponytail-review/SKILL.md +0 -1
  517. package/dist/skills/redesign-existing-projects/SKILL.md +179 -0
  518. package/dist/skills/stitch-design-taste/SKILL.md +185 -0
  519. package/dist/skills/unlazy/CHANGELOG.md +100 -0
  520. package/dist/skills/unlazy/CONTRIBUTING.md +66 -0
  521. package/dist/skills/unlazy/LICENSE +21 -0
  522. package/dist/skills/unlazy/README.md +242 -0
  523. package/dist/skills/unlazy/SECURITY.md +70 -0
  524. package/dist/skills/unlazy/SKILL.md +100 -0
  525. package/dist/skills/unlazy/package.json +13 -0
  526. package/dist/skills/unlazy/references/dispatch.md +82 -0
  527. package/dist/skills/unlazy/references/gates.md +132 -0
  528. package/dist/skills/unlazy/references/method.md +49 -0
  529. package/dist/skills/unlazy/references/orchestration.md +99 -0
  530. package/dist/skills/unlazy/references/parallel.md +123 -0
  531. package/dist/skills/unlazy/references/token-economy.md +39 -0
  532. package/dist/skills/unlazy/research/validation-protocol.md +81 -0
  533. package/dist/skills/unlazy/scripts/dispatch-check.mjs +108 -0
  534. package/dist/skills/unlazy/scripts/gate-check.mjs +894 -0
  535. package/dist/skills/unlazy/scripts/gate-lint.mjs +178 -0
  536. package/dist/skills/unlazy/scripts/install-hooks.mjs +206 -0
  537. package/dist/skills/unlazy/scripts/lib/check-supervisor.mjs +46 -0
  538. package/dist/skills/unlazy/scripts/lib/dispatch.mjs +283 -0
  539. package/dist/skills/unlazy/scripts/lib/gates.mjs +629 -0
  540. package/dist/skills/unlazy/scripts/lib/process-tree.mjs +161 -0
  541. package/dist/skills/unlazy/scripts/lib/regex-worker.mjs +9 -0
  542. package/dist/skills/unlazy/scripts/stop-hook.mjs +174 -0
  543. package/dist/skills/unlazy/templates/PLAN.md +65 -0
  544. package/dist/skills/unlazy/templates/gates-leaf.md +51 -0
  545. package/dist/skills/unlazy/templates/gates-node.md +50 -0
  546. package/dist/skills/unlazy/tests/contract-tests.mjs +127 -0
  547. package/dist/skills/unlazy/tests/dispatch-tests.mjs +481 -0
  548. package/dist/skills/unlazy/tests/hardening-tests.mjs +903 -0
  549. package/dist/skills/unlazy/tests/lint-tests.mjs +242 -0
  550. package/dist/skills/unlazy/tests/run-tests.mjs +554 -0
  551. package/dist/skills/unlazy/tests/self-check.mjs +165 -0
  552. package/dist/skills/unlazy/tests/stress-tests.mjs +356 -0
  553. package/dist/skills/web-design-guidelines/SKILL.md +40 -0
  554. package/docs/settings.md +0 -5
  555. package/package.json +1 -1
  556. package/dist/core/tools/schema-prune.d.ts +0 -16
  557. package/dist/core/tools/schema-prune.js +0 -39
  558. package/dist/core/tools/schema-prune.test.d.ts +0 -1
  559. package/dist/core/tools/schema-prune.test.js +0 -111
  560. package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +0 -172
  561. package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +0 -55
  562. package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +0 -61
  563. package/dist/extensions/pi-subagents/src/agents/task-aware-routing.ts +0 -132
  564. package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +0 -181
  565. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +0 -1354
  566. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +0 -1581
  567. package/dist/extensions/pi-subagents/src/shared/env.ts +0 -16
  568. package/dist/extensions/pi-subagents/src/shared/session-lineage.ts +0 -71
  569. package/dist/extensions/pi-subagents/src/slash/inline-subagents.ts +0 -136
  570. package/dist/extensions/pi-subagents/src/workflows/workflow-auto-relaunch.ts +0 -41
  571. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +0 -466
  572. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +0 -1859
  573. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +0 -733
  574. package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +0 -63
  575. package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +0 -326
  576. package/dist/extensions/pi-subagents/test/unit/handoff-adoption.test.ts +0 -103
  577. package/dist/extensions/pi-subagents/test/unit/inline-subagents.test.ts +0 -127
  578. package/dist/extensions/pi-subagents/test/unit/session-lineage.test.ts +0 -73
  579. package/dist/extensions/pi-subagents/test/unit/task-aware-routing.test.ts +0 -213
  580. package/dist/extensions/pi-subagents/test/unit/workflow-auto-relaunch.test.ts +0 -65
@@ -3,12 +3,12 @@ import * as fs from "node:fs";
3
3
  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
- import { resolveAgentName, type AgentConfig, type AgentScope } from "../../agents/agents.ts";
6
+ import { findBlockingAgentDiagnostic, resolveAgentName, type AgentConfig, type AgentDiscoveryDiagnostic, type AgentScope } from "../../agents/agents.ts";
7
7
  import { getArtifactsDir, getChainRunsDir, getProjectArtifactPackagingWarning, getProjectSubagentsDir } from "../../shared/artifacts.ts";
8
8
  import { writeAtomicJson } from "../../shared/atomic-json.ts";
9
- import { ChainClarifyComponent, type ChainClarifyResult } from "./chain-clarify.ts";
9
+ import { createCapacityResilientJsonWriter } from "../../shared/capacity-resilient-json.ts";
10
+ import { isStorageCapacityError } from "../../shared/file-system-retry.ts";
10
11
  import { resolveEffectiveThinking, toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
11
- import { executeChain } from "./chain-execution.ts";
12
12
  import {
13
13
  beginForegroundChild,
14
14
  finishForegroundChild,
@@ -26,9 +26,11 @@ import { buildDoctorReport } from "../../extension/doctor.ts";
26
26
  import { readSubagentGuide } from "../../extension/subagent-guide.ts";
27
27
  import { normalizePublicSubagentExecution } from "../../extension/public-execution.ts";
28
28
  import { runSync } from "./execution.ts";
29
- import { buildModelCandidates, normalizeParentModel, resolveEffectiveSubagentModel, resolveModelCandidate, type ParentModel } from "../shared/model-fallback.ts";
29
+ import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.ts";
30
+ import type { MainWatchdogRuntime } from "../../watchdog/runtime.ts";
31
+ import { buildModelCandidates, inheritsParentModel, normalizeParentModel, resolveEffectiveSubagentModel, resolveModelCandidate, type ParentModel } from "../shared/model-fallback.ts";
30
32
  import { formatRetainedChildren, listRetainedChildren } from "../background/retained-children.ts";
31
- import type { ModelScopeConfig } from "../shared/model-scope.ts";
33
+ import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
32
34
  import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
33
35
  import { recordRun } from "../shared/run-history.ts";
34
36
  import {
@@ -53,12 +55,15 @@ import {
53
55
  import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
54
56
  import { buildAsyncRunnerSteps, DEFAULT_ASYNC_TIMEOUT_MS, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable, workflowAwaitedAsyncResultPath } from "../background/async-execution.ts";
55
57
  import { updateActiveRunIndex } from "../background/active-run-index.ts";
58
+ import { steeringReceipt } from "../background/steering.ts";
56
59
  import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
60
+ import { isScheduledRunAction, type ScheduledRunAction } from "../background/scheduled-runs.ts";
57
61
  import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
58
62
  import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
59
63
  import { normalizeGateAcceptance, validateExecutionAcceptance } from "../shared/acceptance.ts";
60
- import { createForkContextResolver, forkedChildRequiresThinkingOff } from "../../shared/fork-context.ts";
64
+ import { canPreferFork, createForkContextResolver, forkedChildRequiresThinkingOff, resolveSubagentLaunchContext } from "../../shared/fork-context.ts";
61
65
  import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
66
+ import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
62
67
  import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
63
68
  import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
64
69
  import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
@@ -68,6 +73,7 @@ import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
68
73
  import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
69
74
  import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
70
75
  import { isAgentContractV1 } from "../shared/agent-contract.ts";
76
+ import { normalizeExtensionBindings, type ExtensionBindings } from "../shared/extension-bindings.ts";
71
77
  import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
72
78
  import { cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
73
79
  import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
@@ -83,10 +89,10 @@ import {
83
89
  resolveSubagentResultStatus,
84
90
  stripDetailsOutputsForIntercomReceipt,
85
91
  } from "../../intercom/result-intercom.ts";
86
- import { applySteeringRecoveryAgentConfig, buildRevivedAsyncTask, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
87
- import { deliverCheckpointDecisionRequest, deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
92
+ import { applySteeringRecoveryAgentConfig, asyncReviveRequiresRecoveryDescriptor, buildRevivedAsyncTask, readAsyncRecoveryDescriptor, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
93
+ import { deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
88
94
  import { updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
89
- import { steerAsyncRun } from "./async-steering-action.ts";
95
+ import { canQueueRetainedAsyncFollowUp, steerAsyncRun } from "./async-steering-action.ts";
90
96
  import {
91
97
  removeWorkflowForegroundSteeringRoute,
92
98
  resolveWorkflowForegroundSteeringTarget,
@@ -96,13 +102,17 @@ import {
96
102
  } from "./workflow-foreground-steering.ts";
97
103
  import { stopAsyncRun } from "./async-stop-action.ts";
98
104
  import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
105
+ import { promotePausedWorkflowIfSettled, reconcileDetachedWorkflowChildCompletion } from "./workflow-detach-reconcile.ts";
99
106
  import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
100
107
  import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.ts";
101
108
  import { resultFilePath, writeAsyncResultFile } from "../background/result-files.ts";
102
109
  import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
103
110
  import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
104
111
  import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
112
+ import { isStoppableAsyncStatusStep, resolveAsyncStatusChild } from "../shared/child-identity.ts";
105
113
  import { inspectSubagentStatus } from "../background/run-status.ts";
114
+ import { getExternalJobProvider } from "../../api/external-job-provider.ts";
115
+ import { externalJobFollowUpRequestDigest, externalJobFollowUpRequestId, externalJobFollowUpRunId, externalJobPromptDigest, externalJobStableJson } from "../shared/external-job-runner.ts";
106
116
  import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
107
117
  import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
108
118
  import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
@@ -112,9 +122,8 @@ import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
112
122
  import { resolveAuthorityDecision } from "../../policy/authority.ts";
113
123
  import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
114
124
  import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
115
- import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult } from "../../workflows/scripted-workflow.ts";
116
- import { resolveAutoRelaunchDecision, resolveMaxWorkflowAutoRelaunches } from "../../workflows/workflow-auto-relaunch.ts";
117
- import { renderWorkflowPrompt } from "../../shared/prompt-resources.ts";
125
+ import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
126
+ import { buildWorkflowReceipt, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
118
127
  import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
119
128
  import {
120
129
  cleanupWorktrees,
@@ -148,7 +157,9 @@ import {
148
157
  type ResolvedTurnBudget,
149
158
  type ResolvedToolBudget,
150
159
  type RunFanoutBudgetDescriptor,
160
+ type SteeringRecoveryDescriptor,
151
161
  type SingleResult,
162
+ type SubagentChildStatusEvent,
152
163
  type ToolBudgetConfig,
153
164
  type TurnBudgetConfig,
154
165
  type UsageBudgetConfig,
@@ -168,10 +179,11 @@ import {
168
179
  resolveCurrentMaxSubagentDepth,
169
180
  resolveMaxSubagentSpawnsPerRun,
170
181
  wrapForkTask,
182
+ type ScheduleOrigin,
171
183
  } from "../../shared/types.ts";
172
184
 
173
- const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "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"]);
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"]);
185
+ 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"]);
186
+ const DESTRUCTIVE_MANAGEMENT_ACTIONS = new Set(["delete", "eject", "disable", "reset", "mission.close", "worktree.discard", "refine.rollback", "inspector.close", "project.close", "stop", "interrupt", "schedule.delete"]);
175
187
 
176
188
  function editDistance(left: string, right: string): number {
177
189
  const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
@@ -254,6 +266,7 @@ interface TaskParam {
254
266
  reads?: string[] | boolean;
255
267
  progress?: boolean;
256
268
  model?: string;
269
+ fast?: boolean;
257
270
  skill?: string | string[] | boolean;
258
271
  outputSchema?: JsonSchemaObject;
259
272
  acceptance?: AcceptanceInput;
@@ -268,6 +281,7 @@ export interface SubagentParamsLike {
268
281
  dir?: string;
269
282
  handoffPath?: string;
270
283
  index?: number;
284
+ childId?: string;
271
285
  view?: "fleet" | "transcript";
272
286
  lines?: number;
273
287
  topic?: string;
@@ -277,29 +291,37 @@ export interface SubagentParamsLike {
277
291
  type?: string;
278
292
  agent?: string;
279
293
  task?: string;
294
+ extensionBindings?: ExtensionBindings;
280
295
  /** Retained async child run id. Valid only on workflow runs.run items. */
281
296
  resume?: string;
282
297
  message?: string;
283
298
  steeringRecovery?: boolean;
284
299
  mode?: SteerDeliveryMode;
285
300
  workflowScript?: string;
286
- chatProgress?: "auto" | "off" | "terminal" | "milestones" | "live-card";
301
+ chatProgress?: "auto" | "off" | "live-card";
302
+ isolation?: "none" | "worktree";
287
303
  step?: ChainStep;
288
304
  /** Internal workflow ownership metadata; not part of the public schema. */
289
305
  workflowParentRunId?: string;
290
306
  workflowKey?: string;
307
+ /** Set by the scheduler so this run's completion can name the schedule that produced it. */
308
+ scheduleOrigin?: ScheduleOrigin;
291
309
  workflowChildAsyncId?: string;
310
+ workflowAwaitAsync?: boolean;
311
+ workflowParentDeadlineAt?: number;
292
312
  suppressRoutineResultIntercom?: boolean;
293
313
  /** Internal inherited cumulative run-tree budget. */
294
314
  runFanoutBudget?: RunFanoutBudgetDescriptor;
295
315
  /** Internal workflow host admission proof. */
296
316
  runFanoutAdmitted?: boolean;
317
+ /** Internal inherited tool/agent ceiling for delegated child launches. */
318
+ capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
297
319
  /** Internal durable-run compatibility fields. Public callers must use workflowScript. */
298
320
  chain?: ChainStep[];
299
321
  tasks?: TaskParam[];
300
322
  concurrency?: number;
301
323
  worktree?: boolean;
302
- context?: "fresh" | "fork";
324
+ context?: "fresh" | "fork" | "profile";
303
325
  /** Per-run intercom bridge config. It replaces the global config for this launch only. */
304
326
  intercomBridge?: IntercomBridgeConfig;
305
327
  async?: boolean;
@@ -322,6 +344,7 @@ export interface SubagentParamsLike {
322
344
  artifacts?: boolean;
323
345
  includeProgress?: boolean;
324
346
  model?: string;
347
+ fast?: boolean;
325
348
  thinking?: string | false;
326
349
  scope?: string;
327
350
  target?: string;
@@ -337,8 +360,6 @@ export interface SubagentParamsLike {
337
360
  acceptance?: AcceptanceInput;
338
361
  gate?: string;
339
362
  agentContract?: AgentContract;
340
- schedule?: string;
341
- scheduleName?: string;
342
363
  at?: string;
343
364
  every?: string;
344
365
  on?: string | number;
@@ -371,16 +392,20 @@ interface ExecutorDeps {
371
392
  config: ExtensionConfig;
372
393
  asyncByDefault: boolean;
373
394
  waitToolEnabled?: boolean;
395
+ handleScheduledRunAction?: (params: SubagentParamsLike, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>;
396
+ watchdog?: MainWatchdogRuntime;
374
397
  tempArtifactsDir: string;
375
398
  getSubagentSessionRoot: (parentSessionFile: string | null) => string;
376
399
  expandTilde: (p: string) => string;
377
- discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; modelScope?: ModelScopeConfig };
400
+ discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"] };
378
401
  allowMutatingManagementActions?: boolean;
402
+ activateSupervisorTransport?: () => void;
403
+ refreshResultDelivery?: () => void;
379
404
  kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
380
405
  }
381
406
 
382
- type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string) => string | undefined;
383
- type ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string) => AgentConfig["thinking"] | undefined;
407
+ type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => string | undefined;
408
+ type ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => AgentConfig["thinking"] | undefined;
384
409
 
385
410
  interface ExecutionContextData {
386
411
  params: SubagentParamsLike;
@@ -458,6 +483,7 @@ function promptAuditRedoParams(value: unknown, rewrittenTask: string): SubagentP
458
483
  delete params.workflowParentRunId;
459
484
  delete params.workflowKey;
460
485
  delete params.workflowChildAsyncId;
486
+ delete params.workflowParentDeadlineAt;
461
487
  delete params.suppressRoutineResultIntercom;
462
488
  if (params.worktree === true && Array.isArray(params.tasks)) delete params.cwd;
463
489
  return params;
@@ -549,7 +575,7 @@ function countRequestedSubagentSpawns(params: SubagentParamsLike, config: Extens
549
575
  function staticRunFanoutPaths(params: SubagentParamsLike): string[] {
550
576
  if (params.tasks) return params.tasks.map((_, index) => `tasks[${index}]`);
551
577
  if (params.chain) return params.chain.flatMap((step, stepIndex) => {
552
- if (isDynamicParallelStep(step) || "checkpoint" in step) return [];
578
+ if (isDynamicParallelStep(step)) return [];
553
579
  if (isParallelStep(step)) return step.parallel.map((_, itemIndex) => `chain[${stepIndex}].parallel[${itemIndex}]`);
554
580
  return [`chain[${stepIndex}]`];
555
581
  });
@@ -623,7 +649,7 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
623
649
  };
624
650
  }
625
651
 
626
- function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; checkpoint?: Details["checkpoint"] }): void {
652
+ function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; extensionBindings?: ExtensionBindings }): void {
627
653
  state.foregroundRuns ??= new Map();
628
654
  const previous = state.foregroundRuns.get(input.runId);
629
655
  const updatedAt = Date.now();
@@ -633,7 +659,6 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
633
659
  cwd: input.cwd,
634
660
  ...(input.sessionId ? { sessionId: input.sessionId } : {}),
635
661
  updatedAt,
636
- ...(input.checkpoint ? { checkpoint: input.checkpoint } : {}),
637
662
  children: input.results.map((result, index) => {
638
663
  const child = {
639
664
  agent: result.agent,
@@ -666,6 +691,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
666
691
  ...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
667
692
  ...(result.acceptance ? { acceptance: result.acceptance } : {}),
668
693
  ...(result.launchContractDigest ? { launchContractDigest: result.launchContractDigest } : {}),
694
+ ...(input.extensionBindings ? { extensionBindings: input.extensionBindings } : {}),
669
695
  ...(result.launchResolvedExtensions ? { launchResolvedExtensions: result.launchResolvedExtensions } : {}),
670
696
  ...(result.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: result.runtimeAcknowledgedExtensions } : {}),
671
697
  ...(result.capabilityCeiling ? { capabilityCeiling: result.capabilityCeiling } : {}),
@@ -784,13 +810,13 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
784
810
  });
785
811
  }
786
812
 
787
- function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState): { runId: string; mode: SubagentRunMode; state: "complete"; agent: string; index: number; cwd: string; sessionFile: string; model?: string; thinking?: string; launchContractDigest?: string; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
813
+ function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState, options: { exactOnly?: boolean } = {}): { runId: string; mode: SubagentRunMode; state: "complete"; agent: string; index: number; cwd: string; sessionFile: string; model?: string; thinking?: string; launchContractDigest?: string; extensionBindings?: ExtensionBindings; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
788
814
  const requested = (params.id ?? params.runId)?.trim();
789
815
  if (!requested || !state.foregroundRuns?.size || !state.currentSessionId) return undefined;
790
- const acceptedSessions = new Set(state.sessionLineage?.length ? state.sessionLineage : [state.currentSessionId]);
791
- const sessionRuns = [...state.foregroundRuns.values()].filter((run) => run.sessionId !== undefined && acceptedSessions.has(run.sessionId));
792
- const direct = sessionRuns.find((run) => run.runId === requested);
793
- const matches = direct ? [direct] : sessionRuns.filter((run) => run.runId.startsWith(requested));
816
+ const direct = state.foregroundRuns.get(requested);
817
+ const matches = direct?.sessionId === state.currentSessionId
818
+ ? [direct]
819
+ : options.exactOnly ? [] : [...state.foregroundRuns.values()].filter((run) => run.sessionId === state.currentSessionId && run.runId.startsWith(requested));
794
820
  if (matches.length === 0) return undefined;
795
821
  if (matches.length > 1) throw new Error(`Ambiguous foreground run id prefix '${requested}' matched: ${matches.map((run) => run.runId).join(", ")}. Provide a longer id.`);
796
822
  const run = matches[0]!;
@@ -815,6 +841,7 @@ function resolveForegroundResumeTarget(params: SubagentParamsLike, state: Subage
815
841
  ...(child.model ? { model: child.model } : {}),
816
842
  ...(child.thinking ? { thinking: child.thinking } : {}),
817
843
  ...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
844
+ ...(child.extensionBindings ? { extensionBindings: normalizeExtensionBindings(child.extensionBindings)!.value } : {}),
818
845
  ...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
819
846
  };
820
847
  }
@@ -834,6 +861,7 @@ type NestedResumeSourceTarget = {
834
861
  thinking?: AgentConfig["thinking"];
835
862
  launchContractDigest?: string;
836
863
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
864
+ recoveryDescriptor?: SteeringRecoveryDescriptor;
837
865
  };
838
866
  type ResumeSourceTarget = AsyncResumeSourceTarget | ForegroundResumeSourceTarget | NestedResumeSourceTarget;
839
867
 
@@ -841,6 +869,10 @@ function isAsyncRunNotFound(error: unknown): boolean {
841
869
  return error instanceof Error && error.message.startsWith("Async run not found.");
842
870
  }
843
871
 
872
+ function isMissingExactAsyncStatusFile(error: unknown, requested: string): boolean {
873
+ return error instanceof Error && error.message === `Status file not found for async run '${requested}'.`;
874
+ }
875
+
844
876
  function isResumeAmbiguity(error: unknown): boolean {
845
877
  return error instanceof Error && /Ambiguous .*run id prefix/.test(error.message);
846
878
  }
@@ -858,7 +890,7 @@ function isExactResumeError(error: unknown, source: "async" | "foreground", requ
858
890
  return new RegExp(`\\b${source} run '${escapeRegExp(requested)}'`, "i").test(error.message);
859
891
  }
860
892
 
861
- function resolveResumeTarget(params: SubagentParamsLike, state: SubagentState, options: { asyncRequireSessionFile?: boolean } = {}): ResumeSourceTarget {
893
+ function resolveResumeTarget(params: SubagentParamsLike, state: SubagentState, options: { asyncRequireSessionFile?: boolean; exactOnly?: boolean } = {}): ResumeSourceTarget {
862
894
  const requested = (params.id ?? params.runId)?.trim() ?? "";
863
895
  let foregroundTarget: ForegroundResumeSourceTarget | undefined;
864
896
  let foregroundError: unknown;
@@ -866,15 +898,18 @@ function resolveResumeTarget(params: SubagentParamsLike, state: SubagentState, o
866
898
  let asyncError: unknown;
867
899
 
868
900
  try {
869
- const target = resolveForegroundResumeTarget(params, state);
901
+ const target = resolveForegroundResumeTarget(params, state, options);
870
902
  if (target) foregroundTarget = { kind: "revive", source: "foreground", ...target };
871
903
  } catch (error) {
872
904
  foregroundError = error;
873
905
  }
874
906
  try {
907
+ const asyncParams = options.exactOnly && requested && !params.dir
908
+ ? { ...params, dir: path.join(DIRS.async, requested) }
909
+ : params;
875
910
  asyncTarget = {
876
911
  source: "async",
877
- ...resolveAsyncResumeTarget(params, {}, compactOptional<NonNullable<Parameters<typeof resolveAsyncResumeTarget>[2]>>({
912
+ ...resolveAsyncResumeTarget(asyncParams, {}, compactOptional<NonNullable<Parameters<typeof resolveAsyncResumeTarget>[2]>>({
878
913
  requireSessionFile: options.asyncRequireSessionFile,
879
914
  sessionId: state.currentSessionId ?? undefined,
880
915
  })),
@@ -891,7 +926,7 @@ function resolveResumeTarget(params: SubagentParamsLike, state: SubagentState, o
891
926
  throw new Error(`Resume id '${requested}' is ambiguous between foreground run '${foregroundTarget.runId}' and async run '${asyncTarget.runId}'. Provide a full run id.`);
892
927
  }
893
928
  if (foregroundTarget) {
894
- if (isExactResumeError(asyncError, "async", requested)) throw asyncError;
929
+ if (isExactResumeError(asyncError, "async", requested) && !isMissingExactAsyncStatusFile(asyncError, requested)) throw asyncError;
895
930
  if (isResumeAmbiguity(asyncError) && !resumeTargetExact(foregroundTarget, requested)) throw asyncError;
896
931
  return foregroundTarget;
897
932
  }
@@ -947,11 +982,48 @@ function emitAdvisoryControlEvent(pi: ExtensionAPI, channel: string, payload: un
947
982
  }
948
983
  }
949
984
 
985
+ function persistAsyncWorkflowControlEvent(input: {
986
+ job: AsyncJobState;
987
+ event: ControlEvent;
988
+ controlConfig: ResolvedControlConfig;
989
+ intercomBridge: IntercomBridgeState;
990
+ childIntercomTarget?: string;
991
+ }): void {
992
+ const channels = input.event.type === "active_long_running"
993
+ ? input.controlConfig.notifyChannels.filter((channel) => channel !== "intercom")
994
+ : input.controlConfig.notifyChannels;
995
+ if (channels.length === 0) return;
996
+ const record = {
997
+ ts: Date.now(),
998
+ runId: input.job.asyncId,
999
+ type: "subagent.control",
1000
+ event: input.event,
1001
+ channels,
1002
+ childIntercomTarget: input.childIntercomTarget,
1003
+ noticeText: formatControlNoticeMessage(input.event, input.childIntercomTarget),
1004
+ ...(input.intercomBridge.active && input.intercomBridge.orchestratorTarget && channels.includes("intercom")
1005
+ ? {
1006
+ intercom: {
1007
+ to: input.intercomBridge.orchestratorTarget,
1008
+ message: formatControlIntercomMessage(input.event, input.childIntercomTarget),
1009
+ },
1010
+ }
1011
+ : {}),
1012
+ };
1013
+ try {
1014
+ fs.appendFileSync(path.join(input.job.asyncDir, "events.jsonl"), `${JSON.stringify(record)}\n`, "utf-8");
1015
+ } catch (error) {
1016
+ if (!isStorageCapacityError(error)) throw error;
1017
+ console.error("Failed to append async workflow control event while storage is full:", error);
1018
+ }
1019
+ }
1020
+
950
1021
  function emitControlNotification(input: {
951
1022
  pi: ExtensionAPI;
952
1023
  controlConfig: ResolvedControlConfig;
953
1024
  intercomBridge: IntercomBridgeState;
954
1025
  event: ControlEvent;
1026
+ source?: "foreground" | "async";
955
1027
  }): void {
956
1028
  if (!shouldNotifyControlEvent(input.controlConfig, input.event)) return;
957
1029
  const childIntercomTarget = input.intercomBridge.active
@@ -959,7 +1031,7 @@ function emitControlNotification(input: {
959
1031
  : undefined;
960
1032
  const payload = {
961
1033
  event: input.event,
962
- source: "foreground" as const,
1034
+ source: input.source ?? "foreground",
963
1035
  childIntercomTarget,
964
1036
  noticeText: formatControlNoticeMessage(input.event, childIntercomTarget),
965
1037
  };
@@ -992,9 +1064,9 @@ function interruptAsyncRun(
992
1064
  };
993
1065
  }
994
1066
  const activeSteps = status.steps?.filter((step) => step.status === "running") ?? [];
995
- if (activeSteps.length > 0 && activeSteps.every((step) => step.runner?.type === "external-cli")) {
1067
+ if (activeSteps.length > 0 && activeSteps.every((step) => step.runner?.type === "external-cli" || step.runner?.type === "external-job")) {
996
1068
  return {
997
- content: [{ type: "text", text: `Interrupt is unsupported for one-shot external CLI async run ${target.asyncId}; use stop instead.` }],
1069
+ content: [{ type: "text", text: `Interrupt is unsupported for external async run ${target.asyncId}; use stop instead.` }],
998
1070
  isError: true,
999
1071
  details: { mode: "management", results: [] },
1000
1072
  };
@@ -1211,7 +1283,7 @@ function appendStepToAsyncChain(input: {
1211
1283
  const startIndex = (status.chainStepCount ?? status.steps?.length ?? 0) + pendingAppendRequests.reduce((total, request) => total + request.steps.length, 0);
1212
1284
  const appendPaths = chain.flatMap((step, localIndex) => {
1213
1285
  const absoluteIndex = startIndex + localIndex;
1214
- if (isDynamicParallelStep(step) || "checkpoint" in step) return [];
1286
+ if (isDynamicParallelStep(step)) return [];
1215
1287
  if (isParallelStep(step)) return step.parallel.map((_, itemIndex) => `chain[${absoluteIndex}].parallel[${itemIndex}]`);
1216
1288
  return [`chain[${absoluteIndex}]`];
1217
1289
  });
@@ -1275,6 +1347,14 @@ function validateNestedSessionFile(run: NestedRunSummary, trustedSessionRoots: s
1275
1347
  return realSessionFile;
1276
1348
  }
1277
1349
 
1350
+ export function readNestedRecoveryDescriptor(asyncDir: string | undefined, runId: string, agent: string): SteeringRecoveryDescriptor | undefined {
1351
+ const recoveryDescriptor = readAsyncRecoveryDescriptor(asyncDir);
1352
+ if (!recoveryDescriptor) return undefined;
1353
+ if (recoveryDescriptor.sourceRunId !== runId) throw new Error(`Nested run '${runId}' has a recovery descriptor for a different source run.`);
1354
+ if (recoveryDescriptor.agent !== agent) throw new Error(`Nested run '${runId}' has a recovery descriptor for a different agent.`);
1355
+ return recoveryDescriptor;
1356
+ }
1357
+
1278
1358
  function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "nested" }, trustedSessionRoots: string[]): NestedResumeSourceTarget {
1279
1359
  const run = match.match.run;
1280
1360
  if (run.state === "running" || run.state === "queued") throw new Error(`Nested run '${run.id}' is live; route the follow-up to the owner process instead.`);
@@ -1283,6 +1363,7 @@ function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "neste
1283
1363
  if (!agent) throw new Error(`Could not determine child agent for nested run '${run.id}'.`);
1284
1364
  const state = run.state === "complete" || run.state === "failed" || run.state === "paused" ? run.state : "failed";
1285
1365
  const asyncDir = resolveNestedAsyncDir(match.match.rootRunId, run);
1366
+ const recoveryDescriptor = readNestedRecoveryDescriptor(asyncDir, run.id, agent);
1286
1367
  return compactOptional<NestedResumeSourceTarget>({
1287
1368
  kind: "revive",
1288
1369
  source: "nested",
@@ -1293,6 +1374,7 @@ function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "neste
1293
1374
  cwd: asyncDir ? path.dirname(asyncDir) : undefined,
1294
1375
  sessionFile: validateNestedSessionFile(run, trustedSessionRoots),
1295
1376
  ...(run.capabilityCeiling ? { capabilityCeiling: run.capabilityCeiling } : {}),
1377
+ ...(recoveryDescriptor ? { recoveryDescriptor } : {}),
1296
1378
  });
1297
1379
  }
1298
1380
 
@@ -1369,12 +1451,12 @@ async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & {
1369
1451
  const targets = targetIndexes.map((index) => ({ index, state: steps[index]?.status === "pending" ? "scheduled" as const : "pending" as const }));
1370
1452
  if (targets.every((target) => target.state === "scheduled")) {
1371
1453
  const scheduled = { requestId, state: "scheduled" as const, deliveryStatus: "queued" as const, sourceRunId: run.id, targets };
1372
- return { content: [{ type: "text", text: `Steering scheduled for nested async run ${run.id} (request ${requestId}).` }], details: { mode: "management", results: [], steering: scheduled } };
1454
+ return { content: [{ type: "text", text: steeringReceipt(input.message, `Steering scheduled for nested async run ${run.id} (request ${requestId}).`) }], details: { mode: "management", results: [], steering: scheduled } };
1373
1455
  }
1374
1456
  const waited = await waitForSteeringAction(omitUndefinedProperties({ asyncDir, sourceRunId: run.id, requestId, timeoutMs: 3_000, signal: input.signal }));
1375
1457
  const result = waited ?? { requestId, state: "pending" as const, deliveryStatus: "queued" as const, sourceRunId: run.id, targets };
1376
1458
  const stateText = result.state === "failed" ? "failed" : result.state === "partial" ? "partial" : result.deliveryStatus === "queued" ? "queued" : result.state === "delivered" ? "delivered" : "pending";
1377
- 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 } };
1459
+ return { content: [{ type: "text", text: steeringReceipt(input.message, `Steering ${stateText} for nested async run ${run.id} (request ${requestId}).`) }], ...(result.state === "failed" || result.state === "partial" ? { isError: true } : {}), details: { mode: "management", results: [], steering: result } };
1378
1460
  }
1379
1461
 
1380
1462
  async function interruptNestedRun(target: ResolvedSubagentRunId & { kind: "nested" }): Promise<AgentToolResult<Details>> {
@@ -1404,15 +1486,150 @@ async function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "
1404
1486
  return { content: [{ type: "text", text: `Nested run ${run.id} is not a live async Pi child session with a steering inbox. action='steer' cannot target foreground nested runs.` }], isError: true, details: { mode: "management", results: [] } };
1405
1487
  }
1406
1488
 
1489
+ function externalJobOptionsEqual(left: Record<string, unknown>, right: Record<string, unknown>): boolean {
1490
+ return externalJobStableJson(left) === externalJobStableJson(right);
1491
+ }
1492
+
1493
+ function providerFollowUpSupport(providerName: string): { ok: true } | { ok: false; message: string } {
1494
+ try {
1495
+ const provider = getExternalJobProvider(providerName);
1496
+ if (!provider) return { ok: false, message: `External-job provider '${providerName}' is not registered. Load the provider package, then retry action='resume'.` };
1497
+ if (typeof provider.followUp !== "function") return { ok: false, message: `External-job provider '${providerName}' does not support follow-up. Update or reload the provider package, then retry action='resume'.` };
1498
+ return { ok: true };
1499
+ } catch (error) {
1500
+ return { ok: false, message: `External-job provider registry unavailable: ${error instanceof Error ? error.message : String(error)}` };
1501
+ }
1502
+ }
1503
+
1504
+ function externalJobFollowUpStarted(input: { sourceRunId: string; runId: string; asyncDir: string; duplicate?: boolean; interactive: boolean }): AgentToolResult<Details> {
1505
+ const lines = [
1506
+ input.duplicate ? `External-job follow-up already exists for ${input.sourceRunId}.` : `Started external-job follow-up for ${input.sourceRunId}.`,
1507
+ `Follow-up run: ${input.runId}`,
1508
+ `Async dir: ${input.asyncDir}`,
1509
+ `Status if needed: subagent({ action: "status", id: "${input.runId}" })`,
1510
+ ];
1511
+ return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n"), input.interactive) }], details: { mode: "single", results: [], asyncId: input.runId, asyncDir: input.asyncDir } };
1512
+ }
1513
+
1407
1514
  function externalRunnerControlError(asyncDir: string, action: "steer" | "resume"): AgentToolResult<Details> | undefined {
1408
1515
  const status = readStatus(asyncDir);
1409
- if (!status?.steps?.length || !status.steps.every((step) => step.runner?.type === "external-cli")) return undefined;
1516
+ if (!status?.steps?.length || !status.steps.every((step) => step.runner?.type === "external-cli" || step.runner?.type === "external-job")) return undefined;
1410
1517
  const message = action === "steer"
1411
- ? "One-shot external CLI runners do not accept live steer messages."
1412
- : "One-shot external CLI runners do not persist sessions and cannot be resumed.";
1518
+ ? "External runners do not accept live steer messages."
1519
+ : "External runners do not persist Pi sessions and cannot be resumed.";
1413
1520
  return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
1414
1521
  }
1415
1522
 
1523
+ async function resumeExternalJobFollowUp(input: {
1524
+ target: AsyncResumeSourceTarget;
1525
+ followUp: string;
1526
+ baseAgentConfig: AgentConfig;
1527
+ effectiveCwd: string;
1528
+ requestCwd: string;
1529
+ ctx: ExtensionContext;
1530
+ deps: ExecutorDeps;
1531
+ parentModel?: ParentModel;
1532
+ modelScope?: ModelScopeConfig;
1533
+ intercomBridge: IntercomBridgeState;
1534
+ parentSessionFile: string | null;
1535
+ absoluteDeadlineAt?: number;
1536
+ }): Promise<AgentToolResult<Details>> {
1537
+ if (input.target.kind === "live" || input.target.state === "running" || input.target.state === "queued") {
1538
+ return { content: [{ type: "text", text: `External-job run '${input.target.runId}' is still running. Wait for completion, then use subagent({ action: "resume", id: "${input.target.runId}", message: "..." }).` }], isError: true, details: { mode: "management", results: [] } };
1539
+ }
1540
+ const runner = input.target.runner;
1541
+ const externalJob = input.target.externalJob;
1542
+ if (runner?.type !== "external-job") return { content: [{ type: "text", text: "Internal error: external-job follow-up was requested for a non-external-job runner." }], isError: true, details: { mode: "management", results: [] } };
1543
+ if (!externalJob) return { content: [{ type: "text", text: `External-job run '${input.target.runId}' has no persisted provider metadata. Cannot follow up without the parent provider job id.` }], isError: true, details: { mode: "management", results: [] } };
1544
+ if (externalJob.provider !== runner.provider) return { content: [{ type: "text", text: `External-job run '${input.target.runId}' has mismatched provider metadata. Refusing to follow up.` }], isError: true, details: { mode: "management", results: [] } };
1545
+ if (!externalJobOptionsEqual(externalJob.options, runner.options)) return { content: [{ type: "text", text: `External-job run '${input.target.runId}' has mismatched provider options. Refusing to follow up.` }], isError: true, details: { mode: "management", results: [] } };
1546
+ if (!externalJob.providerJobId) return { content: [{ type: "text", text: `External-job run '${input.target.runId}' has no parent provider job id. Cannot follow up without reopening or redispatching, so this fails closed.` }], isError: true, details: { mode: "management", results: [] } };
1547
+ if (externalJob.state !== "completed") return { content: [{ type: "text", text: `External-job run '${input.target.runId}' provider state is ${externalJob.state}. Wait for completion, then use action='resume'.` }], isError: true, details: { mode: "management", results: [] } };
1548
+ const support = providerFollowUpSupport(runner.provider);
1549
+ if (!support.ok) return { content: [{ type: "text", text: support.message }], isError: true, details: { mode: "management", results: [] } };
1550
+
1551
+ const promptDigest = externalJobPromptDigest(input.followUp);
1552
+ const requestDigest = externalJobFollowUpRequestDigest({ provider: runner.provider, parentProviderJobId: externalJob.providerJobId, promptDigest, options: runner.options });
1553
+ const requestId = externalJobFollowUpRequestId(requestDigest);
1554
+ const runId = externalJobFollowUpRunId(requestDigest);
1555
+ const asyncDir = path.join(DIRS.async, runId);
1556
+ const currentSessionId = input.deps.state.currentSessionId;
1557
+ if (!currentSessionId) return { content: [{ type: "text", text: "External-job follow-up requires an active parent session." }], isError: true, details: { mode: "management", results: [] } };
1558
+ if (fs.existsSync(asyncDir) || fs.existsSync(resultFilePath(DIRS.results, runId))) {
1559
+ return externalJobFollowUpStarted({ sourceRunId: input.target.runId, runId, asyncDir, duplicate: true, interactive: input.ctx.hasUI });
1560
+ }
1561
+
1562
+ const depthState = checkSubagentDepth(input.deps.config.maxSubagentDepth);
1563
+ if (depthState.blocked) {
1564
+ return { content: [{ type: "text", text: `Nested subagent resume blocked (depth=${depthState.depth}, max=${depthState.maxDepth}). Complete the follow-up directly instead.` }], isError: true, details: { mode: "management", results: [] } };
1565
+ }
1566
+ const topLevelResume = depthState.depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.deps.state.workflowControllers?.has(input.target.runId);
1567
+ let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
1568
+ try {
1569
+ activeAsyncCapacity = topLevelResume ? acquireActiveAsyncCapacity({
1570
+ sessionId: currentSessionId,
1571
+ limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
1572
+ runId,
1573
+ kind: "runner",
1574
+ asyncDir,
1575
+ }, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
1576
+ } catch (error) {
1577
+ if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
1578
+ return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
1579
+ }
1580
+
1581
+ const artifactConfig: ArtifactConfig = omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: true, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
1582
+ const artifactsDir = getArtifactsDir(input.parentSessionFile, input.effectiveCwd, artifactConfig.dir);
1583
+ const parentModel = input.parentModel;
1584
+ const agentConfig: AgentConfig = {
1585
+ ...input.baseAgentConfig,
1586
+ runner: { type: "external-job", provider: runner.provider, options: runner.options },
1587
+ };
1588
+ const result = executeAsyncSingle(runId, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
1589
+ agent: input.target.agent,
1590
+ task: input.followUp,
1591
+ goal: input.followUp,
1592
+ agentConfig,
1593
+ recoveryAgentConfig: agentConfig,
1594
+ ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
1595
+ pi: input.deps.pi,
1596
+ cwd: input.requestCwd,
1597
+ currentSessionId,
1598
+ parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
1599
+ currentModelProvider: parentModel?.provider,
1600
+ currentModel: parentModel,
1601
+ modelScope: input.modelScope,
1602
+ interactive: input.ctx.hasUI,
1603
+ permissions: input.deps.config.permissions,
1604
+ }),
1605
+ cwd: input.effectiveCwd,
1606
+ artifactsDir,
1607
+ artifactConfig,
1608
+ shareEnabled: false,
1609
+ ...(input.parentSessionFile ? { sessionRoot: input.deps.getSubagentSessionRoot(input.parentSessionFile) } : {}),
1610
+ maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
1611
+ waitToolEnabled: input.deps.waitToolEnabled,
1612
+ worktreeSetupHook: input.deps.config.worktreeSetupHook,
1613
+ worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
1614
+ worktreeBaseDir: input.deps.config.worktreeBaseDir,
1615
+ controlConfig: resolveControlConfig(input.deps.config.control, undefined),
1616
+ controlIntercomTarget: input.intercomBridge.active ? input.intercomBridge.orchestratorTarget : undefined,
1617
+ childIntercomTarget: input.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
1618
+ availableModels: input.ctx.modelRegistry.getAvailable().map(toModelInfo),
1619
+ outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
1620
+ ...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
1621
+ capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(currentSessionId),
1622
+ runFanoutBudget: createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
1623
+ activeAsyncCapacity,
1624
+ externalJobFollowUp: { sourceRunId: input.target.runId, sourceStepIndex: input.target.index, parentProviderJobId: externalJob.providerJobId, requestId, requestDigest },
1625
+ }));
1626
+ if (result.isError) {
1627
+ activeAsyncCapacity?.rollback();
1628
+ return result;
1629
+ }
1630
+ return externalJobFollowUpStarted({ sourceRunId: input.target.runId, runId: result.details.asyncId ?? runId, asyncDir: result.details.asyncDir ?? asyncDir, interactive: input.ctx.hasUI });
1631
+ }
1632
+
1416
1633
  async function resumeAsyncRun(input: {
1417
1634
  params: SubagentParamsLike;
1418
1635
  requestCwd: string;
@@ -1477,11 +1694,7 @@ async function resumeAsyncRun(input: {
1477
1694
  ];
1478
1695
  target = resolveNestedResumeTarget(resolved, trustedSessionRoots);
1479
1696
  } else {
1480
- if (resolved?.kind === "async" && resolved.location.asyncDir) {
1481
- const unsupported = externalRunnerControlError(resolved.location.asyncDir, "resume");
1482
- if (unsupported) return unsupported;
1483
- }
1484
- target = resolveResumeTarget(input.params, input.deps.state, { asyncRequireSessionFile: !attachChain });
1697
+ target = resolveResumeTarget(input.params, input.deps.state, { asyncRequireSessionFile: false });
1485
1698
  }
1486
1699
  } catch (error) {
1487
1700
  const message = error instanceof Error ? error.message : String(error);
@@ -1519,10 +1732,11 @@ async function resumeAsyncRun(input: {
1519
1732
  const modelScope = discovered.modelScope;
1520
1733
  const sessionName = resolveIntercomSessionTarget(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
1521
1734
  const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
1735
+ const recoveryContext = recoveryDescriptor?.context ?? (input.params.context === "profile" ? undefined : input.params.context);
1522
1736
  const intercomBridge = resolveIntercomBridge({
1523
1737
  config: input.deps.config.intercomBridge,
1524
1738
  override: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
1525
- context: input.params.context,
1739
+ context: recoveryContext,
1526
1740
  orchestratorTarget: sessionName,
1527
1741
  });
1528
1742
  const agents = intercomBridge.active
@@ -1538,6 +1752,7 @@ async function resumeAsyncRun(input: {
1538
1752
  inheritSkills: recoveryDescriptor.inheritSkills,
1539
1753
  source: "project",
1540
1754
  filePath: recoveryDescriptor.agentFilePath ?? path.join(getProjectSubagentsDir(recoveryDescriptor.cwd), "recovery-agent"),
1755
+ ...(discovered.maxThinking ? { maxThinking: discovered.maxThinking } : {}),
1541
1756
  } : undefined);
1542
1757
  if (!baseAgentConfig) {
1543
1758
  return {
@@ -1546,6 +1761,26 @@ async function resumeAsyncRun(input: {
1546
1761
  details: { mode: "management", results: [] },
1547
1762
  };
1548
1763
  }
1764
+ if (target.source === "async" && target.runner?.type === "external-cli") {
1765
+ return { content: [{ type: "text", text: "External runners do not persist Pi sessions and cannot be resumed." }], isError: true, details: { mode: "management", results: [] } };
1766
+ }
1767
+ if (target.source === "async" && target.runner?.type === "external-job") {
1768
+ if (attachChain) return { content: [{ type: "text", text: "External-job follow-up does not support chain attachment. Use action='resume' with message instead." }], isError: true, details: { mode: "management", results: [] } };
1769
+ return resumeExternalJobFollowUp({
1770
+ target,
1771
+ followUp,
1772
+ baseAgentConfig,
1773
+ effectiveCwd,
1774
+ requestCwd: input.requestCwd,
1775
+ ctx: input.ctx,
1776
+ deps: input.deps,
1777
+ parentModel: input.parentModel,
1778
+ modelScope,
1779
+ intercomBridge,
1780
+ parentSessionFile,
1781
+ absoluteDeadlineAt: input.absoluteDeadlineAt,
1782
+ });
1783
+ }
1549
1784
 
1550
1785
  if (attachChain) {
1551
1786
  if (target.source !== "async") {
@@ -1562,7 +1797,7 @@ async function resumeAsyncRun(input: {
1562
1797
  details: { mode: "chain", results: [] },
1563
1798
  };
1564
1799
  }
1565
- const runId = randomUUID().slice(0, 8);
1800
+ const runId = randomUUID();
1566
1801
  const topLevelResume = depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
1567
1802
  let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
1568
1803
  try {
@@ -1618,6 +1853,7 @@ async function resumeAsyncRun(input: {
1618
1853
  sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
1619
1854
  chainSkills: normalized === false ? [] : (normalized ?? []),
1620
1855
  agentContract: input.params.agentContract,
1856
+ fast: input.params.fast,
1621
1857
  dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
1622
1858
  maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
1623
1859
  waitToolEnabled: input.deps.waitToolEnabled,
@@ -1630,6 +1866,7 @@ async function resumeAsyncRun(input: {
1630
1866
  globalConcurrencyLimit: input.deps.config.globalConcurrencyLimit,
1631
1867
  runFanoutBudget: createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
1632
1868
  capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
1869
+ thinkingCeiling: target.thinkingCeiling,
1633
1870
  activeAsyncCapacity,
1634
1871
  }));
1635
1872
  if (result.isError) {
@@ -1652,12 +1889,12 @@ async function resumeAsyncRun(input: {
1652
1889
  const effectiveFollowUp = [...queuedBriefs.map(({ request }) => request.message), followUp].filter(Boolean).join("\n\n");
1653
1890
  const revivalSessionFile = target.sessionFile;
1654
1891
  if (!revivalSessionFile) {
1655
- return { content: [{ type: "text", text: `Async child '${target.runId}' has no persisted session file to resume.` }], isError: true, details: { mode: "management", results: [] } };
1892
+ return { content: [{ type: "text", text: `Async run '${target.runId}' child ${target.index} does not have a persisted session file to resume from.` }], isError: true, details: { mode: "management", results: [] } };
1656
1893
  }
1657
- if (target.source === "async" && !recoveryDescriptor) {
1894
+ if (target.source === "async" && asyncReviveRequiresRecoveryDescriptor(target)) {
1658
1895
  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: [] } };
1659
1896
  }
1660
- const runId = randomUUID().slice(0, 8);
1897
+ const runId = randomUUID();
1661
1898
  const topLevelResume = depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
1662
1899
  let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
1663
1900
  try {
@@ -1718,14 +1955,20 @@ async function resumeAsyncRun(input: {
1718
1955
  sourceRunId: target.runId,
1719
1956
  ...(input.deps.state.currentSessionId ? { parentSessionId: input.deps.state.currentSessionId } : {}),
1720
1957
  },
1958
+ context: recoveryContext,
1721
1959
  modelOverride: recoveryDescriptor?.model ?? target.model,
1960
+ fast: recoveryDescriptor?.fast,
1961
+ modelOverrideFromParent: recoveryDescriptor?.modelOverrideFromParent,
1722
1962
  thinkingOverride: recoveryDescriptor?.thinking ?? target.thinking,
1963
+ thinkingCeiling: recoveryDescriptor?.thinkingCeiling ?? ("thinkingCeiling" in target ? target.thinkingCeiling : undefined),
1964
+ extensionBindings: recoveryDescriptor?.extensionBindings ?? ("extensionBindings" in target ? target.extensionBindings : undefined),
1723
1965
  outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
1724
1966
  maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
1725
1967
  waitToolEnabled: input.deps.waitToolEnabled,
1726
1968
  worktreeSetupHook: input.deps.config.worktreeSetupHook,
1727
1969
  worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
1728
1970
  worktreeBaseDir: input.deps.config.worktreeBaseDir,
1971
+ worktree: input.params.worktree === true,
1729
1972
  controlConfig: recoveryDescriptor?.controlConfig ?? resolveControlConfig(input.deps.config.control, input.params.control),
1730
1973
  intercomBridge: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
1731
1974
  controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
@@ -1769,7 +2012,7 @@ async function resumeAsyncRun(input: {
1769
2012
  const sourceStatus = readStatus(sourceAsyncDir);
1770
2013
  if (sourceStatus?.steering) {
1771
2014
  for (const brief of queuedBriefs) updateSteeringTarget(sourceStatus.steering, brief.request.id, target.index, "delivered", Date.now());
1772
- writeAtomicJson(path.join(sourceAsyncDir, "status.json"), sourceStatus);
2015
+ createCapacityResilientJsonWriter({ keepAlive: true }).write(path.join(sourceAsyncDir, "status.json"), sourceStatus);
1773
2016
  }
1774
2017
  }
1775
2018
 
@@ -1810,6 +2053,7 @@ async function resumeAsyncRun(input: {
1810
2053
  ...(completed.model ? { model: completed.model } : {}),
1811
2054
  ...(completed.attemptedModels ? { attemptedModels: completed.attemptedModels } : {}),
1812
2055
  ...(completed.modelAttempts ? { modelAttempts: completed.modelAttempts } : {}),
2056
+ ...(completed.contextOverflow ? { contextOverflow: true } : {}),
1813
2057
  ...(completed.structuredOutput !== undefined ? { structuredOutput: completed.structuredOutput } : {}),
1814
2058
  ...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
1815
2059
  ...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
@@ -1867,14 +2111,35 @@ function formatFailedSingleRunOutput(result: SingleResult, displayOutput: string
1867
2111
  return lines.join("\n");
1868
2112
  }
1869
2113
 
1870
- function createForegroundControlNotifier(data: Pick<ExecutionContextData, "controlConfig" | "intercomBridge">, deps: Pick<ExecutorDeps, "pi" | "state">): (event: ControlEvent) => void {
2114
+ function createForegroundControlNotifier(data: Pick<ExecutionContextData, "controlConfig" | "contextPolicy" | "intercomBridge" | "params">, deps: Pick<ExecutorDeps, "pi" | "state">): (event: ControlEvent) => void {
1871
2115
  return (event) => {
1872
2116
  applyControlEventToRememberedForegroundRun(deps.state, event);
2117
+ const eventBridge = intercomBridgeAppliesToAgent(data.intercomBridge, data.contextPolicy, event.agent)
2118
+ ? data.intercomBridge
2119
+ : { ...data.intercomBridge, active: false };
2120
+ const parentWorkflowRunId = data.params.workflowParentRunId;
2121
+ const asyncWorkflow = typeof parentWorkflowRunId === "string" ? deps.state.asyncJobs.get(parentWorkflowRunId) : undefined;
2122
+ const workflowKey = typeof data.params.workflowKey === "string" && data.params.workflowKey.trim()
2123
+ ? data.params.workflowKey.trim()
2124
+ : undefined;
2125
+ const enriched = workflowKey && !event.workflowKey ? { ...event, workflowKey } : event;
2126
+ if (asyncWorkflow) {
2127
+ persistAsyncWorkflowControlEvent({
2128
+ job: asyncWorkflow,
2129
+ event: enriched,
2130
+ controlConfig: data.controlConfig,
2131
+ intercomBridge: eventBridge,
2132
+ childIntercomTarget: eventBridge.active
2133
+ ? resolveSubagentIntercomTarget(enriched.runId, enriched.agent, enriched.index)
2134
+ : undefined,
2135
+ });
2136
+ }
1873
2137
  emitControlNotification({
1874
2138
  pi: deps.pi,
1875
2139
  controlConfig: data.controlConfig,
1876
- intercomBridge: data.intercomBridge,
1877
- event,
2140
+ intercomBridge: eventBridge,
2141
+ event: enriched,
2142
+ source: asyncWorkflow ? "async" : "foreground",
1878
2143
  });
1879
2144
  };
1880
2145
  }
@@ -1960,22 +2225,34 @@ async function maybeBuildForegroundIntercomReceipt(input: {
1960
2225
  };
1961
2226
  }
1962
2227
 
1963
- function canonicalizeAgentName(name: string, agents: AgentConfig[]): { name?: string; error?: string } {
2228
+ function canonicalizeAgentName(name: string, agents: AgentConfig[], diagnostics?: AgentDiscoveryDiagnostic[]): { name?: string; error?: string } {
1964
2229
  const resolved = resolveAgentName(name, agents);
2230
+ const candidates = resolved.error ? agents.filter((agent) => resolveAgentName(name, [agent]).agent) : resolved.agent;
2231
+ const diagnostic = findBlockingAgentDiagnostic(name, candidates, diagnostics);
2232
+ if (diagnostic) return { error: `Agent '${name}' has invalid configuration: ${diagnostic.error}` };
1965
2233
  if (resolved.error) return { error: resolved.error };
1966
2234
  if (!resolved.agent) return { error: `Unknown agent: ${name}` };
1967
2235
  return { name: resolved.agent.name };
1968
2236
  }
1969
2237
 
1970
- function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[]): { params?: SubagentParamsLike; error?: string } {
2238
+ function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[], diagnostics?: AgentDiscoveryDiagnostic[]): { params?: SubagentParamsLike; error?: string } {
1971
2239
  const resolve = (name: string, location?: string): { name?: string; error?: string } => {
1972
- const result = canonicalizeAgentName(name, agents);
2240
+ const result = canonicalizeAgentName(name, agents, diagnostics);
1973
2241
  return result.error && location ? { error: `${result.error} (${location})` } : result;
1974
2242
  };
1975
2243
  if (params.agent) {
1976
2244
  const result = resolve(params.agent);
1977
2245
  if (result.error) return { error: result.error };
1978
2246
  params = omitUndefinedProperties({ ...params, agent: result.name });
2247
+ const agent = agents.find((candidate) => candidate.name === result.name);
2248
+ if (params.extensionBindings !== undefined && (agent?.runner?.type === "external-cli" || agent?.runner?.type === "external-job")) return { error: `extensionBindings is not supported for runner.type='${agent.runner.type}'.` };
2249
+ }
2250
+ if (params.extensionBindings !== undefined) {
2251
+ try {
2252
+ params = { ...params, extensionBindings: normalizeExtensionBindings(params.extensionBindings)!.value };
2253
+ } catch (error) {
2254
+ return { error: error instanceof Error ? error.message : String(error) };
2255
+ }
1979
2256
  }
1980
2257
  if (params.tasks) {
1981
2258
  const tasks: TaskParam[] = [];
@@ -2173,13 +2450,44 @@ interface AgentDefaultContextPolicy {
2173
2450
  usesFork: boolean;
2174
2451
  }
2175
2452
 
2176
- function resolveAgentDefaultContextPolicy(params: SubagentParamsLike, agents: AgentConfig[]): AgentDefaultContextPolicy {
2177
- if (params.context !== undefined) {
2178
- return resolveExplicitContextPolicy(params);
2453
+ type AgentDefaultContextPolicyResult = AgentDefaultContextPolicy | { error: string };
2454
+
2455
+ function resolveAgentDefaultContextPolicy(
2456
+ params: SubagentParamsLike,
2457
+ agents: AgentConfig[],
2458
+ defaultSubagentContext: ExtensionConfig["defaultSubagentContext"],
2459
+ canUseDefaultFork = false,
2460
+ ): AgentDefaultContextPolicyResult {
2461
+ if (params.context === "profile") {
2462
+ const byName = new Map(agents.map((agent) => [agent.name, agent]));
2463
+ for (const agentName of collectRequestedAgentNames(params)) {
2464
+ const agent = byName.get(agentName);
2465
+ if (agent && agent.defaultContext === undefined) {
2466
+ return { error: `context: "profile" requires agent '${agentName}' to declare defaultContext.` };
2467
+ }
2468
+ }
2469
+ const contextForAgent = (agentName: string): ContextMode => {
2470
+ const context = byName.get(agentName)?.defaultContext;
2471
+ if (context === undefined) throw new Error(`context: "profile" requires agent '${agentName}' to declare defaultContext.`);
2472
+ return context;
2473
+ };
2474
+ const contextSummary = summarizeContextModes(collectRequestedAgentNames(params).map(contextForAgent));
2475
+ return {
2476
+ params,
2477
+ contextForAgent,
2478
+ contextSummary,
2479
+ usesFork: contextSummary === "fork" || contextSummary === "mixed",
2480
+ };
2179
2481
  }
2482
+ if (params.context === "fresh" || params.context === "fork") return resolveExplicitContextPolicy(params);
2180
2483
  const byName = new Map(agents.map((agent) => [agent.name, agent]));
2181
2484
  const contextForAgent = (agentName: string): ContextMode =>
2182
- byName.get(agentName)?.defaultContext === "fork" ? "fork" : "fresh";
2485
+ resolveSubagentLaunchContext({
2486
+ explicitContext: undefined,
2487
+ agentDefaultContext: byName.get(agentName)?.defaultContext,
2488
+ defaultSubagentContext,
2489
+ canUseImplicitFork: canUseDefaultFork,
2490
+ });
2183
2491
  const requestedAgentNames = collectRequestedAgentNames(params);
2184
2492
  const contextSummary = summarizeContextModes(requestedAgentNames.map((name) => contextForAgent(name)));
2185
2493
  const usesFork = contextSummary === "fork" || contextSummary === "mixed";
@@ -2192,7 +2500,10 @@ function resolveAgentDefaultContextPolicy(params: SubagentParamsLike, agents: Ag
2192
2500
  }
2193
2501
 
2194
2502
  function resolveExplicitContextPolicy(params: SubagentParamsLike): AgentDefaultContextPolicy {
2195
- const context = params.context === "fork" ? "fork" : "fresh";
2503
+ const context = resolveSubagentLaunchContext({
2504
+ explicitContext: params.context === "profile" ? undefined : params.context,
2505
+ canUseImplicitFork: false,
2506
+ });
2196
2507
  return {
2197
2508
  params,
2198
2509
  contextForAgent: () => context,
@@ -2213,6 +2524,31 @@ function shouldForkAgent(contextPolicy: AgentDefaultContextPolicy, agentName: st
2213
2524
  return contextPolicy.contextForAgent(agentName) === "fork";
2214
2525
  }
2215
2526
 
2527
+ function intercomBridgeAppliesToAgent(bridge: IntercomBridgeState, contextPolicy: AgentDefaultContextPolicy, agentName: string): boolean {
2528
+ if (!bridge.active) return false;
2529
+ return bridge.mode !== "fork-only" || shouldForkAgent(contextPolicy, agentName);
2530
+ }
2531
+
2532
+ function applyScopedIntercomBridgeToAgents(agents: AgentConfig[], bridge: IntercomBridgeState, contextPolicy: AgentDefaultContextPolicy): AgentConfig[] {
2533
+ if (!bridge.active) return agents;
2534
+ return agents.map((agent) => intercomBridgeAppliesToAgent(bridge, contextPolicy, agent.name)
2535
+ ? applyIntercomBridgeToAgent(agent, bridge)
2536
+ : agent);
2537
+ }
2538
+
2539
+ function resolveChildIntercomTargetFactory(bridge: IntercomBridgeState, contextPolicy: AgentDefaultContextPolicy, runId: string): ((agent: string, index: number) => string | undefined) | undefined {
2540
+ if (!bridge.active) return undefined;
2541
+ return (agent, index) => intercomBridgeAppliesToAgent(bridge, contextPolicy, agent)
2542
+ ? resolveSubagentIntercomTarget(runId, agent, index)
2543
+ : undefined;
2544
+ }
2545
+
2546
+ function resolveRunLevelIntercomTarget(bridge: IntercomBridgeState, contextPolicy: AgentDefaultContextPolicy): string | undefined {
2547
+ if (!bridge.active) return undefined;
2548
+ if (bridge.mode === "fork-only" && contextPolicy.contextSummary === "mixed") return undefined;
2549
+ return bridge.orchestratorTarget;
2550
+ }
2551
+
2216
2552
  function summarizeResultContext(details: Details, fallback: ContextSummary | undefined): ContextSummary | undefined {
2217
2553
  return summarizeContextModes(details.results.map((result) => result.context)) ?? fallback;
2218
2554
  }
@@ -2224,7 +2560,7 @@ function buildRequestedModeError(params: SubagentParamsLike, message: string): A
2224
2560
  isError: true,
2225
2561
  details: { mode: getRequestedModeLabel(params), results: [] },
2226
2562
  },
2227
- params.context,
2563
+ params.context === "profile" ? undefined : params.context,
2228
2564
  );
2229
2565
  }
2230
2566
 
@@ -2232,12 +2568,16 @@ function applySingleAgentLaunchDefaults(params: SubagentParamsLike, agents: Agen
2232
2568
  if ((params.chain?.length ?? 0) > 0 || (params.tasks?.length ?? 0) > 0 || !params.agent) return params;
2233
2569
  const agent = agents.find((candidate) => candidate.name === params.agent);
2234
2570
  if (!agent) return params;
2571
+ const parentTimeoutMs = params.timeoutMs === undefined && params.maxRuntimeMs === undefined && agent.defaultTimeoutMs === undefined && params.workflowParentDeadlineAt !== undefined
2572
+ ? Math.max(1, params.workflowParentDeadlineAt - Date.now())
2573
+ : undefined;
2235
2574
  return {
2236
2575
  ...params,
2237
2576
  ...(params.async === undefined && agent.defaultAsync !== undefined ? { async: agent.defaultAsync } : {}),
2238
2577
  ...(params.timeoutMs === undefined && params.maxRuntimeMs === undefined && agent.defaultTimeoutMs !== undefined
2239
2578
  ? { timeoutMs: agent.defaultTimeoutMs }
2240
2579
  : {}),
2580
+ ...(parentTimeoutMs !== undefined ? { timeoutMs: parentTimeoutMs } : {}),
2241
2581
  ...(params.turnBudget === undefined && agent.defaultTurnBudget !== undefined
2242
2582
  ? { turnBudget: agent.defaultTurnBudget }
2243
2583
  : {}),
@@ -2508,7 +2848,8 @@ function resolveStaticLaunchSummary(input: {
2508
2848
  thinkingOverrideForTask: ForkThinkingOverrideForTask;
2509
2849
  }): StaticLaunchSummary {
2510
2850
  const agentConfig = input.agents.find((agent) => agent.name === input.agent);
2511
- const externalRunner = agentConfig?.runner?.type === "external-cli";
2851
+ const externalRunner = agentConfig?.runner?.type === "external-cli" || agentConfig?.runner?.type === "external-job";
2852
+ const modelScopes = resolveModelScopesForAgent(input.modelScope, input.agent, input.parentModel);
2512
2853
  const model = externalRunner
2513
2854
  ? undefined
2514
2855
  : resolveEffectiveSubagentModel(
@@ -2516,8 +2857,8 @@ function resolveStaticLaunchSummary(input: {
2516
2857
  agentConfig?.model,
2517
2858
  input.parentModel,
2518
2859
  input.availableModels,
2519
- input.currentProvider,
2520
- input.modelScope === undefined ? {} : { scope: input.modelScope },
2860
+ agentConfig?.modelProvider ?? input.currentProvider,
2861
+ modelScopes.length === 0 ? {} : { scope: modelScopes },
2521
2862
  );
2522
2863
  const thinkingOverride = externalRunner ? undefined : input.thinkingOverrideForTask(input.agent, input.index, model);
2523
2864
  const thinking = externalRunner ? undefined : resolveEffectiveThinking(model, thinkingOverride ?? agentConfig?.thinking);
@@ -2677,7 +3018,68 @@ function preflightForkSessionsForStaticTasks(
2677
3018
  }
2678
3019
  }
2679
3020
 
2680
- function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentToolResult<Details> | null {
3021
+ async function waitForWorkflowAsyncSingleResult(
3022
+ params: SubagentParamsLike,
3023
+ launchResult: AgentToolResult<Details>,
3024
+ options: { runId: string; task: string; signal?: AbortSignal; state: SubagentState; kill?: ExecutorDeps["kill"] },
3025
+ ): Promise<AgentToolResult<Details>> {
3026
+ if (params.workflowAwaitAsync !== true || !launchResult.details.asyncDir) return launchResult;
3027
+ const asyncDir = launchResult.details.asyncDir;
3028
+ const resultPath = workflowAwaitedAsyncResultPath(asyncDir);
3029
+ const stopOnAbort = () => { stopAsyncRun(options.state, options.runId, options.kill, { asyncDir, resolvedId: options.runId }); };
3030
+ if (options.signal?.aborted) stopOnAbort();
3031
+ else options.signal?.addEventListener("abort", stopOnAbort, { once: true });
3032
+ let completed: Awaited<ReturnType<typeof waitForImportedAsyncRoot>>;
3033
+ try {
3034
+ completed = await waitForImportedAsyncRoot({ runId: options.runId, asyncDir, resultPath, index: 0 }, omitUndefinedProperties({
3035
+ shouldAbort: () => options.signal?.aborted === true,
3036
+ timeoutMessage: "Workflow stopped before async child completed.",
3037
+ }));
3038
+ } finally {
3039
+ options.signal?.removeEventListener("abort", stopOnAbort);
3040
+ }
3041
+ fs.rmSync(resultPath, { force: true });
3042
+ const totalCost = completed.totalCost;
3043
+ const childResult: SingleResult = omitUndefinedProperties({
3044
+ index: 0,
3045
+ agent: completed.agent,
3046
+ task: options.task,
3047
+ exitCode: completed.exitCode,
3048
+ usage: {
3049
+ input: totalCost?.inputTokens ?? 0,
3050
+ output: totalCost?.outputTokens ?? 0,
3051
+ cacheRead: 0,
3052
+ cacheWrite: 0,
3053
+ cost: totalCost?.costUsd ?? 0,
3054
+ turns: 0,
3055
+ },
3056
+ finalOutput: completed.output,
3057
+ outputState: completed.output.trim() ? "present" as const : "absent" as const,
3058
+ ...(completed.error ? { error: completed.error } : {}),
3059
+ ...(completed.timedOut ? { timedOut: true } : {}),
3060
+ ...(completed.stopped ? { stopped: true } : {}),
3061
+ ...(completed.sessionFile ? { sessionFile: completed.sessionFile } : {}),
3062
+ ...(completed.model ? { model: completed.model } : {}),
3063
+ ...(completed.attemptedModels ? { attemptedModels: completed.attemptedModels } : {}),
3064
+ ...(completed.modelAttempts ? { modelAttempts: completed.modelAttempts } : {}),
3065
+ ...(completed.contextOverflow ? { contextOverflow: true } : {}),
3066
+ ...(completed.structuredOutput !== undefined ? { structuredOutput: completed.structuredOutput } : {}),
3067
+ ...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
3068
+ ...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
3069
+ ...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
3070
+ });
3071
+ return {
3072
+ content: [{ type: "text", text: completed.output || completed.error || `Async workflow child ${options.runId} completed without output.` }],
3073
+ ...(completed.success ? {} : { isError: true }),
3074
+ details: {
3075
+ ...launchResult.details,
3076
+ runId: options.runId,
3077
+ results: [childResult],
3078
+ },
3079
+ };
3080
+ }
3081
+
3082
+ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details> | null> {
2681
3083
  const {
2682
3084
  params,
2683
3085
  effectiveCwd,
@@ -2701,27 +3103,6 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2701
3103
  const hasSingle = !hasChain && !hasTasks && Boolean(params.agent);
2702
3104
  if (!effectiveAsync) return null;
2703
3105
 
2704
- if (hasChain && params.chain) {
2705
- const chainWorktreeTaskCwdError = buildChainWorktreeTaskCwdError(params.chain as ChainStep[], effectiveCwd);
2706
- if (chainWorktreeTaskCwdError) {
2707
- return {
2708
- content: [{ type: "text", text: chainWorktreeTaskCwdError }],
2709
- isError: true,
2710
- details: { mode: "chain" as const, results: [] },
2711
- };
2712
- }
2713
- }
2714
-
2715
- if (hasTasks && params.tasks) {
2716
- const maxParallelTasks = resolveTopLevelParallelMaxTasks(deps.config.parallel?.maxTasks);
2717
- if (params.tasks.length > maxParallelTasks) {
2718
- return buildParallelModeError(`Max ${maxParallelTasks} tasks`);
2719
- }
2720
- if (params.worktree) {
2721
- const worktreeTaskCwdError = buildParallelWorktreeTaskCwdError(params.tasks, effectiveCwd);
2722
- if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
2723
- }
2724
- }
2725
3106
 
2726
3107
  if (!isAsyncAvailable()) {
2727
3108
  return {
@@ -2746,121 +3127,9 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2746
3127
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
2747
3128
  const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
2748
3129
  const currentProvider = parentModel?.provider;
2749
- const controlIntercomTarget = intercomBridge.active ? intercomBridge.orchestratorTarget : undefined;
2750
- const childIntercomTarget = intercomBridge.active ? (agent: string, index: number) => resolveSubagentIntercomTarget(id, agent, index) : undefined;
2751
-
2752
- if (hasTasks && params.tasks) {
2753
- const skillOverrides = params.tasks.map((task) => normalizeSkillInput(task.skill));
2754
- const parallelTasks = params.tasks.map((task, index) => compactOptional<ParallelTaskItem>({
2755
- agent: task.agent,
2756
- task: shouldForkAgent(contextPolicy, task.agent) ? wrapForkTask(task.task) : task.task,
2757
- cwd: task.cwd,
2758
- ...(task.model !== undefined ? { model: task.model } : {}),
2759
- ...(skillOverrides[index] !== undefined ? { skill: skillOverrides[index] } : {}),
2760
- ...(task.output !== undefined && task.output !== true ? { output: task.output } : {}),
2761
- ...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
2762
- ...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
2763
- ...(task.progress !== undefined ? { progress: task.progress } : {}),
2764
- ...(task.toolBudget !== undefined ? { toolBudget: task.toolBudget } : {}),
2765
- ...(task.outputSchema !== undefined ? { outputSchema: task.outputSchema } : {}),
2766
- ...(task.agentContract !== undefined ? { agentContract: task.agentContract } : {}),
2767
- ...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
2768
- }));
2769
- return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
2770
- chain: [compactOptional<ParallelStep>({
2771
- parallel: parallelTasks,
2772
- concurrency: resolveTopLevelParallelConcurrency(params.concurrency, deps.config.parallel?.concurrency),
2773
- worktree: params.worktree,
2774
- })],
2775
- resultMode: "parallel",
2776
- goal: params.tasks[0]?.task ?? "",
2777
- agents,
2778
- ctx: asyncCtx,
2779
- availableModels,
2780
- cwd: effectiveCwd,
2781
- maxOutput: params.maxOutput,
2782
- artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
2783
- artifactConfig,
2784
- shareEnabled,
2785
- sessionRoot,
2786
- chainSkills: [],
2787
- activeAsyncCapacity: data.activeAsyncCapacity,
2788
- sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index, task.model)),
2789
- thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, task.model)),
2790
- contextForAgent: contextPolicy.contextForAgent,
2791
- maxSubagentDepth: currentMaxSubagentDepth,
2792
- waitToolEnabled: deps.waitToolEnabled,
2793
- worktreeSetupHook: deps.config.worktreeSetupHook,
2794
- worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
2795
- worktreeBaseDir: deps.config.worktreeBaseDir,
2796
- controlConfig,
2797
- agentContract: params.agentContract,
2798
- controlIntercomTarget,
2799
- childIntercomTarget,
2800
- nestedRoute,
2801
- timeoutMs: data.timeoutMs,
2802
- turnBudget: data.turnBudget,
2803
- toolBudget: data.toolBudget,
2804
- usageBudget: data.usageBudget,
2805
- configToolBudget: data.configToolBudget,
2806
- callToolTimeoutMs: data.params?.toolTimeoutMs,
2807
- configToolTimeoutMs: data.configToolTimeoutMs,
2808
- capabilityCeiling: data.capabilityCeiling,
2809
- runFanoutBudget: data.runFanoutBudget,
2810
- globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
2811
- parentWorkflowRunId: params.workflowParentRunId,
2812
- workflowKey: params.workflowKey,
2813
- }));
2814
- }
3130
+ const controlIntercomTarget = resolveRunLevelIntercomTarget(intercomBridge, contextPolicy);
3131
+ const childIntercomTarget = resolveChildIntercomTargetFactory(intercomBridge, contextPolicy, id);
2815
3132
 
2816
- if (hasChain && params.chain) {
2817
- const normalized = normalizeSkillInput(params.skill);
2818
- const chainSkills = normalized === false ? [] : (normalized ?? []);
2819
- const rawChain = params.chain as ChainStep[];
2820
- const chain = wrapChainTasksForFork(rawChain, contextPolicy);
2821
- return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
2822
- chain,
2823
- task: params.task,
2824
- goal: resolveAsyncEventGoal(params.task, rawChain),
2825
- agents,
2826
- ctx: asyncCtx,
2827
- availableModels,
2828
- cwd: effectiveCwd,
2829
- maxOutput: params.maxOutput,
2830
- artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
2831
- artifactConfig,
2832
- shareEnabled,
2833
- sessionRoot,
2834
- chainSkills,
2835
- activeAsyncCapacity: data.activeAsyncCapacity,
2836
- sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
2837
- thinkingOverridesByFlatIndex: collectChainThinkingOverrides(chain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
2838
- contextForAgent: contextPolicy.contextForAgent,
2839
- dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
2840
- maxSubagentDepth: currentMaxSubagentDepth,
2841
- waitToolEnabled: deps.waitToolEnabled,
2842
- worktreeSetupHook: deps.config.worktreeSetupHook,
2843
- worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
2844
- worktreeBaseDir: deps.config.worktreeBaseDir,
2845
- controlConfig,
2846
- agentContract: params.agentContract,
2847
- controlIntercomTarget,
2848
- childIntercomTarget,
2849
- nestedRoute,
2850
- timeoutMs: data.timeoutMs,
2851
- turnBudget: data.turnBudget,
2852
- toolBudget: data.toolBudget,
2853
- usageBudget: data.usageBudget,
2854
- configToolBudget: data.configToolBudget,
2855
- callToolTimeoutMs: data.params?.toolTimeoutMs,
2856
- configToolTimeoutMs: data.configToolTimeoutMs,
2857
- capabilityCeiling: data.capabilityCeiling,
2858
- runFanoutBudget: data.runFanoutBudget,
2859
- globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
2860
- parentWorkflowRunId: params.workflowParentRunId,
2861
- workflowKey: params.workflowKey,
2862
- }));
2863
- }
2864
3133
 
2865
3134
  if (hasSingle) {
2866
3135
  const a = agents.find((x) => x.name === params.agent);
@@ -2873,17 +3142,22 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2873
3142
  }
2874
3143
  const rawOutput = params.output !== undefined ? params.output : a.output;
2875
3144
  const effectiveOutput = normalizeSingleOutputOverride(rawOutput, a.output);
2876
- const effectiveOutputMode = params.outputMode ?? "inline";
3145
+ const effectiveOutputMode = params.outputMode ?? a.outputMode ?? "inline";
2877
3146
  const normalizedSkills = normalizeSkillInput(params.skill);
2878
3147
  const skills = normalizedSkills === false ? [] : normalizedSkills;
2879
3148
  const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
2880
- const externalRunnerWithoutExplicitModel = a.runner?.type === "external-cli"
3149
+ const externalRunnerWithoutExplicitModel = (a.runner?.type === "external-cli" || a.runner?.type === "external-job")
2881
3150
  && params.model === undefined
2882
3151
  && (a.model === undefined || (a.modelSource?.type === "subagents.defaultModel" && a.model === a.modelSource.model));
2883
- const modelOverride = a.runner?.type === "external-cli"
3152
+ if ((a.runner?.type === "external-cli" || a.runner?.type === "external-job") && (params.fast ?? a.fast) === true) {
3153
+ return buildRequestedModeError(params, `Agent '${a.name}' uses runner.type='${a.runner.type}' and does not support fast mode.`);
3154
+ }
3155
+ const modelScopes = resolveModelScopesForAgent(data.modelScope, a.name, parentModel);
3156
+ const modelOverride = a.runner?.type === "external-cli" || a.runner?.type === "external-job"
2884
3157
  ? params.model ?? (externalRunnerWithoutExplicitModel ? undefined : a.model)
2885
- : resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
2886
- return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
3158
+ : resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, a.modelProvider ?? currentProvider, modelScopes.length === 0 ? {} : { scope: modelScopes });
3159
+ const modelOverrideFromParent = inheritsParentModel(params.model as string | undefined, a.model, parentModel);
3160
+ const asyncResult = executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
2887
3161
  agent: params.agent!,
2888
3162
  task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
2889
3163
  goal: params.task ?? "",
@@ -2898,7 +3172,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2898
3172
  shareEnabled,
2899
3173
  activeAsyncCapacity: data.activeAsyncCapacity,
2900
3174
  sessionRoot,
2901
- sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
3175
+ sessionFile: sessionFileForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
2902
3176
  context: contextPolicy.contextForAgent(params.agent!),
2903
3177
  skills,
2904
3178
  output: effectiveOutput,
@@ -2906,9 +3180,13 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2906
3180
  ...(params.reads !== undefined ? { reads: params.reads } : {}),
2907
3181
  outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
2908
3182
  modelOverride,
2909
- thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride),
3183
+ fast: params.fast,
3184
+ modelOverrideFromParent,
3185
+ thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
3186
+ thinkingCeiling: a.maxThinking,
2910
3187
  maxSubagentDepth,
2911
3188
  waitToolEnabled: deps.waitToolEnabled,
3189
+ ...(params.worktree === true ? { worktree: true } : {}),
2912
3190
  worktreeSetupHook: deps.config.worktreeSetupHook,
2913
3191
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
2914
3192
  worktreeBaseDir: deps.config.worktreeBaseDir,
@@ -2919,6 +3197,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2919
3197
  nestedRoute,
2920
3198
  agentContract: params.agentContract,
2921
3199
  structuredOutputSchema: params.outputSchema,
3200
+ extensionBindings: params.extensionBindings,
2922
3201
  acceptance: params.acceptance,
2923
3202
  timeoutMs: data.timeoutMs,
2924
3203
  turnBudget: data.turnBudget,
@@ -2931,314 +3210,19 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2931
3210
  runFanoutBudget: data.runFanoutBudget,
2932
3211
  parentWorkflowRunId: params.workflowParentRunId,
2933
3212
  workflowKey: params.workflowKey,
3213
+ workflowAwaitAsync: params.workflowAwaitAsync,
2934
3214
  }));
3215
+ return waitForWorkflowAsyncSingleResult(params, asyncResult, { runId: id, task: params.task ?? "", signal: data.signal, state: deps.state, kill: deps.kill });
2935
3216
  }
2936
3217
 
2937
3218
  return null;
2938
3219
  }
2939
3220
 
2940
- async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
2941
- const {
2942
- params,
2943
- effectiveCwd,
2944
- agents,
2945
- ctx,
2946
- signal,
2947
- runId,
2948
- shareEnabled,
2949
- sessionDirForIndex,
2950
- sessionFileForIndex,
2951
- sessionFileForTask,
2952
- thinkingOverrideForTask,
2953
- artifactsDir,
2954
- artifactConfig,
2955
- onUpdate,
2956
- sessionRoot,
2957
- controlConfig,
2958
- contextPolicy,
2959
- } = data;
2960
- const onControlEvent = createForegroundControlNotifier(data, deps);
2961
- const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
2962
- const foregroundControl = deps.state.foregroundControls.get(runId);
2963
- const normalized = normalizeSkillInput(params.skill);
2964
- const chainSkills = normalized === false ? [] : (normalized ?? []);
2965
- const chain = wrapChainTasksForFork(params.chain as ChainStep[], contextPolicy);
2966
- const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
2967
- const chainCtx = normalizeParentModel(ctx.model) || !data.parentModel ? ctx : ({ ...ctx, model: data.parentModel } as ExtensionContext);
2968
- const chainResult = await executeChain(compactOptional<Parameters<typeof executeChain>[0]>({
2969
- chain,
2970
- task: params.task,
2971
- agents,
2972
- ctx: chainCtx,
2973
- modelScope: data.modelScope,
2974
- intercomEvents: deps.pi.events,
2975
- signal,
2976
- runId,
2977
- cwd: effectiveCwd,
2978
- shareEnabled,
2979
- sessionDirForIndex,
2980
- sessionFileForIndex,
2981
- sessionFileForTask,
2982
- thinkingOverrideForTask,
2983
- contextForAgent: contextPolicy.contextForAgent,
2984
- artifactsDir,
2985
- artifactConfig,
2986
- includeProgress: params.includeProgress,
2987
- clarify: params.clarify,
2988
- onUpdate,
2989
- onControlEvent,
2990
- controlConfig,
2991
- agentContract: params.agentContract,
2992
- childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
2993
- orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
2994
- foregroundControl,
2995
- nestedRoute: foregroundControl?.nestedRoute,
2996
- chainSkills,
2997
- chainDir: params.chainDir ?? getChainRunsDir(effectiveCwd, artifactConfig.dir),
2998
- dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
2999
- maxSubagentDepth: currentMaxSubagentDepth,
3000
- worktreeSetupHook: deps.config.worktreeSetupHook,
3001
- worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
3002
- worktreeBaseDir: deps.config.worktreeBaseDir,
3003
- timeoutMs: data.timeoutMs,
3004
- deadlineAt: data.deadlineAt,
3005
- turnBudget: data.turnBudget,
3006
- onDetachedExit: (index, result) => {
3007
- try {
3008
- updateRememberedForegroundChild(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, index, result, events: deps.pi.events });
3009
- } finally {
3010
- removeForegroundControlIfIdle(deps.state, runId);
3011
- }
3012
- },
3013
- onForegroundChildSettled: () => {
3014
- removeForegroundControlIfIdle(deps.state, runId);
3015
- },
3016
- toolBudget: data.toolBudget,
3017
- usageBudget: data.usageBudget,
3018
- configToolBudget: data.configToolBudget,
3019
- callToolTimeoutMs: data.params?.toolTimeoutMs,
3020
- configToolTimeoutMs: data.configToolTimeoutMs,
3021
- permissions: deps.config.permissions,
3022
- globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
3023
- capabilityCeiling: data.capabilityCeiling,
3024
- runFanoutBudget: data.runFanoutBudget,
3025
- }));
3026
-
3027
- if (chainResult.requestedAsync) {
3028
- if (!isAsyncAvailable()) {
3029
- return {
3030
- content: [{ type: "text", text: "Background mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the pi-subagents package dependencies are installed." }],
3031
- isError: true,
3032
- details: { mode: "chain" as const, results: [] },
3033
- };
3034
- }
3035
- const id = data.asyncRunId;
3036
- const capacity = acquireClarifiedBackgroundCapacity(data, deps, "chain", id);
3037
- if (capacity.errorResult) return capacity.errorResult;
3038
- const parentModel = data.parentModel;
3039
- const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
3040
- pi: deps.pi,
3041
- cwd: ctx.cwd,
3042
- currentSessionId: data.parentSessionId!,
3043
- parentSessionId: data.parentPiSessionId,
3044
- currentModelProvider: parentModel?.provider,
3045
- currentModel: parentModel,
3046
- modelScope: data.modelScope,
3047
- interactive: ctx.hasUI,
3048
- permissions: deps.config.permissions,
3049
- });
3050
- const rawAsyncChain = chainResult.requestedAsync.chain;
3051
- const asyncChain = wrapChainTasksForFork(rawAsyncChain, contextPolicy);
3052
- const firstAgent = firstChainAgent(rawAsyncChain);
3053
- try {
3054
- return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
3055
- chain: asyncChain,
3056
- task: params.task,
3057
- goal: resolveAsyncEventGoal(params.task, rawAsyncChain, firstAgent ? shouldForkAgent(contextPolicy, firstAgent) : false),
3058
- agents,
3059
- ctx: asyncCtx,
3060
- availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
3061
- cwd: effectiveCwd,
3062
- maxOutput: params.maxOutput,
3063
- artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
3064
- artifactConfig,
3065
- shareEnabled,
3066
- sessionRoot,
3067
- chainSkills: chainResult.requestedAsync.chainSkills,
3068
- activeAsyncCapacity: capacity.activeAsyncCapacity,
3069
- sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
3070
- thinkingOverridesByFlatIndex: collectChainThinkingOverrides(asyncChain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
3071
- contextForAgent: contextPolicy.contextForAgent,
3072
- dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
3073
- maxSubagentDepth: currentMaxSubagentDepth,
3074
- waitToolEnabled: deps.waitToolEnabled,
3075
- worktreeSetupHook: deps.config.worktreeSetupHook,
3076
- worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
3077
- worktreeBaseDir: deps.config.worktreeBaseDir,
3078
- controlConfig,
3079
- agentContract: params.agentContract,
3080
- controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
3081
- childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
3082
- nestedRoute: data.nestedRoute,
3083
- timeoutMs: data.timeoutMs,
3084
- turnBudget: data.turnBudget,
3085
- toolBudget: data.toolBudget,
3086
- usageBudget: data.usageBudget,
3087
- configToolBudget: data.configToolBudget,
3088
- callToolTimeoutMs: data.params?.toolTimeoutMs,
3089
- configToolTimeoutMs: data.configToolTimeoutMs,
3090
- capabilityCeiling: data.capabilityCeiling,
3091
- runFanoutBudget: data.runFanoutBudget,
3092
- globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
3093
- }));
3094
- } finally {
3095
- finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
3096
- }
3097
- }
3098
-
3099
- const rawChainDetails = chainResult.details ? compactOptional<Details>({ ...chainResult.details, runId, timeoutMs: data.timeoutMs }) : undefined;
3100
- if (foregroundControl && rawChainDetails) {
3101
- updateForegroundNestedProjection(foregroundControl);
3102
- attachRootChildrenToSteps(runId, rawChainDetails.results, foregroundControl.nestedChildren);
3103
- rawChainDetails.totalCost = sumResultsCost(rawChainDetails.results);
3104
- const usageBudget = usageBudgetState(data.usageBudget, rawChainDetails.totalCost);
3105
- if (usageBudget === undefined) delete rawChainDetails.usageBudget;
3106
- else rawChainDetails.usageBudget = usageBudget;
3107
- }
3108
- const chainDetails = rawChainDetails ? compactForegroundDetails(rawChainDetails) : undefined;
3109
- if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, results: chainDetails.results, checkpoint: chainDetails.checkpoint });
3110
- const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached)
3111
- ? await maybeBuildForegroundIntercomReceipt({
3112
- pi: deps.pi,
3113
- intercomBridge: data.intercomBridge,
3114
- runId,
3115
- mode: "chain",
3116
- details: chainDetails,
3117
- ...(foregroundControl?.nestedChildren?.length ? { nestedChildren: foregroundControl.nestedChildren } : {}),
3118
- })
3119
- : null;
3120
- if (intercomReceipt) {
3121
- return {
3122
- ...chainResult,
3123
- content: [{ type: "text", text: intercomReceipt.text }],
3124
- details: intercomReceipt.details,
3125
- };
3126
- }
3127
-
3128
- return chainDetails ? { ...chainResult, details: chainDetails } : chainResult;
3129
- }
3130
-
3131
- interface ForegroundParallelRunInput {
3132
- tasks: TaskParam[];
3133
- taskTexts: string[];
3134
- taskDescriptions: string[];
3135
- agents: AgentConfig[];
3136
- ctx: ExtensionContext;
3137
- state: SubagentState;
3138
- intercomEvents: IntercomEventBus;
3139
- parentSessionId: string | null;
3140
- capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
3141
- runFanoutBudget: RunFanoutBudgetDescriptor;
3142
- signal: AbortSignal;
3143
- runId: string;
3144
- sessionDirForIndex: (idx?: number) => string | undefined;
3145
- sessionFileForIndex: (idx?: number) => string | undefined;
3146
- sessionFileForTask: ForkSessionFileForTask;
3147
- thinkingOverrideForTask: ForkThinkingOverrideForTask;
3148
- shareEnabled: boolean;
3149
- artifactConfig: ArtifactConfig;
3150
- artifactsDir: string;
3151
- outputBaseDir: string;
3152
- maxOutput?: MaxOutputConfig;
3153
- paramsCwd: string;
3154
- progressDir: string;
3155
- maxSubagentDepths: number[];
3156
- waitToolEnabled?: boolean;
3157
- availableModels: ModelInfo[];
3158
- modelScope?: ModelScopeConfig;
3159
- parentModel?: ParentModel;
3160
- modelOverrides: (string | undefined)[];
3161
- behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
3162
- firstProgressIndex: number;
3163
- controlConfig: ResolvedControlConfig;
3164
- contextPolicy: AgentDefaultContextPolicy;
3165
- onControlEvent?: (event: ControlEvent) => void;
3166
- childIntercomTarget?: (agent: string, index: number) => string | undefined;
3167
- orchestratorIntercomTarget?: string;
3168
- foregroundControl?: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never;
3169
- concurrencyLimit: number;
3170
- globalSemaphore?: Semaphore;
3171
- liveResults: (SingleResult | undefined)[];
3172
- liveProgress: (AgentProgress | undefined)[];
3173
- onUpdate?: (r: AgentToolResult<Details>) => void;
3174
- worktreeSetup?: WorktreeSetup;
3175
- timeoutMs?: number;
3176
- deadlineAt?: number;
3177
- /** Per-call per-tool timeout, resolved separately for each task's agent. */
3178
- toolTimeoutMs?: number;
3179
- /** Raw global config.toolTimeoutMs, used by each task's resolver. */
3180
- configToolTimeoutMs?: number;
3181
- turnBudget?: ResolvedTurnBudget;
3182
- usageBudget?: UsageBudgetConfig;
3183
- toolBudgets: (ResolvedToolBudget | undefined)[];
3184
- agentContract?: AgentContract;
3185
- permissions?: ExtensionConfig["permissions"];
3186
- }
3187
- function buildParallelModeError(message: string): AgentToolResult<Details> {
3188
- return {
3189
- content: [{ type: "text", text: message }],
3190
- isError: true,
3191
- details: { mode: "parallel" as const, results: [] },
3192
- };
3193
- }
3194
-
3195
- function acquireClarifiedBackgroundCapacity(
3196
- data: ExecutionContextData,
3197
- deps: ExecutorDeps,
3198
- mode: "single" | "parallel" | "chain",
3199
- asyncRunId: string,
3200
- ): { activeAsyncCapacity?: ActiveAsyncCapacityHandle; errorResult?: AgentToolResult<Details> } {
3201
- if (!data.topLevelAsyncCapacityEligible || !data.parentSessionId) return {};
3202
- const liveWorkflowRunIds = new Set(deps.state.workflowControllers?.keys() ?? []);
3203
- const activeLimit = resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession);
3204
- try {
3205
- const activeAsyncCapacity = acquireActiveAsyncCapacity({
3206
- sessionId: data.parentSessionId,
3207
- limit: activeLimit,
3208
- runId: asyncRunId,
3209
- kind: "runner",
3210
- asyncDir: path.join(DIRS.async, asyncRunId),
3211
- }, { liveWorkflowRunIds });
3212
- deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(data.parentSessionId, activeLimit, { liveWorkflowRunIds });
3213
- return { activeAsyncCapacity };
3214
- } catch (error) {
3215
- if (error instanceof ActiveAsyncCapacityError) {
3216
- deps.state.activeAsyncCapacity = error.snapshot;
3217
- return { errorResult: { content: [{ type: "text", text: error.message }], isError: true, details: { mode, results: [], activeAsyncCapacity: error.snapshot } } };
3218
- }
3219
- throw error;
3220
- }
3221
- }
3222
-
3223
- function finalizeClarifiedBackgroundCapacity(
3224
- data: ExecutionContextData,
3225
- deps: ExecutorDeps,
3226
- activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined,
3227
- ): void {
3228
- if (!activeAsyncCapacity || !data.parentSessionId) return;
3229
- if (!activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
3230
- deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(
3231
- data.parentSessionId,
3232
- resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession),
3233
- { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) },
3234
- );
3235
- }
3236
-
3237
- function createParallelWorktreeSetup(
3221
+ function createSingleWorktreeSetup(
3238
3222
  enabled: boolean | undefined,
3239
3223
  cwd: string,
3240
3224
  runId: string,
3241
- tasks: TaskParam[],
3225
+ agent: string,
3242
3226
  setupHook: ExtensionConfig["worktreeSetupHook"],
3243
3227
  setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
3244
3228
  baseDir: ExtensionConfig["worktreeBaseDir"],
@@ -3246,8 +3230,8 @@ function createParallelWorktreeSetup(
3246
3230
  if (!enabled) return {};
3247
3231
  try {
3248
3232
  return {
3249
- setup: createWorktrees(cwd, runId, tasks.length, omitUndefinedProperties({
3250
- agents: tasks.map((task) => task.agent),
3233
+ setup: createWorktrees(cwd, runId, 1, omitUndefinedProperties({
3234
+ agents: [agent],
3251
3235
  setupHook: setupHook
3252
3236
  ? { hookPath: setupHook, ...(setupHookTimeoutMs === undefined ? {} : { timeoutMs: setupHookTimeoutMs }) }
3253
3237
  : undefined,
@@ -3256,27 +3240,25 @@ function createParallelWorktreeSetup(
3256
3240
  };
3257
3241
  } catch (error) {
3258
3242
  const message = error instanceof Error ? error.message : String(error);
3259
- return { errorResult: buildParallelModeError(message) };
3243
+ return { errorResult: { content: [{ type: "text", text: message }], isError: true, details: { mode: "single", results: [] } } };
3260
3244
  }
3261
3245
  }
3262
3246
 
3263
- function buildParallelWorktreeTaskCwdError(
3264
- tasks: ReadonlyArray<{ agent: string; cwd?: string }>,
3265
- sharedCwd: string,
3266
- ): string | undefined {
3267
- const conflict = findWorktreeTaskCwdConflict(tasks, sharedCwd);
3268
- if (!conflict) return undefined;
3269
- return formatWorktreeTaskCwdConflict(conflict, sharedCwd);
3270
- }
3271
-
3272
3247
  function resolveConfiguredSingleRunOutputBaseDir(deps: ExecutorDeps): string | undefined {
3273
3248
  return deps.config.singleRunOutputBaseDir
3274
3249
  ? path.resolve(deps.expandTilde(deps.config.singleRunOutputBaseDir))
3275
3250
  : undefined;
3276
3251
  }
3277
3252
 
3253
+ export function sanitizeRunPathSegment(value: string, maxBytes = 120): string {
3254
+ const sanitized = value.trim().replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
3255
+ if (!sanitized) return "unknown";
3256
+ if (Buffer.byteLength(sanitized, "utf-8") <= maxBytes) return sanitized;
3257
+ return sanitized.slice(0, maxBytes).replace(/_+$/, "") || "unknown";
3258
+ }
3259
+
3278
3260
  function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string, runId: string): string {
3279
- return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", runId);
3261
+ return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", sanitizeRunPathSegment(runId));
3280
3262
  }
3281
3263
 
3282
3264
  function resolveWorkflowAggregateOutputPath(
@@ -3284,9 +3266,7 @@ function resolveWorkflowAggregateOutputPath(
3284
3266
  ctxCwd: string,
3285
3267
  workflowCwd: string,
3286
3268
  outputBaseDir: string,
3287
- configuredOutputBaseDir: string | undefined,
3288
3269
  ): string | undefined {
3289
- if (typeof output === "string" && output && !path.isAbsolute(output) && !configuredOutputBaseDir) return path.resolve(workflowCwd, output);
3290
3270
  return resolveSingleOutputPath(output, ctxCwd, workflowCwd, outputBaseDir);
3291
3271
  }
3292
3272
 
@@ -3295,12 +3275,13 @@ function workflowChildDefaultOutput(aggregateOutputPath: string | undefined, art
3295
3275
  const parsed = path.parse(aggregateOutputPath);
3296
3276
  return path.join(parsed.dir, `${parsed.name}.${workflowKey}${parsed.ext || ".md"}`);
3297
3277
  }
3298
- return path.join(artifactsDir, "outputs", workflowRunId, `${workflowKey}.md`);
3278
+ return path.join(artifactsDir, "outputs", sanitizeRunPathSegment(workflowRunId), `${workflowKey}.md`);
3299
3279
  }
3300
3280
 
3301
- function writeWorkflowAggregateOutput(outputPath: string | undefined, text: string): string | undefined {
3281
+ function writeWorkflowAggregateOutput(outputPath: string | undefined, text: string, producedChildOutputPaths: ReadonlySet<string>): string | undefined {
3302
3282
  if (!outputPath) return undefined;
3303
3283
  try {
3284
+ if (producedChildOutputPaths.has(outputPath)) return undefined;
3304
3285
  fs.mkdirSync(path.dirname(outputPath), { recursive: true });
3305
3286
  fs.writeFileSync(outputPath, text, "utf-8");
3306
3287
  return undefined;
@@ -3321,23 +3302,41 @@ function resolveWorkflowChildOutputPath(input: {
3321
3302
  aggregateOutputPath?: string;
3322
3303
  configuredOutputBaseDir?: string;
3323
3304
  discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
3305
+ agents: AgentConfig[];
3324
3306
  workflowAgentScope?: unknown;
3307
+ state?: SubagentState;
3325
3308
  key: string;
3326
3309
  params: Record<string, unknown>;
3327
- }): string | undefined {
3310
+ }): { path?: string; inherited: boolean } {
3311
+ if (typeof input.params.resume === "string") {
3312
+ if (!input.state) return { path: undefined, inherited: false };
3313
+ const index = input.params.index;
3314
+ const target = resolveResumeTarget({
3315
+ id: input.params.resume.trim(),
3316
+ ...(typeof index === "number" && Number.isInteger(index) ? { index } : {}),
3317
+ }, input.state);
3318
+ return { path: "recoveryDescriptor" in target ? target.recoveryDescriptor?.outputPath : undefined, inherited: false };
3319
+ }
3328
3320
  const rawOutput = input.params.output;
3329
3321
  const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
3330
3322
  const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
3331
3323
  const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
3332
- const agents = input.discoverAgents(childCwd, agentScope).agents;
3333
- const agent = typeof input.params.agent === "string" ? resolveAgentName(input.params.agent, agents).agent : undefined;
3324
+ const discoveredAgents = input.discoverAgents(childCwd, agentScope).agents;
3325
+ const agent = typeof input.params.agent === "string"
3326
+ ? resolveAgentName(input.params.agent, discoveredAgents).agent ?? resolveAgentName(input.params.agent, input.agents).agent
3327
+ : undefined;
3334
3328
  const agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
3335
3329
  const output = rawOutput === true || rawOutput === "true"
3336
3330
  ? agentOutput
3337
3331
  : hasExplicitOutput
3338
3332
  ? rawOutput
3339
- : workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, input.key);
3340
- return resolveSingleOutputPath(output, input.ctxCwd, childCwd, input.configuredOutputBaseDir);
3333
+ : input.aggregateOutputPath
3334
+ ? workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, input.key)
3335
+ : agentOutput;
3336
+ return {
3337
+ path: resolveSingleOutputPath(output, input.ctxCwd, childCwd, input.configuredOutputBaseDir ?? path.join(input.artifactsDir, "outputs", sanitizeRunPathSegment(input.workflowRunId))),
3338
+ inherited: !hasExplicitOutput && !input.aggregateOutputPath && agentOutput !== undefined,
3339
+ };
3341
3340
  }
3342
3341
 
3343
3342
  function workflowChildOutputClaims(input: {
@@ -3348,20 +3347,37 @@ function workflowChildOutputClaims(input: {
3348
3347
  aggregateOutputPath?: string;
3349
3348
  configuredOutputBaseDir?: string;
3350
3349
  discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
3350
+ agents: AgentConfig[];
3351
3351
  workflowAgentScope?: unknown;
3352
+ state: SubagentState;
3352
3353
  claimedOutputPaths: Map<string, string>;
3353
3354
  entries: Array<{ key: string; params: Record<string, unknown> }>;
3354
- }): { error?: string; claims?: Map<string, string> } {
3355
+ }): { error?: string; claims?: Map<string, string>; overrides?: Map<string, string> } {
3356
+ const resolvedEntries = input.entries.map(({ key, params }) => ({
3357
+ key,
3358
+ ...resolveWorkflowChildOutputPath({ ...input, key, params }),
3359
+ }));
3360
+ const paths = new Map<string, number>();
3361
+ for (const claimedPath of input.claimedOutputPaths.keys()) paths.set(claimedPath, 1);
3362
+ for (const { path: resolved } of resolvedEntries) {
3363
+ if (resolved) paths.set(resolved, (paths.get(resolved) ?? 0) + 1);
3364
+ }
3365
+ const overrides = new Map<string, string>();
3366
+ for (const entry of resolvedEntries) {
3367
+ if (entry.inherited && entry.path && (paths.get(entry.path) ?? 0) > 1) {
3368
+ const output = workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, entry.key);
3369
+ overrides.set(entry.key, output);
3370
+ entry.path = output;
3371
+ }
3372
+ }
3355
3373
  const claims = new Map(input.claimedOutputPaths);
3356
- for (const { key, params } of input.entries) {
3357
- const resolved = resolveWorkflowChildOutputPath({ ...input, key, params });
3374
+ for (const { key, path: resolved } of resolvedEntries) {
3358
3375
  if (!resolved) continue;
3359
- if (input.aggregateOutputPath && resolved === input.aggregateOutputPath) return { error: `Workflow child '${key}' output resolves to the workflow aggregate output path: ${resolved}. Use a distinct child output path.` };
3360
3376
  const previous = claims.get(resolved);
3361
3377
  if (previous) return { error: `Workflow children '${previous}' and '${key}' resolve output to the same path: ${resolved}. Use distinct child output paths.` };
3362
3378
  claims.set(resolved, key);
3363
3379
  }
3364
- return { claims };
3380
+ return { claims, overrides };
3365
3381
  }
3366
3382
 
3367
3383
  function applyWorkflowChildOutputClaims(target: Map<string, string>, claims: Map<string, string>): void {
@@ -3375,746 +3391,86 @@ function prepareWorkflowChildLaunchParams(input: {
3375
3391
  parentWorkflowRunId: string;
3376
3392
  workflowKey: string;
3377
3393
  ctxCwd: string;
3378
- workflowCwd: string;
3379
- artifactsDir: string;
3380
- aggregateOutputPath?: string;
3381
- configuredOutputBaseDir?: string;
3382
- discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
3383
- workflowAgentScope?: unknown;
3384
- options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor };
3385
- }): SubagentParamsLike {
3386
- let childParams = input.childParams;
3387
- if (input.childParams.output === undefined && input.childParams.resume === undefined) {
3388
- childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
3389
- } else if (input.childParams.resume === undefined) {
3390
- const resolvedOutput = resolveWorkflowChildOutputPath({ ctxCwd: input.ctxCwd, workflowCwd: input.workflowCwd, artifactsDir: input.artifactsDir, workflowRunId: input.parentWorkflowRunId, aggregateOutputPath: input.aggregateOutputPath, configuredOutputBaseDir: input.configuredOutputBaseDir, discoverAgents: input.discoverAgents, workflowAgentScope: input.workflowAgentScope, key: input.workflowKey, params: input.childParams });
3391
- if (resolvedOutput) childParams = { ...input.childParams, output: resolvedOutput };
3392
- }
3393
- return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, input.options);
3394
- }
3395
-
3396
- function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string): string | undefined {
3397
- for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) {
3398
- const step = chain[stepIndex]!;
3399
- if (!isParallelStep(step) || !step.worktree) continue;
3400
- const stepCwd = resolveChildCwd(sharedCwd, step.cwd);
3401
- const conflict = findWorktreeTaskCwdConflict(step.parallel, stepCwd);
3402
- if (!conflict) continue;
3403
- const detail = formatWorktreeTaskCwdConflict(conflict, stepCwd);
3404
- return `parallel chain step ${stepIndex + 1}: ${detail}`;
3405
- }
3406
- return undefined;
3407
- }
3408
-
3409
- function resolveParallelTaskCwd(
3410
- task: TaskParam,
3411
- paramsCwd: string,
3412
- worktreeSetup: WorktreeSetup | undefined,
3413
- index: number,
3414
- ): string {
3415
- if (worktreeSetup) return worktreeSetup.worktrees[index]!.agentCwd;
3416
- return resolveChildCwd(paramsCwd, task.cwd);
3417
- }
3418
-
3419
- function finalizeParallelWorktreeHandoff(input: {
3420
- worktreeSetup: WorktreeSetup;
3421
- artifactsDir: string;
3422
- runId: string;
3423
- cwd: string;
3424
- tasks: TaskParam[];
3425
- results: SingleResult[];
3426
- }): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
3427
- const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
3428
- const diffs = diffWorktrees(input.worktreeSetup, input.tasks.map((task) => task.agent), diffsDir);
3429
- const diffSummary = formatWorktreeDiffSummary(diffs);
3430
- const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
3431
- const handoff = {
3432
- manifestPath,
3433
- runId: input.runId,
3434
- mode: "parallel" as const,
3435
- source: "foreground" as const,
3436
- cwd: input.cwd,
3437
- stepIndex: 0,
3438
- flatStartIndex: 0,
3439
- setup: input.worktreeSetup,
3440
- diffs,
3441
- results: input.results.map((result) => ({
3442
- agent: result.agent,
3443
- status: resolveSubagentResultStatus(omitUndefinedProperties({
3444
- exitCode: result.exitCode,
3445
- interrupted: result.interrupted,
3446
- detached: result.detached,
3447
- state: result.stopped ? "stopped" : undefined,
3448
- processSignal: result.processSignal,
3449
- timedOut: result.timedOut,
3450
- stopped: result.stopped,
3451
- turnBudgetExceeded: result.turnBudgetExceeded,
3452
- })),
3453
- summary: resultSummaryForIntercom(result),
3454
- ...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
3455
- ...(result.structuredOutput !== undefined ? { structuredOutput: result.structuredOutput } : {}),
3456
- ...(result.structuredOutputPath ? { structuredOutputPath: result.structuredOutputPath } : {}),
3457
- ...(result.sessionFile ? { sessionPath: result.sessionFile } : {}),
3458
- })),
3459
- };
3460
- try {
3461
- writeParallelHandoffGroup(handoff);
3462
- const cleanup = cleanupWorktrees(input.worktreeSetup, { kind: "preserve", capturedDiffs: diffs, handoffManifestPath: manifestPath });
3463
- const reference = writeParallelHandoffGroup({ ...handoff, cleanup });
3464
- return {
3465
- suffix: [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n"),
3466
- reference,
3467
- };
3468
- } catch (error) {
3469
- return { suffix: [diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n") };
3470
- }
3471
- }
3472
-
3473
- function findDuplicateParallelOutputPath(input: {
3474
- tasks: TaskParam[];
3475
- behaviors: ResolvedStepBehavior[];
3476
- paramsCwd: string;
3477
- ctxCwd: string;
3478
- outputBaseDir: string;
3479
- worktreeSetup?: WorktreeSetup;
3480
- }): string | undefined {
3481
- const seen = new Map<string, { index: number; agent: string }>();
3482
- for (let index = 0; index < input.tasks.length; index++) {
3483
- const behavior = input.behaviors[index];
3484
- if (!behavior?.output) continue;
3485
- const task = input.tasks[index]!;
3486
- const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
3487
- const outputPath = resolveSingleOutputPath(behavior.output, input.ctxCwd, taskCwd, input.outputBaseDir);
3488
- if (!outputPath) continue;
3489
- const previous = seen.get(outputPath);
3490
- if (previous) {
3491
- return `Parallel tasks ${previous.index + 1} (${previous.agent}) and ${index + 1} (${task.agent}) resolve output to the same path: ${outputPath}. Use distinct output paths.`;
3492
- }
3493
- seen.set(outputPath, { index, agent: task.agent });
3494
- }
3495
- return undefined;
3496
- }
3497
-
3498
- async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Promise<SingleResult[]> {
3499
- // Pre-warm fork session files sequentially before concurrent dispatch to avoid
3500
- // races where multiple workers simultaneously try to branch the same parent session.
3501
- for (let i = 0; i < input.tasks.length; i++) {
3502
- input.sessionFileForTask(input.tasks[i]!.agent, i, input.modelOverrides[i]);
3503
- }
3504
- const completedResults: SingleResult[] = [];
3505
- if (input.foregroundControl) retainForegroundSchedulingOwner(input.foregroundControl);
3506
- return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
3507
- const budgetState = usageBudgetState(input.usageBudget, sumResultsCost(completedResults));
3508
- if (budgetState?.exhausted) {
3509
- return {
3510
- index,
3511
- agent: task.agent,
3512
- task: input.taskTexts[index] ?? "(skipped)",
3513
- exitCode: 1,
3514
- messages: [],
3515
- usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 },
3516
- error: usageBudgetExceededMessage(budgetState),
3517
- skipped: true,
3518
- } as SingleResult;
3519
- }
3520
- const behavior = input.behaviors[index];
3521
- const effectiveSkills = behavior?.skills;
3522
- const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
3523
- const readInstructions = behavior
3524
- ? buildChainInstructions({ ...behavior, output: false, progress: false }, taskCwd, false)
3525
- : { prefix: "", suffix: "" };
3526
- const progressInstructions = behavior
3527
- ? buildChainInstructions({ ...behavior, output: false, reads: false }, input.progressDir, index === input.firstProgressIndex)
3528
- : { prefix: "", suffix: "" };
3529
- const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd, input.outputBaseDir);
3530
- const agentConfig = input.agents.find((agent) => agent.name === task.agent);
3531
- const taskText = injectSingleOutputInstruction(
3532
- `${readInstructions.prefix}${input.taskTexts[index]!}${progressInstructions.suffix}`,
3533
- outputPath,
3534
- agentConfig,
3535
- );
3536
- const interruptController = new AbortController();
3537
- if (input.foregroundControl) {
3538
- const model = input.modelOverrides[index];
3539
- const thinking = resolveEffectiveThinking(model, input.thinkingOverrideForTask(task.agent, index, model));
3540
- beginForegroundChild(input.foregroundControl, {
3541
- index,
3542
- agent: task.agent,
3543
- authoredTask: input.taskDescriptions[index] ?? "",
3544
- effectivePrompt: taskText,
3545
- cwd: taskCwd,
3546
- ...(outputPath ? { outputPath } : {}),
3547
- rerun: { params: compactOptional<Record<string, unknown>>({
3548
- agent: task.agent,
3549
- task: input.taskDescriptions[index] ?? "",
3550
- async: false,
3551
- cwd: input.worktreeSetup ? undefined : taskCwd,
3552
- worktree: input.worktreeSetup ? true : false,
3553
- context: input.contextPolicy.contextForAgent(task.agent),
3554
- share: input.shareEnabled || undefined,
3555
- maxOutput: input.maxOutput,
3556
- timeoutMs: input.timeoutMs,
3557
- toolTimeoutMs: input.toolTimeoutMs,
3558
- configToolTimeoutMs: input.configToolTimeoutMs,
3559
- turnBudget: input.turnBudget,
3560
- ...(model ? { model } : {}),
3561
- ...(effectiveSkills !== undefined ? { skill: effectiveSkills } : {}),
3562
- ...(outputPath ? { output: outputPath } : {}),
3563
- ...(behavior?.outputMode !== undefined ? { outputMode: behavior.outputMode } : {}),
3564
- ...(behavior?.reads !== undefined ? { reads: behavior.reads } : {}),
3565
- ...(behavior?.progress !== undefined ? { progress: behavior.progress } : {}),
3566
- ...(task.toolBudget !== undefined ? { toolBudget: task.toolBudget } : {}),
3567
- ...(task.outputSchema !== undefined ? { outputSchema: task.outputSchema } : {}),
3568
- ...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
3569
- ...(task.agentContract !== undefined || input.agentContract !== undefined ? { agentContract: task.agentContract ?? input.agentContract } : {}),
3570
- }) },
3571
- description: `${task.agent} child`,
3572
- ...(model ? { model } : {}),
3573
- ...(thinking ? { thinking } : {}),
3574
- interrupt: () => {
3575
- if (interruptController.signal.aborted) return false;
3576
- interruptController.abort();
3577
- return true;
3578
- },
3579
- });
3580
- }
3581
- const structuredRuntime = task.outputSchema
3582
- ? createStructuredOutputRuntime(task.outputSchema, path.join(input.artifactsDir, "structured-output", input.runId))
3583
- : undefined;
3584
- let detachedReceipt = false;
3585
- const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, compactOptional<Parameters<typeof runSync>[4]>({
3586
- permissions: input.permissions,
3587
- parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
3588
- llmIntentArbiter: createTaskMutationArbiter(input.ctx),
3589
- ...workflowForegroundSteeringLaunchOptions(input.foregroundControl, index),
3590
- context: input.contextPolicy.contextForAgent(task.agent),
3591
- runFanoutBudget: { ...input.runFanoutBudget, parentPath: `${input.runFanoutBudget.parentPath ? `${input.runFanoutBudget.parentPath}/` : ""}tasks[${index}]` },
3592
- cwd: taskCwd,
3593
- signal: input.signal,
3594
- interruptSignal: interruptController.signal,
3595
- allowIntercomDetach: agentConfig?.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
3596
- intercomEvents: input.intercomEvents,
3597
- runId: input.runId,
3598
- index,
3599
- sessionDir: input.sessionDirForIndex(index),
3600
- sessionFile: input.sessionFileForTask(task.agent, index, input.modelOverrides[index]),
3601
- share: input.shareEnabled,
3602
- artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
3603
- artifactConfig: input.artifactConfig,
3604
- maxOutput: input.maxOutput,
3605
- outputPath,
3606
- outputMode: behavior?.outputMode,
3607
- maxSubagentDepth: input.maxSubagentDepths[index],
3608
- waitToolEnabled: input.waitToolEnabled,
3609
- capabilityCeiling: input.capabilityCeiling,
3610
- controlConfig: input.controlConfig,
3611
- onControlEvent: input.onControlEvent,
3612
- onDetachedExit: (result) => {
3613
- try {
3614
- updateRememberedForegroundChild(input.state, { runId: input.runId, mode: "parallel", cwd: taskCwd, sessionId: input.parentSessionId, index, result, events: input.intercomEvents });
3615
- } finally {
3616
- try {
3617
- if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
3618
- } finally {
3619
- removeForegroundControlIfIdle(input.state, input.runId);
3620
- }
3621
- }
3622
- },
3623
- intercomSessionName: input.childIntercomTarget?.(task.agent, index),
3624
- orchestratorIntercomTarget: input.orchestratorIntercomTarget,
3625
- nestedRoute: input.foregroundControl?.nestedRoute,
3626
- modelOverride: input.modelOverrides[index],
3627
- thinkingOverride: input.thinkingOverrideForTask(task.agent, index, input.modelOverrides[index]),
3628
- availableModels: input.availableModels,
3629
- preferredModelProvider: input.parentModel?.provider,
3630
- modelScope: input.modelScope,
3631
- skills: effectiveSkills === false ? [] : effectiveSkills,
3632
- structuredOutput: structuredRuntime,
3633
- agentContract: task.agentContract ?? input.agentContract,
3634
- acceptance: task.acceptance,
3635
- acceptanceContext: { mode: "parallel" },
3636
- onEffectivePrompt: input.foregroundControl ? (prompt) => updateLiveEffectivePrompt(input.foregroundControl!, index, prompt) : undefined,
3637
- timeoutMs: input.timeoutMs,
3638
- toolTimeoutMs: input.toolTimeoutMs,
3639
- configToolTimeoutMs: input.configToolTimeoutMs,
3640
- deadlineAt: input.deadlineAt,
3641
- turnBudget: input.turnBudget,
3642
- toolBudget: input.toolBudgets[index],
3643
- onUpdate: input.onUpdate
3644
- ? (progressUpdate) => {
3645
- const stepResults = progressUpdate.details?.results || [];
3646
- const stepProgress = progressUpdate.details?.progress || [];
3647
- if (input.foregroundControl && stepProgress.length > 0) {
3648
- updateForegroundChild(input.foregroundControl, index, stepProgress[0]);
3649
- }
3650
- if (stepResults.length > 0) input.liveResults[index] = stepResults[0];
3651
- if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0];
3652
- const mergedResults = input.liveResults.filter((result): result is SingleResult => result !== undefined);
3653
- const mergedProgress = input.liveProgress.filter((progress): progress is AgentProgress => progress !== undefined);
3654
- input.onUpdate?.({
3655
- content: progressUpdate.content,
3656
- details: compactOptional<Details>({
3657
- mode: "parallel",
3658
- results: mergedResults,
3659
- progress: mergedProgress,
3660
- controlEvents: progressUpdate.details?.controlEvents,
3661
- totalSteps: input.tasks.length,
3662
- }),
3663
- });
3664
- }
3665
- : undefined,
3666
- })).then((result) => {
3667
- detachedReceipt = result.detached === true;
3668
- return result;
3669
- }).finally(() => {
3670
- // mapConcurrent rejects before siblings settle, so every attached child
3671
- // attempts idle removal after releasing its own control. Detached receipts
3672
- // transfer both responsibilities to the authoritative exit callback.
3673
- if (!detachedReceipt) {
3674
- if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
3675
- removeForegroundControlIfIdle(input.state, input.runId);
3676
- }
3677
- });
3678
- completedResults.push(result);
3679
- return result;
3680
- }, input.globalSemaphore, () => {
3681
- if (input.foregroundControl) settleForegroundSchedulingOwner(input.foregroundControl);
3682
- removeForegroundControlIfIdle(input.state, input.runId);
3683
- });
3684
- }
3685
-
3686
- async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
3687
- const {
3688
- params,
3689
- effectiveCwd,
3690
- agents,
3691
- ctx,
3692
- signal,
3693
- runId,
3694
- sessionDirForIndex,
3695
- sessionFileForIndex,
3696
- sessionFileForTask,
3697
- thinkingOverrideForTask,
3698
- shareEnabled,
3699
- artifactConfig,
3700
- artifactsDir,
3701
- backgroundRequestedWhileClarifying,
3702
- onUpdate,
3703
- sessionRoot,
3704
- controlConfig,
3705
- contextPolicy,
3706
- } = data;
3707
- const onControlEvent = createForegroundControlNotifier(data, deps);
3708
- const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget : undefined;
3709
- const allProgress: AgentProgress[] = [];
3710
- const allArtifactPaths: ArtifactPaths[] = [];
3711
- const tasks = params.tasks!;
3712
- const maxParallelTasks = resolveTopLevelParallelMaxTasks(deps.config.parallel?.maxTasks);
3713
- const parallelConcurrency = resolveTopLevelParallelConcurrency(params.concurrency, deps.config.parallel?.concurrency);
3714
-
3715
- if (tasks.length > maxParallelTasks)
3716
- return {
3717
- content: [{ type: "text", text: `Max ${maxParallelTasks} tasks` }],
3718
- isError: true,
3719
- details: { mode: "parallel" as const, results: [] },
3720
- };
3721
-
3722
- const agentConfigs: AgentConfig[] = [];
3723
- for (const t of tasks) {
3724
- const config = agents.find((a) => a.name === t.agent);
3725
- if (!config) {
3726
- return {
3727
- content: [{ type: "text", text: `Unknown agent: ${t.agent}` }],
3728
- isError: true,
3729
- details: { mode: "parallel" as const, results: [] },
3730
- };
3731
- }
3732
- agentConfigs.push(config);
3733
- }
3734
-
3735
- const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
3736
- const maxSubagentDepths = agentConfigs.map((config) =>
3737
- resolveChildMaxSubagentDepth(currentMaxSubagentDepth, config.maxSubagentDepth),
3738
- );
3739
- const toolBudgets: (ResolvedToolBudget | undefined)[] = [];
3740
- for (let index = 0; index < tasks.length; index++) {
3741
- const resolved = resolveEffectiveToolBudget(omitUndefinedProperties({ stepBudget: tasks[index]?.toolBudget, runBudget: data.toolBudget, agentBudget: agentConfigs[index]?.toolBudget, configBudget: data.configToolBudget }));
3742
- if (resolved.error) return buildParallelModeError(resolved.error);
3743
- toolBudgets.push(resolved.toolBudget);
3744
- }
3745
-
3746
- if (params.worktree) {
3747
- const worktreeTaskCwdError = buildParallelWorktreeTaskCwdError(tasks, effectiveCwd);
3748
- if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
3749
- }
3750
-
3751
- const parentModel = data.parentModel;
3752
- const currentProvider = parentModel?.provider;
3753
- const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
3754
- let taskTexts = tasks.map((t) => t.task);
3755
- const skillOverrides: (string[] | false | undefined)[] = tasks.map((t) =>
3756
- normalizeSkillInput(t.skill),
3757
- );
3758
- const behaviorOverrides: StepOverrides[] = tasks.map((task, index) => ({
3759
- ...(task.output !== undefined && task.output !== true ? { output: task.output } : {}),
3760
- ...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
3761
- ...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
3762
- ...(task.progress !== undefined ? { progress: task.progress } : {}),
3763
- ...(skillOverrides[index] !== undefined ? { skills: skillOverrides[index] } : {}),
3764
- ...(task.model !== undefined ? { model: task.model } : {}),
3765
- }));
3766
- const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
3767
- agentConfigs[i]?.runner?.type === "external-cli"
3768
- ? undefined
3769
- : resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope }),
3770
- );
3771
-
3772
- if (params.clarify === true && ctx.hasUI) {
3773
- const behaviors = agentConfigs.map((c, i) =>
3774
- resolveStepBehavior(c, behaviorOverrides[i]!),
3775
- );
3776
- const availableSkills = discoverAvailableSkills(effectiveCwd);
3777
-
3778
- const result = await ctx.ui.custom<ChainClarifyResult>(
3779
- (tui, theme, _kb, done) =>
3780
- new ChainClarifyComponent(
3781
- tui, theme,
3782
- agentConfigs,
3783
- taskTexts,
3784
- "",
3785
- undefined,
3786
- behaviors,
3787
- availableModels,
3788
- currentProvider,
3789
- availableSkills,
3790
- done,
3791
- "parallel",
3792
- ),
3793
- { overlay: true, overlayOptions: { anchor: "center", width: 84, maxHeight: "80%" } },
3794
- );
3795
-
3796
- if (!result || !result.confirmed) {
3797
- return { content: [{ type: "text", text: "Cancelled" }], details: { mode: "parallel", results: [] } };
3798
- }
3799
-
3800
- taskTexts = result.templates;
3801
- for (let i = 0; i < result.behaviorOverrides.length; i++) {
3802
- const override = result.behaviorOverrides[i];
3803
- if (override?.model !== undefined) {
3804
- modelOverrides[i] = agentConfigs[i]?.runner?.type === "external-cli"
3805
- ? undefined
3806
- : resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
3807
- behaviorOverrides[i]!.model = override.model;
3808
- }
3809
- if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
3810
- if (override?.reads !== undefined) behaviorOverrides[i]!.reads = override.reads;
3811
- if (override?.progress !== undefined) behaviorOverrides[i]!.progress = override.progress;
3812
- if (override?.skills !== undefined) {
3813
- skillOverrides[i] = override.skills;
3814
- behaviorOverrides[i]!.skills = override.skills;
3815
- }
3816
- }
3817
-
3818
- if (result.runInBackground) {
3819
- if (!isAsyncAvailable()) {
3820
- return {
3821
- content: [{ type: "text", text: "Background mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the pi-subagents package dependencies are installed." }],
3822
- isError: true,
3823
- details: { mode: "parallel" as const, results: [] },
3824
- };
3825
- }
3826
- const id = data.asyncRunId;
3827
- const capacity = acquireClarifiedBackgroundCapacity(data, deps, "parallel", id);
3828
- if (capacity.errorResult) return capacity.errorResult;
3829
- const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
3830
- pi: deps.pi,
3831
- cwd: ctx.cwd,
3832
- currentSessionId: data.parentSessionId!,
3833
- parentSessionId: data.parentPiSessionId,
3834
- currentModelProvider: parentModel?.provider,
3835
- currentModel: parentModel,
3836
- modelScope: data.modelScope,
3837
- interactive: ctx.hasUI,
3838
- permissions: deps.config.permissions,
3839
- });
3840
- const parallelTasks = tasks.map((t, i) => {
3841
- const taskText = shouldForkAgent(contextPolicy, t.agent) ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!;
3842
- const progress = taskDisallowsFileUpdates(taskText) ? false : behaviorOverrides[i]?.progress;
3843
- return compactOptional<ParallelTaskItem>({
3844
- agent: t.agent,
3845
- task: taskText,
3846
- cwd: t.cwd,
3847
- ...(behaviorOverrides[i]?.model !== undefined ? { model: behaviorOverrides[i]!.model } : {}),
3848
- ...(skillOverrides[i] !== undefined ? { skill: skillOverrides[i] } : {}),
3849
- ...(behaviorOverrides[i]?.output !== undefined ? { output: behaviorOverrides[i]!.output } : {}),
3850
- ...(behaviorOverrides[i]?.outputMode !== undefined ? { outputMode: behaviorOverrides[i]!.outputMode } : {}),
3851
- ...(behaviorOverrides[i]?.reads !== undefined ? { reads: behaviorOverrides[i]!.reads } : {}),
3852
- ...(progress !== undefined ? { progress } : {}),
3853
- ...(t.toolBudget !== undefined ? { toolBudget: t.toolBudget } : {}),
3854
- ...(t.outputSchema !== undefined ? { outputSchema: t.outputSchema } : {}),
3855
- ...(t.acceptance !== undefined ? { acceptance: t.acceptance } : {}),
3856
- ...(t.agentContract !== undefined ? { agentContract: t.agentContract } : {}),
3857
- });
3858
- });
3859
- try {
3860
- return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
3861
- chain: [compactOptional<ParallelStep>({ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree })],
3862
- resultMode: "parallel",
3863
- goal: taskTexts[0] ?? "",
3864
- agents,
3865
- ctx: asyncCtx,
3866
- availableModels,
3867
- cwd: effectiveCwd,
3868
- maxOutput: params.maxOutput,
3869
- artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
3870
- artifactConfig,
3871
- shareEnabled,
3872
- sessionRoot,
3873
- chainSkills: [],
3874
- activeAsyncCapacity: capacity.activeAsyncCapacity,
3875
- sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index, modelOverrides[index])),
3876
- thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, modelOverrides[index])),
3877
- contextForAgent: contextPolicy.contextForAgent,
3878
- maxSubagentDepth: currentMaxSubagentDepth,
3879
- waitToolEnabled: deps.waitToolEnabled,
3880
- worktreeSetupHook: deps.config.worktreeSetupHook,
3881
- worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
3882
- worktreeBaseDir: deps.config.worktreeBaseDir,
3883
- controlConfig,
3884
- agentContract: params.agentContract,
3885
- controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
3886
- childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
3887
- nestedRoute: data.nestedRoute,
3888
- timeoutMs: data.timeoutMs,
3889
- turnBudget: data.turnBudget,
3890
- usageBudget: data.usageBudget,
3891
- toolBudget: data.toolBudget,
3892
- configToolBudget: data.configToolBudget,
3893
- callToolTimeoutMs: data.params?.toolTimeoutMs,
3894
- configToolTimeoutMs: data.configToolTimeoutMs,
3895
- globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
3896
- runFanoutBudget: data.runFanoutBudget,
3897
- }));
3898
- } finally {
3899
- finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
3900
- }
3901
- }
3902
- }
3903
-
3904
- const behaviors = tasks.map((task, index) => {
3905
- let behavior = suppressProgressForReadOnlyTask(resolveStepBehavior(agentConfigs[index]!, behaviorOverrides[index]!), taskTexts[index]);
3906
- if (behaviorOverrides[index]?.output === undefined && typeof behavior.output === "string" && !path.isAbsolute(behavior.output)) {
3907
- behavior = { ...behavior, output: path.join("parallel-0", `${index}-${task.agent}`, behavior.output) };
3908
- }
3909
- return behavior;
3910
- });
3911
- const firstProgressIndex = behaviors.findIndex((behavior) => behavior.progress);
3912
- const liveResults: (SingleResult | undefined)[] = new Array(tasks.length).fill(undefined);
3913
- const liveProgress: (AgentProgress | undefined)[] = new Array(tasks.length).fill(undefined);
3914
- const foregroundControl = deps.state.foregroundControls.get(runId);
3915
- const { setup: worktreeSetup, errorResult } = createParallelWorktreeSetup(
3916
- params.worktree,
3917
- effectiveCwd,
3918
- runId,
3919
- tasks,
3920
- deps.config.worktreeSetupHook,
3921
- deps.config.worktreeSetupHookTimeoutMs,
3922
- deps.config.worktreeBaseDir,
3923
- );
3924
- if (errorResult) return errorResult;
3925
-
3926
- let worktreeCleanupHandled = false;
3927
- let pendingHandoff: Details["parallelHandoff"];
3928
- try {
3929
- if (worktreeSetup) {
3930
- pendingHandoff = writePendingParallelHandoff({
3931
- manifestPath: parallelHandoffPath(artifactsDir, runId),
3932
- runId,
3933
- mode: "parallel",
3934
- source: "foreground",
3935
- cwd: effectiveCwd,
3936
- stepIndex: 0,
3937
- flatStartIndex: 0,
3938
- setup: worktreeSetup,
3939
- });
3940
- }
3941
- const outputBaseDir = path.join(artifactsDir, "outputs", runId);
3942
- const duplicateOutputError = findDuplicateParallelOutputPath(omitUndefinedProperties({
3943
- tasks,
3944
- behaviors,
3945
- paramsCwd: effectiveCwd,
3946
- ctxCwd: ctx.cwd,
3947
- outputBaseDir,
3948
- worktreeSetup,
3949
- }));
3950
- if (duplicateOutputError) return buildParallelModeError(duplicateOutputError);
3951
- for (let index = 0; index < tasks.length; index++) {
3952
- const taskCwd = resolveParallelTaskCwd(tasks[index]!, effectiveCwd, worktreeSetup, index);
3953
- const outputPath = resolveSingleOutputPath(behaviors[index]?.output, ctx.cwd, taskCwd, outputBaseDir);
3954
- const validationError = validateFileOnlyOutputMode(behaviors[index]?.outputMode, outputPath, `Parallel task ${index + 1} (${tasks[index]!.agent})`);
3955
- if (validationError) return buildParallelModeError(validationError);
3956
- }
3957
-
3958
- const parallelProgressPrecreated = firstProgressIndex !== -1;
3959
- const parallelProgressDir = path.join(artifactsDir, "progress", runId);
3960
- if (parallelProgressPrecreated) writeInitialProgressFile(parallelProgressDir);
3961
-
3962
- const taskDescriptions = taskTexts.map((taskText) => taskText.trim());
3963
- for (let i = 0; i < taskTexts.length; i++) {
3964
- if (shouldForkAgent(contextPolicy, tasks[i]!.agent)) taskTexts[i] = wrapForkTask(taskTexts[i]!);
3965
- }
3966
-
3967
- const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
3968
- const results = await runForegroundParallelTasks(compactOptional<ForegroundParallelRunInput>({
3969
- tasks,
3970
- permissions: deps.config.permissions,
3971
- taskTexts,
3972
- taskDescriptions,
3973
- agents,
3974
- ctx,
3975
- state: deps.state,
3976
- intercomEvents: deps.pi.events,
3977
- parentSessionId: data.parentSessionId,
3978
- capabilityCeiling: data.capabilityCeiling,
3979
- runFanoutBudget: data.runFanoutBudget,
3980
- signal,
3981
- runId,
3982
- sessionDirForIndex,
3983
- sessionFileForIndex,
3984
- sessionFileForTask,
3985
- thinkingOverrideForTask,
3986
- shareEnabled,
3987
- artifactConfig,
3988
- artifactsDir,
3989
- outputBaseDir,
3990
- maxOutput: params.maxOutput,
3991
- paramsCwd: effectiveCwd,
3992
- progressDir: parallelProgressDir,
3993
- availableModels,
3994
- modelScope: data.modelScope,
3995
- parentModel,
3996
- modelOverrides,
3997
- behaviors,
3998
- firstProgressIndex: parallelProgressPrecreated ? -1 : firstProgressIndex,
3999
- controlConfig,
4000
- contextPolicy,
4001
- onControlEvent,
4002
- childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
4003
- orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
4004
- foregroundControl,
4005
- concurrencyLimit: parallelConcurrency,
4006
- globalSemaphore: new Semaphore(deps.config.globalConcurrencyLimit ?? DEFAULT_GLOBAL_CONCURRENCY_LIMIT),
4007
- maxSubagentDepths,
4008
- waitToolEnabled: deps.waitToolEnabled,
4009
- liveResults,
4010
- liveProgress,
4011
- onUpdate,
4012
- worktreeSetup,
4013
- timeoutMs: data.timeoutMs,
4014
- deadlineAt,
4015
- toolTimeoutMs: data.params?.toolTimeoutMs,
4016
- configToolTimeoutMs: data.configToolTimeoutMs,
4017
- turnBudget: data.turnBudget,
4018
- usageBudget: data.usageBudget,
4019
- toolBudgets,
4020
- agentContract: params.agentContract,
4021
- }));
4022
- for (let i = 0; i < results.length; i++) {
4023
- const run = results[i]!;
4024
- recordRun(run.agent, taskTexts[i]!, run.exitCode, run.progressSummary?.durationMs ?? 0);
4025
- }
4026
-
4027
- for (const result of results) {
4028
- if (result.progress) allProgress.push(result.progress);
4029
- if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
4030
- }
4031
-
4032
- if (foregroundControl) {
4033
- updateForegroundNestedProjection(foregroundControl);
4034
- attachRootChildrenToSteps(runId, results, foregroundControl.nestedChildren);
4035
- }
4036
- const detached = results.find((result) => result.detached);
4037
- let handoff: ReturnType<typeof finalizeParallelWorktreeHandoff> | undefined;
4038
- if (worktreeSetup) {
4039
- worktreeCleanupHandled = true;
4040
- handoff = detached
4041
- ? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
4042
- : finalizeParallelWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: effectiveCwd, tasks, results });
4043
- }
4044
- const interrupted = results.find((result) => result.interrupted);
4045
- const totalCost = sumResultsCost(results);
4046
- const details = compactForegroundDetails(compactOptional<Details>({
4047
- mode: "parallel",
4048
- runId,
4049
- timeoutMs: data.timeoutMs,
4050
- results,
4051
- progress: params.includeProgress ? allProgress : undefined,
4052
- artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
4053
- totalChildUsage: sumResultsUsage(results),
4054
- totalCost,
4055
- usageBudget: usageBudgetState(data.usageBudget, totalCost),
4056
- ...(handoff?.reference ? { parallelHandoff: handoff.reference } : {}),
4057
- }));
4058
- rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
4059
- if (interrupted) {
4060
- return {
4061
- content: [{ type: "text", text: `Parallel run paused after interrupt (${interrupted.agent}). Waiting for explicit next action.` }],
4062
- details,
4063
- };
4064
- }
4065
- if (detached) {
4066
- const handoffSuffix = handoff?.suffix ? `\n\n${handoff.suffix}` : "";
4067
- return {
4068
- 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}` }],
4069
- details,
4070
- };
4071
- }
4072
-
4073
- const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results });
4074
- if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
4075
- if (!suppressRoutineResultIntercom) {
4076
- const intercomReceipt = await maybeBuildForegroundIntercomReceipt({
4077
- pi: deps.pi,
4078
- intercomBridge: data.intercomBridge,
4079
- runId,
4080
- mode: "parallel",
4081
- details,
4082
- ...(params.workflowParentRunId !== undefined ? { preserveDetailsOutputs: true } : {}),
4083
- ...(foregroundControl?.nestedChildren?.length ? { nestedChildren: foregroundControl.nestedChildren } : {}),
4084
- });
4085
- if (intercomReceipt) {
4086
- return {
4087
- content: [{ type: "text", text: intercomReceipt.text }],
4088
- details: intercomReceipt.details,
4089
- };
4090
- }
4091
- }
3394
+ workflowCwd: string;
3395
+ artifactsDir: string;
3396
+ aggregateOutputPath?: string;
3397
+ configuredOutputBaseDir?: string;
3398
+ discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
3399
+ agents: AgentConfig[];
3400
+ workflowAgentScope?: unknown;
3401
+ outputOverride?: string;
3402
+ options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number; capabilityCeiling?: ResolvedSubagentCapabilityCeiling };
3403
+ }): SubagentParamsLike {
3404
+ let childParams = input.childParams;
3405
+ if (input.childParams.output === undefined && input.childParams.resume === undefined && input.outputOverride !== undefined) {
3406
+ childParams = { ...input.childParams, output: input.outputOverride };
3407
+ } else if (input.childParams.output === undefined && input.childParams.resume === undefined && input.aggregateOutputPath !== undefined) {
3408
+ childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
3409
+ } else if (input.childParams.resume === undefined) {
3410
+ const resolvedOutput = resolveWorkflowChildOutputPath({ ctxCwd: input.ctxCwd, workflowCwd: input.workflowCwd, artifactsDir: input.artifactsDir, workflowRunId: input.parentWorkflowRunId, aggregateOutputPath: input.aggregateOutputPath, configuredOutputBaseDir: input.configuredOutputBaseDir, discoverAgents: input.discoverAgents, agents: input.agents, workflowAgentScope: input.workflowAgentScope, key: input.workflowKey, params: input.childParams });
3411
+ if (resolvedOutput.path) childParams = { ...input.childParams, output: resolvedOutput.path };
3412
+ }
3413
+ const childCwd = typeof childParams.cwd === "string" ? resolveChildCwd(input.workflowCwd, childParams.cwd) : input.workflowCwd;
3414
+ const agentScope = resolveExecutionAgentScope(childParams.agentScope ?? input.workflowAgentScope);
3415
+ const discoveredAgents = input.discoverAgents(childCwd, agentScope).agents;
3416
+ const agent = typeof childParams.agent === "string"
3417
+ ? resolveAgentName(childParams.agent, discoveredAgents).agent ?? resolveAgentName(childParams.agent, input.agents).agent
3418
+ : undefined;
3419
+ const externalAsyncRequired = agent?.runner?.type === "external-cli" || agent?.runner?.type === "external-job";
3420
+ return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, { ...input.options, externalAsyncRequired });
3421
+ }
4092
3422
 
4093
- const worktreeSuffix = handoff?.suffix ?? "";
4094
- const ok = results.filter((result) => result.exitCode === 0).length;
4095
- const downgradeNote = backgroundRequestedWhileClarifying ? " (background requested, but clarify kept this run foreground)" : "";
4096
- const aggregatedOutput = aggregateParallelOutputs(
4097
- results.map((result) => ({
4098
- agent: result.agent,
4099
- output: result.truncation?.text || getSingleResultOutput(result),
4100
- exitCode: result.exitCode,
4101
- ...(result.error === undefined ? {} : { error: result.error }),
4102
- ...(result.timedOut === undefined ? {} : { timedOut: result.timedOut }),
3423
+ function finalizeSingleWorktreeHandoff(input: {
3424
+ worktreeSetup: WorktreeSetup;
3425
+ artifactsDir: string;
3426
+ runId: string;
3427
+ cwd: string;
3428
+ agent: string;
3429
+ result: SingleResult;
3430
+ }): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
3431
+ const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
3432
+ const diffs = diffWorktrees(input.worktreeSetup, [input.agent], diffsDir);
3433
+ const diffSummary = formatWorktreeDiffSummary(diffs);
3434
+ const manifestPath = parallelHandoffPath(input.artifactsDir, input.runId);
3435
+ const handoff = {
3436
+ manifestPath,
3437
+ runId: input.runId,
3438
+ mode: "single" as const,
3439
+ source: "foreground" as const,
3440
+ cwd: input.cwd,
3441
+ stepIndex: 0,
3442
+ flatStartIndex: 0,
3443
+ setup: input.worktreeSetup,
3444
+ diffs,
3445
+ results: [{
3446
+ agent: input.result.agent,
3447
+ status: resolveSubagentResultStatus(omitUndefinedProperties({
3448
+ exitCode: input.result.exitCode,
3449
+ interrupted: input.result.interrupted,
3450
+ detached: input.result.detached,
3451
+ state: input.result.stopped ? "stopped" : undefined,
3452
+ processSignal: input.result.processSignal,
3453
+ timedOut: input.result.timedOut,
3454
+ stopped: input.result.stopped,
3455
+ turnBudgetExceeded: input.result.turnBudgetExceeded,
4103
3456
  })),
4104
- (i, agent) => `=== Task ${i + 1}: ${agent} ===`,
4105
- );
4106
-
4107
- const summary = `${ok}/${results.length} succeeded${downgradeNote}`;
4108
- const fullContent = worktreeSuffix
4109
- ? `${summary}\n\n${aggregatedOutput}\n\n${worktreeSuffix}`
4110
- : `${summary}\n\n${aggregatedOutput}`;
4111
-
3457
+ summary: resultSummaryForIntercom(input.result),
3458
+ ...(input.result.artifactPaths?.outputPath ? { outputPath: input.result.artifactPaths.outputPath } : {}),
3459
+ ...(input.result.structuredOutput !== undefined ? { structuredOutput: input.result.structuredOutput } : {}),
3460
+ ...(input.result.structuredOutputPath ? { structuredOutputPath: input.result.structuredOutputPath } : {}),
3461
+ ...(input.result.sessionFile ? { sessionPath: input.result.sessionFile } : {}),
3462
+ }],
3463
+ };
3464
+ try {
3465
+ writeParallelHandoffGroup(handoff);
3466
+ const cleanup = cleanupWorktrees(input.worktreeSetup, { kind: "preserve", capturedDiffs: diffs, handoffManifestPath: manifestPath });
3467
+ const reference = writeParallelHandoffGroup({ ...handoff, cleanup });
4112
3468
  return {
4113
- content: [{ type: "text", text: fullContent }],
4114
- details,
3469
+ suffix: [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n"),
3470
+ reference,
4115
3471
  };
4116
- } finally {
4117
- if (worktreeSetup && !worktreeCleanupHandled) cleanupWorktrees(worktreeSetup);
3472
+ } catch (error) {
3473
+ return { suffix: [diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n") };
4118
3474
  }
4119
3475
  }
4120
3476
 
@@ -4138,7 +3494,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4138
3494
  contextPolicy,
4139
3495
  } = data;
4140
3496
  const onControlEvent = createForegroundControlNotifier(data, deps);
4141
- const childIntercomTarget = data.intercomBridge.active ? resolveSubagentIntercomTarget(runId, params.agent!, 0) : undefined;
3497
+ const childBridgeActive = intercomBridgeAppliesToAgent(data.intercomBridge, contextPolicy, params.agent!);
3498
+ const childIntercomTarget = childBridgeActive ? resolveSubagentIntercomTarget(runId, params.agent!, 0) : undefined;
4142
3499
  const allProgress: AgentProgress[] = [];
4143
3500
  const allArtifactPaths: ArtifactPaths[] = [];
4144
3501
  const agentConfig = agents.find((a) => a.name === params.agent);
@@ -4155,126 +3512,54 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4155
3512
  const parentModel = data.parentModel;
4156
3513
  const currentProvider = parentModel?.provider;
4157
3514
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
3515
+ const modelScopes = resolveModelScopesForAgent(data.modelScope, agentConfig.name, parentModel);
4158
3516
  let task = params.task ?? "";
4159
3517
  let modelOverride: string | undefined = resolveEffectiveSubagentModel(
4160
3518
  params.model as string | undefined,
4161
3519
  agentConfig.model,
4162
3520
  parentModel,
4163
3521
  availableModels,
4164
- currentProvider,
4165
- data.modelScope === undefined ? {} : { scope: data.modelScope },
3522
+ agentConfig.modelProvider ?? currentProvider,
3523
+ modelScopes.length === 0 ? {} : { scope: modelScopes },
4166
3524
  );
3525
+ const modelOverrideFromParent = inheritsParentModel(params.model as string | undefined, agentConfig.model, parentModel);
4167
3526
  let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
4168
3527
  let readsOverride: string[] | false | undefined = params.reads;
4169
3528
  const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
4170
3529
  let effectiveOutput = normalizeSingleOutputOverride(rawOutput, agentConfig.output);
4171
- const effectiveOutputMode = params.outputMode ?? "inline";
3530
+ const effectiveOutputMode = params.outputMode ?? agentConfig.outputMode ?? "inline";
4172
3531
  const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
4173
3532
  const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
4174
3533
 
4175
- if (params.clarify === true && ctx.hasUI) {
4176
- const behavior = resolveStepBehavior(agentConfig, omitUndefinedProperties({ output: effectiveOutput, skills: skillOverride }));
4177
- const availableSkills = discoverAvailableSkills(effectiveCwd);
4178
-
4179
- const result = await ctx.ui.custom<ChainClarifyResult>(
4180
- (tui, theme, _kb, done) =>
4181
- new ChainClarifyComponent(
4182
- tui, theme,
4183
- [agentConfig],
4184
- [task],
4185
- task,
4186
- undefined,
4187
- [behavior],
4188
- availableModels,
4189
- currentProvider,
4190
- availableSkills,
4191
- done,
4192
- "single",
4193
- ),
4194
- { overlay: true, overlayOptions: { anchor: "center", width: 84, maxHeight: "80%" } },
4195
- );
4196
-
4197
- if (!result || !result.confirmed) {
4198
- return { content: [{ type: "text", text: "Cancelled" }], details: { mode: "single", results: [] } };
4199
- }
4200
-
4201
- task = result.templates[0]!;
4202
- const override = result.behaviorOverrides[0];
4203
- if (override?.model !== undefined) modelOverride = resolveEffectiveSubagentModel(override.model, agentConfig.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
4204
- if (override?.output !== undefined) effectiveOutput = normalizeSingleOutputOverride(override.output, agentConfig.output);
4205
- if (override?.skills !== undefined) skillOverride = override.skills;
4206
- if (override?.reads !== undefined) readsOverride = override.reads;
4207
3534
 
4208
- if (result.runInBackground) {
4209
- if (!isAsyncAvailable()) {
4210
- return {
4211
- content: [{ type: "text", text: "Background mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the pi-subagents package dependencies are installed." }],
4212
- isError: true,
4213
- details: { mode: "single" as const, results: [] },
4214
- };
4215
- }
4216
- const id = data.asyncRunId;
4217
- const capacity = acquireClarifiedBackgroundCapacity(data, deps, "single", id);
4218
- if (capacity.errorResult) return capacity.errorResult;
4219
- const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
4220
- pi: deps.pi,
4221
- cwd: ctx.cwd,
4222
- currentSessionId: data.parentSessionId!,
4223
- parentSessionId: data.parentPiSessionId,
4224
- currentModelProvider: parentModel?.provider,
4225
- currentModel: parentModel,
4226
- modelScope: data.modelScope,
4227
- interactive: ctx.hasUI,
4228
- permissions: deps.config.permissions,
3535
+ const sourceCwd = effectiveCwd;
3536
+ const { setup: worktreeSetup, errorResult: worktreeSetupError } = createSingleWorktreeSetup(
3537
+ params.worktree,
3538
+ sourceCwd,
3539
+ runId,
3540
+ params.agent!,
3541
+ deps.config.worktreeSetupHook,
3542
+ deps.config.worktreeSetupHookTimeoutMs,
3543
+ deps.config.worktreeBaseDir,
3544
+ );
3545
+ if (worktreeSetupError) return worktreeSetupError;
3546
+ const singleCwd = worktreeSetup?.worktrees[0]?.agentCwd ?? sourceCwd;
3547
+ let pendingHandoff: Details["parallelHandoff"];
3548
+ if (worktreeSetup) {
3549
+ try {
3550
+ pendingHandoff = writePendingParallelHandoff({
3551
+ manifestPath: parallelHandoffPath(artifactsDir, runId),
3552
+ runId,
3553
+ mode: "single",
3554
+ source: "foreground",
3555
+ cwd: sourceCwd,
3556
+ stepIndex: 0,
3557
+ flatStartIndex: 0,
3558
+ setup: worktreeSetup,
4229
3559
  });
4230
- try {
4231
- return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
4232
- agent: params.agent!,
4233
- task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
4234
- goal: task,
4235
- agentConfig,
4236
- recoveryAgentConfig: data.recoveryAgents.find((agent) => agent.name === params.agent),
4237
- ctx: asyncCtx,
4238
- availableModels,
4239
- cwd: effectiveCwd,
4240
- maxOutput: params.maxOutput,
4241
- artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
4242
- artifactConfig,
4243
- shareEnabled,
4244
- activeAsyncCapacity: capacity.activeAsyncCapacity,
4245
- sessionRoot,
4246
- sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
4247
- context: contextPolicy.contextForAgent(params.agent!),
4248
- skills: skillOverride === false ? [] : skillOverride,
4249
- output: effectiveOutput,
4250
- outputMode: effectiveOutputMode,
4251
- ...(readsOverride !== undefined ? { reads: readsOverride } : {}),
4252
- outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
4253
- modelOverride,
4254
- thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
4255
- maxSubagentDepth,
4256
- waitToolEnabled: deps.waitToolEnabled,
4257
- worktreeSetupHook: deps.config.worktreeSetupHook,
4258
- worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
4259
- worktreeBaseDir: deps.config.worktreeBaseDir,
4260
- controlConfig,
4261
- intercomBridge: params.intercomBridge,
4262
- controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
4263
- childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
4264
- nestedRoute: data.nestedRoute,
4265
- agentContract: params.agentContract,
4266
- structuredOutputSchema: params.outputSchema,
4267
- acceptance: params.acceptance,
4268
- timeoutMs: data.timeoutMs,
4269
- turnBudget: data.turnBudget,
4270
- toolBudget: effectiveToolBudget.toolBudget,
4271
- usageBudget: data.usageBudget,
4272
- allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
4273
- runFanoutBudget: data.runFanoutBudget,
4274
- }));
4275
- } finally {
4276
- finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
4277
- }
3560
+ } catch (error) {
3561
+ cleanupWorktrees(worktreeSetup);
3562
+ return { content: [{ type: "text", text: formatParallelHandoffError(error) }], isError: true, details: { mode: "single", results: [] } };
4278
3563
  }
4279
3564
  }
4280
3565
 
@@ -4283,18 +3568,19 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4283
3568
  task = wrapForkTask(task);
4284
3569
  }
4285
3570
  const cleanTask = task;
4286
- const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, effectiveCwd, resolveSingleRunOutputBaseDir(deps, artifactsDir, runId));
3571
+ const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, singleCwd, resolveSingleRunOutputBaseDir(deps, artifactsDir, runId));
4287
3572
  const validationError = validateFileOnlyOutputMode(effectiveOutputMode, outputPath, `Single run (${params.agent})`);
4288
3573
  if (validationError) {
3574
+ if (worktreeSetup) cleanupWorktrees(worktreeSetup);
4289
3575
  return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
4290
3576
  }
4291
3577
  const structuredRuntime = params.outputSchema
4292
- ? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined)
3578
+ ? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined, { captureAcceptanceReport: params.acceptance !== false })
4293
3579
  : undefined;
4294
3580
  // Reads: caller override > agent defaultReads > none. `~`/`~/` expand to home;
4295
3581
  // absolute paths pass through; relative paths resolve against the child cwd.
4296
3582
  const reads = readsOverride !== undefined ? readsOverride : agentConfig.defaultReads ?? false;
4297
- const readPaths = Array.isArray(reads) ? resolveExistingReadPaths(reads, effectiveCwd) : [];
3583
+ const readPaths = Array.isArray(reads) ? resolveExistingReadPaths(reads, singleCwd) : [];
4298
3584
  const readsInstruction = readPaths.length > 0
4299
3585
  ? `[Read from: ${readPaths.join(", ")}]\n\n`
4300
3586
  : "";
@@ -4311,13 +3597,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4311
3597
  let detachForeground: ((reason?: string) => boolean) | undefined;
4312
3598
  const foregroundControl = deps.state.foregroundControls.get(runId);
4313
3599
  if (foregroundControl) {
4314
- const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride));
3600
+ const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent));
4315
3601
  beginForegroundChild(foregroundControl, omitUndefinedProperties({
4316
3602
  index: 0,
4317
3603
  agent: params.agent!,
4318
3604
  authoredTask,
4319
3605
  effectivePrompt: task,
4320
- cwd: effectiveCwd,
3606
+ cwd: singleCwd,
4321
3607
  outputPath,
4322
3608
  rerun: { params: { ...params, task: authoredTask, async: params.async ?? false } },
4323
3609
  description: foregroundControl.description,
@@ -4349,14 +3635,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4349
3635
  ...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
4350
3636
  context: data.contextPolicy.contextForAgent(params.agent!),
4351
3637
  runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
4352
- cwd: effectiveCwd,
3638
+ cwd: singleCwd,
4353
3639
  signal,
4354
3640
  interruptSignal: interruptController.signal,
4355
3641
  allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
4356
3642
  intercomEvents: deps.pi.events,
4357
3643
  runId,
4358
3644
  sessionDir: sessionDirForIndex(0),
4359
- sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
3645
+ sessionFile: sessionFileForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
4360
3646
  share: shareEnabled,
4361
3647
  artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
4362
3648
  artifactConfig,
@@ -4369,14 +3655,18 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4369
3655
  controlConfig,
4370
3656
  onControlEvent,
4371
3657
  intercomSessionName: childIntercomTarget,
4372
- orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
3658
+ orchestratorIntercomTarget: childBridgeActive ? data.intercomBridge.orchestratorTarget : undefined,
4373
3659
  nestedRoute: foregroundControl?.nestedRoute,
4374
3660
  index: 0,
4375
3661
  modelOverride,
4376
- thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
3662
+ fast: params.fast,
3663
+ modelOverrideFromParent,
3664
+ thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent),
3665
+ thinkingCeiling: agentConfig.maxThinking,
3666
+ extensionBindings: params.extensionBindings,
4377
3667
  availableModels,
4378
3668
  preferredModelProvider: currentProvider,
4379
- modelScope: data.modelScope,
3669
+ modelScope: modelScopes,
4380
3670
  skills: effectiveSkills,
4381
3671
  structuredOutput: structuredRuntime,
4382
3672
  agentContract: params.agentContract,
@@ -4389,10 +3679,21 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4389
3679
  onDetachedExit: (result) => {
4390
3680
  try {
4391
3681
  try {
4392
- updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: params.workflowParentRunId === undefined });
3682
+ updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: true });
4393
3683
  } catch {
4394
3684
  // Remembered foreground state is best-effort; run history and cleanup must still complete.
4395
3685
  }
3686
+ const workflowParentRunId = params.workflowParentRunId ?? foregroundControl?.parentWorkflowRunId;
3687
+ if (workflowParentRunId) {
3688
+ reconcileDetachedWorkflowChildCompletion({
3689
+ state: deps.state,
3690
+ workflowRunId: workflowParentRunId,
3691
+ childRunId: runId,
3692
+ result,
3693
+ events: deps.pi.events,
3694
+ workflowKey: params.workflowKey ?? foregroundControl?.workflowKey,
3695
+ });
3696
+ }
4396
3697
  } finally {
4397
3698
  try {
4398
3699
  if (!artifactConfig.enabled) cleanupStructuredOutputRuntime(structuredRuntime);
@@ -4416,6 +3717,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4416
3717
  capabilityCeiling: data.capabilityCeiling,
4417
3718
  allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
4418
3719
  }));
3720
+ } catch (error) {
3721
+ if (worktreeSetup) cleanupWorktrees(worktreeSetup);
3722
+ throw error;
4419
3723
  } finally {
4420
3724
  // An attached runSync rejection still owns its child and structured runtime.
4421
3725
  // A successful detached receipt transfers both to onDetachedExit while the
@@ -4429,6 +3733,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4429
3733
  recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
4430
3734
  }
4431
3735
 
3736
+ let worktreeHandoff: ReturnType<typeof finalizeSingleWorktreeHandoff> | undefined;
3737
+ if (worktreeSetup) {
3738
+ worktreeHandoff = r.detached
3739
+ ? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
3740
+ : finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result: r });
3741
+ }
3742
+
4432
3743
  if (r.progress) allProgress.push(r.progress);
4433
3744
  if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
4434
3745
 
@@ -4463,8 +3774,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4463
3774
  totalChildUsage: sumResultsUsage([r]),
4464
3775
  totalCost,
4465
3776
  usageBudget: usageBudgetState(data.usageBudget, totalCost),
3777
+ ...(worktreeHandoff?.reference ? { parallelHandoff: worktreeHandoff.reference } : {}),
4466
3778
  }));
4467
- rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
3779
+ rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, extensionBindings: params.extensionBindings });
4468
3780
 
4469
3781
  const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results: [r] });
4470
3782
  if (!r.detached && !r.interrupted && !suppressRoutineResultIntercom) {
@@ -4487,6 +3799,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4487
3799
  }
4488
3800
  }
4489
3801
 
3802
+ const worktreeSuffix = worktreeHandoff?.suffix ? `\n\n${worktreeHandoff.suffix}` : "";
4490
3803
  if (r.detached) {
4491
3804
  const statusRecovery = `subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.`;
4492
3805
  const blockingRecovery = `subagent_wait({ id: "${runId}" }). Use ${statusRecovery}`;
@@ -4496,26 +3809,26 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
4496
3809
  ? `Detached at user request: ${params.agent}. The child continues independently. Register a completion wake-up with subagent_wait({ id: "${runId}", nonBlocking: true }), or use ${statusRecovery}`
4497
3810
  : `Detached before task completion: ${params.agent}. Wait with ${blockingRecovery}`;
4498
3811
  return {
4499
- content: [{ type: "text", text: message }],
3812
+ content: [{ type: "text", text: `${message}${worktreeSuffix}` }],
4500
3813
  details,
4501
3814
  };
4502
3815
  }
4503
3816
 
4504
3817
  if (r.interrupted) {
4505
3818
  return {
4506
- content: [{ type: "text", text: `Run paused after interrupt (${params.agent}). Waiting for explicit next action.` }],
3819
+ content: [{ type: "text", text: `Run paused after interrupt (${params.agent}). Waiting for explicit next action.${worktreeSuffix}` }],
4507
3820
  details,
4508
3821
  };
4509
3822
  }
4510
3823
 
4511
3824
  if (r.exitCode !== 0)
4512
3825
  return {
4513
- content: [{ type: "text", text: formatFailedSingleRunOutput(r, finalizedOutput.displayOutput) }],
3826
+ content: [{ type: "text", text: `${formatFailedSingleRunOutput(r, finalizedOutput.displayOutput)}${worktreeSuffix}` }],
4514
3827
  details,
4515
3828
  isError: true,
4516
3829
  };
4517
3830
  return {
4518
- content: [{ type: "text", text: finalizedOutput.displayOutput || "(no output)" }],
3831
+ content: [{ type: "text", text: `${finalizedOutput.displayOutput || "(no output)"}${worktreeSuffix}` }],
4519
3832
  details,
4520
3833
  };
4521
3834
  }
@@ -4607,13 +3920,20 @@ export function bindMissionWorkflowChildAsyncLaunch(
4607
3920
  return { ...params, workflowChildAsyncId: id };
4608
3921
  }
4609
3922
 
4610
- function workflowChildResult(key: string, result: AgentToolResult<Details>): WorkflowScriptChildResult {
3923
+ function workflowChildResult(
3924
+ key: string,
3925
+ result: AgentToolResult<Details>,
3926
+ childParams: Record<string, unknown> = {},
3927
+ resumeState?: SubagentState,
3928
+ ): WorkflowScriptChildResult {
4611
3929
  const receiptOutput = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n");
4612
3930
  const output = result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
4613
3931
  ? result.details.results[0].finalOutput
4614
3932
  : receiptOutput;
4615
3933
  const detached = result.details.results.some((child) => child.detached);
4616
- const ok = result.isError !== true && !detached;
3934
+ const interrupted = result.details.results.some((child) => child.interrupted);
3935
+ const stopped = result.details.results.some((child) => child.stopped);
3936
+ const ok = result.isError !== true && !detached && !interrupted && !stopped;
4617
3937
  const artifactPaths = new Set<string>();
4618
3938
  if (result.details.asyncDir) artifactPaths.add(result.details.asyncDir);
4619
3939
  if (result.details.parallelHandoff?.path) artifactPaths.add(result.details.parallelHandoff.path);
@@ -4624,28 +3944,175 @@ function workflowChildResult(key: string, result: AgentToolResult<Details>): Wor
4624
3944
  }
4625
3945
  const structured = result.details.results.map((child) => child.structuredOutput).filter((value) => value !== undefined);
4626
3946
  const resolvedAgents = [...new Set(result.details.results.map((child) => child.agent).filter((agent): agent is string => Boolean(agent)))];
3947
+ const resolvedContexts = [...new Set(result.details.results.map((child) => child.context).filter((context): context is "fresh" | "fork" => context === "fresh" || context === "fork"))];
3948
+ const runId = result.details.runId ?? result.details.asyncId;
3949
+ let resumability: WorkflowScriptChildResult["resumability"];
3950
+ if (!runId || !resumeState) {
3951
+ resumability = { state: "not-resumable", reason: runId ? "resumability was not inspected" : "child produced no run id" };
3952
+ } else {
3953
+ try {
3954
+ const target = resolveResumeTarget({ id: runId }, resumeState, { asyncRequireSessionFile: true, exactOnly: true });
3955
+ resumability = target.kind === "revive"
3956
+ ? { state: "resumable" }
3957
+ : { state: "not-resumable", reason: "child is still running" };
3958
+ } catch (error) {
3959
+ resumability = { state: "not-resumable", reason: error instanceof Error ? error.message : String(error) };
3960
+ }
3961
+ }
3962
+ const requestedContext = childParams.context === "fresh" || childParams.context === "fork" ? childParams.context : undefined;
3963
+ const resolvedContext = result.details.context ?? (resolvedContexts.length === 1 ? resolvedContexts[0] : resolvedContexts.length > 1 ? "mixed" : undefined);
3964
+ const resumeSourceRunId = typeof childParams.resume === "string" && childParams.resume.trim() ? childParams.resume.trim() : undefined;
3965
+ const continuationRunIds = [...new Set([resumeSourceRunId, runId].filter((value): value is string => Boolean(value)))];
3966
+ const outputReference = result.details.results.find((child) => child.savedOutputPath)?.savedOutputPath
3967
+ ?? result.details.results.find((child) => child.outputReference?.path)?.outputReference?.path;
4627
3968
  return {
4628
3969
  key,
4629
3970
  ok,
4630
3971
  ...(resolvedAgents.length === 1 ? { agent: resolvedAgents[0] } : {}),
4631
- ...(result.details.runId || result.details.asyncId ? { runId: result.details.runId ?? result.details.asyncId } : {}),
3972
+ ...(runId ? { runId } : {}),
4632
3973
  output,
4633
3974
  ...(!ok ? { error: receiptOutput || output || "Child run failed." } : {}),
4634
3975
  ...(detached ? { detached: true } : {}),
3976
+ ...(interrupted ? { interrupted: true } : {}),
3977
+ ...(stopped ? { stopped: true } : {}),
4635
3978
  ...(structured.length === 1 ? { structuredOutput: structured[0] } : structured.length > 1 ? { structuredOutput: structured } : {}),
3979
+ ...(requestedContext ? { requestedContext } : {}),
3980
+ ...(resolvedContext ? { resolvedContext } : {}),
3981
+ ...(outputReference ? { outputReference } : {}),
3982
+ resumability,
3983
+ continuation: { runIds: continuationRunIds },
4636
3984
  artifactPaths: [...artifactPaths],
4637
3985
  results: result.details.results,
4638
3986
  };
4639
3987
  }
4640
3988
 
3989
+ function workflowSteerReceipt(key: string, result: AgentToolResult<Details>): WorkflowSteerResult {
3990
+ const steering = result.details.steering;
3991
+ const error = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n") || undefined;
3992
+ if (!steering) return { key, state: "failed", ...(error ? { error } : {}) };
3993
+ const state = result.isError === true || steering.state === "failed" || steering.state === "partial"
3994
+ ? "failed"
3995
+ : steering.deliveryStatus === "delivered" ? "delivered" : "queued";
3996
+ return {
3997
+ key,
3998
+ state,
3999
+ requestId: steering.requestId,
4000
+ deliveryStatus: steering.deliveryStatus,
4001
+ targets: steering.targets.map((target) => ({ index: target.index, state: target.state, ...(target.reason ? { reason: target.reason } : {}) })),
4002
+ ...(state === "failed" && error ? { error } : {}),
4003
+ };
4004
+ }
4005
+
4006
+ function resolveKeyedWorkflowResume(
4007
+ reference: WorkflowReceiptResumeReference,
4008
+ state: SubagentState,
4009
+ ): { runId: string; runIds: string[] } {
4010
+ const entry = resolveWorkflowReceiptResumeEntry({
4011
+ reference,
4012
+ asyncDirRoot: DIRS.async,
4013
+ assertResumable(runId) {
4014
+ const target = resolveResumeTarget({ id: runId }, state, { asyncRequireSessionFile: true, exactOnly: true });
4015
+ if (target.kind !== "revive") throw new Error(`Workflow receipt child '${reference.key}' latest run '${runId}' is still running.`);
4016
+ },
4017
+ });
4018
+ const runId = entry.latestRunId;
4019
+ if (!runId) throw new Error(`Workflow receipt child '${reference.key}' has no retained run id.`);
4020
+ return { runId, runIds: entry.continuation.runIds };
4021
+ }
4022
+
4023
+ function terminalWorkflowReceipt(
4024
+ workflowRunId: string,
4025
+ state: WorkflowReceiptState,
4026
+ children: WorkflowScriptChildResult[],
4027
+ ): WorkflowReceipt {
4028
+ return buildWorkflowReceipt({ workflowRunId, state, children });
4029
+ }
4030
+
4031
+ export async function steerWorkflowChildByKey(input: {
4032
+ state: SubagentState;
4033
+ workflowRunId: string;
4034
+ key: string;
4035
+ message: string;
4036
+ options: WorkflowSteerOptions;
4037
+ signal?: AbortSignal;
4038
+ asyncDirRoot?: string;
4039
+ resolveRunId?: () => string | undefined;
4040
+ }): Promise<WorkflowSteerResult> {
4041
+ const asyncDirRoot = input.asyncDirRoot ?? DIRS.async;
4042
+ const ackTimeoutMs = input.options.ackTimeoutMs ?? 3_000;
4043
+ const deadline = Date.now() + ackTimeoutMs;
4044
+ while (true) {
4045
+ const control = [...input.state.foregroundControls.values()].find((candidate) => candidate.parentWorkflowRunId === input.workflowRunId
4046
+ && candidate.workflowKey === input.key
4047
+ && Boolean(candidate.workflowSteeringDir)
4048
+ && (candidate.activeChildren?.size ?? 0) > 0);
4049
+ if (control) {
4050
+ const result = await steerWorkflowForegroundTarget({
4051
+ target: { control, workflowRunId: input.workflowRunId, sourceRunId: control.runId },
4052
+ message: input.message,
4053
+ mode: input.options.mode,
4054
+ index: input.options.index,
4055
+ ackTimeoutMs: Math.max(1, deadline - Date.now()),
4056
+ signal: input.signal,
4057
+ });
4058
+ return workflowSteerReceipt(input.key, result);
4059
+ }
4060
+
4061
+ const workflowStatus = readStatus(path.join(asyncDirRoot, input.workflowRunId));
4062
+ const step = workflowStatus?.steps?.find((candidate) => candidate.workflowKey === input.key);
4063
+ const childRunId = step?.runId ?? input.resolveRunId?.();
4064
+ if (childRunId) {
4065
+ const asyncDir = path.join(asyncDirRoot, childRunId);
4066
+ const childStatus = reconcileAsyncRun(asyncDir).status;
4067
+ if (childStatus && childStatus.state !== "running" && childStatus.state !== "queued" && (input.options.mode !== "follow_up" || !canQueueRetainedAsyncFollowUp(childStatus, input.options.index))) {
4068
+ return { key: input.key, state: "missed", error: `Workflow child '${input.key}' is ${childStatus.state}.` };
4069
+ }
4070
+ if (childStatus) {
4071
+ const result = await steerAsyncRun({
4072
+ state: input.state,
4073
+ runId: childRunId,
4074
+ message: input.message,
4075
+ mode: input.options.mode,
4076
+ index: input.options.index,
4077
+ ackTimeoutMs: Math.max(1, deadline - Date.now()),
4078
+ location: { asyncDir },
4079
+ signal: input.signal,
4080
+ });
4081
+ return workflowSteerReceipt(input.key, result);
4082
+ }
4083
+ }
4084
+ if (step && step.status !== "running" && step.status !== "pending") {
4085
+ return { key: input.key, state: "missed", error: `Workflow child '${input.key}' is ${step.status}.` };
4086
+ }
4087
+ if (workflowStatus && workflowStatus.state !== "running" && workflowStatus.state !== "queued") {
4088
+ return { key: input.key, state: "missed", error: `Workflow '${input.workflowRunId}' is ${workflowStatus.state}.` };
4089
+ }
4090
+ if (input.signal?.aborted || Date.now() >= deadline) {
4091
+ return { key: input.key, state: "missed", error: `Workflow child '${input.key}' had no live steering route.` };
4092
+ }
4093
+ await new Promise<void>((resolve) => setTimeout(resolve, Math.min(10, Math.max(1, deadline - Date.now()))));
4094
+ }
4095
+ }
4096
+
4641
4097
  export function prepareWorkflowLaunchParams(
4642
4098
  workflowDefaults: SubagentParamsLike,
4643
4099
  childParams: Record<string, unknown>,
4644
4100
  parentWorkflowRunId: string,
4645
4101
  workflowKey: string,
4646
- options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor } = {},
4102
+ options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number; externalAsyncRequired?: boolean; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } = {},
4647
4103
  ): SubagentParamsLike {
4104
+ const capabilityCeiling = intersectSubagentCapabilityCeilings(workflowDefaults.capabilityCeiling, options.capabilityCeiling);
4105
+ const parentTimeoutMs = options.parentDeadlineAt === undefined
4106
+ || childParams.timeoutMs !== undefined
4107
+ || childParams.maxRuntimeMs !== undefined
4108
+ || workflowDefaults.timeoutMs !== undefined
4109
+ || workflowDefaults.maxRuntimeMs !== undefined
4110
+ ? undefined
4111
+ : Math.max(1, options.parentDeadlineAt - Date.now());
4648
4112
  if (typeof childParams.resume === "string") {
4113
+ if (childParams.extensionBindings !== undefined || workflowDefaults.extensionBindings !== undefined) {
4114
+ throw new Error("extensionBindings is not supported with retained resume; resume uses the original retained child binding.");
4115
+ }
4649
4116
  if (childParams.gate !== undefined || workflowDefaults.gate !== undefined) {
4650
4117
  throw new Error("gate is not supported with retained resume; resume uses the retained child contract.");
4651
4118
  }
@@ -4653,33 +4120,41 @@ export function prepareWorkflowLaunchParams(
4653
4120
  const turnBudget = childParams.turnBudget ?? workflowDefaults.turnBudget;
4654
4121
  const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
4655
4122
  const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
4123
+ const worktree = childParams.worktree ?? workflowDefaults.worktree;
4656
4124
  return {
4657
4125
  action: "resume",
4658
4126
  id: childParams.resume.trim(),
4127
+ ...(typeof childParams.index === "number" && Number.isInteger(childParams.index) ? { index: childParams.index } : {}),
4659
4128
  message: typeof childParams.task === "string" ? childParams.task.trim() : "",
4660
4129
  workflowParentRunId: parentWorkflowRunId,
4661
4130
  workflowKey,
4131
+ ...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
4662
4132
  ...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
4663
4133
  ...(options.missionDetached ? { mission: false } : {}),
4664
4134
  ...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
4665
4135
  ...(turnBudget !== undefined ? { turnBudget: turnBudget as TurnBudgetConfig } : {}),
4666
4136
  ...(toolBudget !== undefined ? { toolBudget: toolBudget as ToolBudgetConfig } : {}),
4667
4137
  ...(intercomBridge !== undefined ? { intercomBridge: intercomBridge as IntercomBridgeConfig } : {}),
4138
+ ...(capabilityCeiling ? { capabilityCeiling } : {}),
4668
4139
  };
4669
4140
  }
4670
4141
  const launchParams = {
4671
4142
  ...workflowDefaults,
4672
- async: false,
4143
+ async: options.externalAsyncRequired === true && childParams.async === undefined && workflowDefaults.async === undefined ? true : false,
4673
4144
  ...childParams,
4145
+ ...(options.externalAsyncRequired === true && childParams.async === undefined && workflowDefaults.async === undefined ? { workflowAwaitAsync: true } : {}),
4674
4146
  ...(options.missionDetached ? { mission: false } : {}),
4675
4147
  workflowParentRunId: parentWorkflowRunId,
4676
4148
  workflowKey,
4149
+ ...(parentTimeoutMs !== undefined ? { workflowParentDeadlineAt: options.parentDeadlineAt } : {}),
4677
4150
  ...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
4678
4151
  ...(options.suppressRoutineResultIntercom ? { suppressRoutineResultIntercom: true } : {}),
4152
+ ...(capabilityCeiling ? { capabilityCeiling } : {}),
4679
4153
  } as SubagentParamsLike;
4154
+ if (launchParams.extensionBindings !== undefined) launchParams.extensionBindings = normalizeExtensionBindings(launchParams.extensionBindings)!.value;
4680
4155
  const normalizedGate = normalizeGateParams(launchParams);
4681
4156
  if (!normalizedGate.ok) throw new Error(normalizedGate.error);
4682
- return prepareWorkflowChildParams(normalizedGate.params);
4157
+ return normalizedGate.params;
4683
4158
  }
4684
4159
 
4685
4160
  type GateParamsNormalizationResult =
@@ -4697,41 +4172,6 @@ function normalizeGateParams(params: SubagentParamsLike): GateParamsNormalizatio
4697
4172
  return { ok: true, params: { ...rest, ...(normalized.acceptance !== undefined ? { acceptance: normalized.acceptance } : {}) } };
4698
4173
  }
4699
4174
 
4700
- function prepareWorkflowChildParams(params: SubagentParamsLike): SubagentParamsLike {
4701
- if (params.worktree !== true || !params.agent) return params;
4702
- const {
4703
- agent,
4704
- task = "",
4705
- model,
4706
- skill,
4707
- output,
4708
- outputMode,
4709
- outputSchema,
4710
- acceptance,
4711
- agentContract,
4712
- toolBudget,
4713
- reads,
4714
- ...runParams
4715
- } = params;
4716
- return {
4717
- ...runParams,
4718
- worktree: true,
4719
- tasks: [{
4720
- agent,
4721
- task,
4722
- ...(model !== undefined ? { model } : {}),
4723
- ...(skill !== undefined ? { skill } : {}),
4724
- ...(output !== undefined ? { output } : {}),
4725
- ...(reads !== undefined ? { reads } : {}),
4726
- ...(outputMode !== undefined ? { outputMode } : {}),
4727
- ...(outputSchema !== undefined ? { outputSchema } : {}),
4728
- ...(acceptance !== undefined ? { acceptance } : {}),
4729
- ...(agentContract !== undefined ? { agentContract } : {}),
4730
- ...(toolBudget !== undefined ? { toolBudget } : {}),
4731
- }],
4732
- };
4733
- }
4734
-
4735
4175
  function formatWorkflowValue(value: unknown): string {
4736
4176
  if (value === undefined) return "(undefined)";
4737
4177
  if (typeof value === "string") return value;
@@ -4754,6 +4194,38 @@ function workflowChatProgressUpdate(
4754
4194
  };
4755
4195
  }
4756
4196
 
4197
+ function createScheduledOwnerState(source: SubagentState, ownerSessionId: string, ctx: ExtensionContext): SubagentState {
4198
+ const ownerSpawns = source.subagentSpawns?.sessionId === ownerSessionId
4199
+ ? {
4200
+ ...source.subagentSpawns,
4201
+ grantHistory: [...(source.subagentSpawns.grantHistory ?? [])],
4202
+ }
4203
+ : undefined;
4204
+ return {
4205
+ ...source,
4206
+ baseCwd: ctx.cwd,
4207
+ currentSessionId: ownerSessionId,
4208
+ parentSessionFile: ctx.sessionManager.getSessionFile() ?? null,
4209
+ subagentInProgress: false,
4210
+ ...(ownerSpawns ? { subagentSpawns: ownerSpawns } : { subagentSpawns: undefined }),
4211
+ herdrProjectPanes: new Map(),
4212
+ asyncJobs: new Map(),
4213
+ fleetJobs: new Map(),
4214
+ foregroundRuns: new Map(),
4215
+ foregroundControls: new Map(),
4216
+ lastForegroundControlId: null,
4217
+ cleanupTimers: new Map(),
4218
+ lastUiContext: null,
4219
+ poller: null,
4220
+ completionSeen: new Map(),
4221
+ watcher: null,
4222
+ watcherRestartTimer: null,
4223
+ waitSubscriptions: new Map(),
4224
+ workflowControllers: new Map(),
4225
+ workflowChildStops: new Map(),
4226
+ };
4227
+ }
4228
+
4757
4229
  export function createSubagentExecutor(deps: ExecutorDeps): {
4758
4230
  execute: (
4759
4231
  id: string,
@@ -4782,11 +4254,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4782
4254
  onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
4783
4255
  ctx: ExtensionContext,
4784
4256
  ) => Promise<AgentToolResult<Details>>;
4257
+ /** Scheduled launches retain their owning context without replacing the live active session. */
4258
+ executeScheduled: (
4259
+ id: string,
4260
+ params: SubagentParamsLike,
4261
+ signal: AbortSignal,
4262
+ ctx: ExtensionContext,
4263
+ ) => Promise<AgentToolResult<Details>>;
4785
4264
  } {
4786
4265
  const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
4787
4266
  const delegatedZeroToolBudgets = new WeakSet<object>();
4788
4267
  const delegatedExecutions = new WeakSet<object>();
4789
4268
  const warnedArtifactPackageDirs = new Set<string>();
4269
+ const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
4790
4270
  const execute = async (
4791
4271
  _id: string,
4792
4272
  params: SubagentParamsLike,
@@ -4807,12 +4287,21 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4807
4287
  if (!normalizedGate.ok) return buildRequestedModeError(params, normalizedGate.error);
4808
4288
  const requestParams = normalizedGate.params;
4809
4289
  const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
4290
+ if (normalizedAction === "resume" && requestParams.extensionBindings !== undefined) return buildRequestedModeError(requestParams, "extensionBindings is not supported with action='resume'; resume uses the original retained child binding.");
4810
4291
  if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
4292
+ if (requestParams.extensionBindings !== undefined) {
4293
+ try {
4294
+ requestParams.extensionBindings = normalizeExtensionBindings(requestParams.extensionBindings)!.value;
4295
+ } catch (error) {
4296
+ return buildRequestedModeError(requestParams, error instanceof Error ? error.message : String(error));
4297
+ }
4298
+ }
4811
4299
  const parentCwd = ctx.cwd;
4812
4300
  const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? (requestParams.async === false ? resolveConfigDefaultTimeoutMs(deps.config.timeoutMs) ?? DEFAULT_FOREGROUND_TIMEOUT_MS : undefined);
4813
4301
  const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
4814
4302
  if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
4815
4303
  const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
4304
+ const workflowAgents = deps.discoverAgents(workflowCwd, resolveExecutionAgentScope(requestParams.agentScope)).agents;
4816
4305
  const workflowArtifactConfig: ArtifactConfig = omitUndefinedProperties({
4817
4306
  ...DEFAULT_ARTIFACT_CONFIG,
4818
4307
  enabled: requestParams.artifacts !== false,
@@ -4824,7 +4313,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4824
4313
  const warning = getProjectArtifactPackagingWarning(workflowCwd);
4825
4314
  if (warning) console.warn(`[pi-subagents] ${warning}`);
4826
4315
  }
4827
- const workflowPrompts = { render: (ref: string, vars?: unknown) => renderWorkflowPrompt(ref, vars, workflowCwd) };
4828
4316
  const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd, workflowCwd, background: requestParams.async !== false });
4829
4317
  if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
4830
4318
  const chatProgress = chatProgressResult.projection!;
@@ -4901,12 +4389,18 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4901
4389
  };
4902
4390
  if (workflowRunId) {
4903
4391
  const toolCallId = _id;
4392
+ const workflowSessionRoot = requestParams.sessionDir
4393
+ ? path.resolve(deps.expandTilde(requestParams.sessionDir))
4394
+ : trustedSessionRootsForStatus(ctx, deps)[0];
4904
4395
  const asyncDir = path.join(DIRS.async, workflowRunId);
4905
4396
  const resultPath = resultFilePath(DIRS.results, workflowRunId);
4906
4397
  const statusPath = path.join(asyncDir, "status.json");
4907
4398
  const eventsPath = path.join(asyncDir, "events.jsonl");
4908
4399
  const startedAt = Date.now();
4909
4400
  const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
4401
+ const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(currentSessionId));
4402
+ const completionOwnerId = deps.state.completionOwnerId ?? currentCompletionOwnerId();
4403
+ deps.state.completionOwnerId = completionOwnerId;
4910
4404
  try {
4911
4405
  fs.mkdirSync(asyncDir, { recursive: true });
4912
4406
  writeRunFanoutBudgetDescriptor(asyncDir, workflowFanoutBudget);
@@ -4917,6 +4411,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4917
4411
  }
4918
4412
  const controller = new AbortController();
4919
4413
  deps.state.workflowControllers ??= new Map();
4414
+ deps.state.workflowChildStops ??= new Map();
4920
4415
  deps.state.workflowControllers.set(workflowRunId, controller);
4921
4416
  workflowCapacity?.markWorkflowStarted();
4922
4417
  if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()) });
@@ -4924,51 +4419,114 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4924
4419
  runId: workflowRunId,
4925
4420
  toolCallId,
4926
4421
  sessionId: currentSessionId ?? undefined,
4422
+ completionOwnerId,
4927
4423
  mode: "workflow",
4928
4424
  state: "running",
4929
4425
  startedAt,
4930
4426
  lastUpdate: startedAt,
4931
4427
  ...(timeout !== undefined ? { deadlineAt: startedAt + timeout, timeoutMs: timeout } : {}),
4932
4428
  cwd: workflowCwd,
4429
+ ...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}),
4430
+ ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}),
4933
4431
  pid: process.pid,
4934
4432
  steps: [],
4935
4433
  workflow: { trace: [], emits: [], console: [] },
4936
4434
  runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
4937
4435
  };
4938
- const appendWorkflowEvent = (event: Record<string, unknown>) => fs.appendFileSync(eventsPath, `${JSON.stringify({ ts: Date.now(), runId: workflowRunId, ...event })}\n`, "utf-8");
4436
+ const appendWorkflowEvent = (event: Record<string, unknown>) => {
4437
+ try {
4438
+ fs.appendFileSync(eventsPath, `${JSON.stringify({ ts: Date.now(), runId: workflowRunId, ...event })}\n`, "utf-8");
4439
+ } catch (error) {
4440
+ // The event log is a journal, not workflow truth. Callers append from
4441
+ // inside run-result handling, so losing an entry to a full disk or to a
4442
+ // transient Windows lock must not fail the run being recorded.
4443
+ console.error(`Failed to append async workflow event '${eventsPath}':`, error);
4444
+ }
4445
+ };
4939
4446
  let indexedState: AsyncStatus["state"] | undefined;
4940
- const persist = () => {
4447
+ const indexPersistence = createCapacityResilientJsonWriter({
4448
+ keepAlive: true,
4449
+ onSuccess: (_filePath, payload) => { indexedState = (payload as { state: AsyncStatus["state"] }).state; },
4450
+ onError: (error, filePath) => console.error(`Failed to update async workflow index '${filePath}':`, error),
4451
+ });
4452
+ const queueActiveRunIndex = (): void => {
4453
+ const state = status.state;
4454
+ if (indexedState === state && indexPersistence.pendingCount() === 0) return;
4455
+ indexPersistence.write(asyncDir, { state, toolCallId: status.toolCallId }, (_filePath, payload) => {
4456
+ const indexPayload = payload as { state: AsyncStatus["state"]; toolCallId?: string };
4457
+ updateActiveRunIndex(asyncDir, indexPayload.state, indexPayload.toolCallId, { retryCapacityErrors: true });
4458
+ });
4459
+ };
4460
+ const runPersistence = createCapacityResilientJsonWriter({
4461
+ keepAlive: true,
4462
+ onSuccess: (filePath) => { if (filePath === statusPath) queueActiveRunIndex(); },
4463
+ onError: (error, filePath) => console.error(`Failed to persist async workflow state '${filePath}':`, error),
4464
+ write: (filePath, payload) => filePath === resultPath
4465
+ ? writeAsyncResultFile(filePath, payload as Record<string, unknown>)
4466
+ : writeAtomicJson(filePath, payload),
4467
+ });
4468
+ let initialPersistenceComplete = false;
4469
+ let persistClosed = false;
4470
+ let statusPersistenceDegraded = false;
4471
+ // Progress journalling runs from admit, launch, the launch and progress observers,
4472
+ // onTrace and onEmit -- all inside the promises a workflowScript awaits. Those
4473
+ // callers pass tolerateStatusWriteFailure so a transient lock on status.json cannot
4474
+ // mark a finished child failed or abort its still-running siblings. Initial and
4475
+ // terminal writes stay fail-fast on purpose: no child work is at risk by then, and
4476
+ // silently dropping a terminal write would leave status.json and the active-run
4477
+ // index pinned at "running" after the result already says complete.
4478
+ const persist = (options: { tolerateStatusWriteFailure?: boolean } = {}) => {
4479
+ if (persistClosed) return;
4480
+ const liveJob = deps.state.asyncJobs.get(workflowRunId);
4481
+ if (liveJob && (liveJob.status === "complete" || liveJob.status === "failed") && status.state !== "complete" && status.state !== "failed") return;
4941
4482
  status.lastUpdate = Date.now();
4942
- writeAtomicJson(statusPath, status);
4943
- if (indexedState !== status.state) {
4944
- updateActiveRunIndex(asyncDir, status.state, status.toolCallId);
4945
- indexedState = status.state;
4483
+ if (!initialPersistenceComplete) {
4484
+ writeAtomicJson(statusPath, status);
4485
+ initialPersistenceComplete = true;
4486
+ queueActiveRunIndex();
4487
+ } else if (options.tolerateStatusWriteFailure) {
4488
+ try {
4489
+ runPersistence.write(statusPath, status);
4490
+ statusPersistenceDegraded = false;
4491
+ } catch (error) {
4492
+ const message = `Failed to persist async workflow state ${statusPath}: ${error instanceof Error ? error.message : String(error)}`;
4493
+ console.error(message, error);
4494
+ if (!statusPersistenceDegraded) {
4495
+ statusPersistenceDegraded = true;
4496
+ try {
4497
+ appendWorkflowEvent({ type: "subagent.workflow.status_write_failed", error: message });
4498
+ } catch (eventError) {
4499
+ console.error(`Failed to record degraded status persistence for '${statusPath}':`, eventError);
4500
+ }
4501
+ }
4502
+ }
4503
+ } else {
4504
+ runPersistence.write(statusPath, status);
4946
4505
  }
4947
- const job = deps.state.asyncJobs.get(workflowRunId);
4948
- if (job) {
4949
- job.status = status.state;
4950
- job.updatedAt = status.lastUpdate;
4951
- job.activityState = status.activityState;
4952
- job.lastActivityAt = status.lastActivityAt;
4953
- job.currentTool = status.currentTool;
4954
- job.currentToolStartedAt = status.currentToolStartedAt;
4955
- job.currentPath = status.currentPath;
4956
- job.turnCount = status.turnCount;
4957
- job.toolCount = status.toolCount;
4958
- job.currentStep = status.currentStep;
4506
+ if (liveJob) {
4507
+ liveJob.status = status.state;
4508
+ liveJob.updatedAt = status.lastUpdate;
4509
+ liveJob.activityState = status.activityState;
4510
+ liveJob.lastActivityAt = status.lastActivityAt;
4511
+ liveJob.currentTool = status.currentTool;
4512
+ liveJob.currentToolStartedAt = status.currentToolStartedAt;
4513
+ liveJob.currentPath = status.currentPath;
4514
+ liveJob.turnCount = status.turnCount;
4515
+ liveJob.toolCount = status.toolCount;
4516
+ liveJob.currentStep = status.currentStep;
4959
4517
  if (status.steps) {
4960
- job.steps = status.steps.map((step, index) => ({ ...step, index }));
4961
- job.agents = status.steps.map((step) => step.agent);
4518
+ liveJob.steps = status.steps.map((step, index) => ({ ...step, index }));
4519
+ liveJob.agents = status.steps.map((step) => step.agent);
4962
4520
  } else {
4963
- delete job.steps;
4964
- delete job.agents;
4521
+ delete liveJob.steps;
4522
+ delete liveJob.agents;
4965
4523
  }
4966
- job.workflow = status.workflow;
4524
+ liveJob.workflow = status.workflow;
4967
4525
  }
4968
4526
  };
4969
4527
  const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
4970
4528
  try {
4971
- writeAsyncResultFile(resultPath, payload);
4529
+ runPersistence.write(resultPath, payload);
4972
4530
  return true;
4973
4531
  } catch (error) {
4974
4532
  const message = `Failed to write async workflow result ${resultPath}: ${error instanceof Error ? error.message : String(error)}`;
@@ -4998,20 +4556,33 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4998
4556
  status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
4999
4557
  status.currentStep = runningSteps.length === 1 ? steps.indexOf(runningSteps[0]!) : undefined;
5000
4558
  };
5001
- const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, toolCallId, cwd: workflowCwd, status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow };
4559
+ const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, toolCallId, cwd: workflowCwd, ...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}), status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow };
5002
4560
  deps.state.asyncJobs.set(workflowRunId, workflowJob);
5003
4561
  deps.state.fleetJobs ??= new Map();
5004
4562
  deps.state.fleetJobs.set(workflowRunId, workflowJob);
5005
- persist();
4563
+ try {
4564
+ persist();
4565
+ } catch (error) {
4566
+ deps.state.workflowControllers?.delete(workflowRunId);
4567
+ deps.state.asyncJobs.delete(workflowRunId);
4568
+ deps.state.fleetJobs?.delete(workflowRunId);
4569
+ workflowCapacity?.rollback();
4570
+ indexPersistence.dispose();
4571
+ return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
4572
+ }
5006
4573
  appendWorkflowEvent({ type: "subagent.workflow.started" });
5007
4574
  const { workflowScript, async: _workflowAsync, chatProgress: _chatProgress, ...workflowRequest } = requestParams;
5008
4575
  void Promise.resolve().then(async () => {
4576
+ const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
5009
4577
  const workflowResults: SingleResult[] = [];
4578
+ const workflowChildRunIds = new Map<string, string>();
5010
4579
  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;
5011
4580
  const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
5012
4581
  const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
5013
- const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId), configuredOutputBaseDir);
4582
+ const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
5014
4583
  const claimedOutputPaths = new Map<string, string>();
4584
+ const childOutputOverrides = new Map<string, string>();
4585
+ const producedChildOutputPaths = new Set<string>();
5015
4586
  const workflowSteps = new Map<string, NonNullable<AsyncStatus["steps"]>[number]>();
5016
4587
  let projectedTraceLength = 0;
5017
4588
  let projectedTraceTail: NonNullable<Details["workflow"]>["trace"][number] | undefined;
@@ -5069,62 +4640,59 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5069
4640
  status.steps?.push(step);
5070
4641
  workflowSteps.set(entry.key, step);
5071
4642
  }
4643
+ const projectedStep = workflowSteps.get(entry.key);
4644
+ if (entry.state === "stopped" && projectedStep) {
4645
+ appendWorkflowEvent({
4646
+ type: "subagent.child-status",
4647
+ version: 1,
4648
+ childId: entry.key,
4649
+ status: "stopped",
4650
+ reason: "user",
4651
+ source: "async",
4652
+ stepIndex: status.steps?.indexOf(projectedStep),
4653
+ agent: projectedStep.agent,
4654
+ ...(projectedStep.runId ? { childRunId: projectedStep.runId } : {}),
4655
+ workflowKey: entry.key,
4656
+ ...(projectedStep.phase ? { phase: projectedStep.phase } : {}),
4657
+ ...(projectedStep.label ? { label: projectedStep.label } : {}),
4658
+ });
4659
+ }
5072
4660
  }
5073
4661
  projectedTraceLength = trace.length;
5074
4662
  projectedTraceTail = trace.at(-1);
5075
4663
  projectWorkflowActivity();
5076
- persist();
4664
+ persist({ tolerateStatusWriteFailure: true });
5077
4665
  appendWorkflowEvent({ type: "subagent.workflow.trace", trace });
5078
4666
  };
5079
4667
  try {
5080
- const autoRelaunchCap = resolveMaxWorkflowAutoRelaunches(deps.config.maxWorkflowAutoRelaunches);
5081
- let relaunched = 0;
5082
- let capReached = false;
5083
- let stallReason: "fanout-limited" | "usage-budget" | "no-progress" | undefined;
5084
- let workflow: Awaited<ReturnType<typeof runWorkflowScript>> | undefined;
5085
- let roundProgress = { launched: 0, fanoutRejected: 0, usageBudgetBlocked: 0, stateWrites: 0 };
5086
- while (true) {
5087
- if (relaunched > 0) {
5088
- workflowFanoutBudget = createRunFanoutBudget(`${workflowRunId}#${relaunched + 1}`, workflowFanoutBudget.limit);
5089
- workflowSteps.clear();
5090
- projectedTraceLength = 0;
5091
- projectedTraceTail = undefined;
5092
- status = { ...status, steps: [], currentStep: undefined, workflow: { trace: [], emits: [], console: [] } };
5093
- }
5094
- roundProgress = { launched: 0, fanoutRejected: 0, usageBudgetBlocked: 0, stateWrites: 0 };
5095
- workflow = await runWorkflowScript({
5096
- script: workflowScript,
4668
+ const workflow = await runWorkflowScript({
4669
+ script: workflowScript,
5097
4670
  timeoutMs: timeout,
5098
4671
  signal: controller.signal,
5099
- prompts: workflowPrompts,
5100
- ...(workflowState ? { state: { get: (key: string) => workflowState.get(key), set: (key: string, value: unknown) => { roundProgress.stateWrites += 1; workflowState.set(key, value); } } } : {}),
4672
+ registerStopChild: (stop) => {
4673
+ if (stop) deps.state.workflowChildStops?.set(workflowRunId, stop);
4674
+ else deps.state.workflowChildStops?.delete(workflowRunId);
4675
+ },
4676
+ ...(workflowState ? { state: workflowState } : {}),
5101
4677
  onTrace: updateTrace,
5102
4678
  admit: (calls) => {
5103
- const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, claimedOutputPaths, entries: calls });
4679
+ const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
5104
4680
  if (outputClaims.error) throw new Error(outputClaims.error);
5105
- try {
5106
- status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
5107
- } catch (error) {
5108
- if (error instanceof RunFanoutLimitError) roundProgress.fanoutRejected += calls.length;
5109
- throw error;
5110
- }
4681
+ status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
5111
4682
  if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
5112
- persist();
4683
+ if (outputClaims.overrides) for (const [key, output] of outputClaims.overrides) childOutputOverrides.set(key, output);
4684
+ persist({ tolerateStatusWriteFailure: true });
5113
4685
  },
5114
4686
  onEmit: (emits) => {
5115
4687
  // Each emit is validated at the host boundary in runWorkflowScript before onEmit fires.
5116
4688
  status.workflow = { ...(status.workflow ?? { trace: [], console: [] }), emits };
5117
- persist();
4689
+ persist({ tolerateStatusWriteFailure: true });
5118
4690
  appendWorkflowEvent({ type: "subagent.workflow.emit", value: emits.at(-1) });
5119
4691
  },
5120
4692
  launch: async (key, childParams, workflowSignal, admission) => {
5121
- if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
4693
+ if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."), childParams, deps.state);
5122
4694
  const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
5123
- if (budgetState?.exhausted) {
5124
- roundProgress.usageBudgetBlocked += 1;
5125
- return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
5126
- }
5127
- roundProgress.launched += 1;
4695
+ if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state);
5128
4696
  const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
5129
4697
  const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
5130
4698
  recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
@@ -5136,7 +4704,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5136
4704
  });
5137
4705
  const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
5138
4706
  const childRequest = bindMissionWorkflowChildAsyncLaunch(
5139
- { ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: workflowRunId, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, options: { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget } }), runFanoutAdmitted: admission.admitted },
4707
+ { ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: workflowRunId, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), options: { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt, capabilityCeiling: workflowCapabilityCeiling } }), runFanoutAdmitted: admission.admitted },
5140
4708
  missionBinding,
5141
4709
  deps.asyncByDefault,
5142
4710
  );
@@ -5147,7 +4715,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5147
4715
  step.sessionFile = launch.sessionFile;
5148
4716
  step.async = launch.async;
5149
4717
  if (launch.runId) step.runId = launch.runId;
5150
- persist();
4718
+ persist({ tolerateStatusWriteFailure: true });
5151
4719
  }
5152
4720
  recordMissionWorkflowChild(missionBinding, workflowRunId, key, { status: "running", agent: launch.agent, ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}) });
5153
4721
  });
@@ -5170,7 +4738,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5170
4738
  step.thinking = progress.thinking;
5171
4739
  step.error = progress.error;
5172
4740
  projectWorkflowActivity();
5173
- persist();
4741
+ persist({ tolerateStatusWriteFailure: true });
5174
4742
  recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
5175
4743
  status: step.status,
5176
4744
  heartbeat: { status: step.status, ...(childPhase ? { phase: childPhase } : {}) },
@@ -5178,7 +4746,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5178
4746
  }, ctx, preserveActiveSession);
5179
4747
  });
5180
4748
  workflowResults.push(...result.details.results);
5181
- const child = workflowChildResult(key, result);
4749
+ for (const childResult of result.details.results) {
4750
+ if (childResult.savedOutputPath) producedChildOutputPaths.add(childResult.savedOutputPath);
4751
+ }
4752
+ const child = workflowChildResult(key, result, childParams, deps.state);
4753
+ if (child.runId) workflowChildRunIds.set(key, child.runId);
5182
4754
  const step = status.steps?.find((candidate) => candidate.workflowKey === key);
5183
4755
  if (step) {
5184
4756
  step.async = Boolean(result.details.asyncId || result.details.asyncDir);
@@ -5202,46 +4774,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5202
4774
  return child;
5203
4775
  },
5204
4776
  status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
4777
+ resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
4778
+ steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
5205
4779
  });
5206
- const decision = resolveAutoRelaunchDecision(workflow?.value, relaunched, autoRelaunchCap, roundProgress);
5207
- if (!decision.relaunch) {
5208
- capReached = decision.capped;
5209
- stallReason = decision.stallReason;
5210
- break;
5211
- }
5212
- relaunched += 1;
5213
- status = { ...status, workflowRelaunchCount: relaunched };
5214
- persist();
5215
- appendWorkflowEvent({ type: "subagent.workflow.relaunched", relaunched });
5216
- }
5217
- if (!workflow) throw new Error("Workflow script produced no result.");
5218
4780
  const returnPreview = formatWorkflowValue(workflow.value).slice(0, 1_000);
5219
4781
  const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${workflow.emits.map(formatWorkflowValue).join(", ").slice(0, 1_000)}` : "";
5220
- const relaunchNote = relaunched > 0 ? `Auto-relaunched ${relaunched} time(s) with a fresh fan-out budget. ` : "";
5221
- const summary = `${relaunchNote}Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
4782
+ const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
4783
+ const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
4784
+ const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
5222
4785
  const workflowUsage = sumResultsUsage(workflowResults);
5223
- if (capReached || stallReason) {
5224
- const relaunchCapError = stallReason === "usage-budget"
5225
- ? "Workflow auto-relaunch stopped because the usage budget is exhausted; a fresh fan-out budget cannot make another child run."
5226
- : stallReason === "fanout-limited"
5227
- ? `Workflow auto-relaunch stopped because the fan-out budget rejected ${roundProgress.fanoutRejected} child launch(es) and the fresh budget has the same limit.`
5228
- : stallReason === "no-progress"
5229
- ? "Workflow auto-relaunch stopped because the budget result made no progress: no child launched and no workflow state changed."
5230
- : `Workflow auto-relaunch cap reached after ${relaunched} relaunch(es); the goal is not clean yet. The progress file is current; raise config.maxWorkflowAutoRelaunches or relaunch manually.`;
5231
- status = compactOptional<AsyncStatus>({ ...status, state: "failed", error: relaunchCapError, endedAt: Date.now(), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console } });
5232
- const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, relaunchCapError);
5233
- const resultSummary = appendWorkflowOutputWarning(relaunchCapError, outputWarning);
5234
- if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: "failed", summary: resultSummary, error: relaunchCapError, 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 })) return;
5235
- persist();
5236
- appendWorkflowEvent({ type: "subagent.workflow.completed", state: "failed", error: status.error });
5237
- } else {
5238
- const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary);
5239
- const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
5240
- 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) };
5241
- if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, 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 })) return;
5242
- persist();
5243
- appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
4786
+ 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) };
4787
+ const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children);
4788
+ let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
4789
+ try {
4790
+ workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
4791
+ } catch (receiptError) {
4792
+ appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
5244
4793
  }
4794
+ if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, 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.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
4795
+ persist();
4796
+ persistClosed = true;
4797
+ appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
5245
4798
  } catch (error) {
5246
4799
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
5247
4800
  const stopped = controller.signal.aborted;
@@ -5251,18 +4804,41 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5251
4804
  const state = stopped ? "stopped" : pauseForDetached ? "paused" : "failed";
5252
4805
  for (const step of status.steps ?? []) {
5253
4806
  if (step.workflowKey && detachedChildKeys.has(step.workflowKey)) {
4807
+ if (step.status === "completed" || step.status === "complete" || step.status === "failed") continue;
5254
4808
  step.status = "paused";
5255
4809
  step.activityState = "needs_attention";
4810
+ } else if (pauseForDetached && step.status === "running") {
4811
+ step.status = "stopped";
4812
+ step.stopped = true;
4813
+ delete step.activityState;
5256
4814
  }
5257
4815
  }
5258
4816
  status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached ? "needs_attention" : undefined, error: error instanceof Error ? error.message : String(error), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console } });
5259
- const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed."));
5260
- const resultSummary = appendWorkflowOutputWarning(status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed."), outputWarning);
5261
- if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: resultSummary, error: status.error, stopped: status.stopped, activityState: status.activityState, 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.detached ? { detached: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
4817
+ if (pauseForDetached) {
4818
+ const promoted = promotePausedWorkflowIfSettled(status);
4819
+ if (promoted) status = promoted;
4820
+ }
4821
+ const terminalSummary = status.state === "complete"
4822
+ ? "Workflow completed after detached child finished."
4823
+ : status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.");
4824
+ const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, terminalSummary, producedChildOutputPaths);
4825
+ const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
4826
+ const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
4827
+ const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children);
4828
+ let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
4829
+ try {
4830
+ workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
4831
+ } catch (receiptError) {
4832
+ appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
4833
+ }
4834
+ if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: status.state === "complete", state: status.state, summary: resultSummary, error: status.state === "complete" ? undefined : status.error, stopped: status.stopped, activityState: status.activityState, 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.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(child.detached && status.state !== "complete" ? { detached: true } : {}), ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, ...(workflowReceipt ? { workflowReceipt } : {}), asyncDir, cwd: workflowCwd, sessionId: currentSessionId, completionOwnerId, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), timestamp: Date.now(), durationMs: Date.now() - startedAt })) return;
5262
4835
  persist();
5263
- appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error, ...(status.activityState ? { activityState: status.activityState } : {}) });
4836
+ persistClosed = true;
4837
+ appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
5264
4838
  } finally {
4839
+ persistClosed = true;
5265
4840
  deps.state.workflowControllers?.delete(workflowRunId);
4841
+ deps.state.workflowChildStops?.delete(workflowRunId);
5266
4842
  deps.state.activeAsyncCapacity = workflowCapacity?.reconcile(new Set(deps.state.workflowControllers?.keys() ?? []))
5267
4843
  ?? deps.state.activeAsyncCapacity;
5268
4844
  }
@@ -5275,10 +4851,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5275
4851
  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;
5276
4852
  const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
5277
4853
  const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
5278
- const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id), configuredOutputBaseDir);
4854
+ const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id));
5279
4855
  const claimedOutputPaths = new Map<string, string>();
4856
+ const childOutputOverrides = new Map<string, string>();
4857
+ const producedChildOutputPaths = new Set<string>();
5280
4858
  const workflowResults: SingleResult[] = [];
4859
+ const workflowChildRunIds = new Map<string, string>();
5281
4860
  let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
4861
+ const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
4862
+ const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
5282
4863
  const sendWorkflowProgress = () => {
5283
4864
  const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow);
5284
4865
  if (update) onUpdate?.(update);
@@ -5288,26 +4869,26 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5288
4869
  script: requestParams.workflowScript,
5289
4870
  timeoutMs: timeout,
5290
4871
  signal,
5291
- prompts: workflowPrompts,
5292
4872
  ...(workflowState ? { state: workflowState } : {}),
5293
4873
  onTrace: (trace) => {
5294
4874
  liveWorkflow = { ...liveWorkflow, trace };
5295
4875
  sendWorkflowProgress();
5296
4876
  },
5297
4877
  admit: (calls) => {
5298
- const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, claimedOutputPaths, entries: calls });
4878
+ const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
5299
4879
  if (outputClaims.error) throw new Error(outputClaims.error);
5300
4880
  claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
5301
4881
  if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
4882
+ if (outputClaims.overrides) for (const [key, output] of outputClaims.overrides) childOutputOverrides.set(key, output);
5302
4883
  },
5303
4884
  onEmit: (emits) => {
5304
4885
  liveWorkflow = { ...liveWorkflow, emits };
5305
4886
  sendWorkflowProgress();
5306
4887
  },
5307
4888
  launch: async (key, childParams, workflowSignal, admission) => {
5308
- if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
4889
+ if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."), childParams, deps.state);
5309
4890
  const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
5310
- if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
4891
+ if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state);
5311
4892
  const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
5312
4893
  const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
5313
4894
  recordMissionWorkflowChild(missionBinding, _id, key, {
@@ -5319,7 +4900,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5319
4900
  });
5320
4901
  const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
5321
4902
  const childRequest = bindMissionWorkflowChildAsyncLaunch(
5322
- { ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: _id, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget } }), runFanoutAdmitted: admission.admitted },
4903
+ { ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: _id, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt, capabilityCeiling: workflowCapabilityCeiling } }), runFanoutAdmitted: admission.admitted },
5323
4904
  missionBinding,
5324
4905
  deps.asyncByDefault,
5325
4906
  );
@@ -5339,8 +4920,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5339
4920
  }, ctx, preserveActiveSession);
5340
4921
  });
5341
4922
  workflowResults.push(...result.details.results);
4923
+ for (const childResult of result.details.results) {
4924
+ if (childResult.savedOutputPath) producedChildOutputPaths.add(childResult.savedOutputPath);
4925
+ }
5342
4926
  if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
5343
- const child = workflowChildResult(key, result);
4927
+ const child = workflowChildResult(key, result, childParams, deps.state);
4928
+ if (child.runId) workflowChildRunIds.set(key, child.runId);
5344
4929
  const childStatus = missionWorkflowChildStatus(result);
5345
4930
  recordMissionWorkflowChild(missionBinding, _id, key, {
5346
4931
  status: childStatus,
@@ -5354,18 +4939,21 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5354
4939
  return child;
5355
4940
  },
5356
4941
  status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
4942
+ resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
4943
+ steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: _id, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
5357
4944
  });
4945
+ const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children);
5358
4946
  const traceLines = workflow.trace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.durationMs !== undefined ? ` in ${entry.durationMs}ms` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
5359
4947
  const sections = ["Workflow completed.", `Return:\n${formatWorkflowValue(workflow.value)}`];
5360
4948
  if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
5361
4949
  if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
5362
4950
  if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
5363
4951
  const workflowText = sections.join("\n\n");
5364
- const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText);
4952
+ const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
5365
4953
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
5366
4954
  return attachWorkflowMission(withRunFanoutBudget({
5367
4955
  content: [{ type: "text", text: displayText }],
5368
- 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 }),
4956
+ 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, receipt }, chatProgress }),
5369
4957
  }, workflowFanoutBudget));
5370
4958
  } catch (error) {
5371
4959
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
@@ -5376,16 +4964,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5376
4964
  if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
5377
4965
  if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
5378
4966
  const workflowText = sections.join("\n\n");
5379
- const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText);
4967
+ const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
5380
4968
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
4969
+ const receipt = terminalWorkflowReceipt(_id, "failed", partial.children);
5381
4970
  return attachWorkflowMission(withRunFanoutBudget({
5382
4971
  content: [{ type: "text", text: displayText }],
5383
4972
  isError: true,
5384
- 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 }),
4973
+ 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, receipt }, chatProgress }),
5385
4974
  }, workflowFanoutBudget));
5386
4975
  }
5387
4976
  }
5388
- const directParams = prepareWorkflowChildParams(requestParams);
4977
+ const directParams = requestParams;
5389
4978
  const requestCwd = resolveRequestedCwd(ctx.cwd, directParams.cwd);
5390
4979
  const paramsWithResolvedCwd = directParams.cwd === undefined ? directParams : { ...directParams, cwd: requestCwd };
5391
4980
  const action = paramsWithResolvedCwd.action;
@@ -5434,7 +5023,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5434
5023
  if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
5435
5024
  return { content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }], isError: true, details: { mode: "management", results: [] } };
5436
5025
  }
5437
- return handleHerdrProjectPaneAction(action as (typeof HERDR_PROJECT_PANE_ACTIONS)[number], paramsWithResolvedCwd, { cwd: requestCwd, signal });
5026
+ deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
5027
+ return handleHerdrProjectPaneAction(action as (typeof HERDR_PROJECT_PANE_ACTIONS)[number], paramsWithResolvedCwd, { cwd: requestCwd, state: deps.state, signal });
5438
5028
  }
5439
5029
  if ((HERDR_INSPECTOR_ACTIONS as readonly string[]).includes(action)) {
5440
5030
  if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
@@ -5464,7 +5054,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5464
5054
  ...(currentSessionId ? { currentSessionId } : {}),
5465
5055
  });
5466
5056
  }
5467
- const policyAction = action === "stop" ? "stopRun" : action === "steer" ? "steerRun" : undefined;
5057
+ const policyAction = action === "stop" ? "stopRun" : action === "steer" ? "steerRun" : action === "schedule.create" ? "scheduleCreate" : undefined;
5468
5058
  if (policyAction) {
5469
5059
  const decision = resolveAuthorityDecision({ action: policyAction, ...(deps.config.authorityPolicy === undefined ? {} : { policy: deps.config.authorityPolicy }) });
5470
5060
  if (decision === "forbid") {
@@ -5476,6 +5066,16 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5476
5066
  if (!confirmed) return { content: [{ type: "text", text: `Action '${action}' canceled; authority was not granted.` }], details: { mode: "management", results: [] } };
5477
5067
  }
5478
5068
  }
5069
+ if ((WATCHDOG_TOOL_ACTIONS as readonly string[]).includes(action)) {
5070
+ if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
5071
+ return {
5072
+ content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
5073
+ isError: true,
5074
+ details: { mode: "management" as const, results: [] },
5075
+ };
5076
+ }
5077
+ return handleWatchdogToolAction(action, paramsWithResolvedCwd, ctx, deps.watchdog);
5078
+ }
5479
5079
  if (action === "refine" || action === "refine.show" || action === "refine.rollback") {
5480
5080
  if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
5481
5081
  return {
@@ -5587,7 +5187,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5587
5187
  }
5588
5188
  if (action === "children.list") {
5589
5189
  deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
5590
- const children = listRetainedChildren(DIRS.async, deps.state.sessionLineage?.length ? deps.state.sessionLineage : [deps.state.currentSessionId]);
5190
+ const children = listRetainedChildren(DIRS.async, deps.state.currentSessionId);
5591
5191
  return {
5592
5192
  content: [{ type: "text", text: formatRetainedChildren(children) }],
5593
5193
  details: { mode: "management", results: [] },
@@ -5621,7 +5221,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5621
5221
  cwd: requestCwd,
5622
5222
  config: deps.config,
5623
5223
  state: deps.state,
5624
- context: paramsWithResolvedCwd.context,
5224
+ context: paramsWithResolvedCwd.context === "profile" ? undefined : paramsWithResolvedCwd.context,
5625
5225
  requestedSessionDir: paramsWithResolvedCwd.sessionDir,
5626
5226
  currentSessionFile,
5627
5227
  currentSessionId,
@@ -5692,61 +5292,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5692
5292
  }
5693
5293
  return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
5694
5294
  }
5695
-
5696
- if (action === "approve-checkpoint" || action === "reject-checkpoint") {
5697
- deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
5698
- const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
5699
- if (!targetRunId && !paramsWithResolvedCwd.dir) {
5700
- return { content: [{ type: "text", text: `action='${action}' requires id or dir.` }], isError: true, details: { mode: "management", results: [] } };
5701
- }
5702
- try {
5703
- const resolved = targetRunId ? resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) })) : undefined;
5704
- const decision = action === "approve-checkpoint" ? "approved" : "rejected";
5705
- if (resolved?.kind === "foreground") {
5706
- const run = deps.state.foregroundRuns?.get(resolved.id);
5707
- if (!run?.checkpoint || run.checkpoint.status !== "pending") {
5708
- return { content: [{ type: "text", text: `Run '${resolved.id}' is not paused at an approval checkpoint.` }], isError: true, details: { mode: "management", results: [] } };
5709
- }
5710
- if (deps.state.currentSessionId && run.sessionId !== deps.state.currentSessionId) {
5711
- return { content: [{ type: "text", text: `Run '${resolved.id}' was not found in the active session.` }], isError: true, details: { mode: "management", results: [] } };
5712
- }
5713
- const decidedAt = Date.now();
5714
- const checkpoint: NonNullable<Details["checkpoint"]> = decision === "approved"
5715
- ? { ...run.checkpoint, status: "approved", approvedAt: decidedAt }
5716
- : { ...run.checkpoint, status: "rejected", rejectedAt: decidedAt };
5717
- run.checkpoint = checkpoint;
5718
- run.updatedAt = decidedAt;
5719
- return {
5720
- content: [{ type: "text", text: `Checkpoint '${checkpoint.name}' ${decision} for foreground run ${resolved.id}.` }],
5721
- details: { mode: "management", results: [], checkpoint },
5722
- };
5723
- }
5724
- const location = paramsWithResolvedCwd.dir
5725
- ? resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results)
5726
- : resolved?.kind === "async"
5727
- ? resolved.location
5728
- : undefined;
5729
- if (!location?.asyncDir) {
5730
- return { content: [{ type: "text", text: `action='${action}' targets paused foreground or async checkpoints. No matching run found.` }], isError: true, details: { mode: "management", results: [] } };
5731
- }
5732
- const status = readStatus(location.asyncDir);
5733
- const runId = status?.runId ?? location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir);
5734
- if (!status?.checkpoint || status.state !== "paused") {
5735
- return { content: [{ type: "text", text: `Run '${runId}' is not paused at an approval checkpoint.` }], isError: true, details: { mode: "management", results: [] } };
5736
- }
5737
- if (deps.state.currentSessionId && status.sessionId !== deps.state.currentSessionId) {
5738
- return { content: [{ type: "text", text: `Run '${runId}' was not found in the active session.` }], isError: true, details: { mode: "management", results: [] } };
5739
- }
5740
- deliverCheckpointDecisionRequest({ asyncDir: location.asyncDir, decision, source: "subagent-action", ...(paramsWithResolvedCwd.message ? { reason: paramsWithResolvedCwd.message } : {}) });
5741
- return {
5742
- content: [{ type: "text", text: `Checkpoint '${status.checkpoint.name}' ${decision} for run ${runId}.` }],
5743
- details: { mode: "management", results: [], checkpoint: { ...status.checkpoint, status: decision } },
5744
- };
5745
- } catch (error) {
5746
- const text = error instanceof Error ? error.message : String(error);
5747
- return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
5748
- }
5749
- }
5750
5295
  if (action === "resume") {
5751
5296
  return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel, signal }));
5752
5297
  }
@@ -5844,6 +5389,26 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5844
5389
  if (action === "append-step") {
5845
5390
  return appendStepToAsyncChain(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel }));
5846
5391
  }
5392
+ if (action.startsWith("schedule.")) {
5393
+ if (!isScheduledRunAction(action)) {
5394
+ return { content: [{ type: "text", text: unknownSubagentActionMessage(action) }], isError: true, details: { mode: "management", results: [] } };
5395
+ }
5396
+ if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
5397
+ return {
5398
+ content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
5399
+ isError: true,
5400
+ details: { mode: "management", results: [] },
5401
+ };
5402
+ }
5403
+ if (!deps.handleScheduledRunAction) {
5404
+ return {
5405
+ content: [{ type: "text", text: `Action '${action}' is not available in this subagent context.` }],
5406
+ isError: true,
5407
+ details: { mode: "management", results: [] },
5408
+ };
5409
+ }
5410
+ return deps.handleScheduledRunAction(paramsWithResolvedCwd, ctx);
5411
+ }
5847
5412
  if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
5848
5413
  return {
5849
5414
  content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
@@ -5870,6 +5435,40 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5870
5435
  const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
5871
5436
  const workflowController = targetRunId ? deps.state.workflowControllers?.get(targetRunId) : undefined;
5872
5437
  if (workflowController) {
5438
+ if (paramsWithResolvedCwd.childId !== undefined) {
5439
+ const workflowRunId = targetRunId!;
5440
+ const asyncJob = deps.state.asyncJobs.get(workflowRunId);
5441
+ if (!asyncJob?.asyncDir) return { content: [{ type: "text", text: `Status file not found for async workflow '${workflowRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
5442
+ const status = readStatus(asyncJob.asyncDir);
5443
+ if (!status) return { content: [{ type: "text", text: `Status file not found for async workflow '${workflowRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
5444
+ const resolution = resolveAsyncStatusChild(status, paramsWithResolvedCwd.childId);
5445
+ if (!resolution.ok) return { content: [{ type: "text", text: resolution.message }], isError: true, details: { mode: "management", results: [] } };
5446
+ if (!isStoppableAsyncStatusStep(resolution.child.step)) return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in async run '${targetRunId}' is ${resolution.child.step.status}; stop only supports pending or running children.` }], isError: true, details: { mode: "management", results: [] } };
5447
+ const stopChild = deps.state.workflowChildStops?.get(targetRunId!);
5448
+ if (!stopChild) return { content: [{ type: "text", text: `Workflow ${targetRunId} child stop is unavailable in this extension runtime.` }], isError: true, details: { mode: "management", results: [] } };
5449
+ if (!stopChild(resolution.child.id)) return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in workflow ${workflowRunId} is not available to stop.` }], isError: true, details: { mode: "management", results: [] } };
5450
+ try {
5451
+ fs.appendFileSync(path.join(asyncJob.asyncDir, "events.jsonl"), `${JSON.stringify({
5452
+ type: "subagent.child-status",
5453
+ version: 1,
5454
+ runId: workflowRunId,
5455
+ childId: resolution.child.id,
5456
+ status: "stopping",
5457
+ ts: Date.now(),
5458
+ reason: "user",
5459
+ source: "async",
5460
+ stepIndex: resolution.child.index,
5461
+ agent: resolution.child.step.agent,
5462
+ ...(resolution.child.step.runId ? { childRunId: resolution.child.step.runId } : {}),
5463
+ ...(resolution.child.step.workflowKey ? { workflowKey: resolution.child.step.workflowKey } : {}),
5464
+ ...(resolution.child.step.phase ? { phase: resolution.child.step.phase } : {}),
5465
+ ...(resolution.child.step.label ? { label: resolution.child.step.label } : {}),
5466
+ } satisfies SubagentChildStatusEvent)}\n`, "utf-8");
5467
+ } catch (error) {
5468
+ console.error(`Failed to append child status event for workflow ${workflowRunId}:`, error);
5469
+ }
5470
+ return { content: [{ type: "text", text: `Stop requested for child ${resolution.child.id} in async workflow ${workflowRunId}.` }], details: { mode: "management", results: [] } };
5471
+ }
5873
5472
  workflowController.abort(new Error("Workflow stopped by user."));
5874
5473
  return { content: [{ type: "text", text: `Stop requested for async workflow ${targetRunId}.` }], details: { mode: "management", results: [] } };
5875
5474
  }
@@ -5881,7 +5480,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5881
5480
  if (existingStatus?.mode === "workflow" && existingStatus.state === "running") {
5882
5481
  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: [] } };
5883
5482
  }
5884
- const stopResult = stopAsyncRun(deps.state, location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir), deps.kill, location);
5483
+ const stopResult = stopAsyncRun(deps.state, location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir), deps.kill, location, paramsWithResolvedCwd.childId);
5885
5484
  return stopResult ?? { content: [{ type: "text", text: `No running or queued async run was found for '${targetRunId ?? paramsWithResolvedCwd.dir}'.` }], isError: true, details: { mode: "management", results: [] } };
5886
5485
  } catch (error) {
5887
5486
  const text = error instanceof Error ? error.message : String(error);
@@ -5908,6 +5507,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5908
5507
  resolved?.kind === "async" ? resolved.id : targetRunId,
5909
5508
  deps.kill,
5910
5509
  resolved?.kind === "async" ? resolved.location : undefined,
5510
+ paramsWithResolvedCwd.childId,
5911
5511
  );
5912
5512
  if (stopResult) return stopResult;
5913
5513
  return {
@@ -5970,6 +5570,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5970
5570
  cwd: requestCwd,
5971
5571
  config: deps.config,
5972
5572
  currentSessionId: deps.state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined,
5573
+ runtimeAgentOwner: deps.pi,
5973
5574
  });
5974
5575
  }
5975
5576
 
@@ -6015,26 +5616,37 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6015
5616
  const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
6016
5617
  const discovered = deps.discoverAgents(effectiveCwd, scope);
6017
5618
  const discoveredAgents = discovered.agents;
6018
- const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents);
5619
+ const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents, discovered.agentDiagnostics);
6019
5620
  if (canonicalParams.error) return buildRequestedModeError(effectiveParams, canonicalParams.error);
6020
5621
  effectiveParams = canonicalParams.params!;
6021
5622
  const modelScope = discovered.modelScope;
6022
5623
  effectiveParams = applySingleAgentLaunchDefaults(effectiveParams, discoveredAgents);
6023
5624
  const turnBudget = resolveTurnBudgetConfig(effectiveParams.turnBudget ?? deps.config.turnBudget);
6024
5625
  if (turnBudget.error) return buildRequestedModeError(effectiveParams, turnBudget.error);
6025
- const contextPolicy = resolveAgentDefaultContextPolicy(effectiveParams, discoveredAgents);
5626
+ // An agent-level defaultContext is a preference, unlike an explicit request.
5627
+ // Prefer fork only when the parent session is persisted and has a current leaf;
5628
+ // otherwise use fresh immediately instead of launching a guaranteed-to-fail fork.
5629
+ // Explicit context:"fork" remains strict.
5630
+ const contextPolicyResult = resolveAgentDefaultContextPolicy(
5631
+ effectiveParams,
5632
+ discoveredAgents,
5633
+ deps.config.defaultSubagentContext,
5634
+ canPreferFork(ctx.sessionManager),
5635
+ );
5636
+ if ("error" in contextPolicyResult) return buildRequestedModeError(effectiveParams, contextPolicyResult.error);
5637
+ const contextPolicy = contextPolicyResult;
6026
5638
  effectiveParams = contextPolicy.params;
6027
5639
  const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
6028
5640
  const intercomBridge = resolveIntercomBridge({
6029
5641
  config: deps.config.intercomBridge,
6030
5642
  override: effectiveParams.intercomBridge,
6031
- context: effectiveParams.context ?? (contextPolicy.usesFork ? "fork" : undefined),
5643
+ context: effectiveParams.context === "fresh" || effectiveParams.context === "fork"
5644
+ ? effectiveParams.context
5645
+ : contextPolicy.usesFork ? "fork" : undefined,
6032
5646
  orchestratorTarget: sessionName,
6033
5647
  });
6034
- const agents = intercomBridge.active
6035
- ? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
6036
- : discoveredAgents;
6037
- const runId = randomUUID().slice(0, 8);
5648
+ const agents = applyScopedIntercomBridgeToAgents(discoveredAgents, intercomBridge, contextPolicy);
5649
+ const runId = randomUUID();
6038
5650
  const inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
6039
5651
  const nestedParentAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
6040
5652
  const shareEnabled = effectiveParams.share === true;
@@ -6068,30 +5680,33 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6068
5680
 
6069
5681
  let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
6070
5682
  let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
6071
- let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string): void => {};
5683
+ let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string, _modelOverrideFromParent?: boolean): void => {};
6072
5684
  const forkThinkingRequirements = new Map<number, boolean>();
6073
5685
  const forkThinkingDowngrades = new Map<number, string>();
6074
5686
  try {
6075
5687
  const forkAvailableModels = contextPolicy.usesFork ? ctx.modelRegistry.getAvailable().map(toModelInfo) : [];
6076
5688
  const parentModel = requestParentModel;
6077
- prepareForkThinking = (agentName, index, modelOverride) => {
5689
+ prepareForkThinking = (agentName, index, modelOverride, modelOverrideFromParent) => {
6078
5690
  const agentConfig = agents.find((agent) => agent.name === agentName);
6079
- if (agentConfig?.runner?.type === "external-cli") {
5691
+ if (agentConfig?.runner?.type === "external-cli" || agentConfig?.runner?.type === "external-job") {
6080
5692
  forkThinkingRequirements.set(index, true);
6081
5693
  return;
6082
5694
  }
6083
- const primaryModel = resolveEffectiveSubagentModel(
6084
- modelOverride,
6085
- agentConfig?.model,
6086
- parentModel,
6087
- forkAvailableModels,
6088
- parentModel?.provider,
6089
- );
5695
+ const primaryModel = modelOverrideFromParent
5696
+ ? modelOverride
5697
+ : resolveEffectiveSubagentModel(
5698
+ modelOverride,
5699
+ agentConfig?.model,
5700
+ parentModel,
5701
+ forkAvailableModels,
5702
+ agentConfig?.modelProvider ?? parentModel?.provider,
5703
+ );
6090
5704
  const candidates = buildModelCandidates(
6091
5705
  primaryModel,
6092
5706
  agentConfig?.fallbackModels,
6093
5707
  forkAvailableModels,
6094
- parentModel?.provider,
5708
+ agentConfig?.modelProvider ?? parentModel?.provider,
5709
+ { primaryModelFromParent: modelOverrideFromParent ?? inheritsParentModel(modelOverride, agentConfig?.model, parentModel) },
6095
5710
  );
6096
5711
  forkThinkingRequirements.set(
6097
5712
  index,
@@ -6107,9 +5722,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6107
5722
  } catch (error) {
6108
5723
  return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
6109
5724
  }
6110
- const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
6111
- const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
6112
- const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
6113
5725
  const selectedAgentNames = hasSingle
6114
5726
  ? [effectiveParams.agent!]
6115
5727
  : hasTasks
@@ -6117,9 +5729,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6117
5729
  : (effectiveParams.chain ?? []).flatMap((step) => getStepAgents(step as ChainStep));
6118
5730
  const externalAgent = selectedAgentNames
6119
5731
  .map((name) => agents.find((agent) => agent.name === name))
6120
- .find((agent) => agent?.runner?.type === "external-cli");
5732
+ .find((agent) => agent?.runner?.type === "external-cli" || agent?.runner?.type === "external-job");
5733
+ const externalAsyncRequired = Boolean(externalAgent) && effectiveParams.async === undefined && effectiveParams.clarify !== true && effectiveParams.foregroundOnly !== true;
5734
+ const requestedAsync = externalAsyncRequired ? true : effectiveParams.async ?? deps.asyncByDefault;
5735
+ const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
5736
+ const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
6121
5737
  if (externalAgent && (!effectiveAsync || effectiveParams.foregroundOnly === true)) {
6122
- 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.`);
5738
+ return buildRequestedModeError(effectiveParams, `Agent '${externalAgent.name}' uses runner.type='${externalAgent.runner?.type}', which currently supports async/background execution only. Omit async or pass async:true; clarify and foregroundOnly are unsupported.`);
6123
5739
  }
6124
5740
  const foregroundTimeout = resolveSingleAgentLaunchTimeout(
6125
5741
  effectiveParams,
@@ -6203,20 +5819,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6203
5819
  }
6204
5820
  const sessionDirForIndex = (idx?: number) =>
6205
5821
  path.join(sessionRoot, `run-${idx ?? 0}`);
6206
- const forkSessionFileForTask: ForkSessionFileForTask = (agentName, idx = 0, modelOverride) => {
5822
+ const forkSessionFileForTask: ForkSessionFileForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
6207
5823
  if (!shouldForkAgent(contextPolicy, agentName)) return undefined;
6208
- prepareForkThinking(agentName, idx, modelOverride);
5824
+ prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
6209
5825
  return forkSessionFileForIndex(idx);
6210
5826
  };
6211
- const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride) => {
5827
+ const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
6212
5828
  if (!shouldForkAgent(contextPolicy, agentName)) return delegatedThinkingOverride;
6213
- prepareForkThinking(agentName, idx, modelOverride);
5829
+ prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
6214
5830
  const override = forkThinkingOverrideForIndex(idx);
6215
5831
  if (override === "off") forkThinkingDowngrades.set(idx, agentName);
6216
5832
  return override ?? delegatedThinkingOverride;
6217
5833
  };
6218
- const childSessionFileForTask: ForkSessionFileForTask = (agentName, idx, modelOverride) =>
6219
- forkSessionFileForTask(agentName, idx, modelOverride) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
5834
+ const childSessionFileForTask: ForkSessionFileForTask = (agentName, idx, modelOverride, modelOverrideFromParent) =>
5835
+ forkSessionFileForTask(agentName, idx, modelOverride, modelOverrideFromParent) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
6220
5836
  const childSessionFileForIndex = (idx?: number) =>
6221
5837
  path.join(sessionDirForIndex(idx), "session.jsonl");
6222
5838
  try {
@@ -6322,7 +5938,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6322
5938
  parentModel: requestParentModel,
6323
5939
  parentSessionId: requestSessionId,
6324
5940
  parentPiSessionId: requestPiSessionId,
6325
- capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(requestSessionId),
5941
+ capabilityCeiling: intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(requestSessionId)),
6326
5942
  runFanoutBudget,
6327
5943
  topLevelAsyncCapacityEligible,
6328
5944
  activeAsyncCapacity,
@@ -6398,6 +6014,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6398
6014
  };
6399
6015
  deps.state.foregroundControls.set(runId, foregroundControl);
6400
6016
  deps.state.lastForegroundControlId = runId;
6017
+ deps.activateSupervisorTransport?.();
6018
+ deps.refreshResultDelivery?.();
6401
6019
  }
6402
6020
 
6403
6021
  const writeNestedForegroundEvent = (type: "subagent.nested.started" | "subagent.nested.completed", result?: AgentToolResult<Details>): void => {
@@ -6431,7 +6049,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6431
6049
  startedLaunches = selectedAgentNames.map((agent) => ({ agent }));
6432
6050
  }
6433
6051
  const agentsForSummary = startedLaunches.map((launch) => launch.agent);
6434
- const leafIntercomTarget = intercomBridge.active && agentsForSummary[0]
6052
+ const leafIntercomTarget = agentsForSummary[0] && intercomBridgeAppliesToAgent(intercomBridge, contextPolicy, agentsForSummary[0])
6435
6053
  ? resolveSubagentIntercomTarget(runId, agentsForSummary[0], 0)
6436
6054
  : undefined;
6437
6055
  try {
@@ -6504,7 +6122,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6504
6122
  workflowLaunchObserver(launch);
6505
6123
  }
6506
6124
  }
6507
- const asyncResult = runAsyncPath(execData, deps);
6125
+ const asyncResult = await runAsyncPath(execData, deps);
6508
6126
  if (asyncResult) {
6509
6127
  asyncLaunchFailed = asyncResult.isError === true;
6510
6128
  return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget));
@@ -6514,16 +6132,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6514
6132
  nestedForegroundStarted = true;
6515
6133
  }
6516
6134
  const runFanoutAnnotateContent = !delegatedExecution;
6517
- if (hasChain && effectiveParams.chain) {
6518
- const result = await runChainPath(execData, deps);
6519
- writeNestedForegroundEvent("subagent.nested.completed", result);
6520
- return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
6521
- }
6522
- if (hasTasks && effectiveParams.tasks) {
6523
- const result = await runParallelPath(execData, deps);
6524
- writeNestedForegroundEvent("subagent.nested.completed", result);
6525
- return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
6526
- }
6527
6135
  if (hasSingle) {
6528
6136
  const result = await runSinglePath(execData, deps);
6529
6137
  writeNestedForegroundEvent("subagent.nested.completed", result);
@@ -6580,7 +6188,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6580
6188
  onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
6581
6189
  ctx: ExtensionContext,
6582
6190
  ): Promise<AgentToolResult<Details>> => {
6583
- const normalized = normalizePublicSubagentExecution(params);
6191
+ const normalized = normalizePublicSubagentExecution(params, { asyncByDefault: deps.asyncByDefault });
6584
6192
  if (!normalized.ok) {
6585
6193
  return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
6586
6194
  }
@@ -6609,5 +6217,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6609
6217
  return execute(id, delegatedParams, signal, onUpdate, ctx);
6610
6218
  };
6611
6219
 
6612
- return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated };
6220
+ const executeScheduled = (
6221
+ id: string,
6222
+ params: SubagentParamsLike,
6223
+ signal: AbortSignal,
6224
+ ctx: ExtensionContext,
6225
+ ) => {
6226
+ const ownerSessionId = resolveCurrentSessionId(ctx.sessionManager);
6227
+ let ownerExecutor = scheduledOwnerExecutors.get(ownerSessionId);
6228
+ if (!ownerExecutor) {
6229
+ ownerExecutor = createSubagentExecutor({
6230
+ ...deps,
6231
+ state: createScheduledOwnerState(deps.state, ownerSessionId, ctx),
6232
+ });
6233
+ scheduledOwnerExecutors.set(ownerSessionId, ownerExecutor);
6234
+ }
6235
+ return ownerExecutor.execute(id, params, signal, undefined, ctx);
6236
+ };
6237
+
6238
+ return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated, executeScheduled };
6613
6239
  }