@selesai/code 0.12.0 → 0.13.1

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 (466) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/core/agent-session.d.ts +1 -1
  3. package/dist/core/agent-session.js +1 -1
  4. package/dist/core/sdk.d.ts +2 -2
  5. package/dist/core/sdk.js +1 -1
  6. package/dist/core/system-prompt.d.ts +1 -1
  7. package/dist/core/system-prompt.js +1 -1
  8. package/dist/core/usage-totals.d.ts +2 -0
  9. package/dist/core/usage-totals.js +10 -0
  10. package/dist/extensions/ascii.txt +9 -0
  11. package/dist/extensions/package.json +1 -2
  12. package/dist/extensions/pi-hermes-memory/CHANGELOG.md +371 -0
  13. package/dist/extensions/pi-hermes-memory/LICENSE +21 -0
  14. package/dist/extensions/pi-hermes-memory/README.md +643 -0
  15. package/dist/extensions/pi-hermes-memory/docs/0.1/TASKS.md +197 -0
  16. package/dist/extensions/pi-hermes-memory/docs/0.2/PLAN.md +290 -0
  17. package/dist/extensions/pi-hermes-memory/docs/0.2/TASKS.md +134 -0
  18. package/dist/extensions/pi-hermes-memory/docs/0.2/TEST-PLAN.md +216 -0
  19. package/dist/extensions/pi-hermes-memory/docs/0.3/PLAN.md +330 -0
  20. package/dist/extensions/pi-hermes-memory/docs/0.3/TASKS.md +125 -0
  21. package/dist/extensions/pi-hermes-memory/docs/0.4/PLAN.md +160 -0
  22. package/dist/extensions/pi-hermes-memory/docs/0.4/TASKS.md +113 -0
  23. package/dist/extensions/pi-hermes-memory/docs/0.6/CHANGELOG.md +57 -0
  24. package/dist/extensions/pi-hermes-memory/docs/0.6/PLAN.md +202 -0
  25. package/dist/extensions/pi-hermes-memory/docs/0.6/TASKS.md +144 -0
  26. package/dist/extensions/pi-hermes-memory/docs/0.7/PLAN.md +349 -0
  27. package/dist/extensions/pi-hermes-memory/docs/0.7/TASKS.md +110 -0
  28. package/dist/extensions/pi-hermes-memory/docs/PUBLISHING.md +149 -0
  29. package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +468 -0
  30. package/dist/extensions/pi-hermes-memory/docs/images/memory-architecture.svg +1 -0
  31. package/dist/extensions/pi-hermes-memory/docs/images/pi-logo.svg +22 -0
  32. package/dist/extensions/pi-hermes-memory/docs/images/pi_memory.png +0 -0
  33. package/dist/extensions/pi-hermes-memory/docs/images/pi_memory.svg +19 -0
  34. package/dist/extensions/pi-hermes-memory/docs/images/security-flow.svg +1 -0
  35. package/dist/extensions/pi-hermes-memory/docs/images/session-lifecycle.svg +1 -0
  36. package/dist/extensions/pi-hermes-memory/docs/images/source-architecture.svg +1 -0
  37. package/dist/extensions/pi-hermes-memory/docs/mermaid/memory-architecture.mmd +49 -0
  38. package/dist/extensions/pi-hermes-memory/docs/mermaid/security-flow.mmd +40 -0
  39. package/dist/extensions/pi-hermes-memory/docs/mermaid/session-lifecycle.mmd +56 -0
  40. package/dist/extensions/pi-hermes-memory/docs/mermaid/source-architecture.mmd +54 -0
  41. package/dist/extensions/pi-hermes-memory/package-lock.json +3799 -0
  42. package/dist/extensions/pi-hermes-memory/package.json +66 -0
  43. package/dist/extensions/pi-hermes-memory/scripts/check-min-sdk.mjs +99 -0
  44. package/dist/extensions/pi-hermes-memory/scripts/ensure-dev.mjs +57 -0
  45. package/dist/extensions/pi-hermes-memory/src/auto-consolidation-warning.ts +6 -0
  46. package/dist/extensions/pi-hermes-memory/src/config.ts +178 -0
  47. package/dist/extensions/pi-hermes-memory/src/constants.ts +424 -0
  48. package/dist/extensions/pi-hermes-memory/src/extension-root-migration.ts +639 -0
  49. package/dist/extensions/pi-hermes-memory/src/handlers/auto-consolidate.ts +401 -0
  50. package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +297 -0
  51. package/dist/extensions/pi-hermes-memory/src/handlers/child-process-watchdog.mjs +90 -0
  52. package/dist/extensions/pi-hermes-memory/src/handlers/correction-detector.ts +300 -0
  53. package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +86 -0
  54. package/dist/extensions/pi-hermes-memory/src/handlers/insights.ts +78 -0
  55. package/dist/extensions/pi-hermes-memory/src/handlers/interview.ts +37 -0
  56. package/dist/extensions/pi-hermes-memory/src/handlers/learn-memory.ts +186 -0
  57. package/dist/extensions/pi-hermes-memory/src/handlers/message-parts.ts +27 -0
  58. package/dist/extensions/pi-hermes-memory/src/handlers/pi-child-process.ts +485 -0
  59. package/dist/extensions/pi-hermes-memory/src/handlers/preview-context.ts +105 -0
  60. package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +506 -0
  61. package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +145 -0
  62. package/dist/extensions/pi-hermes-memory/src/handlers/session-flush.ts +151 -0
  63. package/dist/extensions/pi-hermes-memory/src/handlers/session-live-index.ts +94 -0
  64. package/dist/extensions/pi-hermes-memory/src/handlers/skills-command.ts +1334 -0
  65. package/dist/extensions/pi-hermes-memory/src/handlers/standing-pin.ts +111 -0
  66. package/dist/extensions/pi-hermes-memory/src/handlers/switch-project.ts +75 -0
  67. package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +266 -0
  68. package/dist/extensions/pi-hermes-memory/src/index.ts +378 -0
  69. package/dist/extensions/pi-hermes-memory/src/lifecycle-timing.ts +54 -0
  70. package/dist/extensions/pi-hermes-memory/src/paths.ts +61 -0
  71. package/dist/extensions/pi-hermes-memory/src/project-context.ts +13 -0
  72. package/dist/extensions/pi-hermes-memory/src/project-memory-migration.ts +93 -0
  73. package/dist/extensions/pi-hermes-memory/src/project.ts +157 -0
  74. package/dist/extensions/pi-hermes-memory/src/prompt-context.ts +55 -0
  75. package/dist/extensions/pi-hermes-memory/src/store/atomic-lock-coordinator.ts +375 -0
  76. package/dist/extensions/pi-hermes-memory/src/store/canonical-storage-path.ts +54 -0
  77. package/dist/extensions/pi-hermes-memory/src/store/content-scanner.ts +101 -0
  78. package/dist/extensions/pi-hermes-memory/src/store/db.ts +1180 -0
  79. package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +101 -0
  80. package/dist/extensions/pi-hermes-memory/src/store/markdown-mutation-lock.ts +38 -0
  81. package/dist/extensions/pi-hermes-memory/src/store/memory-lookup.ts +24 -0
  82. package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +1191 -0
  83. package/dist/extensions/pi-hermes-memory/src/store/recovery-maintenance.ts +66 -0
  84. package/dist/extensions/pi-hermes-memory/src/store/schema.ts +118 -0
  85. package/dist/extensions/pi-hermes-memory/src/store/session-anchor-search.ts +472 -0
  86. package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +511 -0
  87. package/dist/extensions/pi-hermes-memory/src/store/session-parser.ts +213 -0
  88. package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +234 -0
  89. package/dist/extensions/pi-hermes-memory/src/store/skill-store.ts +950 -0
  90. package/dist/extensions/pi-hermes-memory/src/store/skill-utils.ts +133 -0
  91. package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +1002 -0
  92. package/dist/extensions/pi-hermes-memory/src/store/sqlite-native.ts +241 -0
  93. package/dist/extensions/pi-hermes-memory/src/store/standing-instructions.ts +247 -0
  94. package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +95 -0
  95. package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +469 -0
  96. package/dist/extensions/pi-hermes-memory/src/tools/session-search-tool.ts +246 -0
  97. package/dist/extensions/pi-hermes-memory/src/tools/shared-output-view.ts +170 -0
  98. package/dist/extensions/pi-hermes-memory/src/tools/skill-tool.ts +333 -0
  99. package/dist/extensions/pi-hermes-memory/src/tools/tool-result-views.ts +105 -0
  100. package/dist/extensions/pi-hermes-memory/src/types.ts +214 -0
  101. package/dist/extensions/pi-hermes-memory/tests/config.test.ts +434 -0
  102. package/dist/extensions/pi-hermes-memory/tests/extension-root-migration.test.ts +368 -0
  103. package/dist/extensions/pi-hermes-memory/tests/handlers/auto-consolidate.test.ts +984 -0
  104. package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +950 -0
  105. package/dist/extensions/pi-hermes-memory/tests/handlers/correction-detector.test.ts +671 -0
  106. package/dist/extensions/pi-hermes-memory/tests/handlers/insights.test.ts +157 -0
  107. package/dist/extensions/pi-hermes-memory/tests/handlers/interview.test.ts +127 -0
  108. package/dist/extensions/pi-hermes-memory/tests/handlers/pi-child-process.test.ts +905 -0
  109. package/dist/extensions/pi-hermes-memory/tests/handlers/preview-context.test.ts +152 -0
  110. package/dist/extensions/pi-hermes-memory/tests/handlers/prompt-context.test.ts +166 -0
  111. package/dist/extensions/pi-hermes-memory/tests/handlers/resources-discover.test.ts +71 -0
  112. package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +614 -0
  113. package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +192 -0
  114. package/dist/extensions/pi-hermes-memory/tests/handlers/session-flush.test.ts +677 -0
  115. package/dist/extensions/pi-hermes-memory/tests/handlers/session-live-index.test.ts +225 -0
  116. package/dist/extensions/pi-hermes-memory/tests/handlers/skills-command.test.ts +827 -0
  117. package/dist/extensions/pi-hermes-memory/tests/handlers/standing-pin.test.ts +134 -0
  118. package/dist/extensions/pi-hermes-memory/tests/handlers/sync-markdown-memories.test.ts +491 -0
  119. package/dist/extensions/pi-hermes-memory/tests/handlers/system-prompt.test.ts +199 -0
  120. package/dist/extensions/pi-hermes-memory/tests/handlers/thinking-max.test.ts +64 -0
  121. package/dist/extensions/pi-hermes-memory/tests/index.test.ts +30 -0
  122. package/dist/extensions/pi-hermes-memory/tests/integration/flow.test.ts +126 -0
  123. package/dist/extensions/pi-hermes-memory/tests/lifecycle-timing.test.ts +76 -0
  124. package/dist/extensions/pi-hermes-memory/tests/paths.test.ts +23 -0
  125. package/dist/extensions/pi-hermes-memory/tests/project-memory-migration.test.ts +80 -0
  126. package/dist/extensions/pi-hermes-memory/tests/project-rebinding.test.ts +115 -0
  127. package/dist/extensions/pi-hermes-memory/tests/project.test.ts +120 -0
  128. package/dist/extensions/pi-hermes-memory/tests/run-all-timeout.test.ts +107 -0
  129. package/dist/extensions/pi-hermes-memory/tests/run-all.sh +45 -0
  130. package/dist/extensions/pi-hermes-memory/tests/store/atomic-lock-coordinator.test.ts +381 -0
  131. package/dist/extensions/pi-hermes-memory/tests/store/content-scanner.test.ts +388 -0
  132. package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +1046 -0
  133. package/dist/extensions/pi-hermes-memory/tests/store/markdown-mutation-lock.test.ts +34 -0
  134. package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +2150 -0
  135. package/dist/extensions/pi-hermes-memory/tests/store/recovery-maintenance.test.ts +170 -0
  136. package/dist/extensions/pi-hermes-memory/tests/store/session-anchor-search.test.ts +210 -0
  137. package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +643 -0
  138. package/dist/extensions/pi-hermes-memory/tests/store/session-parser.test.ts +282 -0
  139. package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +287 -0
  140. package/dist/extensions/pi-hermes-memory/tests/store/skill-store.test.ts +678 -0
  141. package/dist/extensions/pi-hermes-memory/tests/store/skill-utils.test.ts +21 -0
  142. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-lazy-load.test.ts +78 -0
  143. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +543 -0
  144. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +164 -0
  145. package/dist/extensions/pi-hermes-memory/tests/store/standing-instructions.test.ts +171 -0
  146. package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +84 -0
  147. package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +709 -0
  148. package/dist/extensions/pi-hermes-memory/tests/tools/session-search-tool.test.ts +345 -0
  149. package/dist/extensions/pi-hermes-memory/tests/tools/shared-output-view.test.ts +362 -0
  150. package/dist/extensions/pi-hermes-memory/tests/tools/skill-tool.test.ts +478 -0
  151. package/dist/extensions/pi-hermes-memory/tests/tools/tool-result-renderer-wiring.test.ts +40 -0
  152. package/dist/extensions/pi-hermes-memory/tsconfig.json +19 -0
  153. package/dist/extensions/pi-subagents/.oxlintrc.json +43 -0
  154. package/dist/extensions/pi-subagents/AGENTS.md +5 -0
  155. package/dist/extensions/pi-subagents/CHANGELOG.md +157 -0
  156. package/dist/extensions/pi-subagents/README.md +12 -6
  157. package/dist/extensions/pi-subagents/VISION.md +81 -0
  158. package/dist/extensions/pi-subagents/agents/claude-code-writer.md +15 -0
  159. package/dist/extensions/pi-subagents/agents/claude-code.md +15 -0
  160. package/dist/extensions/pi-subagents/agents/codex-exec-writer.md +15 -0
  161. package/dist/extensions/pi-subagents/agents/codex-exec.md +15 -0
  162. package/dist/extensions/pi-subagents/agents/cursor-agent-writer.md +14 -0
  163. package/dist/extensions/pi-subagents/agents/cursor-agent.md +14 -0
  164. package/dist/extensions/pi-subagents/agents/reviewer.md +1 -3
  165. package/dist/extensions/pi-subagents/docs/agents.md +488 -0
  166. package/dist/extensions/pi-subagents/docs/configuration.md +46 -9
  167. package/dist/extensions/pi-subagents/docs/extension-api.md +42 -3
  168. package/dist/extensions/pi-subagents/docs/models.md +27 -4
  169. package/dist/extensions/pi-subagents/docs/observability.md +10 -9
  170. package/dist/extensions/pi-subagents/docs/tool-reference.md +102 -6
  171. package/dist/extensions/pi-subagents/docs/workflows.md +135 -0
  172. package/dist/extensions/pi-subagents/package-lock.json +389 -2
  173. package/dist/extensions/pi-subagents/package.json +3 -1
  174. package/dist/extensions/pi-subagents/prompts/review-loop.md +2 -2
  175. package/dist/extensions/pi-subagents/skills/council-mode/SKILL.md +1 -1
  176. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +3 -1
  177. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +7 -3
  178. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  179. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +26 -5
  180. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +41 -22
  181. package/dist/extensions/pi-subagents/src/agents/agent-refinements.ts +4 -4
  182. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +9 -2
  183. package/dist/extensions/pi-subagents/src/agents/agents.ts +322 -75
  184. package/dist/extensions/pi-subagents/src/agents/builtin-names.ts +6 -0
  185. package/dist/extensions/pi-subagents/src/agents/runtime-agent-events.ts +70 -0
  186. package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +27 -20
  187. package/dist/extensions/pi-subagents/src/api/agents.ts +10 -5
  188. package/dist/extensions/pi-subagents/src/api/background-work.ts +5 -1
  189. package/dist/extensions/pi-subagents/src/api/delegation.ts +0 -7
  190. package/dist/extensions/pi-subagents/src/api/preflight.ts +34 -12
  191. package/dist/extensions/pi-subagents/src/extension/config.ts +70 -0
  192. package/dist/extensions/pi-subagents/src/extension/doctor.ts +3 -3
  193. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +5 -3
  194. package/dist/extensions/pi-subagents/src/extension/index.ts +83 -30
  195. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +44 -15
  196. package/dist/extensions/pi-subagents/src/extension/rpc.ts +55 -19
  197. package/dist/extensions/pi-subagents/src/extension/schemas.ts +41 -15
  198. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -31
  199. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +2 -1
  200. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +2 -10
  201. package/dist/extensions/pi-subagents/src/inspectors/herdr/session-roots-codec.ts +42 -0
  202. package/dist/extensions/pi-subagents/src/integrations/herdr-status.ts +51 -3
  203. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +2 -0
  204. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +5 -6
  205. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +77 -10
  206. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +176 -53
  207. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +19 -12
  208. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +29 -27
  209. package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +20 -3
  210. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +23 -261
  211. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +73 -7
  212. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +8 -3
  213. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +74 -8
  214. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +37 -21
  215. package/dist/extensions/pi-subagents/src/runs/background/inspect-rpc.ts +8 -8
  216. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +184 -9
  217. package/dist/extensions/pi-subagents/src/runs/background/result-delivery-ownership.ts +45 -0
  218. package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +28 -14
  219. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +38 -15
  220. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +1 -1
  221. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +1 -1
  222. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +50 -13
  223. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +93 -8
  224. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +10 -4
  225. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +4 -14
  226. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +549 -377
  227. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +58 -10
  228. package/dist/extensions/pi-subagents/src/runs/background/terminal-run-index.ts +1 -1
  229. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +26 -1
  230. package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +23 -9
  231. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +9 -2
  232. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +2 -2
  233. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +204 -129
  234. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +9 -0
  235. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +23 -1
  236. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +773 -311
  237. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +140 -115
  238. package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +119 -0
  239. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +463 -0
  240. package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +19 -4
  241. package/dist/extensions/pi-subagents/src/runs/shared/child-launch-plan.ts +151 -0
  242. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +21 -7
  243. package/dist/extensions/pi-subagents/src/runs/shared/claude-code-adapter.ts +129 -0
  244. package/dist/extensions/pi-subagents/src/runs/shared/codex-exec-adapter.ts +129 -0
  245. package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +89 -0
  246. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +5 -4
  247. package/dist/extensions/pi-subagents/src/runs/shared/cursor-agent-adapter.ts +114 -0
  248. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +3 -3
  249. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-contract.ts +167 -0
  250. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +122 -0
  251. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +348 -55
  252. package/dist/extensions/pi-subagents/src/runs/shared/fast-mode-extension.ts +5 -5
  253. package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +230 -0
  254. package/dist/extensions/pi-subagents/src/runs/shared/lane-metadata.ts +105 -0
  255. package/dist/extensions/pi-subagents/src/runs/shared/launch-cwd.ts +16 -0
  256. package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +2 -1
  257. package/dist/extensions/pi-subagents/src/runs/shared/mcp-config-sources.ts +422 -0
  258. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +221 -158
  259. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
  260. package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +69 -7
  261. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +47 -6
  262. package/dist/extensions/pi-subagents/src/runs/shared/mutation-evidence.ts +7 -2
  263. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +9 -3
  264. package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +9 -5
  265. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +419 -7
  266. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +15 -1
  267. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +81 -8
  268. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +13 -0
  269. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +21 -1
  270. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +44 -4
  271. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +98 -13
  272. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +1 -1
  273. package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +847 -0
  274. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +18 -0
  275. package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +46 -0
  276. package/dist/extensions/pi-subagents/src/shared/extension-context.ts +24 -0
  277. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +21 -0
  278. package/dist/extensions/pi-subagents/src/shared/formatters.ts +18 -3
  279. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +5 -1
  280. package/dist/extensions/pi-subagents/src/shared/pruned-fork.ts +450 -0
  281. package/dist/extensions/pi-subagents/src/shared/session-file-trust.ts +19 -0
  282. package/dist/extensions/pi-subagents/src/shared/session-tokens.ts +14 -3
  283. package/dist/extensions/pi-subagents/src/shared/settings.ts +19 -105
  284. package/dist/extensions/pi-subagents/src/shared/shortcuts.ts +17 -0
  285. package/dist/extensions/pi-subagents/src/shared/types.ts +359 -40
  286. package/dist/extensions/pi-subagents/src/shared/utils.ts +41 -84
  287. package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +116 -0
  288. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +1 -9
  289. package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +0 -4
  290. package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +1 -2
  291. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +378 -88
  292. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +22 -11
  293. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +3 -0
  294. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +147 -79
  295. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +11 -5
  296. package/dist/extensions/pi-subagents/src/tui/fleet.ts +39 -18
  297. package/dist/extensions/pi-subagents/src/tui/render.ts +369 -46
  298. package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +1 -1
  299. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +6 -3
  300. package/dist/extensions/pi-subagents/src/workflows/host-command.ts +230 -0
  301. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +740 -58
  302. package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +121 -0
  303. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +270 -0
  304. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +195 -6
  305. package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +246 -0
  306. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +2 -2
  307. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1114 -260
  308. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +34 -0
  309. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +150 -3
  310. package/dist/extensions/pi-subagents/test/integration/claude-code-smoke.test.ts +52 -0
  311. package/dist/extensions/pi-subagents/test/integration/claude-code-writer-smoke.test.ts +55 -0
  312. package/dist/extensions/pi-subagents/test/integration/codex-exec-smoke.test.ts +53 -0
  313. package/dist/extensions/pi-subagents/test/integration/codex-exec-writer-smoke.test.ts +57 -0
  314. package/dist/extensions/pi-subagents/test/integration/cursor-agent-smoke.test.ts +59 -0
  315. package/dist/extensions/pi-subagents/test/integration/cursor-agent-writer-smoke.test.ts +62 -0
  316. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +57 -1
  317. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
  318. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +20 -0
  319. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +4 -3
  320. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +2 -2
  321. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +54 -4
  322. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +482 -4
  323. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +157 -9
  324. package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +5 -5
  325. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1508 -291
  326. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +423 -1
  327. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +8 -0
  328. package/dist/extensions/pi-subagents/test/support/cursor-smoke-inputs.ts +51 -0
  329. package/dist/extensions/pi-subagents/test/support/helpers.ts +2 -0
  330. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +5 -5
  331. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +6 -6
  332. package/dist/extensions/pi-subagents/test/unit/abort-recovery.test.ts +152 -0
  333. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +48 -0
  334. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +184 -6
  335. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +66 -6
  336. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +77 -0
  337. package/dist/extensions/pi-subagents/test/unit/agent-refinements.test.ts +18 -0
  338. package/dist/extensions/pi-subagents/test/unit/anti-slop-oxlint.test.ts +86 -0
  339. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +1 -1
  340. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +101 -1
  341. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +121 -0
  342. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +28 -0
  343. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +14 -7
  344. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +53 -0
  345. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +190 -0
  346. package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +42 -0
  347. package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +8 -4
  348. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +23 -0
  349. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +78 -1
  350. package/dist/extensions/pi-subagents/test/unit/child-launch-plan.test.ts +94 -0
  351. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +21 -0
  352. package/dist/extensions/pi-subagents/test/unit/child-session-name.test.ts +50 -0
  353. package/dist/extensions/pi-subagents/test/unit/claude-code-adapter.test.ts +245 -0
  354. package/dist/extensions/pi-subagents/test/unit/codex-exec-adapter.test.ts +192 -0
  355. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +27 -0
  356. package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +150 -0
  357. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +21 -0
  358. package/dist/extensions/pi-subagents/test/unit/cursor-agent-adapter.test.ts +259 -0
  359. package/dist/extensions/pi-subagents/test/unit/cursor-smoke-inputs.test.ts +77 -0
  360. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +1 -4
  361. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +1 -1
  362. package/dist/extensions/pi-subagents/test/unit/extension-context.test.ts +27 -0
  363. package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +223 -0
  364. package/dist/extensions/pi-subagents/test/unit/fast-mode-extension.test.ts +27 -0
  365. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +208 -2
  366. package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +29 -1
  367. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +110 -1
  368. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +10 -0
  369. package/dist/extensions/pi-subagents/test/unit/formatters.test.ts +14 -0
  370. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +18 -9
  371. package/dist/extensions/pi-subagents/test/unit/herdr-session-roots-codec.test.ts +63 -0
  372. package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +2 -2
  373. package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +132 -0
  374. package/dist/extensions/pi-subagents/test/unit/host-command.test.ts +156 -0
  375. package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +98 -0
  376. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +178 -26
  377. package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +2 -1
  378. package/dist/extensions/pi-subagents/test/unit/inspect-rpc.test.ts +21 -0
  379. package/dist/extensions/pi-subagents/test/unit/mcp-direct-tool-grant.test.ts +138 -0
  380. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
  381. package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +91 -0
  382. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +72 -7
  383. package/dist/extensions/pi-subagents/test/unit/mutation-evidence.test.ts +21 -0
  384. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +1 -0
  385. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +20 -2
  386. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +129 -1
  387. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +2 -2
  388. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +4 -0
  389. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +358 -0
  390. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +574 -21
  391. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +109 -1
  392. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +71 -1
  393. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +7 -7
  394. package/dist/extensions/pi-subagents/test/unit/pruned-fork.test.ts +214 -0
  395. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +29 -8
  396. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +53 -0
  397. package/dist/extensions/pi-subagents/test/unit/result-delivery-ownership.test.ts +34 -0
  398. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +101 -3
  399. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +1 -0
  400. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +10 -8
  401. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +71 -1
  402. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +41 -7
  403. package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +136 -2
  404. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +137 -0
  405. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +33 -12
  406. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +504 -1
  407. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +8 -0
  408. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
  409. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +12 -1
  410. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +3 -5
  411. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +5 -6
  412. package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +0 -8
  413. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +5 -0
  414. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +157 -2
  415. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +1 -1
  416. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +63 -4
  417. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +27 -23
  418. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +44 -0
  419. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +1 -0
  420. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +7 -5
  421. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +106 -0
  422. package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +484 -9
  423. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +38 -4
  424. package/dist/extensions/pi-subagents/test/unit/workflow-preflight.test.ts +155 -0
  425. package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +318 -0
  426. package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +365 -0
  427. package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +349 -0
  428. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +21 -0
  429. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +2 -7
  430. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/effect/index.ts +13 -0
  431. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/effect/rules/no-service-constructor-imports.ts +52 -0
  432. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/index.ts +41 -0
  433. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-chained-type-assertions.ts +77 -0
  434. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-conditional-empty-object-spread.ts +49 -0
  435. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-known-value-widening.ts +247 -0
  436. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-module-mocking.ts +91 -0
  437. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-object-parameters.ts +126 -0
  438. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-reflect-apply.ts +28 -0
  439. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-reflect-get.ts +28 -0
  440. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-runtime-typeof.ts +67 -0
  441. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-shape-in-symbol-names.ts +39 -0
  442. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-parameters.ts +83 -0
  443. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-returns.ts +115 -0
  444. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-type-aliases.ts +72 -0
  445. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unsafe-dictionary-type.ts +134 -0
  446. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-widen-then-assert.ts +366 -0
  447. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/require-safety-comment-for-type-assertion.ts +62 -0
  448. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/dictionary-types.ts +502 -0
  449. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/lexical-type-parameters.ts +60 -0
  450. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/reflect-method.ts +35 -0
  451. package/dist/extensions/pi-tool-display/tests/capabilities.test.ts +1 -1
  452. package/dist/extensions/pi-tool-display/tests/settings-inspector.test.ts +2 -2
  453. package/dist/extensions/pi-tool-display/tests/tool-overrides-mcp.test.ts +1 -1
  454. package/dist/extensions/pi-zentui/extensions/zentui/format.ts +2 -1
  455. package/dist/extensions/pi-zentui/test/format.test.ts +15 -1
  456. package/dist/modes/interactive/components/footer.js +2 -2
  457. package/dist/modes/interactive/interactive-mode.js +11 -11
  458. package/dist/skills/workflow/SKILL.md +65 -50
  459. package/package.json +112 -110
  460. package/dist/extensions/enable-readonly-tools.test.ts +0 -63
  461. package/dist/extensions/enable-readonly-tools.ts +0 -24
  462. package/dist/extensions/model-prompt-injector/config.json +0 -16
  463. package/dist/extensions/model-prompt-injector/index.test.ts +0 -102
  464. package/dist/extensions/model-prompt-injector/index.ts +0 -148
  465. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +0 -98
  466. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +0 -250
@@ -3,7 +3,7 @@ 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 { findBlockingAgentDiagnostic, resolveAgentName, type AgentConfig, type AgentDiscoveryDiagnostic, type AgentScope } from "../../agents/agents.ts";
6
+ import { discoverAgents, findBlockingAgentDiagnostic, formatUnknownAgentError, resolveAgentName, unknownAgentDiagnosticContext, type AgentConfig, type AgentDiscoveryDiagnostic, type AgentScope, type UnknownAgentDiagnosticContext } 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
9
  import { createCapacityResilientJsonWriter } from "../../shared/capacity-resilient-json.ts";
@@ -13,13 +13,13 @@ import {
13
13
  beginForegroundChild,
14
14
  finishForegroundChild,
15
15
  foregroundSchedulingSettled,
16
- retainForegroundSchedulingOwner,
17
16
  settleForegroundSchedulingOwner,
18
17
  updateForegroundChild,
19
18
  } from "./foreground-control.ts";
20
19
  import { getLivePromptAudit, rewritePromptWithGuidance, updateLiveEffectivePrompt } from "./prompt-audit.ts";
21
20
  import { persistForegroundRunHistory, MAX_REMEMBERED_FOREGROUND_RUNS } from "./foreground-history.ts";
22
21
  import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
22
+ import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
23
23
  import { handleManagementAction } from "../../agents/agent-management.ts";
24
24
  import { handleRefinementAction } from "../../agents/agent-refinements.ts";
25
25
  import { buildDoctorReport } from "../../extension/doctor.ts";
@@ -28,45 +28,36 @@ import { normalizePublicSubagentExecution } from "../../extension/public-executi
28
28
  import { runSync } from "./execution.ts";
29
29
  import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.ts";
30
30
  import type { MainWatchdogRuntime } from "../../watchdog/runtime.ts";
31
- import { buildModelCandidates, inheritsParentModel, normalizeParentModel, resolveEffectiveSubagentModel, resolveModelCandidate, type ParentModel } from "../shared/model-fallback.ts";
31
+ import { buildModelCandidates, inheritsParentModel, normalizeParentModel, resolveEffectiveSubagentModel, type ParentModel } from "../shared/model-fallback.ts";
32
32
  import { formatRetainedChildren, listRetainedChildren } from "../background/retained-children.ts";
33
33
  import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
34
- import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
35
34
  import { recordRun } from "../shared/run-history.ts";
36
35
  import {
37
- buildChainInstructions,
38
- writeInitialProgressFile,
39
36
  getStepAgents,
40
37
  isParallelStep,
41
38
  isDynamicParallelStep,
42
- resolveChainPath,
43
39
  resolveExistingReadPaths,
44
- resolveStepBehavior,
45
- suppressProgressForReadOnlyTask,
46
- taskDisallowsFileUpdates,
47
40
  type ChainStep,
48
41
  type DynamicParallelStep,
49
42
  type ParallelStep,
50
43
  type ParallelTaskItem,
51
- type ResolvedStepBehavior,
52
44
  type SequentialStep,
53
- type StepOverrides,
54
45
  } from "../../shared/settings.ts";
55
- import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
46
+ import { normalizeSkillInput } from "../../agents/skills.ts";
56
47
  import { buildAsyncRunnerSteps, DEFAULT_ASYNC_TIMEOUT_MS, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable, workflowAwaitedAsyncResultPath } from "../background/async-execution.ts";
57
48
  import { updateActiveRunIndex } from "../background/active-run-index.ts";
58
49
  import { steeringReceipt } from "../background/steering.ts";
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";
50
+ import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveAbandonedSlotReleaseAfterMs, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
51
+ import { isScheduledRunAction } from "../background/scheduled-runs.ts";
61
52
  import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
62
53
  import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
63
54
  import { normalizeGateAcceptance, validateExecutionAcceptance } from "../shared/acceptance.ts";
64
55
  import { canPreferFork, createForkContextResolver, forkedChildRequiresThinkingOff, resolveSubagentLaunchContext } from "../../shared/fork-context.ts";
56
+ import { createPrunedForkSessionWriter } from "../../shared/pruned-fork.ts";
65
57
  import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
66
58
  import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
67
59
  import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
68
60
  import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
69
- import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
70
61
  import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.ts";
71
62
  import { claimRunFanoutBatch, claimRunFanoutBatchWithCommit, createRunFanoutBudget, decodeRunFanoutBudgetDescriptor, formatRunFanoutBudget, getRunFanoutBudgetSnapshot, readRunFanoutBudgetDescriptor, RunFanoutLimitError, RUN_FANOUT_BUDGET_ENV, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
72
63
  import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
@@ -74,12 +65,11 @@ import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig
74
65
  import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
75
66
  import { isAgentContractV1 } from "../shared/agent-contract.ts";
76
67
  import { normalizeExtensionBindings, type ExtensionBindings } from "../shared/extension-bindings.ts";
77
- import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
78
- import { cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
79
- import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
68
+ import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, outputPathMappingFromTask, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
69
+ import { assertJsonSchemaObject, cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
70
+ import { compactForegroundDetails, getSingleResultOutput, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage, toAgentToolUsage } from "../../shared/utils.ts";
80
71
  import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
81
- import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
82
- import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
72
+ import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, formatStoredParallelHandoffCleanup, parallelHandoffPath, readParallelHandoffManifest, recordParallelHandoffMerge, recordParallelHandoffSupersession, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
83
73
  import { summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
84
74
  import {
85
75
  attachNestedChildrenToResultChildren,
@@ -113,6 +103,7 @@ import { isStoppableAsyncStatusStep, resolveAsyncStatusChild } from "../shared/c
113
103
  import { inspectSubagentStatus } from "../background/run-status.ts";
114
104
  import { getExternalJobProvider } from "../../api/external-job-provider.ts";
115
105
  import { externalJobFollowUpRequestDigest, externalJobFollowUpRequestId, externalJobFollowUpRunId, externalJobPromptDigest, externalJobStableJson } from "../shared/external-job-runner.ts";
106
+ import { externalCliReceiptMetadata, normalizeExternalCliRunnerStatus } from "../shared/external-cli-contract.ts";
116
107
  import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
117
108
  import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
118
109
  import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
@@ -122,18 +113,23 @@ import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
122
113
  import { resolveAuthorityDecision } from "../../policy/authority.ts";
123
114
  import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
124
115
  import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
125
- import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
116
+ import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError, type WorkflowReceiptResumeReference, type WorkflowScriptChildResult, type WorkflowSteerOptions, type WorkflowSteerResult } from "../../workflows/scripted-workflow.ts";
117
+ import { executeWorkflowHostCommand, resolveWorkflowHostOutputClaimPath, type WorkflowHostCommandParams, type WorkflowHostCommandResult } from "../../workflows/host-command.ts";
126
118
  import { buildWorkflowReceipt, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt, type WorkflowReceipt, type WorkflowReceiptState } from "../../workflows/workflow-receipt.ts";
119
+ import { upsertHostStep, validHostStepNodes } from "../shared/host-step-status.ts";
120
+ import { assertWorkflowLaneKey, normalizeWorkflowLaneMetadata } from "../shared/lane-metadata.ts";
121
+ import { parseWorkflowChildSummary, workflowChildSummary } from "../../workflows/workflow-child-summary.ts";
127
122
  import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
123
+ import { annotateWorkflowPreflightTrace, formatWorkflowPreflight, formatWorkflowPreflightWarnings, normalizeWorkflowPreflight, workflowPreflightWarnings } from "../../workflows/workflow-preflight.ts";
124
+ import { claimWorkflowChildPermit, validateWorkflowChildPermitRoot, type WorkflowChildPermit, type WorkflowChildPermitContext } from "../../shared/workflow-child-permit.ts";
128
125
  import {
129
126
  cleanupWorktrees,
130
127
  createWorktrees,
131
128
  diffWorktrees,
132
- findWorktreeTaskCwdConflict,
133
129
  formatWorktreeDiffSummary,
134
- formatWorktreeTaskCwdConflict,
135
130
  type WorktreeSetup,
136
131
  } from "../shared/worktree.ts";
132
+ import { createWorktreeCleanupPlan, formatWorktreeCleanupPlan } from "../shared/worktree-cleanup-plan.ts";
137
133
  import {
138
134
  type AgentProgress,
139
135
  type AsyncJobState,
@@ -146,23 +142,26 @@ import {
146
142
  type ControlEvent,
147
143
  type Details,
148
144
  type ExtensionConfig,
145
+ type ForegroundResumeChild,
149
146
  type ForegroundRunControl,
147
+ type HostStepNodeV1,
150
148
  type IntercomBridgeConfig,
151
149
  type IntercomEventBus,
152
150
  type JsonSchemaObject,
153
151
  type MaxOutputConfig,
154
152
  type NestedRouteInfo,
155
153
  type NestedRunSummary,
154
+ type OutputMode,
156
155
  type ResolvedControlConfig,
157
- type ResolvedTurnBudget,
158
156
  type ResolvedToolBudget,
159
157
  type RunFanoutBudgetDescriptor,
160
158
  type SteeringRecoveryDescriptor,
161
159
  type SingleResult,
162
160
  type SubagentChildStatusEvent,
163
161
  type ToolBudgetConfig,
164
- type TurnBudgetConfig,
162
+ type Usage,
165
163
  type UsageBudgetConfig,
164
+ type WorkflowTerminalOutcome,
166
165
  type SubagentRunMode,
167
166
  type SubagentState,
168
167
  DIRS,
@@ -173,18 +172,21 @@ import {
173
172
  SUBAGENT_CONTROL_INTERCOM_EVENT,
174
173
  SUBAGENT_FOREGROUND_COMPLETE_EVENT,
175
174
  checkSubagentDepth,
176
- resolveTopLevelParallelConcurrency,
177
- resolveTopLevelParallelMaxTasks,
178
175
  resolveChildMaxSubagentDepth,
179
176
  resolveCurrentMaxSubagentDepth,
180
177
  resolveMaxSubagentSpawnsPerRun,
181
178
  wrapForkTask,
182
179
  type ScheduleOrigin,
183
180
  } from "../../shared/types.ts";
181
+ import { deriveChildSessionName } from "../../shared/child-session-name.ts";
184
182
 
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"]);
183
+ 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", "worktree.cleanup", "lane.recordMerge", "lane.recordSupersession", "refine", "refine.rollback", "dismiss", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
186
184
  const DESTRUCTIVE_MANAGEMENT_ACTIONS = new Set(["delete", "eject", "disable", "reset", "mission.close", "worktree.discard", "refine.rollback", "inspector.close", "project.close", "stop", "interrupt", "schedule.delete"]);
187
185
 
186
+ function resolveSteerDeliveryMode(mode: SubagentParamsLike["mode"]): SteerDeliveryMode | undefined {
187
+ return mode === "steer" || mode === "follow_up" || mode === "auto" ? mode : undefined;
188
+ }
189
+
188
190
  function editDistance(left: string, right: string): number {
189
191
  const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
190
192
  for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
@@ -280,6 +282,9 @@ export interface SubagentParamsLike {
280
282
  runId?: string;
281
283
  dir?: string;
282
284
  handoffPath?: string;
285
+ laneId?: string;
286
+ merge?: unknown;
287
+ supersession?: unknown;
283
288
  index?: number;
284
289
  childId?: string;
285
290
  view?: "fleet" | "transcript";
@@ -296,19 +301,27 @@ export interface SubagentParamsLike {
296
301
  resume?: string;
297
302
  message?: string;
298
303
  steeringRecovery?: boolean;
299
- mode?: SteerDeliveryMode;
304
+ mode?: SteerDeliveryMode | "plan" | "apply";
305
+ repo?: string;
306
+ planId?: string;
300
307
  workflowScript?: string;
308
+ workflowScriptPath?: string;
309
+ preflight?: import("../../shared/types.ts").WorkflowPreflightV1;
301
310
  chatProgress?: "auto" | "off" | "live-card";
302
311
  isolation?: "none" | "worktree";
303
312
  step?: ChainStep;
304
313
  /** Internal workflow ownership metadata; not part of the public schema. */
305
314
  workflowParentRunId?: string;
306
315
  workflowKey?: string;
316
+ lane?: import("../../shared/types.ts").WorkflowLaneMetadata;
307
317
  /** Set by the scheduler so this run's completion can name the schedule that produced it. */
308
318
  scheduleOrigin?: ScheduleOrigin;
309
319
  workflowChildAsyncId?: string;
310
320
  workflowAwaitAsync?: boolean;
321
+ /** Internal async-workflow bridge: keep the live VM await pending across supervisor detachment. */
322
+ workflowAwaitDetached?: boolean;
311
323
  workflowParentDeadlineAt?: number;
324
+ workflowOutputClaimPath?: string;
312
325
  suppressRoutineResultIntercom?: boolean;
313
326
  /** Internal inherited cumulative run-tree budget. */
314
327
  runFanoutBudget?: RunFanoutBudgetDescriptor;
@@ -330,9 +343,6 @@ export interface SubagentParamsLike {
330
343
  maxRuntimeMs?: number;
331
344
  /** Optional hard per-tool-call timeout (ms). Known-fast tools also have a default. */
332
345
  toolTimeoutMs?: number;
333
- turnBudget?: TurnBudgetConfig;
334
- /** Internal-only strict turn-boundary enforcement for versioned foreground delegation. */
335
- enforceHardTurnLimit?: boolean;
336
346
  toolBudget?: ToolBudgetConfig;
337
347
  usageBudget?: UsageBudgetConfig;
338
348
  clarify?: boolean;
@@ -392,12 +402,13 @@ interface ExecutorDeps {
392
402
  config: ExtensionConfig;
393
403
  asyncByDefault: boolean;
394
404
  waitToolEnabled?: boolean;
405
+ waitToolDefaultTimeoutMs?: number;
395
406
  handleScheduledRunAction?: (params: SubagentParamsLike, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>;
396
407
  watchdog?: MainWatchdogRuntime;
397
408
  tempArtifactsDir: string;
398
409
  getSubagentSessionRoot: (parentSessionFile: string | null) => string;
399
410
  expandTilde: (p: string) => string;
400
- discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"] };
411
+ discoverAgents: (cwd: string, scope: AgentScope, preferredModelProvider?: string) => { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; modelScope?: ModelScopeConfig; maxThinking?: AgentConfig["maxThinking"]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] };
401
412
  allowMutatingManagementActions?: boolean;
402
413
  activateSupervisorTransport?: () => void;
403
414
  refreshResultDelivery?: () => void;
@@ -405,15 +416,19 @@ interface ExecutorDeps {
405
416
  }
406
417
 
407
418
  type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => string | undefined;
419
+ type PrepareForkSessionForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => Promise<void>;
408
420
  type ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean) => AgentConfig["thinking"] | undefined;
409
421
 
410
422
  interface ExecutionContextData {
411
423
  params: SubagentParamsLike;
412
424
  effectiveCwd: string;
425
+ requestedCwd?: string;
413
426
  ctx: ExtensionContext;
414
427
  signal: AbortSignal;
415
428
  onUpdate?: (r: AgentToolResult<Details>) => void;
416
429
  agents: AgentConfig[];
430
+ /** Exact filesystem discovery provenance retained through defensive execution backstops. */
431
+ unknownAgentDiagnosticContext: UnknownAgentDiagnosticContext;
417
432
  /** Discovered agent definitions before per-run bridge injection. */
418
433
  recoveryAgents: AgentConfig[];
419
434
  runId: string;
@@ -435,7 +450,6 @@ interface ExecutionContextData {
435
450
  deadlineAt?: number;
436
451
  /** Raw global config.toolTimeoutMs, for per-step resolution in async runners. */
437
452
  configToolTimeoutMs?: number;
438
- turnBudget?: ResolvedTurnBudget;
439
453
  toolBudget?: ResolvedToolBudget;
440
454
  usageBudget?: UsageBudgetConfig;
441
455
  allowZeroToolBudget?: boolean;
@@ -449,12 +463,27 @@ interface ExecutionContextData {
449
463
  runFanoutBudget: RunFanoutBudgetDescriptor;
450
464
  topLevelAsyncCapacityEligible: boolean;
451
465
  activeAsyncCapacity?: ActiveAsyncCapacityHandle;
466
+ workflowChildPermitLaunch?: WorkflowChildPermitContext;
452
467
  }
453
468
 
454
469
  function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
455
470
  return requestedCwd ? path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
456
471
  }
457
472
 
473
+ function loadWorkflowScriptPath(params: SubagentParamsLike, runtimeCwd: string): { params?: SubagentParamsLike; error?: string } {
474
+ if (params.workflowScriptPath === undefined) return { params };
475
+ const scriptPath = path.resolve(resolveRequestedCwd(runtimeCwd, params.cwd), params.workflowScriptPath);
476
+ let workflowScript: string;
477
+ try {
478
+ workflowScript = fs.readFileSync(scriptPath, "utf8");
479
+ } catch (error) {
480
+ return { error: `Failed to read workflowScriptPath '${scriptPath}': ${error instanceof Error ? error.message : String(error)}` };
481
+ }
482
+ if (!workflowScript.trim()) return { error: `workflowScriptPath file '${scriptPath}' is empty.` };
483
+ const { workflowScriptPath: _workflowScriptPath, ...rest } = params;
484
+ return { params: { ...rest, workflowScript } };
485
+ }
486
+
458
487
  function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
459
488
  const control = state.foregroundControls.get(runId);
460
489
  if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
@@ -477,12 +506,13 @@ function getForegroundControl(state: SubagentState, runId: string | undefined) {
477
506
  return newest;
478
507
  }
479
508
 
480
- function promptAuditRedoParams(value: unknown, rewrittenTask: string): SubagentParamsLike {
509
+ export function promptAuditRedoParams(value: unknown, rewrittenTask: string): SubagentParamsLike {
481
510
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Prompt redo is missing a safe live launch contract.");
482
511
  const params = { ...(value as SubagentParamsLike), task: rewrittenTask, async: false };
483
512
  delete params.workflowParentRunId;
484
513
  delete params.workflowKey;
485
514
  delete params.workflowChildAsyncId;
515
+ delete params.workflowAwaitDetached;
486
516
  delete params.workflowParentDeadlineAt;
487
517
  delete params.suppressRoutineResultIntercom;
488
518
  if (params.worktree === true && Array.isArray(params.tasks)) delete params.cwd;
@@ -543,6 +573,7 @@ function withSpawnBudgetStatus(
543
573
  const activeAsyncCapacity = sessionId
544
574
  ? getActiveAsyncCapacitySnapshot(sessionId, resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession), {
545
575
  liveWorkflowRunIds: new Set(state.workflowControllers?.keys() ?? []),
576
+ abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(config.capacity?.abandonedSlotReleaseAfterMs),
546
577
  })
547
578
  : { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession) ?? 0 };
548
579
  state.activeAsyncCapacity = activeAsyncCapacity;
@@ -645,11 +676,13 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
645
676
  ...(progress?.currentPath ? { currentPath: progress.currentPath } : {}),
646
677
  ...(progress?.turnCount !== undefined ? { turnCount: progress.turnCount } : {}),
647
678
  ...(progress?.tokens !== undefined ? { tokens: progress.tokens } : {}),
679
+ ...(progress?.window !== undefined ? { window: progress.window } : {}),
680
+ ...(progress?.windowPeak !== undefined ? { windowPeak: progress.windowPeak } : {}),
648
681
  ...(progress?.toolCount !== undefined ? { toolCount: progress.toolCount } : {}),
649
682
  };
650
683
  }
651
684
 
652
- function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; extensionBindings?: ExtensionBindings }): void {
685
+ function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; params: SubagentParamsLike; effectiveOutput?: string | boolean; effectiveOutputMode: OutputMode; extensionBindings?: ExtensionBindings }): void {
653
686
  state.foregroundRuns ??= new Map();
654
687
  const previous = state.foregroundRuns.get(input.runId);
655
688
  const updatedAt = Date.now();
@@ -660,9 +693,17 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
660
693
  ...(input.sessionId ? { sessionId: input.sessionId } : {}),
661
694
  updatedAt,
662
695
  children: input.results.map((result, index) => {
696
+ const resumeContract = omitUndefinedProperties({
697
+ outputSchema: input.params.outputSchema,
698
+ agentContract: input.params.agentContract,
699
+ acceptance: input.params.acceptance,
700
+ output: input.effectiveOutput,
701
+ outputMode: input.effectiveOutputMode,
702
+ });
663
703
  const child = {
664
704
  agent: result.agent,
665
705
  index,
706
+ ...(result.sessionName ? { sessionName: result.sessionName } : {}),
666
707
  ...(result.context ? { context: result.context } : {}),
667
708
  status: resolveSubagentResultStatus(omitUndefinedProperties({
668
709
  exitCode: result.exitCode,
@@ -690,6 +731,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
690
731
  ...(result.transcriptError ? { transcriptError: result.transcriptError } : {}),
691
732
  ...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
692
733
  ...(result.acceptance ? { acceptance: result.acceptance } : {}),
734
+ ...(Object.keys(resumeContract).length ? { resumeContract } : {}),
693
735
  ...(result.launchContractDigest ? { launchContractDigest: result.launchContractDigest } : {}),
694
736
  ...(input.extensionBindings ? { extensionBindings: input.extensionBindings } : {}),
695
737
  ...(result.launchResolvedExtensions ? { launchResolvedExtensions: result.launchResolvedExtensions } : {}),
@@ -810,7 +852,7 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
810
852
  });
811
853
  }
812
854
 
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 {
855
+ 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; resumeContract?: ForegroundResumeChild["resumeContract"]; extensionBindings?: ExtensionBindings; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
814
856
  const requested = (params.id ?? params.runId)?.trim();
815
857
  if (!requested || !state.foregroundRuns?.size || !state.currentSessionId) return undefined;
816
858
  const direct = state.foregroundRuns.get(requested);
@@ -841,6 +883,7 @@ function resolveForegroundResumeTarget(params: SubagentParamsLike, state: Subage
841
883
  ...(child.model ? { model: child.model } : {}),
842
884
  ...(child.thinking ? { thinking: child.thinking } : {}),
843
885
  ...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
886
+ ...(child.resumeContract ? { resumeContract: child.resumeContract } : {}),
844
887
  ...(child.extensionBindings ? { extensionBindings: normalizeExtensionBindings(child.extensionBindings)!.value } : {}),
845
888
  ...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
846
889
  };
@@ -1215,7 +1258,7 @@ function appendStepToAsyncChain(input: {
1215
1258
  }
1216
1259
 
1217
1260
  const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
1218
- const discoveredForAppend = input.deps.discoverAgents(input.requestCwd, scope);
1261
+ const discoveredForAppend = input.deps.discoverAgents(input.requestCwd, scope, input.parentModel?.provider);
1219
1262
  const agents = discoveredForAppend.agents;
1220
1263
  const contextPolicy = resolveExplicitContextPolicy(input.params);
1221
1264
  const chainSkillInput = normalizeSkillInput(input.params.skill);
@@ -1239,11 +1282,13 @@ function appendStepToAsyncChain(input: {
1239
1282
  agents,
1240
1283
  ctx: asyncCtx,
1241
1284
  availableModels: input.ctx.modelRegistry.getAvailable().map(toModelInfo),
1285
+ unknownAgentDiagnosticContext: diagnosticContextFromDiscovery(discoveredForAppend, input.requestCwd, scope),
1242
1286
  cwd: status.cwd ?? input.requestCwd,
1243
1287
  chainSkills,
1244
1288
  dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
1245
1289
  maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
1246
1290
  waitToolEnabled: input.deps.waitToolEnabled,
1291
+ waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
1247
1292
  contextForAgent: contextPolicy.contextForAgent,
1248
1293
  asyncDir: resolved.location.asyncDir,
1249
1294
  validateOutputBindings: false,
@@ -1514,9 +1559,14 @@ function externalJobFollowUpStarted(input: { sourceRunId: string; runId: string;
1514
1559
  function externalRunnerControlError(asyncDir: string, action: "steer" | "resume"): AgentToolResult<Details> | undefined {
1515
1560
  const status = readStatus(asyncDir);
1516
1561
  if (!status?.steps?.length || !status.steps.every((step) => step.runner?.type === "external-cli" || step.runner?.type === "external-job")) return undefined;
1517
- const message = action === "steer"
1518
- ? "External runners do not accept live steer messages."
1519
- : "External runners do not persist Pi sessions and cannot be resumed.";
1562
+ const externalCli = normalizeExternalCliRunnerStatus(status.steps.find((step) => step.runner?.type === "external-cli")?.runner);
1563
+ const message = externalCli
1564
+ ? action === "steer"
1565
+ ? `External adapter '${externalCli.adapter.id}' does not support runs.steer: ${externalCli.unsupportedReasons.steer}`
1566
+ : `External adapter '${externalCli.adapter.id}' cannot resume: ${externalCli.nonResumableReason}`
1567
+ : action === "steer"
1568
+ ? "External runners do not accept live steer messages."
1569
+ : "External runners do not persist Pi sessions and cannot be resumed.";
1520
1570
  return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
1521
1571
  }
1522
1572
 
@@ -1572,7 +1622,7 @@ async function resumeExternalJobFollowUp(input: {
1572
1622
  runId,
1573
1623
  kind: "runner",
1574
1624
  asyncDir,
1575
- }, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
1625
+ }, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) }) : undefined;
1576
1626
  } catch (error) {
1577
1627
  if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
1578
1628
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
@@ -1609,6 +1659,7 @@ async function resumeExternalJobFollowUp(input: {
1609
1659
  ...(input.parentSessionFile ? { sessionRoot: input.deps.getSubagentSessionRoot(input.parentSessionFile) } : {}),
1610
1660
  maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
1611
1661
  waitToolEnabled: input.deps.waitToolEnabled,
1662
+ waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
1612
1663
  worktreeSetupHook: input.deps.config.worktreeSetupHook,
1613
1664
  worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
1614
1665
  worktreeBaseDir: input.deps.config.worktreeBaseDir,
@@ -1727,8 +1778,9 @@ async function resumeAsyncRun(input: {
1727
1778
  input.deps.state.currentSessionId = resolveCurrentSessionId(input.ctx.sessionManager);
1728
1779
  const effectiveCwd = target.cwd ?? input.requestCwd;
1729
1780
  const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
1730
- const discovered = input.deps.discoverAgents(effectiveCwd, scope);
1781
+ const discovered = input.deps.discoverAgents(effectiveCwd, scope, input.parentModel?.provider);
1731
1782
  const discoveredAgents = discovered.agents;
1783
+ const unknownAgentDiagnosticContext = diagnosticContextFromDiscovery(discovered, effectiveCwd, scope);
1732
1784
  const modelScope = discovered.modelScope;
1733
1785
  const sessionName = resolveIntercomSessionTarget(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
1734
1786
  const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
@@ -1749,6 +1801,7 @@ async function resumeAsyncRun(input: {
1749
1801
  systemPrompt: "",
1750
1802
  systemPromptMode: recoveryDescriptor.systemPromptMode,
1751
1803
  inheritProjectContext: recoveryDescriptor.inheritProjectContext,
1804
+ inheritGlobalContext: recoveryDescriptor.inheritGlobalContext,
1752
1805
  inheritSkills: recoveryDescriptor.inheritSkills,
1753
1806
  source: "project",
1754
1807
  filePath: recoveryDescriptor.agentFilePath ?? path.join(getProjectSubagentsDir(recoveryDescriptor.cwd), "recovery-agent"),
@@ -1756,13 +1809,17 @@ async function resumeAsyncRun(input: {
1756
1809
  } : undefined);
1757
1810
  if (!baseAgentConfig) {
1758
1811
  return {
1759
- content: [{ type: "text", text: `Unknown agent for resume: ${target.agent}` }],
1812
+ content: [{ type: "text", text: formatUnknownAgentError(target.agent, unknownAgentDiagnosticContext, "Unknown agent for resume") }],
1760
1813
  isError: true,
1761
1814
  details: { mode: "management", results: [] },
1762
1815
  };
1763
1816
  }
1764
1817
  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: [] } };
1818
+ const runner = normalizeExternalCliRunnerStatus(target.runner);
1819
+ const message = runner
1820
+ ? `External adapter '${runner.adapter.id}' cannot resume: ${runner.nonResumableReason}`
1821
+ : "External runners do not persist Pi sessions and cannot be resumed.";
1822
+ return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
1766
1823
  }
1767
1824
  if (target.source === "async" && target.runner?.type === "external-job") {
1768
1825
  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: [] } };
@@ -1807,7 +1864,7 @@ async function resumeAsyncRun(input: {
1807
1864
  runId,
1808
1865
  kind: "runner",
1809
1866
  asyncDir: path.join(DIRS.async, runId),
1810
- }, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
1867
+ }, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) }) : undefined;
1811
1868
  } catch (error) {
1812
1869
  if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "chain", results: [], activeAsyncCapacity: error.snapshot } };
1813
1870
  throw error;
@@ -1833,6 +1890,7 @@ async function resumeAsyncRun(input: {
1833
1890
  label: `Attached ${target.runId}`,
1834
1891
  },
1835
1892
  agents,
1893
+ unknownAgentDiagnosticContext,
1836
1894
  ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
1837
1895
  pi: input.deps.pi,
1838
1896
  cwd: input.requestCwd,
@@ -1857,6 +1915,7 @@ async function resumeAsyncRun(input: {
1857
1915
  dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
1858
1916
  maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
1859
1917
  waitToolEnabled: input.deps.waitToolEnabled,
1918
+ waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
1860
1919
  worktreeSetupHook: input.deps.config.worktreeSetupHook,
1861
1920
  worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
1862
1921
  worktreeBaseDir: input.deps.config.worktreeBaseDir,
@@ -1905,20 +1964,23 @@ async function resumeAsyncRun(input: {
1905
1964
  sourceRunId: target.runId,
1906
1965
  runId,
1907
1966
  asyncDir: path.join(DIRS.async, runId),
1908
- })
1967
+ }, { abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) })
1909
1968
  : acquireActiveAsyncCapacity({
1910
1969
  sessionId: input.deps.state.currentSessionId!,
1911
1970
  limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
1912
1971
  runId,
1913
1972
  kind: "runner",
1914
1973
  asyncDir: path.join(DIRS.async, runId),
1915
- });
1974
+ }, { abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(input.deps.config.capacity?.abandonedSlotReleaseAfterMs) });
1916
1975
  } catch (error) {
1917
1976
  if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
1918
1977
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
1919
1978
  }
1920
1979
  const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : baseAgentConfig;
1921
1980
  const agentConfig = intercomBridge.active ? applyIntercomBridgeToAgent(recoveryAgentConfig, intercomBridge) : recoveryAgentConfig;
1981
+ const foregroundContract = target.source === "foreground" ? target.resumeContract : undefined;
1982
+ const outputSchema = input.params.outputSchema ?? foregroundContract?.outputSchema ?? recoveryDescriptor?.structuredOutputSchema;
1983
+ const agentContract = input.params.agentContract ?? foregroundContract?.agentContract ?? recoveryDescriptor?.agentContract;
1922
1984
  const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
1923
1985
  const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
1924
1986
  const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
@@ -1965,24 +2027,25 @@ async function resumeAsyncRun(input: {
1965
2027
  outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
1966
2028
  maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
1967
2029
  waitToolEnabled: input.deps.waitToolEnabled,
2030
+ waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
1968
2031
  worktreeSetupHook: input.deps.config.worktreeSetupHook,
1969
2032
  worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
1970
2033
  worktreeBaseDir: input.deps.config.worktreeBaseDir,
1971
2034
  worktree: input.params.worktree === true,
2035
+ lane: input.params.lane ?? recoveryDescriptor?.lane,
1972
2036
  controlConfig: recoveryDescriptor?.controlConfig ?? resolveControlConfig(input.deps.config.control, input.params.control),
1973
2037
  intercomBridge: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
1974
2038
  controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
1975
2039
  childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
1976
2040
  availableModels,
1977
- output: typeof input.params.output === "string" ? input.params.output : recoveryDescriptor?.outputPath,
1978
- outputMode: input.params.outputMode ?? recoveryDescriptor?.outputMode,
1979
- ...(recoveryDescriptor?.agentContract ? { agentContract: recoveryDescriptor.agentContract } : {}),
1980
- ...(recoveryDescriptor?.structuredOutputSchema ? { structuredOutputSchema: recoveryDescriptor.structuredOutputSchema } : {}),
2041
+ output: input.params.output !== undefined ? input.params.output : foregroundContract?.output ?? recoveryDescriptor?.outputPath,
2042
+ outputMode: input.params.outputMode ?? foregroundContract?.outputMode ?? recoveryDescriptor?.outputMode,
2043
+ ...(agentContract ? { agentContract } : {}),
2044
+ ...(outputSchema ? { structuredOutputSchema: outputSchema } : {}),
1981
2045
  ...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
1982
- ...(recoveryDescriptor?.acceptance !== undefined && input.params.acceptance === undefined ? { acceptance: recoveryDescriptor.acceptance } : {}),
2046
+ ...(input.params.acceptance !== undefined ? { acceptance: input.params.acceptance } : foregroundContract?.acceptance !== undefined ? { acceptance: foregroundContract.acceptance } : recoveryDescriptor?.acceptance !== undefined ? { acceptance: recoveryDescriptor.acceptance } : {}),
1983
2047
  ...(input.params.timeoutMs !== undefined ? { timeoutMs: input.params.timeoutMs } : {}),
1984
2048
  ...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
1985
- ...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
1986
2049
  ...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
1987
2050
  capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, recoveryDescriptor?.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
1988
2051
  runFanoutBudget: input.params.runFanoutBudget ?? recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
@@ -2030,20 +2093,14 @@ async function resumeAsyncRun(input: {
2030
2093
  input.signal?.removeEventListener("abort", stopOnAbort);
2031
2094
  }
2032
2095
  fs.rmSync(resultPath, { force: true });
2033
- const totalCost = completed.totalCost;
2096
+ const usage = importedAsyncRootUsage(completed);
2034
2097
  const childResult: SingleResult = {
2035
2098
  index: 0,
2036
2099
  agent: completed.agent,
2100
+ ...(completed.sessionName ? { sessionName: completed.sessionName } : {}),
2037
2101
  task: effectiveFollowUp,
2038
2102
  exitCode: completed.exitCode,
2039
- usage: {
2040
- input: totalCost?.inputTokens ?? 0,
2041
- output: totalCost?.outputTokens ?? 0,
2042
- cacheRead: 0,
2043
- cacheWrite: 0,
2044
- cost: totalCost?.costUsd ?? 0,
2045
- turns: 0,
2046
- },
2103
+ usage,
2047
2104
  finalOutput: completed.output,
2048
2105
  outputState: completed.output.trim() ? "present" : "absent",
2049
2106
  ...(completed.error ? { error: completed.error } : {}),
@@ -2058,9 +2115,13 @@ async function resumeAsyncRun(input: {
2058
2115
  ...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
2059
2116
  ...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
2060
2117
  ...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
2118
+ ...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
2119
+ ...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
2120
+ ...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
2121
+ ...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
2061
2122
  };
2062
2123
  return {
2063
- content: [{ type: "text", text: completed.output || completed.error || `Revived ${target.source} subagent ${revivedId} completed without output.` }],
2124
+ content: [{ type: "text", text: completed.success ? completed.output || completed.error || `Revived ${target.source} subagent ${revivedId} completed without output.` : completed.error || completed.output || `Revived ${target.source} subagent ${revivedId} completed without output.` }],
2064
2125
  ...(completed.success ? {} : { isError: true }),
2065
2126
  details: {
2066
2127
  ...result.details,
@@ -2176,6 +2237,7 @@ async function emitForegroundResultIntercom(input: {
2176
2237
  if (!input.intercomBridge.active || !input.intercomBridge.resultDelivery || !input.intercomBridge.orchestratorTarget) return null;
2177
2238
  const children = input.results.flatMap((result, index) => result.detached ? [] : [omitUndefinedProperties({
2178
2239
  agent: result.agent,
2240
+ ...(result.sessionName ? { sessionName: result.sessionName } : {}),
2179
2241
  status: foregroundResultIntercomStatus(result),
2180
2242
  outputState: result.outputState ?? "unknown",
2181
2243
  summary: resultSummaryForIntercom(result),
@@ -2225,19 +2287,28 @@ async function maybeBuildForegroundIntercomReceipt(input: {
2225
2287
  };
2226
2288
  }
2227
2289
 
2228
- function canonicalizeAgentName(name: string, agents: AgentConfig[], diagnostics?: AgentDiscoveryDiagnostic[]): { name?: string; error?: string } {
2290
+ function diagnosticContextFromDiscovery(
2291
+ discovered: { agents: AgentConfig[]; cwd?: string; scope?: AgentScope; directories?: UnknownAgentDiagnosticContext["directories"] },
2292
+ cwd: string,
2293
+ scope: AgentScope,
2294
+ ): UnknownAgentDiagnosticContext {
2295
+ if (discovered.cwd && discovered.scope && discovered.directories) return unknownAgentDiagnosticContext({ ...discovered, cwd: discovered.cwd, scope: discovered.scope, directories: discovered.directories });
2296
+ return unknownAgentDiagnosticContext(discoverAgents(path.resolve(cwd), scope));
2297
+ }
2298
+
2299
+ function canonicalizeAgentName(name: string, agents: AgentConfig[], diagnostics: AgentDiscoveryDiagnostic[] | undefined, context: UnknownAgentDiagnosticContext): { name?: string; error?: string } {
2229
2300
  const resolved = resolveAgentName(name, agents);
2230
2301
  const candidates = resolved.error ? agents.filter((agent) => resolveAgentName(name, [agent]).agent) : resolved.agent;
2231
2302
  const diagnostic = findBlockingAgentDiagnostic(name, candidates, diagnostics);
2232
2303
  if (diagnostic) return { error: `Agent '${name}' has invalid configuration: ${diagnostic.error}` };
2233
2304
  if (resolved.error) return { error: resolved.error };
2234
- if (!resolved.agent) return { error: `Unknown agent: ${name}` };
2305
+ if (!resolved.agent) return { error: formatUnknownAgentError(name, context) };
2235
2306
  return { name: resolved.agent.name };
2236
2307
  }
2237
2308
 
2238
- function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[], diagnostics?: AgentDiscoveryDiagnostic[]): { params?: SubagentParamsLike; error?: string } {
2309
+ function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[], diagnostics: AgentDiscoveryDiagnostic[] | undefined, context: UnknownAgentDiagnosticContext): { params?: SubagentParamsLike; error?: string } {
2239
2310
  const resolve = (name: string, location?: string): { name?: string; error?: string } => {
2240
- const result = canonicalizeAgentName(name, agents, diagnostics);
2311
+ const result = canonicalizeAgentName(name, agents, diagnostics, context);
2241
2312
  return result.error && location ? { error: `${result.error} (${location})` } : result;
2242
2313
  };
2243
2314
  if (params.agent) {
@@ -2305,6 +2376,7 @@ function validateExecutionInput(
2305
2376
  hasTasks: boolean,
2306
2377
  hasSingle: boolean,
2307
2378
  allowClarifyTaskPrompt: boolean,
2379
+ context: UnknownAgentDiagnosticContext,
2308
2380
  ): AgentToolResult<Details> | null {
2309
2381
  if (Number(hasChain) + Number(hasTasks) + Number(hasSingle) !== 1) {
2310
2382
  return {
@@ -2330,7 +2402,7 @@ function validateExecutionInput(
2330
2402
 
2331
2403
  if (hasSingle && params.agent && !agents.find((agent) => agent.name === params.agent)) {
2332
2404
  return {
2333
- content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
2405
+ content: [{ type: "text", text: formatUnknownAgentError(params.agent, context) }],
2334
2406
  isError: true,
2335
2407
  details: { mode: "single" as const, results: [] },
2336
2408
  };
@@ -2341,7 +2413,7 @@ function validateExecutionInput(
2341
2413
  const task = params.tasks[i]!;
2342
2414
  if (!agents.find((agent) => agent.name === task.agent)) {
2343
2415
  return {
2344
- content: [{ type: "text", text: `Unknown agent: ${task.agent} (task ${i + 1})` }],
2416
+ content: [{ type: "text", text: `${formatUnknownAgentError(task.agent, context)} (task ${i + 1})` }],
2345
2417
  isError: true,
2346
2418
  details: { mode: "parallel" as const, results: [] },
2347
2419
  };
@@ -2386,7 +2458,7 @@ function validateExecutionInput(
2386
2458
  for (const agentName of stepAgents) {
2387
2459
  if (!agents.find((a) => a.name === agentName)) {
2388
2460
  return {
2389
- content: [{ type: "text", text: `Unknown agent: ${agentName} (step ${i + 1})` }],
2461
+ content: [{ type: "text", text: `${formatUnknownAgentError(agentName, context)} (step ${i + 1})` }],
2390
2462
  isError: true,
2391
2463
  details: { mode: "chain" as const, results: [] },
2392
2464
  };
@@ -2578,9 +2650,6 @@ function applySingleAgentLaunchDefaults(params: SubagentParamsLike, agents: Agen
2578
2650
  ? { timeoutMs: agent.defaultTimeoutMs }
2579
2651
  : {}),
2580
2652
  ...(parentTimeoutMs !== undefined ? { timeoutMs: parentTimeoutMs } : {}),
2581
- ...(params.turnBudget === undefined && agent.defaultTurnBudget !== undefined
2582
- ? { turnBudget: agent.defaultTurnBudget }
2583
- : {}),
2584
2653
  ...(params.acceptance === undefined && agent.defaultAcceptance !== undefined
2585
2654
  ? { acceptance: agent.defaultAcceptance }
2586
2655
  : {}),
@@ -2749,6 +2818,14 @@ function withResolvedContext(
2749
2818
  };
2750
2819
  }
2751
2820
 
2821
+ function withAggregatedToolUsage(result: AgentToolResult<Details>): AgentToolResult<Details> {
2822
+ if (result.details.results.length === 0) return result;
2823
+ const usage = sumResultsUsage(result.details.results);
2824
+ return usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0
2825
+ ? { ...result, usage: toAgentToolUsage(usage) }
2826
+ : result;
2827
+ }
2828
+
2752
2829
  function withForkThinkingNotes(
2753
2830
  result: AgentToolResult<Details>,
2754
2831
  downgrades: Map<number, string>,
@@ -2774,66 +2851,6 @@ function toExecutionErrorResult(params: SubagentParamsLike, error: unknown, cont
2774
2851
  );
2775
2852
  }
2776
2853
 
2777
- function collectChainSessionFiles(
2778
- chain: ChainStep[],
2779
- sessionFileForTask: ForkSessionFileForTask,
2780
- dynamicFanoutMaxItems?: number,
2781
- ): (string | undefined)[] {
2782
- const sessionFiles: (string | undefined)[] = [];
2783
- let flatIndex = 0;
2784
- for (const step of chain) {
2785
- if (isParallelStep(step)) {
2786
- for (const task of step.parallel) {
2787
- sessionFiles.push(sessionFileForTask(task.agent, flatIndex, task.model));
2788
- flatIndex++;
2789
- }
2790
- continue;
2791
- }
2792
- if (isDynamicParallelStep(step)) {
2793
- const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
2794
- for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) {
2795
- sessionFiles.push(sessionFileForTask(step.parallel.agent, flatIndex, step.parallel.model));
2796
- flatIndex++;
2797
- }
2798
- continue;
2799
- }
2800
- const sequential = step as SequentialStep;
2801
- sessionFiles.push(sessionFileForTask(sequential.agent, flatIndex, sequential.model));
2802
- flatIndex++;
2803
- }
2804
- return sessionFiles;
2805
- }
2806
-
2807
- function collectChainThinkingOverrides(
2808
- chain: ChainStep[],
2809
- thinkingOverrideForTask: ForkThinkingOverrideForTask,
2810
- dynamicFanoutMaxItems?: number,
2811
- ): (AgentConfig["thinking"] | undefined)[] {
2812
- const thinkingOverrides: (AgentConfig["thinking"] | undefined)[] = [];
2813
- let flatIndex = 0;
2814
- for (const step of chain) {
2815
- if (isParallelStep(step)) {
2816
- for (const task of step.parallel) {
2817
- thinkingOverrides.push(thinkingOverrideForTask(task.agent, flatIndex, task.model));
2818
- flatIndex++;
2819
- }
2820
- continue;
2821
- }
2822
- if (isDynamicParallelStep(step)) {
2823
- const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
2824
- for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) {
2825
- thinkingOverrides.push(thinkingOverrideForTask(step.parallel.agent, flatIndex, step.parallel.model));
2826
- flatIndex++;
2827
- }
2828
- continue;
2829
- }
2830
- const sequential = step as SequentialStep;
2831
- thinkingOverrides.push(thinkingOverrideForTask(sequential.agent, flatIndex, sequential.model));
2832
- flatIndex++;
2833
- }
2834
- return thinkingOverrides;
2835
- }
2836
-
2837
2854
  type StaticLaunchSummary = { agent: string; model?: string; thinking?: string };
2838
2855
 
2839
2856
  function resolveStaticLaunchSummary(input: {
@@ -2919,14 +2936,6 @@ function collectStaticLaunchSummaries(input: {
2919
2936
  return input.params.agent ? [summary(input.params.agent, 0, input.params.model as string | undefined)] : [];
2920
2937
  }
2921
2938
 
2922
- function firstChainAgent(chain: ChainStep[]): string | undefined {
2923
- const first = chain[0];
2924
- if (!first) return undefined;
2925
- if (isParallelStep(first)) return first.parallel[0]?.agent;
2926
- if (isDynamicParallelStep(first)) return first.parallel.agent;
2927
- return (first as SequentialStep).agent;
2928
- }
2929
-
2930
2939
  function firstRawChainTask(chain: ChainStep[]): string | undefined {
2931
2940
  const first = chain[0];
2932
2941
  if (!first) return undefined;
@@ -2977,21 +2986,21 @@ function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultCo
2977
2986
  });
2978
2987
  }
2979
2988
 
2980
- function preflightForkSessionsForStaticTasks(
2989
+ async function preflightForkSessionsForStaticTasks(
2981
2990
  params: SubagentParamsLike,
2982
2991
  contextPolicy: AgentDefaultContextPolicy,
2983
- sessionFileForTask: ForkSessionFileForTask,
2992
+ prepareSessionForTask: PrepareForkSessionForTask,
2984
2993
  dynamicFanoutMaxItems?: number,
2985
- ): void {
2994
+ ): Promise<void> {
2986
2995
  if (!contextPolicy.usesFork) return;
2987
2996
  if (params.agent) {
2988
- if (shouldForkAgent(contextPolicy, params.agent)) sessionFileForTask(params.agent, 0, params.model);
2997
+ if (shouldForkAgent(contextPolicy, params.agent)) await prepareSessionForTask(params.agent, 0, params.model);
2989
2998
  return;
2990
2999
  }
2991
3000
  if (params.tasks) {
2992
- params.tasks.forEach((task, index) => {
2993
- if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, index, task.model);
2994
- });
3001
+ for (const [index, task] of params.tasks.entries()) {
3002
+ if (shouldForkAgent(contextPolicy, task.agent)) await prepareSessionForTask(task.agent, index, task.model);
3003
+ }
2995
3004
  return;
2996
3005
  }
2997
3006
  if (!params.chain?.length) return;
@@ -2999,7 +3008,7 @@ function preflightForkSessionsForStaticTasks(
2999
3008
  for (const step of params.chain) {
3000
3009
  if (isParallelStep(step)) {
3001
3010
  for (const task of step.parallel) {
3002
- if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, flatIndex, task.model);
3011
+ if (shouldForkAgent(contextPolicy, task.agent)) await prepareSessionForTask(task.agent, flatIndex, task.model);
3003
3012
  flatIndex++;
3004
3013
  }
3005
3014
  continue;
@@ -3007,17 +3016,29 @@ function preflightForkSessionsForStaticTasks(
3007
3016
  if (isDynamicParallelStep(step)) {
3008
3017
  const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
3009
3018
  if (shouldForkAgent(contextPolicy, step.parallel.agent)) {
3010
- for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) sessionFileForTask(step.parallel.agent, flatIndex + itemIndex, step.parallel.model);
3019
+ for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) await prepareSessionForTask(step.parallel.agent, flatIndex + itemIndex, step.parallel.model);
3011
3020
  }
3012
3021
  flatIndex += maxItems;
3013
3022
  continue;
3014
3023
  }
3015
3024
  const sequential = step as SequentialStep;
3016
- if (shouldForkAgent(contextPolicy, sequential.agent)) sessionFileForTask(sequential.agent, flatIndex, sequential.model);
3025
+ if (shouldForkAgent(contextPolicy, sequential.agent)) await prepareSessionForTask(sequential.agent, flatIndex, sequential.model);
3017
3026
  flatIndex++;
3018
3027
  }
3019
3028
  }
3020
3029
 
3030
+ function importedAsyncRootUsage(completed: Awaited<ReturnType<typeof waitForImportedAsyncRoot>>): Usage {
3031
+ const totalCost = completed.totalCost;
3032
+ return completed.usage ?? {
3033
+ input: totalCost?.inputTokens ?? 0,
3034
+ output: totalCost?.outputTokens ?? 0,
3035
+ cacheRead: 0,
3036
+ cacheWrite: 0,
3037
+ cost: totalCost?.costUsd ?? 0,
3038
+ turns: 0,
3039
+ };
3040
+ }
3041
+
3021
3042
  async function waitForWorkflowAsyncSingleResult(
3022
3043
  params: SubagentParamsLike,
3023
3044
  launchResult: AgentToolResult<Details>,
@@ -3039,20 +3060,14 @@ async function waitForWorkflowAsyncSingleResult(
3039
3060
  options.signal?.removeEventListener("abort", stopOnAbort);
3040
3061
  }
3041
3062
  fs.rmSync(resultPath, { force: true });
3042
- const totalCost = completed.totalCost;
3063
+ const usage = importedAsyncRootUsage(completed);
3043
3064
  const childResult: SingleResult = omitUndefinedProperties({
3044
3065
  index: 0,
3045
3066
  agent: completed.agent,
3067
+ ...(completed.sessionName ? { sessionName: completed.sessionName } : {}),
3046
3068
  task: options.task,
3047
3069
  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
- },
3070
+ usage,
3056
3071
  finalOutput: completed.output,
3057
3072
  outputState: completed.output.trim() ? "present" as const : "absent" as const,
3058
3073
  ...(completed.error ? { error: completed.error } : {}),
@@ -3067,9 +3082,13 @@ async function waitForWorkflowAsyncSingleResult(
3067
3082
  ...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
3068
3083
  ...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
3069
3084
  ...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
3085
+ ...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
3086
+ ...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
3087
+ ...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
3088
+ ...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
3070
3089
  });
3071
3090
  return {
3072
- content: [{ type: "text", text: completed.output || completed.error || `Async workflow child ${options.runId} completed without output.` }],
3091
+ content: [{ type: "text", text: completed.success ? completed.output || completed.error || `Async workflow child ${options.runId} completed without output.` : completed.error || completed.output || `Async workflow child ${options.runId} completed without output.` }],
3073
3092
  ...(completed.success ? {} : { isError: true }),
3074
3093
  details: {
3075
3094
  ...launchResult.details,
@@ -3087,7 +3106,6 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3087
3106
  ctx,
3088
3107
  shareEnabled,
3089
3108
  sessionRoot,
3090
- sessionFileForIndex,
3091
3109
  sessionFileForTask,
3092
3110
  thinkingOverrideForTask,
3093
3111
  artifactConfig,
@@ -3097,6 +3115,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3097
3115
  intercomBridge,
3098
3116
  nestedRoute,
3099
3117
  contextPolicy,
3118
+ unknownAgentDiagnosticContext,
3100
3119
  } = data;
3101
3120
  const hasChain = (params.chain?.length ?? 0) > 0;
3102
3121
  const hasTasks = (params.tasks?.length ?? 0) > 0;
@@ -3135,7 +3154,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3135
3154
  const a = agents.find((x) => x.name === params.agent);
3136
3155
  if (!a) {
3137
3156
  return {
3138
- content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
3157
+ content: [{ type: "text", text: formatUnknownAgentError(params.agent!, unknownAgentDiagnosticContext) }],
3139
3158
  isError: true,
3140
3159
  details: { mode: "single" as const, results: [] },
3141
3160
  };
@@ -3166,6 +3185,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3166
3185
  ctx: asyncCtx,
3167
3186
  availableModels,
3168
3187
  cwd: effectiveCwd,
3188
+ requestedCwd: data.requestedCwd,
3169
3189
  maxOutput: params.maxOutput,
3170
3190
  artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
3171
3191
  artifactConfig,
@@ -3177,6 +3197,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3177
3197
  skills,
3178
3198
  output: effectiveOutput,
3179
3199
  outputMode: effectiveOutputMode,
3200
+ outputClaimPath: params.workflowOutputClaimPath,
3180
3201
  ...(params.reads !== undefined ? { reads: params.reads } : {}),
3181
3202
  outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
3182
3203
  modelOverride,
@@ -3186,6 +3207,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3186
3207
  thinkingCeiling: a.maxThinking,
3187
3208
  maxSubagentDepth,
3188
3209
  waitToolEnabled: deps.waitToolEnabled,
3210
+ waitToolDefaultTimeoutMs: deps.waitToolDefaultTimeoutMs,
3189
3211
  ...(params.worktree === true ? { worktree: true } : {}),
3190
3212
  worktreeSetupHook: deps.config.worktreeSetupHook,
3191
3213
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
@@ -3200,7 +3222,6 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3200
3222
  extensionBindings: params.extensionBindings,
3201
3223
  acceptance: params.acceptance,
3202
3224
  timeoutMs: data.timeoutMs,
3203
- turnBudget: data.turnBudget,
3204
3225
  toolBudget: data.toolBudget,
3205
3226
  usageBudget: data.usageBudget,
3206
3227
  configToolBudget: data.configToolBudget,
@@ -3210,6 +3231,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
3210
3231
  runFanoutBudget: data.runFanoutBudget,
3211
3232
  parentWorkflowRunId: params.workflowParentRunId,
3212
3233
  workflowKey: params.workflowKey,
3234
+ lane: params.lane,
3213
3235
  workflowAwaitAsync: params.workflowAwaitAsync,
3214
3236
  }));
3215
3237
  return waitForWorkflowAsyncSingleResult(params, asyncResult, { runId: id, task: params.task ?? "", signal: data.signal, state: deps.state, kill: deps.kill });
@@ -3226,6 +3248,7 @@ function createSingleWorktreeSetup(
3226
3248
  setupHook: ExtensionConfig["worktreeSetupHook"],
3227
3249
  setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
3228
3250
  baseDir: ExtensionConfig["worktreeBaseDir"],
3251
+ beforeCreate?: (setup: WorktreeSetup) => void,
3229
3252
  ): { setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> } {
3230
3253
  if (!enabled) return {};
3231
3254
  try {
@@ -3236,6 +3259,7 @@ function createSingleWorktreeSetup(
3236
3259
  ? { hookPath: setupHook, ...(setupHookTimeoutMs === undefined ? {} : { timeoutMs: setupHookTimeoutMs }) }
3237
3260
  : undefined,
3238
3261
  baseDir,
3262
+ beforeCreate,
3239
3263
  })),
3240
3264
  };
3241
3265
  } catch (error) {
@@ -3278,10 +3302,30 @@ function workflowChildDefaultOutput(aggregateOutputPath: string | undefined, art
3278
3302
  return path.join(artifactsDir, "outputs", sanitizeRunPathSegment(workflowRunId), `${workflowKey}.md`);
3279
3303
  }
3280
3304
 
3305
+ function workflowHostCommandRunner(input: {
3306
+ workflowCwd: string;
3307
+ artifactsDir: string;
3308
+ workflowRunId: string;
3309
+ claimedOutputPaths: Map<string, string>;
3310
+ producedOutputPaths: Set<string>;
3311
+ }) {
3312
+ return async (key: string, params: WorkflowHostCommandParams, signal: AbortSignal): Promise<WorkflowHostCommandResult> => {
3313
+ const defaultOutputPath = path.join(input.artifactsDir, "outputs", sanitizeRunPathSegment(input.workflowRunId), "host", `${sanitizeRunPathSegment(key)}.log`);
3314
+ const outputPath = params.output ? path.resolve(input.workflowCwd, params.output) : defaultOutputPath;
3315
+ const claimPath = resolveWorkflowHostOutputClaimPath(outputPath);
3316
+ const previous = input.claimedOutputPaths.get(claimPath);
3317
+ if (previous) throw new Error(`runs.host('${key}') output path is already claimed by '${previous}': ${outputPath}.`);
3318
+ input.claimedOutputPaths.set(claimPath, `host:${key}`);
3319
+ const result = await executeWorkflowHostCommand({ key, params, cwd: input.workflowCwd, defaultOutputPath, claimedOutputPath: claimPath, signal });
3320
+ input.producedOutputPaths.add(resolveWorkflowHostOutputClaimPath(result.outputPath));
3321
+ return result;
3322
+ };
3323
+ }
3324
+
3281
3325
  function writeWorkflowAggregateOutput(outputPath: string | undefined, text: string, producedChildOutputPaths: ReadonlySet<string>): string | undefined {
3282
3326
  if (!outputPath) return undefined;
3283
3327
  try {
3284
- if (producedChildOutputPaths.has(outputPath)) return undefined;
3328
+ if (producedChildOutputPaths.has(resolveWorkflowHostOutputClaimPath(outputPath))) return undefined;
3285
3329
  fs.mkdirSync(path.dirname(outputPath), { recursive: true });
3286
3330
  fs.writeFileSync(outputPath, text, "utf-8");
3287
3331
  return undefined;
@@ -3352,7 +3396,7 @@ function workflowChildOutputClaims(input: {
3352
3396
  state: SubagentState;
3353
3397
  claimedOutputPaths: Map<string, string>;
3354
3398
  entries: Array<{ key: string; params: Record<string, unknown> }>;
3355
- }): { error?: string; claims?: Map<string, string>; overrides?: Map<string, string> } {
3399
+ }): { error?: string; claims?: Map<string, string>; childClaims?: Map<string, string>; overrides?: Map<string, string> } {
3356
3400
  const resolvedEntries = input.entries.map(({ key, params }) => ({
3357
3401
  key,
3358
3402
  ...resolveWorkflowChildOutputPath({ ...input, key, params }),
@@ -3360,24 +3404,30 @@ function workflowChildOutputClaims(input: {
3360
3404
  const paths = new Map<string, number>();
3361
3405
  for (const claimedPath of input.claimedOutputPaths.keys()) paths.set(claimedPath, 1);
3362
3406
  for (const { path: resolved } of resolvedEntries) {
3363
- if (resolved) paths.set(resolved, (paths.get(resolved) ?? 0) + 1);
3407
+ if (resolved) {
3408
+ const claimPath = resolveWorkflowHostOutputClaimPath(resolved);
3409
+ paths.set(claimPath, (paths.get(claimPath) ?? 0) + 1);
3410
+ }
3364
3411
  }
3365
3412
  const overrides = new Map<string, string>();
3366
3413
  for (const entry of resolvedEntries) {
3367
- if (entry.inherited && entry.path && (paths.get(entry.path) ?? 0) > 1) {
3414
+ if (entry.inherited && entry.path && (paths.get(resolveWorkflowHostOutputClaimPath(entry.path)) ?? 0) > 1) {
3368
3415
  const output = workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, entry.key);
3369
3416
  overrides.set(entry.key, output);
3370
3417
  entry.path = output;
3371
3418
  }
3372
3419
  }
3373
3420
  const claims = new Map(input.claimedOutputPaths);
3421
+ const childClaims = new Map<string, string>();
3374
3422
  for (const { key, path: resolved } of resolvedEntries) {
3375
3423
  if (!resolved) continue;
3376
- const previous = claims.get(resolved);
3424
+ const claimPath = resolveWorkflowHostOutputClaimPath(resolved);
3425
+ const previous = claims.get(claimPath);
3377
3426
  if (previous) return { error: `Workflow children '${previous}' and '${key}' resolve output to the same path: ${resolved}. Use distinct child output paths.` };
3378
- claims.set(resolved, key);
3427
+ claims.set(claimPath, key);
3428
+ childClaims.set(key, claimPath);
3379
3429
  }
3380
- return { claims, overrides };
3430
+ return { claims, childClaims, overrides };
3381
3431
  }
3382
3432
 
3383
3433
  function applyWorkflowChildOutputClaims(target: Map<string, string>, claims: Map<string, string>): void {
@@ -3399,12 +3449,14 @@ function prepareWorkflowChildLaunchParams(input: {
3399
3449
  agents: AgentConfig[];
3400
3450
  workflowAgentScope?: unknown;
3401
3451
  outputOverride?: string;
3402
- options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number; capabilityCeiling?: ResolvedSubagentCapabilityCeiling };
3452
+ outputClaimPath?: string;
3453
+ options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; awaitDetachedChild?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number; capabilityCeiling?: ResolvedSubagentCapabilityCeiling };
3403
3454
  }): SubagentParamsLike {
3404
3455
  let childParams = input.childParams;
3405
- if (input.childParams.output === undefined && input.childParams.resume === undefined && input.outputOverride !== undefined) {
3456
+ const usesDefaultOutput = input.childParams.output === undefined && input.childParams.resume === undefined;
3457
+ if (usesDefaultOutput && input.outputOverride !== undefined) {
3406
3458
  childParams = { ...input.childParams, output: input.outputOverride };
3407
- } else if (input.childParams.output === undefined && input.childParams.resume === undefined && input.aggregateOutputPath !== undefined) {
3459
+ } else if (usesDefaultOutput && input.aggregateOutputPath !== undefined) {
3408
3460
  childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
3409
3461
  } else if (input.childParams.resume === undefined) {
3410
3462
  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 });
@@ -3417,7 +3469,7 @@ function prepareWorkflowChildLaunchParams(input: {
3417
3469
  ? resolveAgentName(childParams.agent, discoveredAgents).agent ?? resolveAgentName(childParams.agent, input.agents).agent
3418
3470
  : undefined;
3419
3471
  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 });
3472
+ return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, { ...input.options, externalAsyncRequired, outputClaimPath: input.outputClaimPath });
3421
3473
  }
3422
3474
 
3423
3475
  function finalizeSingleWorktreeHandoff(input: {
@@ -3427,6 +3479,8 @@ function finalizeSingleWorktreeHandoff(input: {
3427
3479
  cwd: string;
3428
3480
  agent: string;
3429
3481
  result: SingleResult;
3482
+ workflowKey?: string;
3483
+ lane?: import("../../shared/types.ts").WorkflowLaneMetadata;
3430
3484
  }): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
3431
3485
  const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
3432
3486
  const diffs = diffWorktrees(input.worktreeSetup, [input.agent], diffsDir);
@@ -3441,9 +3495,13 @@ function finalizeSingleWorktreeHandoff(input: {
3441
3495
  stepIndex: 0,
3442
3496
  flatStartIndex: 0,
3443
3497
  setup: input.worktreeSetup,
3498
+ laneBindings: input.workflowKey || input.lane ? [{ index: 0, taskIndex: 0, ...(input.workflowKey ? { workflowKey: input.workflowKey } : {}), runId: input.workflowKey ? input.runId : undefined, ...(input.lane ? { lane: input.lane } : {}) }] : undefined,
3444
3499
  diffs,
3445
3500
  results: [{
3446
3501
  agent: input.result.agent,
3502
+ ...(input.workflowKey ? { workflowKey: input.workflowKey } : {}),
3503
+ ...(input.workflowKey ? { runId: input.runId } : {}),
3504
+ ...(input.lane ? { lane: input.lane } : {}),
3447
3505
  status: resolveSubagentResultStatus(omitUndefinedProperties({
3448
3506
  exitCode: input.result.exitCode,
3449
3507
  interrupted: input.result.interrupted,
@@ -3489,10 +3547,16 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3489
3547
  artifactConfig,
3490
3548
  artifactsDir,
3491
3549
  onUpdate,
3492
- sessionRoot,
3493
3550
  controlConfig,
3494
3551
  contextPolicy,
3495
3552
  } = data;
3553
+ let lane: import("../../shared/types.ts").WorkflowLaneMetadata | undefined;
3554
+ try {
3555
+ lane = normalizeWorkflowLaneMetadata(params.lane, "lane");
3556
+ assertWorkflowLaneKey(lane, params.workflowKey, "lane");
3557
+ } catch (error) {
3558
+ return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
3559
+ }
3496
3560
  const onControlEvent = createForegroundControlNotifier(data, deps);
3497
3561
  const childBridgeActive = intercomBridgeAppliesToAgent(data.intercomBridge, contextPolicy, params.agent!);
3498
3562
  const childIntercomTarget = childBridgeActive ? resolveSubagentIntercomTarget(runId, params.agent!, 0) : undefined;
@@ -3501,7 +3565,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3501
3565
  const agentConfig = agents.find((a) => a.name === params.agent);
3502
3566
  if (!agentConfig) {
3503
3567
  return {
3504
- content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
3568
+ content: [{ type: "text", text: formatUnknownAgentError(params.agent!, data.unknownAgentDiagnosticContext) }],
3505
3569
  isError: true,
3506
3570
  details: { mode: "single", results: [] },
3507
3571
  };
@@ -3513,7 +3577,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3513
3577
  const currentProvider = parentModel?.provider;
3514
3578
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
3515
3579
  const modelScopes = resolveModelScopesForAgent(data.modelScope, agentConfig.name, parentModel);
3516
- let task = params.task ?? "";
3580
+ let task = typeof params.task === "string" ? params.task : "";
3517
3581
  let modelOverride: string | undefined = resolveEffectiveSubagentModel(
3518
3582
  params.model as string | undefined,
3519
3583
  agentConfig.model,
@@ -3533,6 +3597,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3533
3597
 
3534
3598
 
3535
3599
  const sourceCwd = effectiveCwd;
3600
+ let pendingHandoff: Details["parallelHandoff"];
3536
3601
  const { setup: worktreeSetup, errorResult: worktreeSetupError } = createSingleWorktreeSetup(
3537
3602
  params.worktree,
3538
3603
  sourceCwd,
@@ -3541,12 +3606,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3541
3606
  deps.config.worktreeSetupHook,
3542
3607
  deps.config.worktreeSetupHookTimeoutMs,
3543
3608
  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 {
3609
+ (plannedSetup) => {
3550
3610
  pendingHandoff = writePendingParallelHandoff({
3551
3611
  manifestPath: parallelHandoffPath(artifactsDir, runId),
3552
3612
  runId,
@@ -3555,13 +3615,13 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3555
3615
  cwd: sourceCwd,
3556
3616
  stepIndex: 0,
3557
3617
  flatStartIndex: 0,
3558
- setup: worktreeSetup,
3618
+ setup: plannedSetup,
3619
+ laneBindings: params.workflowKey || lane ? [{ index: 0, taskIndex: 0, ...(params.workflowKey ? { workflowKey: params.workflowKey, runId } : {}), ...(lane ? { lane } : {}) }] : undefined,
3559
3620
  });
3560
- } catch (error) {
3561
- cleanupWorktrees(worktreeSetup);
3562
- return { content: [{ type: "text", text: formatParallelHandoffError(error) }], isError: true, details: { mode: "single", results: [] } };
3563
- }
3564
- }
3621
+ },
3622
+ );
3623
+ if (worktreeSetupError) return worktreeSetupError;
3624
+ const singleCwd = worktreeSetup?.worktrees[0]?.agentCwd ?? sourceCwd;
3565
3625
 
3566
3626
  const authoredTask = task;
3567
3627
  if (shouldForkAgent(contextPolicy, params.agent!)) {
@@ -3627,15 +3687,22 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3627
3687
 
3628
3688
  const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
3629
3689
  let r: Awaited<ReturnType<typeof runSync>> | undefined;
3690
+ let resolveDetachedWorkflowChild: ((result: Awaited<ReturnType<typeof runSync>>) => void) | undefined;
3691
+ const detachedWorkflowChild = params.workflowAwaitDetached === true
3692
+ ? new Promise<Awaited<ReturnType<typeof runSync>>>((resolve) => { resolveDetachedWorkflowChild = resolve; })
3693
+ : undefined;
3630
3694
  try {
3631
- r = await runSync(ctx.cwd, agents, params.agent!, task, compactOptional<Parameters<typeof runSync>[4]>({
3695
+ const launched = await runSync(ctx.cwd, agents, params.agent!, task, compactOptional<Parameters<typeof runSync>[4]>({
3632
3696
  permissions: deps.config.permissions,
3697
+ runtimeSnapshotHost: deps.pi,
3633
3698
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
3634
3699
  llmIntentArbiter: createTaskMutationArbiter(ctx),
3635
3700
  ...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
3636
3701
  context: data.contextPolicy.contextForAgent(params.agent!),
3702
+ unknownAgentDiagnosticContext: data.unknownAgentDiagnosticContext,
3637
3703
  runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
3638
3704
  cwd: singleCwd,
3705
+ requestedCwd: data.requestedCwd,
3639
3706
  signal,
3640
3707
  interruptSignal: interruptController.signal,
3641
3708
  allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
@@ -3648,9 +3715,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3648
3715
  artifactConfig,
3649
3716
  maxOutput: params.maxOutput,
3650
3717
  outputPath,
3718
+ outputClaimPath: params.workflowOutputClaimPath,
3651
3719
  outputMode: effectiveOutputMode,
3652
3720
  maxSubagentDepth,
3653
3721
  waitToolEnabled: deps.waitToolEnabled,
3722
+ waitToolDefaultTimeoutMs: deps.waitToolDefaultTimeoutMs,
3654
3723
  onUpdate: forwardSingleUpdate,
3655
3724
  controlConfig,
3656
3725
  onControlEvent,
@@ -3672,12 +3741,20 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3672
3741
  agentContract: params.agentContract,
3673
3742
  acceptance: params.acceptance,
3674
3743
  acceptanceContext: { mode: "single" },
3744
+ workflowChildPermitLaunch: data.workflowChildPermitLaunch,
3675
3745
  onEffectivePrompt: foregroundControl ? (prompt) => updateLiveEffectivePrompt(foregroundControl, 0, prompt) : undefined,
3676
3746
  onDetachReady: (detach) => {
3677
3747
  detachForeground = detach;
3678
3748
  },
3679
3749
  onDetachedExit: (result) => {
3750
+ if (resolveDetachedWorkflowChild) {
3751
+ resolveDetachedWorkflowChild(result);
3752
+ return;
3753
+ }
3680
3754
  try {
3755
+ if (worktreeSetup) {
3756
+ finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result, workflowKey: params.workflowKey, lane });
3757
+ }
3681
3758
  try {
3682
3759
  updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: true });
3683
3760
  } catch {
@@ -3705,18 +3782,17 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3705
3782
  }
3706
3783
  }
3707
3784
  }
3708
- recordRun(params.agent!, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0);
3785
+ recordRun(params.agent!, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0, result);
3709
3786
  },
3710
3787
  timeoutMs: data.timeoutMs,
3711
3788
  deadlineAt,
3712
3789
  toolTimeoutMs: params.toolTimeoutMs,
3713
3790
  configToolTimeoutMs: data.configToolTimeoutMs,
3714
- turnBudget: data.turnBudget,
3715
- enforceHardTurnLimit: params.enforceHardTurnLimit,
3716
3791
  toolBudget: effectiveToolBudget.toolBudget,
3717
3792
  capabilityCeiling: data.capabilityCeiling,
3718
3793
  allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
3719
3794
  }));
3795
+ r = launched.detached && detachedWorkflowChild ? await detachedWorkflowChild : launched;
3720
3796
  } catch (error) {
3721
3797
  if (worktreeSetup) cleanupWorktrees(worktreeSetup);
3722
3798
  throw error;
@@ -3730,14 +3806,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3730
3806
  }
3731
3807
  }
3732
3808
  if (!r.detached) {
3733
- recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
3809
+ recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0, r);
3734
3810
  }
3735
3811
 
3736
3812
  let worktreeHandoff: ReturnType<typeof finalizeSingleWorktreeHandoff> | undefined;
3737
3813
  if (worktreeSetup) {
3738
3814
  worktreeHandoff = r.detached
3739
3815
  ? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
3740
- : finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result: r });
3816
+ : finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent!, result: r, workflowKey: params.workflowKey, lane });
3741
3817
  }
3742
3818
 
3743
3819
  if (r.progress) allProgress.push(r.progress);
@@ -3766,7 +3842,6 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3766
3842
  runId,
3767
3843
  timeoutMs: data.timeoutMs,
3768
3844
  results: [r],
3769
- ...(data.turnBudget ? { turnBudget: data.turnBudget } : {}),
3770
3845
  ...(effectiveToolBudget.toolBudget ? { toolBudget: effectiveToolBudget.toolBudget } : {}),
3771
3846
  progress: params.includeProgress ? allProgress : undefined,
3772
3847
  artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
@@ -3776,7 +3851,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3776
3851
  usageBudget: usageBudgetState(data.usageBudget, totalCost),
3777
3852
  ...(worktreeHandoff?.reference ? { parallelHandoff: worktreeHandoff.reference } : {}),
3778
3853
  }));
3779
- rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, extensionBindings: params.extensionBindings });
3854
+ rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings });
3780
3855
 
3781
3856
  const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results: [r] });
3782
3857
  if (!r.detached && !r.interrupted && !suppressRoutineResultIntercom) {
@@ -3851,7 +3926,7 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
3851
3926
  };
3852
3927
  }
3853
3928
 
3854
- const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string; async: boolean; runId?: string }) => void>();
3929
+ const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionName?: string; sessionFile?: string; async: boolean; runId?: string }) => void>();
3855
3930
 
3856
3931
  /**
3857
3932
  * Terminal-mission retention can remove a mission while its children still
@@ -3923,16 +3998,27 @@ export function bindMissionWorkflowChildAsyncLaunch(
3923
3998
  function workflowChildResult(
3924
3999
  key: string,
3925
4000
  result: AgentToolResult<Details>,
3926
- childParams: Record<string, unknown> = {},
4001
+ childParams: SubagentParamsLike | Record<string, unknown> = {},
3927
4002
  resumeState?: SubagentState,
4003
+ forcedTerminalOutcome?: WorkflowTerminalOutcome,
3928
4004
  ): WorkflowScriptChildResult {
3929
4005
  const receiptOutput = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n");
3930
4006
  const output = result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
3931
4007
  ? result.details.results[0].finalOutput
3932
4008
  : receiptOutput;
4009
+ const childError = result.details.results.map((child) => child.error).find((error): error is string => Boolean(error));
4010
+ const failureError = childError && receiptOutput && receiptOutput !== childError
4011
+ ? `${childError}\n\n${receiptOutput}`
4012
+ : childError || receiptOutput || output || "Child run failed.";
3933
4013
  const detached = result.details.results.some((child) => child.detached);
3934
4014
  const interrupted = result.details.results.some((child) => child.interrupted);
3935
4015
  const stopped = result.details.results.some((child) => child.stopped);
4016
+ const terminalOutcome = forcedTerminalOutcome
4017
+ ?? (result.details.results.some((child) => child.timedOut)
4018
+ ? { state: "partial" as const, reason: "timeout" as const }
4019
+ : result.details.usageBudget?.exhausted || result.details.results.some((child) => child.turnBudgetExceeded || child.toolBudgetBlocked)
4020
+ ? { state: "partial" as const, reason: "budget_exhausted" as const }
4021
+ : undefined);
3936
4022
  const ok = result.isError !== true && !detached && !interrupted && !stopped;
3937
4023
  const artifactPaths = new Set<string>();
3938
4024
  if (result.details.asyncDir) artifactPaths.add(result.details.asyncDir);
@@ -3965,13 +4051,25 @@ function workflowChildResult(
3965
4051
  const continuationRunIds = [...new Set([resumeSourceRunId, runId].filter((value): value is string => Boolean(value)))];
3966
4052
  const outputReference = result.details.results.find((child) => child.savedOutputPath)?.savedOutputPath
3967
4053
  ?? result.details.results.find((child) => child.outputReference?.path)?.outputReference?.path;
4054
+ const outputPathMapping = typeof childParams.task === "string" ? outputPathMappingFromTask(childParams.task, outputReference) : undefined;
4055
+ const externalResult = result.details.results.length === 1 && result.details.results[0]?.runner?.type === "external-cli" ? result.details.results[0] : undefined;
4056
+ const externalStatus = result.details.asyncDir ? readStatus(result.details.asyncDir) : undefined;
4057
+ const externalStatusStep = externalStatus?.steps?.length === 1 && externalStatus.steps[0]?.runner?.type === "external-cli" ? externalStatus.steps[0] : undefined;
4058
+ const externalRunner = normalizeExternalCliRunnerStatus(externalResult?.runner ?? externalStatusStep?.runner);
4059
+ const externalProcess = externalResult?.externalProcess ?? externalStatusStep?.externalProcess;
4060
+ const externalAdapter = externalRunner ? externalCliReceiptMetadata({ runner: externalRunner, externalProcess, outputReference }) : undefined;
4061
+ if (externalAdapter) resumability = { state: "not-resumable", reason: externalAdapter.nonResumableReason };
4062
+ const lane = normalizeWorkflowLaneMetadata(childParams.lane, `workflow child '${key}'.lane`);
4063
+ assertWorkflowLaneKey(lane, key, `workflow child '${key}'.lane`);
3968
4064
  return {
3969
4065
  key,
3970
4066
  ok,
4067
+ ...(lane ? { lane } : {}),
4068
+ ...(terminalOutcome ? { terminalOutcome } : {}),
3971
4069
  ...(resolvedAgents.length === 1 ? { agent: resolvedAgents[0] } : {}),
3972
4070
  ...(runId ? { runId } : {}),
3973
4071
  output,
3974
- ...(!ok ? { error: receiptOutput || output || "Child run failed." } : {}),
4072
+ ...(!ok ? { error: failureError } : {}),
3975
4073
  ...(detached ? { detached: true } : {}),
3976
4074
  ...(interrupted ? { interrupted: true } : {}),
3977
4075
  ...(stopped ? { stopped: true } : {}),
@@ -3979,6 +4077,8 @@ function workflowChildResult(
3979
4077
  ...(requestedContext ? { requestedContext } : {}),
3980
4078
  ...(resolvedContext ? { resolvedContext } : {}),
3981
4079
  ...(outputReference ? { outputReference } : {}),
4080
+ ...(outputPathMapping ? { outputPathMapping } : {}),
4081
+ ...(externalAdapter ? { externalAdapter } : {}),
3982
4082
  resumability,
3983
4083
  continuation: { runIds: continuationRunIds },
3984
4084
  artifactPaths: [...artifactPaths],
@@ -3986,6 +4086,23 @@ function workflowChildResult(
3986
4086
  };
3987
4087
  }
3988
4088
 
4089
+ function workflowChildAccountingFields(child: WorkflowScriptChildResult): { usage?: Usage; sessionFile?: string } {
4090
+ if (!child.results?.length) return {};
4091
+ const usage = sumResultsUsage(child.results);
4092
+ const sessionFile = child.results.find((result) => result.sessionFile)?.sessionFile;
4093
+ return {
4094
+ ...(usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0 ? { usage } : {}),
4095
+ ...(sessionFile ? { sessionFile } : {}),
4096
+ };
4097
+ }
4098
+
4099
+ function workflowOutputPathMappingSummary(children: WorkflowScriptChildResult[]): string {
4100
+ const mappings = children.flatMap((child) => child.outputPathMapping
4101
+ ? [`'${child.key}': requested ${child.outputPathMapping.requestedPath} -> saved ${child.outputPathMapping.savedPath}`]
4102
+ : []);
4103
+ return mappings.length > 0 ? ` Output path mappings: ${mappings.join("; ")}.` : "";
4104
+ }
4105
+
3989
4106
  function workflowSteerReceipt(key: string, result: AgentToolResult<Details>): WorkflowSteerResult {
3990
4107
  const steering = result.details.steering;
3991
4108
  const error = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n") || undefined;
@@ -4003,29 +4120,122 @@ function workflowSteerReceipt(key: string, result: AgentToolResult<Details>): Wo
4003
4120
  };
4004
4121
  }
4005
4122
 
4123
+ const MAX_WORKFLOW_RESUME_HINT_BYTES = 1024;
4124
+ const MAX_WORKFLOW_CHILD_RUN_ID_BYTES = 256;
4125
+ const WORKFLOW_RESUME_HINT_PARENT_STATES = new Set(["complete", "failed", "partial"]);
4126
+ const WORKFLOW_STEP_STATES = new Set(["pending", "running", "complete", "completed", "failed", "partial", "paused", "stopped", "rejected"]);
4127
+
4128
+ function isSafeWorkflowChildRunId(value: unknown): value is string {
4129
+ if (typeof value !== "string") return false;
4130
+ const runId = value.trim();
4131
+ return value === runId
4132
+ && Boolean(runId)
4133
+ && Buffer.byteLength(runId, "utf8") <= MAX_WORKFLOW_CHILD_RUN_ID_BYTES
4134
+ && path.basename(runId) === runId
4135
+ && path.win32.basename(runId) === runId
4136
+ && !/[\\/]/.test(runId)
4137
+ && !runId.includes("..")
4138
+ && !/[\u0000-\u001f\u007f]/.test(runId);
4139
+ }
4140
+
4141
+ function isMissingWorkflowReceiptDiagnostic(error: unknown, workflowRunId: string): error is Error {
4142
+ return error instanceof Error
4143
+ && error.message.startsWith(`Workflow receipt '${workflowRunId}' is not available because the workflow may still be active or terminal receipt writing failed.`);
4144
+ }
4145
+
4146
+ function missingWorkflowReceiptResumeHint(reference: WorkflowReceiptResumeReference, state: SubagentState): string | undefined {
4147
+ try {
4148
+ if (!state.currentSessionId) return undefined;
4149
+ const workflowRunId = reference.workflowRunId.trim();
4150
+ const workflowStatus = readStatus(path.join(DIRS.async, workflowRunId));
4151
+ if (!workflowStatus
4152
+ || workflowStatus.runId !== workflowRunId
4153
+ || workflowStatus.mode !== "workflow"
4154
+ || workflowStatus.sessionId !== state.currentSessionId
4155
+ || typeof workflowStatus.startedAt !== "number"
4156
+ || !Number.isFinite(workflowStatus.startedAt)
4157
+ || !WORKFLOW_RESUME_HINT_PARENT_STATES.has(workflowStatus.state)
4158
+ || !Array.isArray(workflowStatus.steps)
4159
+ || workflowStatus.steps.some((step) => {
4160
+ if (!step || typeof step !== "object" || Array.isArray(step)) return true;
4161
+ const record = step as Record<string, unknown>;
4162
+ return typeof record.agent !== "string"
4163
+ || typeof record.status !== "string"
4164
+ || !WORKFLOW_STEP_STATES.has(record.status)
4165
+ || (record.workflowKey !== undefined && typeof record.workflowKey !== "string")
4166
+ || (record.runId !== undefined && typeof record.runId !== "string");
4167
+ })) return undefined;
4168
+ const matchingSteps = workflowStatus.steps.filter((step) => step.workflowKey === reference.key);
4169
+ if (matchingSteps.length !== 1) return undefined;
4170
+ const childRunId = matchingSteps[0]?.runId;
4171
+ if (!isSafeWorkflowChildRunId(childRunId)) return undefined;
4172
+
4173
+ const workflowChildren = parseWorkflowChildSummary(workflowStatus.workflowChildren);
4174
+ if (workflowChildren) {
4175
+ if (workflowChildren.workflowRunId !== workflowRunId) return undefined;
4176
+ const matchingChildren = workflowChildren.children.filter((child) => child.childId === reference.key);
4177
+ if (matchingChildren.length > 1 || (workflowChildren.inventoryComplete && matchingChildren.length !== 1)) return undefined;
4178
+ if (matchingChildren.length === 1 && matchingChildren[0]?.runId !== childRunId) return undefined;
4179
+ }
4180
+
4181
+ const target = resolveResumeTarget({ id: childRunId }, state, { asyncRequireSessionFile: true, exactOnly: true });
4182
+ if (target.kind !== "revive") return undefined;
4183
+ const hint = `Direct resumable child for workflow key '${reference.key}': subagent({ action: "resume", id: ${JSON.stringify(childRunId)}, message: "..." })`;
4184
+ return Buffer.byteLength(hint, "utf8") <= MAX_WORKFLOW_RESUME_HINT_BYTES ? hint : undefined;
4185
+ } catch {
4186
+ return undefined;
4187
+ }
4188
+ }
4189
+
4006
4190
  function resolveKeyedWorkflowResume(
4007
4191
  reference: WorkflowReceiptResumeReference,
4008
4192
  state: SubagentState,
4009
4193
  ): { 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 };
4194
+ try {
4195
+ const entry = resolveWorkflowReceiptResumeEntry({
4196
+ reference,
4197
+ asyncDirRoot: DIRS.async,
4198
+ assertResumable(runId) {
4199
+ const target = resolveResumeTarget({ id: runId }, state, { asyncRequireSessionFile: true, exactOnly: true });
4200
+ if (target.kind !== "revive") throw new Error(`Workflow receipt child '${reference.key}' latest run '${runId}' is still running.`);
4201
+ },
4202
+ });
4203
+ const runId = entry.latestRunId;
4204
+ if (!runId) throw new Error(`Workflow receipt child '${reference.key}' has no retained run id.`);
4205
+ return { runId, runIds: entry.continuation.runIds };
4206
+ } catch (error) {
4207
+ const workflowRunId = reference.workflowRunId.trim();
4208
+ if (isMissingWorkflowReceiptDiagnostic(error, workflowRunId)) {
4209
+ const hint = missingWorkflowReceiptResumeHint(reference, state);
4210
+ if (hint) throw new Error(`${error.message} ${hint}`, { cause: error });
4211
+ }
4212
+ throw error;
4213
+ }
4021
4214
  }
4022
4215
 
4023
4216
  function terminalWorkflowReceipt(
4024
4217
  workflowRunId: string,
4025
4218
  state: WorkflowReceiptState,
4026
4219
  children: WorkflowScriptChildResult[],
4220
+ workflowChildren?: WorkflowReceipt["workflowChildren"],
4221
+ terminalOutcome?: WorkflowTerminalOutcome,
4222
+ hostSteps?: WorkflowReceipt["hostSteps"],
4027
4223
  ): WorkflowReceipt {
4028
- return buildWorkflowReceipt({ workflowRunId, state, children });
4224
+ return buildWorkflowReceipt({ workflowRunId, state, children, workflowChildren, terminalOutcome, hostSteps });
4225
+ }
4226
+
4227
+ function workflowFailureTerminalOutcome(error: unknown, _children: WorkflowScriptChildResult[], usageBudget: ReturnType<typeof usageBudgetState>): WorkflowTerminalOutcome | undefined {
4228
+ if (usageBudget?.exhausted) return { state: "partial", reason: "budget_exhausted" };
4229
+ return error instanceof WorkflowScriptError && error.errorKind === "timeout" ? { state: "partial", reason: "timeout" } : undefined;
4230
+ }
4231
+
4232
+ function workflowFailureMessage(error: unknown, workflowRunId: string, children: WorkflowScriptChildResult[]): string {
4233
+ const text = error instanceof Error ? error.message : String(error);
4234
+ const validationPrefix = "workflowScript validation failed before child launch; no children launched.";
4235
+ if (children.length === 0 && text.includes(validationPrefix)) {
4236
+ return `Workflow '${workflowRunId}' validation failed before child launch; no children launched.${text.slice(text.indexOf(validationPrefix) + validationPrefix.length)}`;
4237
+ }
4238
+ return text;
4029
4239
  }
4030
4240
 
4031
4241
  export async function steerWorkflowChildByKey(input: {
@@ -4068,6 +4278,8 @@ export async function steerWorkflowChildByKey(input: {
4068
4278
  return { key: input.key, state: "missed", error: `Workflow child '${input.key}' is ${childStatus.state}.` };
4069
4279
  }
4070
4280
  if (childStatus) {
4281
+ const unsupported = externalRunnerControlError(asyncDir, "steer");
4282
+ if (unsupported) return workflowSteerReceipt(input.key, unsupported);
4071
4283
  const result = await steerAsyncRun({
4072
4284
  state: input.state,
4073
4285
  runId: childRunId,
@@ -4099,9 +4311,11 @@ export function prepareWorkflowLaunchParams(
4099
4311
  childParams: Record<string, unknown>,
4100
4312
  parentWorkflowRunId: string,
4101
4313
  workflowKey: string,
4102
- options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number; externalAsyncRequired?: boolean; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } = {},
4314
+ options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; awaitDetachedChild?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor; parentDeadlineAt?: number; externalAsyncRequired?: boolean; capabilityCeiling?: ResolvedSubagentCapabilityCeiling; outputClaimPath?: string } = {},
4103
4315
  ): SubagentParamsLike {
4104
4316
  const capabilityCeiling = intersectSubagentCapabilityCeilings(workflowDefaults.capabilityCeiling, options.capabilityCeiling);
4317
+ const lane = normalizeWorkflowLaneMetadata(Object.hasOwn(childParams, "lane") ? childParams.lane : workflowDefaults.lane, `workflow child '${workflowKey}'.lane`);
4318
+ assertWorkflowLaneKey(lane, workflowKey, `workflow child '${workflowKey}'.lane`);
4105
4319
  const parentTimeoutMs = options.parentDeadlineAt === undefined
4106
4320
  || childParams.timeoutMs !== undefined
4107
4321
  || childParams.maxRuntimeMs !== undefined
@@ -4117,10 +4331,18 @@ export function prepareWorkflowLaunchParams(
4117
4331
  throw new Error("gate is not supported with retained resume; resume uses the retained child contract.");
4118
4332
  }
4119
4333
  const timeoutMs = childParams.timeoutMs ?? childParams.maxRuntimeMs ?? workflowDefaults.timeoutMs ?? workflowDefaults.maxRuntimeMs;
4120
- const turnBudget = childParams.turnBudget ?? workflowDefaults.turnBudget;
4121
4334
  const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
4122
4335
  const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
4123
4336
  const worktree = childParams.worktree ?? workflowDefaults.worktree;
4337
+ const outputSchema = Object.hasOwn(childParams, "outputSchema") ? childParams.outputSchema : workflowDefaults.outputSchema;
4338
+ if (outputSchema !== undefined) assertJsonSchemaObject(outputSchema, "outputSchema");
4339
+ const agentContract = Object.hasOwn(childParams, "agentContract") ? childParams.agentContract : workflowDefaults.agentContract;
4340
+ if (agentContract !== undefined && !isAgentContractV1(agentContract as AgentContract)) throw new Error("agentContract must be { version: 1 }.");
4341
+ const acceptance = Object.hasOwn(childParams, "acceptance") ? childParams.acceptance : workflowDefaults.acceptance;
4342
+ const output = Object.hasOwn(childParams, "output") ? childParams.output : workflowDefaults.output;
4343
+ if (output !== undefined && typeof output !== "string" && typeof output !== "boolean") throw new Error("output must be a path string or boolean.");
4344
+ const outputMode = Object.hasOwn(childParams, "outputMode") ? childParams.outputMode : workflowDefaults.outputMode;
4345
+ if (outputMode !== undefined && outputMode !== "inline" && outputMode !== "file-only") throw new Error("outputMode must be 'inline' or 'file-only'.");
4124
4346
  return {
4125
4347
  action: "resume",
4126
4348
  id: childParams.resume.trim(),
@@ -4128,11 +4350,16 @@ export function prepareWorkflowLaunchParams(
4128
4350
  message: typeof childParams.task === "string" ? childParams.task.trim() : "",
4129
4351
  workflowParentRunId: parentWorkflowRunId,
4130
4352
  workflowKey,
4353
+ ...(lane ? { lane } : {}),
4131
4354
  ...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
4355
+ ...(outputSchema !== undefined ? { outputSchema: outputSchema as JsonSchemaObject } : {}),
4356
+ ...(agentContract !== undefined ? { agentContract: agentContract as AgentContract } : {}),
4357
+ ...(acceptance !== undefined ? { acceptance: acceptance as AcceptanceInput } : {}),
4358
+ ...(output !== undefined ? { output: output as string | boolean } : {}),
4359
+ ...(outputMode !== undefined ? { outputMode: outputMode as OutputMode } : {}),
4132
4360
  ...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
4133
4361
  ...(options.missionDetached ? { mission: false } : {}),
4134
4362
  ...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
4135
- ...(turnBudget !== undefined ? { turnBudget: turnBudget as TurnBudgetConfig } : {}),
4136
4363
  ...(toolBudget !== undefined ? { toolBudget: toolBudget as ToolBudgetConfig } : {}),
4137
4364
  ...(intercomBridge !== undefined ? { intercomBridge: intercomBridge as IntercomBridgeConfig } : {}),
4138
4365
  ...(capabilityCeiling ? { capabilityCeiling } : {}),
@@ -4146,6 +4373,9 @@ export function prepareWorkflowLaunchParams(
4146
4373
  ...(options.missionDetached ? { mission: false } : {}),
4147
4374
  workflowParentRunId: parentWorkflowRunId,
4148
4375
  workflowKey,
4376
+ ...(options.outputClaimPath ? { workflowOutputClaimPath: options.outputClaimPath } : {}),
4377
+ ...(options.awaitDetachedChild ? { workflowAwaitDetached: true } : {}),
4378
+ ...(lane ? { lane } : {}),
4149
4379
  ...(parentTimeoutMs !== undefined ? { workflowParentDeadlineAt: options.parentDeadlineAt } : {}),
4150
4380
  ...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
4151
4381
  ...(options.suppressRoutineResultIntercom ? { suppressRoutineResultIntercom: true } : {}),
@@ -4186,11 +4416,13 @@ function workflowChatProgressUpdate(
4186
4416
  runId: string,
4187
4417
  chatProgress: WorkflowChatProgressProjection,
4188
4418
  workflow: NonNullable<Details["workflow"]>,
4419
+ workflowChildren?: Details["workflowChildren"],
4420
+ preflight?: import("../../shared/types.ts").WorkflowPreflightV1,
4189
4421
  ): AgentToolResult<Details> | undefined {
4190
4422
  if (chatProgress.mode !== "live-card") return undefined;
4191
4423
  return {
4192
4424
  content: [{ type: "text", text: "Workflow running." }],
4193
- details: { mode: "workflow", runId, results: [], workflow, chatProgress },
4425
+ details: { mode: "workflow", runId, results: [], workflow, ...(preflight ? { preflight } : {}), ...(workflowChildren ? { workflowChildren } : {}), chatProgress },
4194
4426
  };
4195
4427
  }
4196
4428
 
@@ -4201,10 +4433,12 @@ function createScheduledOwnerState(source: SubagentState, ownerSessionId: string
4201
4433
  grantHistory: [...(source.subagentSpawns.grantHistory ?? [])],
4202
4434
  }
4203
4435
  : undefined;
4436
+ const ownerParentModel = source.currentSessionId === ownerSessionId ? source.lastParentModel : undefined;
4204
4437
  return {
4205
4438
  ...source,
4206
4439
  baseCwd: ctx.cwd,
4207
4440
  currentSessionId: ownerSessionId,
4441
+ lastParentModel: ownerParentModel,
4208
4442
  parentSessionFile: ctx.sessionManager.getSessionFile() ?? null,
4209
4443
  subagentInProgress: false,
4210
4444
  ...(ownerSpawns ? { subagentSpawns: ownerSpawns } : { subagentSpawns: undefined }),
@@ -4265,6 +4499,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4265
4499
  const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
4266
4500
  const delegatedZeroToolBudgets = new WeakSet<object>();
4267
4501
  const delegatedExecutions = new WeakSet<object>();
4502
+ const workflowPermitContexts = new WeakMap<object, { root: WorkflowChildPermit } | { child: WorkflowChildPermitContext }>();
4268
4503
  const warnedArtifactPackageDirs = new Set<string>();
4269
4504
  const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
4270
4505
  const execute = async (
@@ -4274,21 +4509,67 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4274
4509
  onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
4275
4510
  ctx: ExtensionContext,
4276
4511
  preserveActiveSession = false,
4512
+ parentModelOverride?: ParentModel | null,
4277
4513
  ): Promise<AgentToolResult<Details>> => {
4278
4514
  const workflowLaunchObserver = workflowLaunchObservers.get(params);
4279
4515
  const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
4280
4516
  const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
4281
4517
  const delegatedExecution = delegatedExecutions.has(params);
4518
+ const workflowPermitContext = workflowPermitContexts.get(params);
4519
+ const delegatedWorkflowPermit = workflowPermitContext && "root" in workflowPermitContext ? workflowPermitContext.root : undefined;
4520
+ const workflowChildPermitLaunch = workflowPermitContext && "child" in workflowPermitContext ? workflowPermitContext.child : undefined;
4282
4521
  if (!preserveActiveSession) deps.state.baseCwd = ctx.cwd;
4283
4522
  deps.state.foregroundRuns ??= new Map();
4284
4523
  deps.state.foregroundControls ??= new Map();
4285
4524
  deps.state.lastForegroundControlId ??= null;
4286
4525
  const normalizedGate = normalizeGateParams(params);
4287
4526
  if (!normalizedGate.ok) return buildRequestedModeError(params, normalizedGate.error);
4288
- const requestParams = normalizedGate.params;
4527
+ let requestParams = normalizedGate.params;
4528
+ let workflowPreflight: import("../../shared/types.ts").WorkflowPreflightV1 | undefined;
4529
+ try {
4530
+ if (requestParams.preflight !== undefined && requestParams.workflowScript === undefined && requestParams.workflowScriptPath === undefined) {
4531
+ throw new Error("preflight requires workflowScript or workflowScriptPath.");
4532
+ }
4533
+ workflowPreflight = normalizeWorkflowPreflight(requestParams.preflight);
4534
+ if (workflowPreflight) requestParams = { ...requestParams, preflight: workflowPreflight };
4535
+ } catch (error) {
4536
+ const message = error instanceof Error ? error.message : String(error);
4537
+ if (requestParams.action?.trim() === "validate") {
4538
+ const validation = validateWorkflowScript(requestParams.workflowScript ?? "");
4539
+ const invalidValidation = { ...validation, ok: false, errors: [...validation.errors, { message }] };
4540
+ return {
4541
+ content: [{ type: "text", text: JSON.stringify(invalidValidation) }],
4542
+ isError: true,
4543
+ details: { mode: "management", results: [] },
4544
+ };
4545
+ }
4546
+ return buildRequestedModeError(requestParams, message);
4547
+ }
4548
+ if (requestParams.action?.trim() === "validate") {
4549
+ const validation = validateWorkflowScript(requestParams.workflowScript ?? "");
4550
+ const payload = workflowPreflight ? { ...validation, preflight: workflowPreflight } : validation;
4551
+ return {
4552
+ content: [{ type: "text", text: JSON.stringify(payload) }],
4553
+ ...(validation.ok ? {} : { isError: true }),
4554
+ details: { mode: "management", results: [] },
4555
+ };
4556
+ }
4289
4557
  const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
4290
4558
  if (normalizedAction === "resume" && requestParams.extensionBindings !== undefined) return buildRequestedModeError(requestParams, "extensionBindings is not supported with action='resume'; resume uses the original retained child binding.");
4291
4559
  if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
4560
+ if (delegatedWorkflowPermit) {
4561
+ const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit, _id);
4562
+ if (permitError) return buildRequestedModeError(requestParams, permitError);
4563
+ if (requestParams.async !== false) return buildRequestedModeError(requestParams, "Workflow child permit supports foreground workflow roots only; set async:false.");
4564
+ }
4565
+ const workflowParentModel = parentModelOverride !== undefined
4566
+ ? parentModelOverride
4567
+ : (() => {
4568
+ const currentParentModel = normalizeParentModel(ctx.model);
4569
+ return (preserveActiveSession
4570
+ ? currentParentModel
4571
+ : rememberParentModel(deps.state, resolveCurrentSessionId(ctx.sessionManager), currentParentModel)) ?? null;
4572
+ })();
4292
4573
  if (requestParams.extensionBindings !== undefined) {
4293
4574
  try {
4294
4575
  requestParams.extensionBindings = normalizeExtensionBindings(requestParams.extensionBindings)!.value;
@@ -4301,7 +4582,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4301
4582
  const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
4302
4583
  if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
4303
4584
  const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
4304
- const workflowAgents = deps.discoverAgents(workflowCwd, resolveExecutionAgentScope(requestParams.agentScope)).agents;
4585
+ const discoverWorkflowAgents = (cwd: string, scope: AgentScope) => deps.discoverAgents(cwd, scope, workflowParentModel?.provider);
4586
+ const workflowAgents = discoverWorkflowAgents(workflowCwd, resolveExecutionAgentScope(requestParams.agentScope)).agents;
4305
4587
  const workflowArtifactConfig: ArtifactConfig = omitUndefinedProperties({
4306
4588
  ...DEFAULT_ARTIFACT_CONFIG,
4307
4589
  enabled: requestParams.artifacts !== false,
@@ -4340,7 +4622,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4340
4622
  runId: workflowRunId,
4341
4623
  kind: "workflow",
4342
4624
  asyncDir: path.join(DIRS.async, workflowRunId),
4343
- }, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
4625
+ }, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
4344
4626
  } catch (error) {
4345
4627
  if (error instanceof ActiveAsyncCapacityError) {
4346
4628
  deps.state.activeAsyncCapacity = error.snapshot;
@@ -4414,7 +4696,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4414
4696
  deps.state.workflowChildStops ??= new Map();
4415
4697
  deps.state.workflowControllers.set(workflowRunId, controller);
4416
4698
  workflowCapacity?.markWorkflowStarted();
4417
- if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()) });
4699
+ if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
4418
4700
  let status: AsyncStatus = {
4419
4701
  runId: workflowRunId,
4420
4702
  toolCallId,
@@ -4430,7 +4712,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4430
4712
  ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}),
4431
4713
  pid: process.pid,
4432
4714
  steps: [],
4715
+ ...(workflowPreflight ? { preflight: workflowPreflight } : {}),
4433
4716
  workflow: { trace: [], emits: [], console: [] },
4717
+ workflowChildren: workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "running", inventoryComplete: false }),
4434
4718
  runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
4435
4719
  };
4436
4720
  const appendWorkflowEvent = (event: Record<string, unknown>) => {
@@ -4479,6 +4763,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4479
4763
  if (persistClosed) return;
4480
4764
  const liveJob = deps.state.asyncJobs.get(workflowRunId);
4481
4765
  if (liveJob && (liveJob.status === "complete" || liveJob.status === "failed") && status.state !== "complete" && status.state !== "failed") return;
4766
+ const workflowState = status.state === "complete" ? "completed" : status.state === "failed" || status.state === "rejected" ? "failed" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "running";
4767
+ status.workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: workflowState !== "running", trace: status.workflow?.trace, steps: status.steps });
4482
4768
  status.lastUpdate = Date.now();
4483
4769
  if (!initialPersistenceComplete) {
4484
4770
  writeAtomicJson(statusPath, status);
@@ -4514,6 +4800,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4514
4800
  liveJob.turnCount = status.turnCount;
4515
4801
  liveJob.toolCount = status.toolCount;
4516
4802
  liveJob.currentStep = status.currentStep;
4803
+ liveJob.preflight = status.preflight;
4517
4804
  if (status.steps) {
4518
4805
  liveJob.steps = status.steps.map((step, index) => ({ ...step, index }));
4519
4806
  liveJob.agents = status.steps.map((step) => step.agent);
@@ -4522,6 +4809,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4522
4809
  delete liveJob.agents;
4523
4810
  }
4524
4811
  liveJob.workflow = status.workflow;
4812
+ liveJob.workflowChildren = status.workflowChildren;
4525
4813
  }
4526
4814
  };
4527
4815
  const writeWorkflowResult = (payload: Record<string, unknown>): boolean => {
@@ -4556,7 +4844,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4556
4844
  status.toolCount = toolCounts.length > 0 ? toolCounts.reduce((total, count) => total + count, 0) : undefined;
4557
4845
  status.currentStep = runningSteps.length === 1 ? steps.indexOf(runningSteps[0]!) : undefined;
4558
4846
  };
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 };
4847
+ const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, toolCallId, cwd: workflowCwd, ...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}), status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], ...(workflowPreflight ? { preflight: workflowPreflight } : {}), startedAt, updatedAt: startedAt, ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}), ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow, workflowChildren: status.workflowChildren };
4560
4848
  deps.state.asyncJobs.set(workflowRunId, workflowJob);
4561
4849
  deps.state.fleetJobs ??= new Map();
4562
4850
  deps.state.fleetJobs.set(workflowRunId, workflowJob);
@@ -4576,18 +4864,26 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4576
4864
  const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
4577
4865
  const workflowResults: SingleResult[] = [];
4578
4866
  const workflowChildRunIds = new Map<string, string>();
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;
4867
+ 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, preflight: _preflight, ...workflowChildDefaults } = workflowRequest;
4580
4868
  const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
4581
4869
  const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
4582
- const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
4870
+ const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, parentCwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId));
4583
4871
  const claimedOutputPaths = new Map<string, string>();
4584
4872
  const childOutputOverrides = new Map<string, string>();
4873
+ const childOutputClaimPaths = new Map<string, string>();
4585
4874
  const producedChildOutputPaths = new Set<string>();
4586
4875
  const workflowSteps = new Map<string, NonNullable<AsyncStatus["steps"]>[number]>();
4876
+ const runHostCommand = workflowHostCommandRunner({ workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, claimedOutputPaths, producedOutputPaths: producedChildOutputPaths });
4587
4877
  let projectedTraceLength = 0;
4588
4878
  let projectedTraceTail: NonNullable<Details["workflow"]>["trace"][number] | undefined;
4589
4879
  const updateTrace = (trace: NonNullable<Details["workflow"]>["trace"]) => {
4590
- status.workflow = { ...(status.workflow ?? { emits: [], console: [] }), trace };
4880
+ const projectedTrace = annotateWorkflowPreflightTrace(trace, workflowPreflight);
4881
+ const preflightWarnings = workflowPreflightWarnings(workflowPreflight, trace);
4882
+ status.workflow = {
4883
+ ...(status.workflow ?? { emits: [], console: [] }),
4884
+ trace: projectedTrace,
4885
+ ...(preflightWarnings.length ? { preflightWarnings } : {}),
4886
+ };
4591
4887
  const rebuild = trace.length < projectedTraceLength
4592
4888
  || (projectedTraceLength > 0 && trace[projectedTraceLength - 1] !== projectedTraceTail);
4593
4889
  if (rebuild) {
@@ -4600,8 +4896,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4600
4896
  for (let index = projectedTraceLength; index < trace.length; index += 1) {
4601
4897
  const entry = trace[index]!;
4602
4898
  if (entry.operation !== "run") continue;
4899
+ const entryLabel = entry.label?.trim() || undefined;
4900
+ const entryPhase = entry.phase?.trim() || undefined;
4901
+ const shouldProjectPhase = entryPhase !== undefined && (entryPhase !== "auto-resume" || workflowSteps.get(entry.key)?.phase === undefined);
4603
4902
  const existing = workflowSteps.get(entry.key);
4604
- if (entry.state === "reused" && existing) continue;
4903
+ if (existing) {
4904
+ if (entryLabel) existing.label = entryLabel;
4905
+ if (shouldProjectPhase) existing.phase = entryPhase;
4906
+ }
4907
+ if (entry.state === "reused" && existing) {
4908
+ continue;
4909
+ }
4605
4910
  const mapped = entry.state === "started" || entry.state === "reused"
4606
4911
  ? "running"
4607
4912
  : entry.state === "completed"
@@ -4625,13 +4930,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4625
4930
  if (entry.durationMs === undefined) delete existing.durationMs;
4626
4931
  else existing.durationMs = entry.durationMs;
4627
4932
  } else {
4933
+ // Naming uses the explicit label only — never the workflow key — so the
4934
+ // placeholder cannot diverge from the task-derived name the child
4935
+ // session actually gets (launch/progress updates overwrite this).
4936
+ const stepSessionName = deriveChildSessionName({ agent: entry.agent ?? entry.key, label: entryLabel });
4628
4937
  const step: NonNullable<AsyncStatus["steps"]>[number] = {
4629
4938
  agent: entry.agent ?? entry.key,
4630
- label: entry.key,
4939
+ ...(stepSessionName ? { sessionName: stepSessionName } : {}),
4940
+ label: entryLabel ?? entry.key,
4631
4941
  workflowKey: entry.key,
4632
4942
  parentWorkflowRunId: workflowRunId,
4633
4943
  status: mapped,
4634
4944
  startedAt: Date.now(),
4945
+ ...(entryPhase ? { phase: entryPhase } : {}),
4635
4946
  ...(entry.runId ? { runId: entry.runId } : {}),
4636
4947
  ...(entry.state === "failed" && !entry.runId ? { async: false } : {}),
4637
4948
  ...(entry.state === "detached" ? { activityState: "needs_attention" as const } : {}),
@@ -4647,7 +4958,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4647
4958
  version: 1,
4648
4959
  childId: entry.key,
4649
4960
  status: "stopped",
4650
- reason: "user",
4961
+ reason: "subagent-action",
4651
4962
  source: "async",
4652
4963
  stepIndex: status.steps?.indexOf(projectedStep),
4653
4964
  agent: projectedStep.agent,
@@ -4667,6 +4978,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4667
4978
  try {
4668
4979
  const workflow = await runWorkflowScript({
4669
4980
  script: workflowScript,
4981
+ globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
4670
4982
  timeoutMs: timeout,
4671
4983
  signal: controller.signal,
4672
4984
  registerStopChild: (stop) => {
@@ -4675,11 +4987,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4675
4987
  },
4676
4988
  ...(workflowState ? { state: workflowState } : {}),
4677
4989
  onTrace: updateTrace,
4990
+ host: runHostCommand,
4991
+ onHostStep: (hostStep) => {
4992
+ status = upsertHostStep({ status, hostStep, persist: (nextStatus) => {
4993
+ status = nextStatus;
4994
+ persist({ tolerateStatusWriteFailure: true });
4995
+ } });
4996
+ },
4678
4997
  admit: (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 });
4998
+ const outputClaims = workflowChildOutputClaims({ ctxCwd: parentCwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
4680
4999
  if (outputClaims.error) throw new Error(outputClaims.error);
4681
5000
  status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
4682
5001
  if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
5002
+ if (outputClaims.childClaims) for (const [key, claimPath] of outputClaims.childClaims) childOutputClaimPaths.set(key, claimPath);
4683
5003
  if (outputClaims.overrides) for (const [key, output] of outputClaims.overrides) childOutputOverrides.set(key, output);
4684
5004
  persist({ tolerateStatusWriteFailure: true });
4685
5005
  },
@@ -4692,7 +5012,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4692
5012
  launch: async (key, childParams, workflowSignal, admission) => {
4693
5013
  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);
4694
5014
  const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
4695
- if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state);
5015
+ if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state, { state: "partial", reason: "budget_exhausted" });
4696
5016
  const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
4697
5017
  const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
4698
5018
  recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
@@ -4702,19 +5022,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4702
5022
  ...(childPhase ? { phase: childPhase } : {}),
4703
5023
  heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
4704
5024
  });
5025
+ let preparedChildParams: SubagentParamsLike | undefined;
4705
5026
  const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
4706
5027
  const childRequest = bindMissionWorkflowChildAsyncLaunch(
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 },
5028
+ { ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: workflowRunId, workflowKey: key, ctxCwd: parentCwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), outputClaimPath: childOutputClaimPaths.get(key), options: { missionDetached: detachWorkflowChildMissions, awaitDetachedChild: true, runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt, capabilityCeiling: workflowCapabilityCeiling } }), runFanoutAdmitted: admission.admitted },
4708
5029
  missionBinding,
4709
5030
  deps.asyncByDefault,
4710
5031
  );
5032
+ preparedChildParams = childRequest;
4711
5033
  workflowLaunchObservers.set(childRequest, (launch) => {
4712
5034
  const step = status.steps?.find((candidate) => candidate.workflowKey === key);
4713
5035
  if (step) {
4714
5036
  step.agent = launch.agent;
5037
+ if (launch.sessionName) step.sessionName = launch.sessionName;
4715
5038
  step.sessionFile = launch.sessionFile;
4716
5039
  step.async = launch.async;
4717
5040
  if (launch.runId) step.runId = launch.runId;
5041
+ if (childRequest.lane) step.lane = childRequest.lane;
4718
5042
  persist({ tolerateStatusWriteFailure: true });
4719
5043
  }
4720
5044
  recordMissionWorkflowChild(missionBinding, workflowRunId, key, { status: "running", agent: launch.agent, ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}) });
@@ -4736,6 +5060,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4736
5060
  step.toolCount = progress.toolCount;
4737
5061
  step.model = progress.model;
4738
5062
  step.thinking = progress.thinking;
5063
+ if (progress.sessionName) step.sessionName = progress.sessionName;
4739
5064
  step.error = progress.error;
4740
5065
  projectWorkflowActivity();
4741
5066
  persist({ tolerateStatusWriteFailure: true });
@@ -4743,18 +5068,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4743
5068
  status: step.status,
4744
5069
  heartbeat: { status: step.status, ...(childPhase ? { phase: childPhase } : {}) },
4745
5070
  });
4746
- }, ctx, preserveActiveSession);
5071
+ }, ctx, preserveActiveSession, workflowParentModel);
4747
5072
  });
4748
5073
  workflowResults.push(...result.details.results);
4749
5074
  for (const childResult of result.details.results) {
4750
- if (childResult.savedOutputPath) producedChildOutputPaths.add(childResult.savedOutputPath);
5075
+ if (childResult.savedOutputPath) producedChildOutputPaths.add(resolveWorkflowHostOutputClaimPath(childResult.savedOutputPath));
4751
5076
  }
4752
- const child = workflowChildResult(key, result, childParams, deps.state);
5077
+ const child = workflowChildResult(key, result, preparedChildParams ?? childParams, deps.state);
4753
5078
  if (child.runId) workflowChildRunIds.set(key, child.runId);
4754
5079
  const step = status.steps?.find((candidate) => candidate.workflowKey === key);
4755
5080
  if (step) {
4756
5081
  step.async = Boolean(result.details.asyncId || result.details.asyncDir);
4757
5082
  if (child.runId) step.runId = child.runId;
5083
+ if (child.lane) step.lane = child.lane;
4758
5084
  }
4759
5085
  if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
4760
5086
  const childStatus = missionWorkflowChildStatus(result);
@@ -4773,25 +5099,28 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4773
5099
  }
4774
5100
  return child;
4775
5101
  },
4776
- status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
5102
+ status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
4777
5103
  resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
4778
5104
  steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
4779
5105
  });
5106
+ const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
5107
+ const finalPreflightTrace = annotateWorkflowPreflightTrace(workflow.trace, workflowPreflight);
4780
5108
  const returnPreview = formatWorkflowValue(workflow.value).slice(0, 1_000);
4781
5109
  const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${workflow.emits.map(formatWorkflowValue).join(", ").slice(0, 1_000)}` : "";
4782
- const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
5110
+ const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).${workflowOutputPathMappingSummary(workflow.children)}${finalPreflightWarnings.length ? ` ${finalPreflightWarnings.join(" ")}` : ""}`;
4783
5111
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
4784
5112
  const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
4785
5113
  const workflowUsage = sumResultsUsage(workflowResults);
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);
5114
+ const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children, steps: status.steps });
5115
+ status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
5116
+ const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren, undefined, validHostStepNodes(status.workflowGraph));
4788
5117
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
4789
5118
  try {
4790
5119
  workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
4791
5120
  } catch (receiptError) {
4792
5121
  appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
4793
5122
  }
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;
5123
+ if (!writeWorkflowResult({ id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, workflowChildren, results: workflow.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), ...(status.steps?.find((step) => step.workflowKey === child.key)?.sessionName ? { sessionName: status.steps?.find((step) => step.workflowKey === child.key)?.sessionName } : {}), ...workflowChildAccountingFields(child), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.outputReference ? { outputReference: child.outputReference } : {}), ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(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
5124
  persist();
4796
5125
  persistClosed = true;
4797
5126
  appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}) });
@@ -4813,28 +5142,33 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4813
5142
  delete step.activityState;
4814
5143
  }
4815
5144
  }
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 } });
5145
+ const workflowState = state === "paused" ? "paused" : state === "stopped" ? "stopped" : "failed";
5146
+ const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
5147
+ const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
5148
+ const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
5149
+ status = compactOptional<AsyncStatus>({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren });
4817
5150
  if (pauseForDetached) {
4818
5151
  const promoted = promotePausedWorkflowIfSettled(status);
4819
5152
  if (promoted) status = promoted;
4820
5153
  }
4821
- const terminalSummary = status.state === "complete"
5154
+ const terminalSummary = `${status.state === "complete"
4822
5155
  ? "Workflow completed after detached child finished."
4823
- : status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.");
5156
+ : status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")}${workflowOutputPathMappingSummary(partial.children)}${finalPreflightWarnings.length ? ` ${finalPreflightWarnings.join(" ")}` : ""}`;
4824
5157
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, terminalSummary, producedChildOutputPaths);
4825
5158
  const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
4826
5159
  const receiptState: WorkflowReceiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
4827
- const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children);
5160
+ const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
5161
+ const receipt = terminalWorkflowReceipt(workflowRunId, receiptState, partial.children, workflowChildren, terminalOutcome, validHostStepNodes(status.workflowGraph));
4828
5162
  let workflowReceipt: { path: string; receipt: WorkflowReceipt } | undefined;
4829
5163
  try {
4830
5164
  workflowReceipt = { path: writeWorkflowReceipt(asyncDir, receipt), receipt };
4831
5165
  } catch (receiptError) {
4832
5166
  appendWorkflowEvent({ type: "subagent.workflow.receipt_write_failed", error: `Failed to persist async workflow receipt: ${receiptError instanceof Error ? receiptError.message : String(receiptError)}` });
4833
5167
  }
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;
5168
+ 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, workflowChildren, ...(terminalOutcome ? { terminalOutcome } : {}), results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), ...(status.steps?.find((step) => step.workflowKey === child.key)?.sessionName ? { sessionName: status.steps?.find((step) => step.workflowKey === child.key)?.sessionName } : {}), ...workflowChildAccountingFields(child), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.outputReference ? { outputReference: child.outputReference } : {}), ...(child.terminalOutcome ? { terminalOutcome: child.terminalOutcome } : {}), ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(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;
4835
5169
  persist();
4836
5170
  persistClosed = true;
4837
- appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
5171
+ appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, ...(terminalOutcome ? { terminalOutcome } : {}), ...(status.error ? { error: status.error } : {}), ...(status.activityState ? { activityState: status.activityState } : {}) });
4838
5172
  } finally {
4839
5173
  persistClosed = true;
4840
5174
  deps.state.workflowControllers?.delete(workflowRunId);
@@ -4844,41 +5178,55 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4844
5178
  }
4845
5179
  });
4846
5180
  return attachWorkflowMission(withRunFanoutBudget({
4847
- content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
4848
- details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
5181
+ content: [{ type: "text", text: `${workflowPreflight ? `${formatWorkflowPreflight(workflowPreflight)}\n\n` : ""}${formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true)}` }],
5182
+ details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren: status.workflowChildren, chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
4849
5183
  }, workflowFanoutBudget));
4850
5184
  }
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;
5185
+ 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, preflight: _preflight, ...workflowChildDefaults } = requestParams;
4852
5186
  const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
4853
5187
  const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
4854
5188
  const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id));
4855
5189
  const claimedOutputPaths = new Map<string, string>();
4856
5190
  const childOutputOverrides = new Map<string, string>();
5191
+ const childOutputClaimPaths = new Map<string, string>();
4857
5192
  const producedChildOutputPaths = new Set<string>();
4858
5193
  const workflowResults: SingleResult[] = [];
4859
5194
  const workflowChildRunIds = new Map<string, string>();
5195
+ const runHostCommand = workflowHostCommandRunner({ workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, claimedOutputPaths, producedOutputPaths: producedChildOutputPaths });
5196
+ const workflowHostSteps = new Map<string, HostStepNodeV1>();
4860
5197
  let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
5198
+ let liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false });
4861
5199
  const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
4862
5200
  const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
4863
5201
  const sendWorkflowProgress = () => {
4864
- const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow);
5202
+ const update = workflowChatProgressUpdate(_id, chatProgress, liveWorkflow, liveWorkflowChildren, workflowPreflight);
4865
5203
  if (update) onUpdate?.(update);
4866
5204
  };
4867
5205
  try {
4868
5206
  const workflow = await runWorkflowScript({
4869
5207
  script: requestParams.workflowScript,
5208
+ ...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit, _id, key) } } : {}),
5209
+ globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
4870
5210
  timeoutMs: timeout,
4871
5211
  signal,
4872
5212
  ...(workflowState ? { state: workflowState } : {}),
5213
+ ...(!delegatedWorkflowPermit ? { host: runHostCommand } : {}),
5214
+ onHostStep: (hostStep) => {
5215
+ workflowHostSteps.set(hostStep.id, { ...hostStep, ...(hostStep.freshness ? { freshness: { ...hostStep.freshness } } : {}) });
5216
+ },
4873
5217
  onTrace: (trace) => {
4874
- liveWorkflow = { ...liveWorkflow, trace };
5218
+ const projectedTrace = annotateWorkflowPreflightTrace(trace, workflowPreflight);
5219
+ const preflightWarnings = workflowPreflightWarnings(workflowPreflight, trace);
5220
+ liveWorkflow = { ...liveWorkflow, trace: projectedTrace, ...(preflightWarnings.length ? { preflightWarnings } : {}) };
5221
+ liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "running", inventoryComplete: false, trace });
4875
5222
  sendWorkflowProgress();
4876
5223
  },
4877
5224
  admit: (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 });
5225
+ const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
4879
5226
  if (outputClaims.error) throw new Error(outputClaims.error);
4880
5227
  claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
4881
5228
  if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
5229
+ if (outputClaims.childClaims) for (const [key, claimPath] of outputClaims.childClaims) childOutputClaimPaths.set(key, claimPath);
4882
5230
  if (outputClaims.overrides) for (const [key, output] of outputClaims.overrides) childOutputOverrides.set(key, output);
4883
5231
  },
4884
5232
  onEmit: (emits) => {
@@ -4886,9 +5234,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4886
5234
  sendWorkflowProgress();
4887
5235
  },
4888
5236
  launch: async (key, childParams, workflowSignal, admission) => {
5237
+ if (delegatedWorkflowPermit && admission.batch) throw new Error("Workflow child permit does not support runs.all.");
5238
+ if (delegatedWorkflowPermit && childParams.resume !== undefined) throw new Error("Workflow child permit does not support retained resume.");
5239
+ if (delegatedWorkflowPermit && childParams.async === true) throw new Error("Workflow child permit supports one foreground child only.");
4889
5240
  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);
4890
5241
  const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
4891
- if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state);
5242
+ if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state, { state: "partial", reason: "budget_exhausted" });
4892
5243
  const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
4893
5244
  const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
4894
5245
  recordMissionWorkflowChild(missionBinding, _id, key, {
@@ -4898,12 +5249,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4898
5249
  ...(childPhase ? { phase: childPhase } : {}),
4899
5250
  heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
4900
5251
  });
5252
+ let preparedChildParams: SubagentParamsLike | undefined;
4901
5253
  const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
4902
5254
  const childRequest = bindMissionWorkflowChildAsyncLaunch(
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 },
5255
+ { ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: _id, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), outputClaimPath: childOutputClaimPaths.get(key), options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt, capabilityCeiling: workflowCapabilityCeiling } }), runFanoutAdmitted: admission.admitted },
4904
5256
  missionBinding,
4905
5257
  deps.asyncByDefault,
4906
5258
  );
5259
+ preparedChildParams = childRequest;
5260
+ if (delegatedWorkflowPermit) {
5261
+ if (childRequest.async !== false) throw new Error("Workflow child permit supports one foreground child only.");
5262
+ const childCwd = resolveRequestedCwd(workflowCwd, childRequest.cwd);
5263
+ const childAgent = typeof childRequest.agent === "string"
5264
+ ? resolveAgentName(childRequest.agent, discoverWorkflowAgents(childCwd, resolveExecutionAgentScope(childRequest.agentScope)).agents).agent
5265
+ : undefined;
5266
+ if (childAgent?.runner?.type === "external-cli" || childAgent?.runner?.type === "external-job") throw new Error("Workflow child permit supports native Pi children only.");
5267
+ workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId: _id, childKey: key } });
5268
+ }
4907
5269
  workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding, _id, key, {
4908
5270
  status: "running",
4909
5271
  agent: launch.agent,
@@ -4917,14 +5279,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4917
5279
  status: progressStatus,
4918
5280
  heartbeat: { status: progressStatus, ...(childPhase ? { phase: childPhase } : {}) },
4919
5281
  });
4920
- }, ctx, preserveActiveSession);
5282
+ }, ctx, preserveActiveSession, workflowParentModel);
4921
5283
  });
4922
5284
  workflowResults.push(...result.details.results);
4923
5285
  for (const childResult of result.details.results) {
4924
- if (childResult.savedOutputPath) producedChildOutputPaths.add(childResult.savedOutputPath);
5286
+ if (childResult.savedOutputPath) producedChildOutputPaths.add(resolveWorkflowHostOutputClaimPath(childResult.savedOutputPath));
4925
5287
  }
4926
5288
  if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
4927
- const child = workflowChildResult(key, result, childParams, deps.state);
5289
+ const child = workflowChildResult(key, result, preparedChildParams ?? childParams, deps.state);
4928
5290
  if (child.runId) workflowChildRunIds.set(key, child.runId);
4929
5291
  const childStatus = missionWorkflowChildStatus(result);
4930
5292
  recordMissionWorkflowChild(missionBinding, _id, key, {
@@ -4938,43 +5300,64 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4938
5300
  });
4939
5301
  return child;
4940
5302
  },
4941
- status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
5303
+ status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
4942
5304
  resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
4943
5305
  steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: _id, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
4944
5306
  });
4945
- const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children);
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}` : ""}`);
4947
- const sections = ["Workflow completed.", `Return:\n${formatWorkflowValue(workflow.value)}`];
5307
+ const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
5308
+ const finalPreflightTrace = annotateWorkflowPreflightTrace(workflow.trace, workflowPreflight);
5309
+ const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children });
5310
+ const receipt = terminalWorkflowReceipt(_id, "complete", workflow.children, workflowChildren, undefined, [...workflowHostSteps.values()]);
5311
+ const traceLines = finalPreflightTrace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.durationMs !== undefined ? ` in ${entry.durationMs}ms` : ""}${entry.warning ? ` — ${entry.warning}` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
5312
+ const sections = [
5313
+ ...(workflowPreflight ? [formatWorkflowPreflight(workflowPreflight)] : []),
5314
+ "Workflow completed.",
5315
+ `Return:\n${formatWorkflowValue(workflow.value)}`,
5316
+ ];
4948
5317
  if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
4949
5318
  if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
4950
5319
  if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
5320
+ if (finalPreflightWarnings.length > 0) sections.push(formatWorkflowPreflightWarnings(finalPreflightWarnings));
5321
+ const outputMappings = workflowOutputPathMappingSummary(workflow.children).trim();
5322
+ if (outputMappings) sections.push(outputMappings);
4951
5323
  const workflowText = sections.join("\n\n");
4952
5324
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
4953
5325
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
4954
5326
  return attachWorkflowMission(withRunFanoutBudget({
4955
5327
  content: [{ type: "text", text: displayText }],
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 }),
5328
+ details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
4957
5329
  }, workflowFanoutBudget));
4958
5330
  } catch (error) {
4959
5331
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
4960
- const text = error instanceof Error ? error.message : String(error);
4961
- const traceLines = partial.trace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
4962
- const sections = [`Workflow failed: ${text}`];
5332
+ const text = workflowFailureMessage(error, _id, partial.children);
5333
+ const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
5334
+ const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
5335
+ const traceLines = finalPreflightTrace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.warning ? ` — ${entry.warning}` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
5336
+ const sections = [
5337
+ ...(workflowPreflight ? [formatWorkflowPreflight(workflowPreflight)] : []),
5338
+ `Workflow failed: ${text}`,
5339
+ ];
4963
5340
  if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
4964
5341
  if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
4965
5342
  if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
5343
+ if (finalPreflightWarnings.length > 0) sections.push(formatWorkflowPreflightWarnings(finalPreflightWarnings));
5344
+ const outputMappings = workflowOutputPathMappingSummary(partial.children).trim();
5345
+ if (outputMappings) sections.push(outputMappings);
4966
5346
  const workflowText = sections.join("\n\n");
4967
5347
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
4968
5348
  const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
4969
- const receipt = terminalWorkflowReceipt(_id, "failed", partial.children);
5349
+ const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: _id, workflowState: "failed", inventoryComplete: true, trace: partial.trace, children: partial.children });
5350
+ const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
5351
+ const receipt = terminalWorkflowReceipt(_id, "failed", partial.children, workflowChildren, terminalOutcome, [...workflowHostSteps.values()]);
4970
5352
  return attachWorkflowMission(withRunFanoutBudget({
4971
5353
  content: [{ type: "text", text: displayText }],
4972
5354
  isError: true,
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 }),
5355
+ details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
4974
5356
  }, workflowFanoutBudget));
4975
5357
  }
4976
5358
  }
4977
5359
  const directParams = requestParams;
5360
+ const requestedCwd = params.cwd;
4978
5361
  const requestCwd = resolveRequestedCwd(ctx.cwd, directParams.cwd);
4979
5362
  const paramsWithResolvedCwd = directParams.cwd === undefined ? directParams : { ...directParams, cwd: requestCwd };
4980
5363
  const action = paramsWithResolvedCwd.action;
@@ -4984,14 +5367,45 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4984
5367
  try {
4985
5368
  requestSessionId = resolveCurrentSessionId(ctx.sessionManager);
4986
5369
  requestPiSessionId = ctx.sessionManager.getSessionId() ?? undefined;
4987
- requestParentModel = preserveActiveSession
4988
- ? normalizeParentModel(ctx.model)
4989
- : rememberParentModel(deps.state, requestSessionId, ctx.model);
5370
+ requestParentModel = parentModelOverride !== undefined
5371
+ ? parentModelOverride ?? undefined
5372
+ : preserveActiveSession
5373
+ ? normalizeParentModel(ctx.model)
5374
+ : rememberParentModel(deps.state, requestSessionId, ctx.model);
4990
5375
  } catch (error) {
4991
5376
  if (action?.toLowerCase() !== "doctor" && action?.toLowerCase() !== "guide") throw error;
4992
5377
  requestParentModel = normalizeParentModel(ctx.model);
4993
5378
  }
4994
5379
  if (action) {
5380
+ if (action === "worktree.cleanup") {
5381
+ if (deps.allowMutatingManagementActions === false) {
5382
+ return { content: [{ type: "text", text: "Action 'worktree.cleanup' is not available from child-safe subagent fanout mode." }], isError: true, details: { mode: "management", results: [] } };
5383
+ }
5384
+ if (paramsWithResolvedCwd.mode !== "plan") {
5385
+ return { content: [{ type: "text", text: "worktree.cleanup currently supports mode='plan' only; apply/removal is not available yet." }], isError: true, details: { mode: "management", results: [] } };
5386
+ }
5387
+ if (paramsWithResolvedCwd.planId !== undefined) {
5388
+ return { content: [{ type: "text", text: "worktree.cleanup plan mode does not accept planId; apply is not available yet." }], isError: true, details: { mode: "management", results: [] } };
5389
+ }
5390
+ try {
5391
+ const created = createWorktreeCleanupPlan({
5392
+ repo: paramsWithResolvedCwd.repo?.trim()
5393
+ ? path.isAbsolute(paramsWithResolvedCwd.repo) ? paramsWithResolvedCwd.repo : path.resolve(requestCwd, paramsWithResolvedCwd.repo)
5394
+ : requestCwd,
5395
+ ...(paramsWithResolvedCwd.handoffPath ? { handoffPath: path.isAbsolute(paramsWithResolvedCwd.handoffPath) ? paramsWithResolvedCwd.handoffPath : path.resolve(requestCwd, paramsWithResolvedCwd.handoffPath) } : {}),
5396
+ ...(deps.config.worktreeBaseDir ? { worktreeBaseDir: deps.config.worktreeBaseDir } : {}),
5397
+ foregroundRunOwnership: (runId: string) => {
5398
+ if (deps.state.foregroundControls.has(runId)) return "active" as const;
5399
+ const remembered = deps.state.foregroundRuns?.get(runId);
5400
+ if (!remembered || remembered.children.length === 0 || remembered.children.some((child) => child.status === "detached")) return "unknown" as const;
5401
+ return "terminal" as const;
5402
+ },
5403
+ });
5404
+ return { content: [{ type: "text", text: formatWorktreeCleanupPlan(created) }], details: { mode: "management", results: [] } };
5405
+ } catch (error) {
5406
+ return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "management", results: [] } };
5407
+ }
5408
+ }
4995
5409
  if (action === "worktree.discard") {
4996
5410
  if (deps.allowMutatingManagementActions === false) {
4997
5411
  return { content: [{ type: "text", text: "Action 'worktree.discard' is not available from child-safe subagent fanout mode." }], isError: true, details: { mode: "management", results: [] } };
@@ -5019,6 +5433,30 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5019
5433
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "management", results: [] } };
5020
5434
  }
5021
5435
  }
5436
+ if (action === "lane.status" || action === "lane.recordMerge" || action === "lane.recordSupersession") {
5437
+ if (action !== "lane.status" && deps.allowMutatingManagementActions === false) {
5438
+ return { content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }], isError: true, details: { mode: "management", results: [] } };
5439
+ }
5440
+ const laneId = paramsWithResolvedCwd.laneId?.trim();
5441
+ if (!laneId) return { content: [{ type: "text", text: `${action} requires laneId.` }], isError: true, details: { mode: "management", results: [] } };
5442
+ const handoffPath = paramsWithResolvedCwd.handoffPath?.trim();
5443
+ if (!handoffPath) return { content: [{ type: "text", text: `${action} requires handoffPath for the existing parallel handoff manifest.` }], isError: true, details: { mode: "management", results: [] } };
5444
+ const manifestPath = path.isAbsolute(handoffPath) ? handoffPath : path.resolve(requestCwd, handoffPath);
5445
+ try {
5446
+ if (action === "lane.status") {
5447
+ let manifest;
5448
+ try { manifest = readParallelHandoffManifest(manifestPath); } catch { manifest = undefined; }
5449
+ if (manifest && manifest.runId !== laneId) throw new Error(`Lane '${laneId}' does not match manifest run '${manifest.runId}'.`);
5450
+ return { content: [{ type: "text", text: formatStoredParallelHandoffCleanup(manifestPath, manifest) }], details: { mode: "management", results: [] } };
5451
+ }
5452
+ const recorded = action === "lane.recordMerge"
5453
+ ? recordParallelHandoffMerge({ manifestPath, laneId, merge: paramsWithResolvedCwd.merge })
5454
+ : recordParallelHandoffSupersession({ manifestPath, laneId, supersession: paramsWithResolvedCwd.supersession });
5455
+ return { content: [{ type: "text", text: recorded.text }], details: { mode: "management", results: [], parallelHandoff: recorded.reference } };
5456
+ } catch (error) {
5457
+ return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "management", results: [] } };
5458
+ }
5459
+ }
5022
5460
  if ((HERDR_PROJECT_PANE_ACTIONS as readonly string[]).includes(action)) {
5023
5461
  if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
5024
5462
  return { content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }], isError: true, details: { mode: "management", results: [] } };
@@ -5211,7 +5649,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5211
5649
  }
5212
5650
  const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, currentSessionId);
5213
5651
  const activeAsyncCapacity = currentSessionId
5214
- ? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) })
5652
+ ? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })
5215
5653
  : { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession) ?? 0 };
5216
5654
  deps.state.activeAsyncCapacity = activeAsyncCapacity;
5217
5655
  return {
@@ -5256,10 +5694,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5256
5694
  if (paramsWithResolvedCwd.view) {
5257
5695
  return withBudget({ content: [{ type: "text", text: "action='debug.run' does not support status views." }], isError: true, details: { mode: "management", results: [] } });
5258
5696
  }
5259
- return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
5697
+ return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
5260
5698
  }
5261
5699
  if (paramsWithResolvedCwd.view === "fleet") {
5262
- return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
5700
+ return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
5263
5701
  }
5264
5702
  if (targetRunId) {
5265
5703
  try {
@@ -5290,12 +5728,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5290
5728
  });
5291
5729
  }
5292
5730
  }
5293
- return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
5731
+ return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
5294
5732
  }
5295
5733
  if (action === "resume") {
5296
5734
  return resumeAsyncRun(omitUndefinedProperties({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel, signal }));
5297
5735
  }
5298
5736
  if (action === "steer") {
5737
+ if (paramsWithResolvedCwd.mode !== undefined && resolveSteerDeliveryMode(paramsWithResolvedCwd.mode) === undefined) {
5738
+ return { content: [{ type: "text", text: "action='steer' mode must be 'steer', 'follow_up', or 'auto'." }], isError: true, details: { mode: "management", results: [] } };
5739
+ }
5299
5740
  deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
5300
5741
  const message = (paramsWithResolvedCwd.message ?? paramsWithResolvedCwd.task ?? "").trim();
5301
5742
  if (!message) return { content: [{ type: "text", text: "action='steer' requires message." }], isError: true, details: { mode: "management", results: [] } };
@@ -5308,7 +5749,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5308
5749
  if (directoryStatus?.mode === "workflow") {
5309
5750
  const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: directoryStatus.runId || runId, asyncDirRoot: DIRS.async });
5310
5751
  if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
5311
- return steerWorkflowForegroundTarget({ target: route.target, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal });
5752
+ return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal });
5312
5753
  }
5313
5754
  if (location.asyncDir) {
5314
5755
  const unsupported = externalRunnerControlError(location.asyncDir, "steer");
@@ -5318,7 +5759,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5318
5759
  state: deps.state,
5319
5760
  runId,
5320
5761
  message,
5321
- mode: paramsWithResolvedCwd.mode,
5762
+ mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode),
5322
5763
  index: paramsWithResolvedCwd.index,
5323
5764
  kill: deps.kill,
5324
5765
  location,
@@ -5344,18 +5785,18 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5344
5785
  const text = error instanceof Error ? error.message : String(error);
5345
5786
  return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
5346
5787
  }
5347
- if (resolved?.kind === "nested") return steerNestedRun(omitUndefinedProperties({ target: resolved, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal }));
5788
+ if (resolved?.kind === "nested") return steerNestedRun(omitUndefinedProperties({ target: resolved, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal }));
5348
5789
  if (resolved?.kind === "foreground") {
5349
5790
  const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, childRunId: resolved.id, asyncDirRoot: DIRS.async });
5350
5791
  if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
5351
- return steerWorkflowForegroundTarget({ target: route.target, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal });
5792
+ return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal });
5352
5793
  }
5353
5794
  if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
5354
5795
  const resolvedStatus = resolved.location.asyncDir ? readStatus(resolved.location.asyncDir) : null;
5355
5796
  if (resolvedStatus?.mode === "workflow") {
5356
5797
  const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: resolvedStatus.runId || resolved.id, asyncDirRoot: DIRS.async });
5357
5798
  if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
5358
- return steerWorkflowForegroundTarget({ target: route.target, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal });
5799
+ return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index, signal });
5359
5800
  }
5360
5801
  if (resolved.location.asyncDir) {
5361
5802
  const unsupported = externalRunnerControlError(resolved.location.asyncDir, "steer");
@@ -5365,7 +5806,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5365
5806
  state: deps.state,
5366
5807
  runId: resolved.id,
5367
5808
  message,
5368
- mode: paramsWithResolvedCwd.mode,
5809
+ mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode),
5369
5810
  index: paramsWithResolvedCwd.index,
5370
5811
  kill: deps.kill,
5371
5812
  location: resolved.location,
@@ -5446,7 +5887,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5446
5887
  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
5888
  const stopChild = deps.state.workflowChildStops?.get(targetRunId!);
5448
5889
  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: [] } };
5890
+ if (!stopChild(resolution.child.id, `Workflow child '${resolution.child.id}' stopped.`)) return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in workflow ${workflowRunId} is not available to stop.` }], isError: true, details: { mode: "management", results: [] } };
5450
5891
  try {
5451
5892
  fs.appendFileSync(path.join(asyncJob.asyncDir, "events.jsonl"), `${JSON.stringify({
5452
5893
  type: "subagent.child-status",
@@ -5455,7 +5896,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5455
5896
  childId: resolution.child.id,
5456
5897
  status: "stopping",
5457
5898
  ts: Date.now(),
5458
- reason: "user",
5899
+ reason: "subagent-action",
5459
5900
  source: "async",
5460
5901
  stepIndex: resolution.child.index,
5461
5902
  agent: resolution.child.step.agent,
@@ -5469,17 +5910,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5469
5910
  }
5470
5911
  return { content: [{ type: "text", text: `Stop requested for child ${resolution.child.id} in async workflow ${workflowRunId}.` }], details: { mode: "management", results: [] } };
5471
5912
  }
5472
- workflowController.abort(new Error("Workflow stopped by user."));
5913
+ workflowController.abort(new Error("Workflow stopped."));
5473
5914
  return { content: [{ type: "text", text: `Stop requested for async workflow ${targetRunId}.` }], details: { mode: "management", results: [] } };
5474
5915
  }
5475
5916
  let resolved: ResolvedSubagentRunId | undefined;
5476
5917
  if (paramsWithResolvedCwd.dir) {
5477
5918
  try {
5478
5919
  const location = resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results);
5479
- const existingStatus = readStatus(location.asyncDir ?? "");
5480
- if (existingStatus?.mode === "workflow" && existingStatus.state === "running") {
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: [] } };
5482
- }
5483
5920
  const stopResult = stopAsyncRun(deps.state, location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir), deps.kill, location, paramsWithResolvedCwd.childId);
5484
5921
  return stopResult ?? { content: [{ type: "text", text: `No running or queued async run was found for '${targetRunId ?? paramsWithResolvedCwd.dir}'.` }], isError: true, details: { mode: "management", results: [] } };
5485
5922
  } catch (error) {
@@ -5496,12 +5933,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5496
5933
  }
5497
5934
  if (resolved?.kind === "nested") return { content: [{ type: "text", text: "action='stop' supports current-session top-level async runs only." }], isError: true, details: { mode: "management", results: [] } };
5498
5935
  if (resolved?.kind === "foreground") return { content: [{ type: "text", text: "action='stop' supports async runs only. Use action='interrupt' for foreground runs." }], isError: true, details: { mode: "management", results: [] } };
5499
- if (resolved?.kind === "async") {
5500
- const existingStatus = readStatus(resolved.location.asyncDir ?? "");
5501
- if (existingStatus?.mode === "workflow" && existingStatus.state === "running") {
5502
- return { content: [{ type: "text", text: `Workflow ${resolved.id} is not controlled by this extension runtime; reload recovery cannot stop it safely.` }], isError: true, details: { mode: "management", results: [] } };
5503
- }
5504
- }
5505
5936
  const stopResult = stopAsyncRun(
5506
5937
  deps.state,
5507
5938
  resolved?.kind === "async" ? resolved.id : targetRunId,
@@ -5613,16 +6044,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5613
6044
 
5614
6045
  const scope: AgentScope = resolveExecutionAgentScope(effectiveParams.agentScope);
5615
6046
  const effectiveCwd = effectiveParams.cwd ?? ctx.cwd;
6047
+ const cwdError = preflightLaunchCwd(requestedCwd ?? effectiveCwd, effectiveCwd);
6048
+ if (cwdError) return buildRequestedModeError(effectiveParams, cwdError);
5616
6049
  const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
5617
- const discovered = deps.discoverAgents(effectiveCwd, scope);
6050
+ const discovered = deps.discoverAgents(effectiveCwd, scope, requestParentModel?.provider);
5618
6051
  const discoveredAgents = discovered.agents;
5619
- const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents, discovered.agentDiagnostics);
6052
+ const unknownAgentDiagnosticContext = diagnosticContextFromDiscovery(discovered, effectiveCwd, scope);
6053
+ const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents, discovered.agentDiagnostics, unknownAgentDiagnosticContext);
5620
6054
  if (canonicalParams.error) return buildRequestedModeError(effectiveParams, canonicalParams.error);
5621
6055
  effectiveParams = canonicalParams.params!;
5622
6056
  const modelScope = discovered.modelScope;
5623
6057
  effectiveParams = applySingleAgentLaunchDefaults(effectiveParams, discoveredAgents);
5624
- const turnBudget = resolveTurnBudgetConfig(effectiveParams.turnBudget ?? deps.config.turnBudget);
5625
- if (turnBudget.error) return buildRequestedModeError(effectiveParams, turnBudget.error);
5626
6058
  // An agent-level defaultContext is a preference, unlike an explicit request.
5627
6059
  // Prefer fork only when the parent session is persisted and has a current leaf;
5628
6060
  // otherwise use fresh immediately instead of launching a guaranteed-to-fail fork.
@@ -5665,6 +6097,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5665
6097
  hasTasks,
5666
6098
  hasSingle,
5667
6099
  allowClarifyTaskPrompt,
6100
+ unknownAgentDiagnosticContext,
5668
6101
  );
5669
6102
  if (validationError) return validationError;
5670
6103
 
@@ -5679,6 +6112,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5679
6112
  if (spawnPreflight.error) return spawnBudgetErrorResult(spawnPreflight.error, foregroundMode);
5680
6113
 
5681
6114
  let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
6115
+ let prepareForkSessionForIndex: (idx?: number) => Promise<void> = async () => {};
5682
6116
  let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
5683
6117
  let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string, _modelOverrideFromParent?: boolean): void => {};
5684
6118
  const forkThinkingRequirements = new Map<number, boolean>();
@@ -5700,6 +6134,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5700
6134
  parentModel,
5701
6135
  forkAvailableModels,
5702
6136
  agentConfig?.modelProvider ?? parentModel?.provider,
6137
+ { source: "inherited" },
5703
6138
  );
5704
6139
  const candidates = buildModelCandidates(
5705
6140
  primaryModel,
@@ -5714,9 +6149,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5714
6149
  || candidates.some((candidate) => forkedChildRequiresThinkingOff(candidate, forkAvailableModels, parentModel?.provider)),
5715
6150
  );
5716
6151
  };
6152
+ const pruneSession = contextPolicy.usesFork && deps.config.forkContext?.mode === "pruned"
6153
+ ? await createPrunedForkSessionWriter(ctx, deps.config.forkContext, signal)
6154
+ : undefined;
5717
6155
  const forkContextResolver = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined, {
5718
6156
  forceThinkingOffForIndex: (index) => forkThinkingRequirements.get(index) ?? true,
6157
+ ...(pruneSession ? { pruneSession } : {}),
5719
6158
  });
6159
+ prepareForkSessionForIndex = forkContextResolver.prepareSessionForIndex;
5720
6160
  forkSessionFileForIndex = forkContextResolver.sessionFileForIndex;
5721
6161
  forkThinkingOverrideForIndex = forkContextResolver.thinkingOverrideForIndex;
5722
6162
  } catch (error) {
@@ -5760,8 +6200,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5760
6200
  runId: asyncRunId,
5761
6201
  kind: "runner",
5762
6202
  asyncDir: path.join(DIRS.async, asyncRunId),
5763
- }, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
5764
- deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(requestSessionId, activeLimit, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
6203
+ }, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
6204
+ deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(requestSessionId, activeLimit, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) });
5765
6205
  } catch (error) {
5766
6206
  if (error instanceof ActiveAsyncCapacityError) {
5767
6207
  deps.state.activeAsyncCapacity = error.snapshot;
@@ -5824,6 +6264,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5824
6264
  prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
5825
6265
  return forkSessionFileForIndex(idx);
5826
6266
  };
6267
+ const prepareForkSessionForTask: PrepareForkSessionForTask = async (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
6268
+ if (!shouldForkAgent(contextPolicy, agentName)) return;
6269
+ prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
6270
+ await prepareForkSessionForIndex(idx);
6271
+ };
5827
6272
  const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride, modelOverrideFromParent) => {
5828
6273
  if (!shouldForkAgent(contextPolicy, agentName)) return delegatedThinkingOverride;
5829
6274
  prepareForkThinking(agentName, idx, modelOverride, modelOverrideFromParent);
@@ -5836,8 +6281,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5836
6281
  const childSessionFileForIndex = (idx?: number) =>
5837
6282
  path.join(sessionDirForIndex(idx), "session.jsonl");
5838
6283
  try {
5839
- if (!(effectiveParams.clarify === true && ctx.hasUI)) {
5840
- preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems);
6284
+ if (!(effectiveParams.clarify === true && ctx.hasUI) || deps.config.forkContext?.mode === "pruned") {
6285
+ await preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, prepareForkSessionForTask, deps.config.chain?.dynamicFanout?.maxItems);
5841
6286
  }
5842
6287
  } catch (error) {
5843
6288
  activeAsyncCapacity?.rollback();
@@ -5906,10 +6351,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5906
6351
  const execData: ExecutionContextData = omitUndefinedProperties({
5907
6352
  params: effectiveParams,
5908
6353
  effectiveCwd,
6354
+ requestedCwd,
5909
6355
  ctx,
5910
6356
  signal,
5911
6357
  onUpdate: onUpdateWithContext,
5912
6358
  agents,
6359
+ unknownAgentDiagnosticContext,
5913
6360
  recoveryAgents: discoveredAgents,
5914
6361
  runId,
5915
6362
  shareEnabled,
@@ -5927,7 +6374,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5927
6374
  intercomBridge,
5928
6375
  nestedRoute,
5929
6376
  timeoutMs: foregroundTimeout.timeoutMs,
5930
- turnBudget: turnBudget.turnBudget,
5931
6377
  toolBudget: runToolBudget.toolBudget,
5932
6378
  usageBudget: usageBudget.budget,
5933
6379
  allowZeroToolBudget,
@@ -5942,6 +6388,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5942
6388
  runFanoutBudget,
5943
6389
  topLevelAsyncCapacityEligible,
5944
6390
  activeAsyncCapacity,
6391
+ workflowChildPermitLaunch,
5945
6392
  });
5946
6393
 
5947
6394
  const foregroundDescription = selectedAgentNames.length === 1
@@ -6071,6 +6518,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6071
6518
  mode: foregroundMode,
6072
6519
  state,
6073
6520
  agent: agentsForSummary[0],
6521
+ ...(details?.results.length === 1 && details.results[0]?.sessionName ? { sessionName: details.results[0].sessionName } : {}),
6074
6522
  agents: agentsForSummary,
6075
6523
  ...(agentsForSummary.length === 1 && (type === "subagent.nested.started" ? startedLaunches[0]?.model : details?.results[0]?.model) ? { model: type === "subagent.nested.started" ? startedLaunches[0]?.model : details?.results[0]?.model } : {}),
6076
6524
  ...(agentsForSummary.length === 1 && (type === "subagent.nested.started" ? startedLaunches[0]?.thinking : details?.results[0]?.thinking) ? { thinking: type === "subagent.nested.started" ? startedLaunches[0]?.thinking : details?.results[0]?.thinking } : {}),
@@ -6089,6 +6537,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6089
6537
  : details?.results.length
6090
6538
  ? { steps: details.results.map((child) => ({
6091
6539
  agent: child.agent,
6540
+ ...(child.sessionName ? { sessionName: child.sessionName } : {}),
6092
6541
  status: child.interrupted || child.detached ? "paused" as const : child.exitCode === 0 ? "complete" as const : "failed" as const,
6093
6542
  ...(child.model ? { model: child.model } : {}),
6094
6543
  ...(child.thinking ? { thinking: child.thinking } : {}),
@@ -6112,10 +6561,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6112
6561
  }
6113
6562
  if (workflowLaunchObserver) {
6114
6563
  const singleTask = hasTasks && effectiveParams.tasks?.length === 1 ? effectiveParams.tasks[0] : undefined;
6564
+ const singleSessionName = hasSingle
6565
+ ? deriveChildSessionName({ agent: effectiveParams.agent!, task: effectiveParams.task })
6566
+ : singleTask
6567
+ ? deriveChildSessionName({ agent: singleTask.agent, task: singleTask.task })
6568
+ : undefined;
6115
6569
  const launch = hasSingle
6116
- ? { agent: effectiveParams.agent!, sessionFile: childSessionFileForTask(effectiveParams.agent!, 0, effectiveParams.model), async: effectiveAsync, ...(effectiveAsync ? { runId: asyncRunId } : {}) }
6570
+ ? { agent: effectiveParams.agent!, ...(singleSessionName ? { sessionName: singleSessionName } : {}), sessionFile: childSessionFileForTask(effectiveParams.agent!, 0, effectiveParams.model), async: effectiveAsync, runId: effectiveAsync ? asyncRunId : runId }
6117
6571
  : singleTask
6118
- ? { agent: singleTask.agent, sessionFile: childSessionFileForTask(singleTask.agent, 0, singleTask.model), async: effectiveAsync, ...(effectiveAsync ? { runId: asyncRunId } : {}) }
6572
+ ? { agent: singleTask.agent, ...(singleSessionName ? { sessionName: singleSessionName } : {}), sessionFile: childSessionFileForTask(singleTask.agent, 0, singleTask.model), async: effectiveAsync, runId: effectiveAsync ? asyncRunId : runId }
6119
6573
  : undefined;
6120
6574
  if (launch) {
6121
6575
  workflowLaunchObservers.delete(params);
@@ -6167,15 +6621,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6167
6621
  ): Promise<AgentToolResult<Details>> => {
6168
6622
  const normalizedAction = typeof params.action === "string" ? params.action.trim() : params.action;
6169
6623
  const requestParams = normalizedAction ? { ...params, action: normalizedAction } : params;
6170
- if (normalizedAction) return execute(id, requestParams, signal, onUpdate, ctx);
6624
+ if (normalizedAction) return execute(id, requestParams, signal, onUpdate, ctx).then(withAggregatedToolUsage);
6171
6625
  const { depth } = checkSubagentDepth(deps.config.maxSubagentDepth);
6172
6626
  const dispatchParams = applyForceTopLevelAsyncOverride(requestParams, depth, deps.config.forceTopLevelAsync === true);
6173
6627
  const runsForeground = dispatchParams.clarify === true || (dispatchParams.async ?? deps.asyncByDefault) !== true;
6174
- if (!runsForeground) return execute(id, requestParams, signal, onUpdate, ctx);
6628
+ if (!runsForeground) return execute(id, requestParams, signal, onUpdate, ctx).then(withAggregatedToolUsage);
6175
6629
  if (deps.state.subagentInProgress === true) return duplicateSubagentCallResult(requestParams);
6176
6630
  deps.state.subagentInProgress = true;
6177
6631
  try {
6178
- return await execute(id, requestParams, signal, onUpdate, ctx);
6632
+ return withAggregatedToolUsage(await execute(id, requestParams, signal, onUpdate, ctx));
6179
6633
  } finally {
6180
6634
  deps.state.subagentInProgress = false;
6181
6635
  }
@@ -6188,11 +6642,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6188
6642
  onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
6189
6643
  ctx: ExtensionContext,
6190
6644
  ): Promise<AgentToolResult<Details>> => {
6191
- const normalized = normalizePublicSubagentExecution(params, { asyncByDefault: deps.asyncByDefault });
6645
+ const normalized = normalizePublicSubagentExecution(params);
6192
6646
  if (!normalized.ok) {
6193
6647
  return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
6194
6648
  }
6195
- return executeWithSingleDispatchGuard(id, normalized.params, signal, onUpdate, ctx);
6649
+ const loaded = loadWorkflowScriptPath(normalized.params, ctx.cwd);
6650
+ if (loaded.error) {
6651
+ return Promise.resolve({ content: [{ type: "text", text: loaded.error }], isError: true, details: { mode: normalized.params.action ? "management" : "workflow", results: [] } });
6652
+ }
6653
+ return executeWithSingleDispatchGuard(id, loaded.params!, signal, onUpdate, ctx);
6196
6654
  };
6197
6655
 
6198
6656
  const executeDelegated = async (
@@ -6206,15 +6664,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
6206
6664
  const privateParams = delegatedParams as SubagentParamsLike & {
6207
6665
  delegatedThinkingOverride?: AgentConfig["thinking"];
6208
6666
  delegatedAllowZeroToolBudget?: true;
6667
+ delegatedWorkflowPermit?: WorkflowChildPermit;
6209
6668
  };
6210
6669
  const thinkingOverride = privateParams.delegatedThinkingOverride;
6211
6670
  const allowZeroToolBudget = privateParams.delegatedAllowZeroToolBudget === true;
6671
+ const workflowPermit = privateParams.delegatedWorkflowPermit;
6212
6672
  delete privateParams.delegatedThinkingOverride;
6213
6673
  delete privateParams.delegatedAllowZeroToolBudget;
6674
+ delete privateParams.delegatedWorkflowPermit;
6214
6675
  if (thinkingOverride !== undefined) delegatedThinkingOverrides.set(delegatedParams, thinkingOverride);
6215
6676
  if (allowZeroToolBudget) delegatedZeroToolBudgets.add(delegatedParams);
6677
+ if (workflowPermit) workflowPermitContexts.set(delegatedParams, { root: workflowPermit });
6216
6678
  delegatedExecutions.add(delegatedParams);
6217
- return execute(id, delegatedParams, signal, onUpdate, ctx);
6679
+ return withAggregatedToolUsage(await execute(id, delegatedParams, signal, onUpdate, ctx));
6218
6680
  };
6219
6681
 
6220
6682
  const executeScheduled = (