@selesai/code 0.5.28 → 0.6.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 (341) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +1 -1
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +9 -1
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/config-selector.d.ts.map +1 -1
  7. package/dist/cli/config-selector.js +1 -1
  8. package/dist/cli/config-selector.js.map +1 -1
  9. package/dist/cli/credential-print.d.ts +23 -0
  10. package/dist/cli/credential-print.d.ts.map +1 -0
  11. package/dist/cli/credential-print.js +117 -0
  12. package/dist/cli/credential-print.js.map +1 -0
  13. package/dist/cli/startup-ui.d.ts.map +1 -1
  14. package/dist/cli/startup-ui.js +1 -1
  15. package/dist/cli/startup-ui.js.map +1 -1
  16. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  17. package/dist/core/agent-session-runtime.js +3 -0
  18. package/dist/core/agent-session-runtime.js.map +1 -1
  19. package/dist/core/agent-session.d.ts +12 -1
  20. package/dist/core/agent-session.d.ts.map +1 -1
  21. package/dist/core/agent-session.js +20 -14
  22. package/dist/core/agent-session.js.map +1 -1
  23. package/dist/core/compaction/compaction.d.ts.map +1 -1
  24. package/dist/core/compaction/compaction.js +11 -3
  25. package/dist/core/compaction/compaction.js.map +1 -1
  26. package/dist/core/extensions/runner.d.ts +1 -0
  27. package/dist/core/extensions/runner.d.ts.map +1 -1
  28. package/dist/core/extensions/runner.js +11 -0
  29. package/dist/core/extensions/runner.js.map +1 -1
  30. package/dist/core/extensions/types.d.ts +14 -1
  31. package/dist/core/extensions/types.d.ts.map +1 -1
  32. package/dist/core/extensions/types.js.map +1 -1
  33. package/dist/core/footer-data-provider.d.ts +10 -0
  34. package/dist/core/footer-data-provider.d.ts.map +1 -1
  35. package/dist/core/footer-data-provider.js +1 -1
  36. package/dist/core/footer-data-provider.js.map +1 -1
  37. package/dist/core/llama/provider.d.ts.map +1 -1
  38. package/dist/core/llama/provider.js +8 -3
  39. package/dist/core/llama/provider.js.map +1 -1
  40. package/dist/core/model-config.d.ts +30 -0
  41. package/dist/core/model-config.d.ts.map +1 -1
  42. package/dist/core/model-config.js +6 -0
  43. package/dist/core/model-config.js.map +1 -1
  44. package/dist/core/model-registry.d.ts.map +1 -1
  45. package/dist/core/model-registry.js +2 -2
  46. package/dist/core/model-registry.js.map +1 -1
  47. package/dist/core/model-resolver.d.ts +1 -0
  48. package/dist/core/model-resolver.d.ts.map +1 -1
  49. package/dist/core/model-resolver.js +20 -3
  50. package/dist/core/model-resolver.js.map +1 -1
  51. package/dist/core/model-runtime.d.ts +2 -0
  52. package/dist/core/model-runtime.d.ts.map +1 -1
  53. package/dist/core/model-runtime.js +5 -4
  54. package/dist/core/model-runtime.js.map +1 -1
  55. package/dist/core/package-manager.d.ts.map +1 -1
  56. package/dist/core/package-manager.js +13 -6
  57. package/dist/core/package-manager.js.map +1 -1
  58. package/dist/core/remote-catalog-provider.d.ts +1 -1
  59. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  60. package/dist/core/remote-catalog-provider.js +24 -11
  61. package/dist/core/remote-catalog-provider.js.map +1 -1
  62. package/dist/core/resource-loader.d.ts +15 -0
  63. package/dist/core/resource-loader.d.ts.map +1 -1
  64. package/dist/core/resource-loader.js +66 -9
  65. package/dist/core/resource-loader.js.map +1 -1
  66. package/dist/core/settings-manager.d.ts +1 -1
  67. package/dist/core/settings-manager.d.ts.map +1 -1
  68. package/dist/core/settings-manager.js.map +1 -1
  69. package/dist/core/system-prompt.d.ts.map +1 -1
  70. package/dist/core/system-prompt.js +19 -1
  71. package/dist/core/system-prompt.js.map +1 -1
  72. package/dist/core/system-prompt.test.d.ts +2 -0
  73. package/dist/core/system-prompt.test.d.ts.map +1 -0
  74. package/dist/core/system-prompt.test.js +89 -0
  75. package/dist/core/system-prompt.test.js.map +1 -0
  76. package/dist/core/tools/bash.d.ts +2 -0
  77. package/dist/core/tools/bash.d.ts.map +1 -1
  78. package/dist/core/tools/bash.js +34 -5
  79. package/dist/core/tools/bash.js.map +1 -1
  80. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  81. package/dist/core/tools/tool-definition-wrapper.js +3 -1
  82. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  83. package/dist/defaults/models.json +13 -45
  84. package/dist/defaults/settings.json +1 -2
  85. package/dist/extensions/copy-turn.test.ts +131 -0
  86. package/dist/extensions/copy-turn.ts +6 -1
  87. package/dist/extensions/package.json +0 -1
  88. package/dist/extensions/pi-intercom/CHANGELOG.md +249 -0
  89. package/dist/extensions/pi-intercom/README.md +102 -39
  90. package/dist/extensions/pi-intercom/broker/broker.ts +1233 -36
  91. package/dist/extensions/pi-intercom/broker/client.test.ts +83 -0
  92. package/dist/extensions/pi-intercom/broker/client.ts +315 -12
  93. package/dist/extensions/pi-intercom/broker/extension-state.ts +186 -0
  94. package/dist/extensions/pi-intercom/broker/extension.test.ts +387 -0
  95. package/dist/extensions/pi-intercom/broker/framing.test.ts +114 -0
  96. package/dist/extensions/pi-intercom/broker/framing.ts +82 -24
  97. package/dist/extensions/pi-intercom/broker/paths.test.ts +153 -0
  98. package/dist/extensions/pi-intercom/broker/paths.ts +117 -8
  99. package/dist/extensions/pi-intercom/broker/runtime-claim.test.ts +34 -0
  100. package/dist/extensions/pi-intercom/broker/runtime-claim.ts +21 -0
  101. package/dist/extensions/pi-intercom/broker/spawn.test.ts +160 -23
  102. package/dist/extensions/pi-intercom/broker/spawn.ts +113 -27
  103. package/dist/extensions/pi-intercom/config.test.ts +93 -0
  104. package/dist/extensions/pi-intercom/config.ts +55 -6
  105. package/dist/extensions/pi-intercom/cwd.test.ts +40 -0
  106. package/dist/extensions/pi-intercom/cwd.ts +31 -0
  107. package/dist/extensions/pi-intercom/extension-api.ts +44 -0
  108. package/dist/extensions/pi-intercom/format-context.test.ts +31 -0
  109. package/dist/extensions/pi-intercom/format-context.ts +32 -0
  110. package/dist/extensions/pi-intercom/index.ts +742 -145
  111. package/dist/extensions/pi-intercom/intercom.integration.test.ts +2646 -0
  112. package/dist/extensions/pi-intercom/package.json +15 -5
  113. package/dist/extensions/pi-intercom/reply-tracker.test.ts +134 -0
  114. package/dist/extensions/pi-intercom/reply-tracker.ts +31 -13
  115. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +13 -11
  116. package/dist/extensions/pi-intercom/test/inline-message.test.ts +184 -0
  117. package/dist/extensions/pi-intercom/test/overlay-width.test.ts +66 -0
  118. package/dist/extensions/pi-intercom/types.ts +94 -4
  119. package/dist/extensions/pi-intercom/ui/compose.ts +8 -4
  120. package/dist/extensions/pi-intercom/ui/inline-message.ts +61 -25
  121. package/dist/extensions/pi-intercom/ui/session-list.ts +7 -3
  122. package/dist/extensions/pi-subagents/CHANGELOG.md +88 -0
  123. package/dist/extensions/pi-subagents/LICENSE +21 -0
  124. package/dist/extensions/pi-subagents/README.md +158 -69
  125. package/dist/extensions/pi-subagents/agents/architect.md +5 -4
  126. package/dist/extensions/pi-subagents/agents/builder.md +6 -4
  127. package/dist/extensions/pi-subagents/agents/commentator.md +3 -2
  128. package/dist/extensions/pi-subagents/agents/explorer.md +3 -2
  129. package/dist/extensions/pi-subagents/agents/recapper.md +3 -2
  130. package/dist/extensions/pi-subagents/agents/researcher.md +4 -3
  131. package/dist/extensions/pi-subagents/package-lock.json +2 -2
  132. package/dist/extensions/pi-subagents/package.json +3 -3
  133. package/dist/extensions/pi-subagents/prompts/review-loop.md +1 -1
  134. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +22 -988
  135. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +257 -0
  136. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +431 -0
  137. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +144 -0
  138. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +281 -0
  139. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +125 -37
  140. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +4 -0
  141. package/dist/extensions/pi-subagents/src/agents/agents.ts +118 -9
  142. package/dist/extensions/pi-subagents/src/agents/skills.ts +14 -12
  143. package/dist/extensions/pi-subagents/src/agents/task-aware-routing.ts +125 -0
  144. package/dist/extensions/pi-subagents/src/api/delegation.ts +3 -0
  145. package/dist/extensions/pi-subagents/src/api/preflight.ts +17 -13
  146. package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +17 -1
  147. package/dist/extensions/pi-subagents/src/extension/index.ts +22 -8
  148. package/dist/extensions/pi-subagents/src/extension/rpc.ts +248 -6
  149. package/dist/extensions/pi-subagents/src/extension/schemas.ts +33 -8
  150. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +32 -14
  151. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +9 -4
  152. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +33 -4
  153. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +86 -21
  154. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +24 -13
  155. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +7 -5
  156. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
  157. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +48 -5
  158. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +68 -1
  159. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +56 -5
  160. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +92 -11
  161. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +29 -3
  162. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +388 -29
  163. package/dist/extensions/pi-subagents/src/runs/foreground/async-stop-action.ts +65 -0
  164. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +3 -3
  165. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +214 -60
  166. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +546 -252
  167. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +42 -0
  168. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +553 -154
  169. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +19 -13
  170. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +51 -19
  171. package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +3 -1
  172. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  173. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +44 -11
  174. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +8 -0
  175. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +97 -20
  176. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +43 -12
  177. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +52 -4
  178. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +19 -0
  179. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +45 -9
  180. package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +71 -0
  181. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +63 -9
  182. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +31 -1
  183. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +101 -0
  184. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +22 -1
  185. package/dist/extensions/pi-subagents/src/runs/shared/usage-budget.ts +65 -0
  186. package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +26 -1
  187. package/dist/extensions/pi-subagents/src/shared/settings.ts +17 -1
  188. package/dist/extensions/pi-subagents/src/shared/types.ts +196 -12
  189. package/dist/extensions/pi-subagents/src/shared/utils.ts +102 -5
  190. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +10 -1
  191. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +46 -4
  192. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +5 -3
  193. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +87 -24
  194. package/dist/extensions/pi-subagents/src/tui/fleet.ts +182 -9
  195. package/dist/extensions/pi-subagents/src/tui/render.ts +55 -34
  196. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +14 -7
  197. package/dist/extensions/pi-subagents/src/watchdog/review.ts +5 -4
  198. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +170 -17
  199. package/dist/extensions/pi-subagents/src/watchdog/scope.ts +62 -0
  200. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +41 -1
  201. package/dist/extensions/pi-subagents/src/watchdog/types.ts +10 -0
  202. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +113 -8
  203. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +419 -47
  204. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +84 -0
  205. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +4 -2
  206. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +51 -2
  207. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +62 -22
  208. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +48 -0
  209. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +8 -6
  210. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +231 -19
  211. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +48 -7
  212. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +44 -0
  213. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +199 -17
  214. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +898 -16
  215. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +112 -23
  216. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -5
  217. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +2 -0
  218. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +18 -2
  219. package/dist/extensions/pi-subagents/test/support/register-loader.mjs +3 -3
  220. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +3 -1
  221. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +1 -1
  222. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +198 -6
  223. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +161 -1
  224. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +47 -2
  225. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +60 -0
  226. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +8 -0
  227. package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +63 -0
  228. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +136 -0
  229. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +1 -1
  230. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +6 -0
  231. package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +1 -1
  232. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +48 -48
  233. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +23 -0
  234. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +27 -0
  235. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +28 -2
  236. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +1 -0
  237. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +176 -8
  238. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +164 -5
  239. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +41 -1
  240. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +17 -12
  241. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +18 -2
  242. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +20 -1
  243. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +30 -2
  244. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +39 -2
  245. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +22 -0
  246. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +103 -0
  247. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +18 -0
  248. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +48 -0
  249. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +13 -1
  250. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +29 -4
  251. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +223 -4
  252. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +30 -0
  253. package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +52 -0
  254. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +46 -2
  255. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +91 -1
  256. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
  257. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +1 -1
  258. package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +78 -0
  259. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +41 -0
  260. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +77 -0
  261. package/dist/extensions/pi-subagents/test/unit/task-aware-routing.test.ts +213 -0
  262. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +26 -1
  263. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +61 -10
  264. package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +1 -0
  265. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +210 -1
  266. package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +33 -0
  267. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +32 -0
  268. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +1 -1
  269. package/dist/main.d.ts.map +1 -1
  270. package/dist/main.js +43 -0
  271. package/dist/main.js.map +1 -1
  272. package/dist/modes/interactive/components/custom-message.d.ts +3 -1
  273. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  274. package/dist/modes/interactive/components/custom-message.js +10 -2
  275. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  276. package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
  277. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  278. package/dist/modes/interactive/components/extension-editor.js +16 -46
  279. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  280. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  281. package/dist/modes/interactive/components/model-selector.js +4 -1
  282. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  283. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  284. package/dist/modes/interactive/components/scoped-models-selector.js +22 -13
  285. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  286. package/dist/modes/interactive/external-editor.d.ts +12 -0
  287. package/dist/modes/interactive/external-editor.d.ts.map +1 -0
  288. package/dist/modes/interactive/external-editor.js +37 -0
  289. package/dist/modes/interactive/external-editor.js.map +1 -0
  290. package/dist/modes/interactive/interactive-mode.d.ts +1 -1
  291. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  292. package/dist/modes/interactive/interactive-mode.js +72 -64
  293. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  294. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  295. package/dist/modes/rpc/rpc-mode.js +14 -0
  296. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  297. package/dist/skills/ponytail/SKILL.md +1 -3
  298. package/dist/utils/clipboard.d.ts.map +1 -1
  299. package/dist/utils/clipboard.js +19 -8
  300. package/dist/utils/clipboard.js.map +1 -1
  301. package/dist/utils/version-check.d.ts.map +1 -1
  302. package/dist/utils/version-check.js +1 -1
  303. package/dist/utils/version-check.js.map +1 -1
  304. package/docs/compaction.md +1 -1
  305. package/docs/custom-provider.md +14 -5
  306. package/docs/environment-variables.md +86 -0
  307. package/docs/extensions.md +15 -5
  308. package/docs/index.md +1 -0
  309. package/docs/models.md +9 -2
  310. package/docs/plans/subagent-delegation/phase-0-correctness.md +265 -0
  311. package/docs/plans/subagent-delegation/phase-1-behavioral-contract.md +486 -0
  312. package/docs/plans/subagent-delegation/phase-2-context-controls.md +282 -0
  313. package/docs/plans/subagent-delegation/phase-3-advisory-routing.md +362 -0
  314. package/docs/plans/subagent-delegation/phase-4-optional-enforcement.md +381 -0
  315. package/docs/providers.md +21 -2
  316. package/docs/rpc.md +23 -6
  317. package/docs/session-format.md +2 -0
  318. package/docs/settings.md +1 -1
  319. package/docs/usage.md +0 -13
  320. package/examples/extensions/custom-compaction.ts +5 -2
  321. package/examples/extensions/custom-provider-anthropic/index.ts +9 -3
  322. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  323. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  324. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  325. package/examples/extensions/gondolin/package-lock.json +2 -2
  326. package/examples/extensions/gondolin/package.json +1 -1
  327. package/examples/extensions/handoff.ts +11 -3
  328. package/examples/extensions/message-renderer.ts +3 -3
  329. package/examples/extensions/sandbox/package-lock.json +2 -2
  330. package/examples/extensions/sandbox/package.json +1 -1
  331. package/examples/extensions/summarize.ts +5 -2
  332. package/examples/extensions/with-deps/package-lock.json +2 -2
  333. package/examples/extensions/with-deps/package.json +1 -1
  334. package/examples/sdk/12-full-control.ts +3 -1
  335. package/package.json +16 -7
  336. package/dist/extensions/caveman/caveman-instructions.cjs +0 -11
  337. package/dist/extensions/caveman/index.js +0 -118
  338. package/dist/extensions/caveman/package.json +0 -8
  339. package/dist/extensions/caveman/test/extension.test.js +0 -203
  340. package/dist/extensions/caveman/test/helpers.test.js +0 -58
  341. package/dist/skills/caveman/SKILL.md +0 -50
@@ -3,12 +3,19 @@ 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 type { AgentConfig, AgentScope } from "../../agents/agents.ts";
6
+ import { resolveAgentName, type AgentConfig, type AgentScope } from "../../agents/agents.ts";
7
7
  import { getArtifactsDir, getProjectChainRunsDir } from "../../shared/artifacts.ts";
8
8
  import { ChainClarifyComponent, type ChainClarifyResult } from "./chain-clarify.ts";
9
- import { toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
9
+ import { resolveEffectiveThinking, toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
10
10
  import { executeChain } from "./chain-execution.ts";
11
- import { beginForegroundChild, finishForegroundChild, updateForegroundChild } from "./foreground-control.ts";
11
+ import {
12
+ beginForegroundChild,
13
+ finishForegroundChild,
14
+ foregroundSchedulingSettled,
15
+ retainForegroundSchedulingOwner,
16
+ settleForegroundSchedulingOwner,
17
+ updateForegroundChild,
18
+ } from "./foreground-control.ts";
12
19
  import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
13
20
  import { handleManagementAction } from "../../agents/agent-management.ts";
14
21
  import { buildDoctorReport } from "../../extension/doctor.ts";
@@ -16,9 +23,8 @@ import { clearPendingForegroundControlNotices } from "../../extension/control-no
16
23
  import { runSync } from "./execution.ts";
17
24
  import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.ts";
18
25
  import type { MainWatchdogRuntime } from "../../watchdog/runtime.ts";
19
- import { buildModelCandidates, resolveEffectiveSubagentModel, resolveModelCandidate } from "../shared/model-fallback.ts";
26
+ import { buildModelCandidates, normalizeParentModel, resolveEffectiveSubagentModel, resolveModelCandidate, type ParentModel } from "../shared/model-fallback.ts";
20
27
  import type { ModelScopeConfig } from "../shared/model-scope.ts";
21
- import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
22
28
  import { recordRun } from "../shared/run-history.ts";
23
29
  import {
24
30
  buildChainInstructions,
@@ -47,9 +53,10 @@ import { formatControlIntercomMessage, formatControlNoticeMessage, resolveContro
47
53
  import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
48
54
  import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.ts";
49
55
  import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
56
+ import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
50
57
  import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
51
58
  import { isAgentContractV1 } from "../shared/agent-contract.ts";
52
- import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
59
+ import { finalizeSingleOutput, formatBoundedPersistenceFallback, formatSavedOutputReference, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
53
60
  import { cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
54
61
  import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
55
62
  import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
@@ -64,12 +71,13 @@ import {
64
71
  stripDetailsOutputsForIntercomReceipt,
65
72
  } from "../../intercom/result-intercom.ts";
66
73
  import { applySteeringRecoveryAgentConfig, buildRevivedAsyncTask, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
67
- import { deliverInterruptRequest, deliverStopRequest, requestAsyncSteer } from "../background/control-channel.ts";
74
+ import { deliverCheckpointDecisionRequest, deliverInterruptRequest, requestAsyncSteer } from "../background/control-channel.ts";
68
75
  import { waitForSteeringAction } from "../background/steering.ts";
69
76
  import { steerAsyncRun } from "./async-steering-action.ts";
77
+ import { stopAsyncRun } from "./async-stop-action.ts";
70
78
  import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
71
79
  import { resolveAsyncRootResultPath } from "../background/chain-root-attachment.ts";
72
- import { attachRootChildrenToSteps, createNestedRoute, readNestedControlResults, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
80
+ import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
73
81
  import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
74
82
  import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
75
83
  import { inspectSubagentStatus } from "../background/run-status.ts";
@@ -106,6 +114,7 @@ import {
106
114
  type SingleResult,
107
115
  type ToolBudgetConfig,
108
116
  type TurnBudgetConfig,
117
+ type UsageBudgetConfig,
109
118
  type SubagentRunMode,
110
119
  type SubagentState,
111
120
  ASYNC_DIR,
@@ -167,6 +176,7 @@ export interface SubagentParamsLike {
167
176
  /** Internal-only strict turn-boundary enforcement for versioned foreground delegation. */
168
177
  enforceHardTurnLimit?: boolean;
169
178
  toolBudget?: ToolBudgetConfig;
179
+ usageBudget?: UsageBudgetConfig;
170
180
  clarify?: boolean;
171
181
  share?: boolean;
172
182
  control?: ControlConfig;
@@ -192,6 +202,15 @@ export interface SubagentParamsLike {
192
202
  additional?: number;
193
203
  }
194
204
 
205
+ function rememberParentModel(state: { currentSessionId?: string | null; lastParentModel?: ParentModel }, sessionId: string | null, model: unknown): ParentModel | undefined {
206
+ if (state.currentSessionId !== sessionId) state.lastParentModel = undefined;
207
+ state.currentSessionId = sessionId;
208
+ const parentModel = normalizeParentModel(model);
209
+ if (!sessionId) return parentModel;
210
+ if (parentModel) state.lastParentModel = parentModel;
211
+ return parentModel ?? state.lastParentModel;
212
+ }
213
+
195
214
  interface ExecutorDeps {
196
215
  pi: ExtensionAPI;
197
216
  state: SubagentState;
@@ -236,10 +255,12 @@ interface ExecutionContextData {
236
255
  deadlineAt?: number;
237
256
  turnBudget?: ResolvedTurnBudget;
238
257
  toolBudget?: ResolvedToolBudget;
258
+ usageBudget?: UsageBudgetConfig;
239
259
  allowZeroToolBudget?: boolean;
240
260
  configToolBudget?: ResolvedToolBudget;
241
261
  contextPolicy: AgentDefaultContextPolicy;
242
262
  modelScope?: ModelScopeConfig;
263
+ parentModel?: ParentModel;
243
264
  parentSessionId: string | null;
244
265
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
245
266
  }
@@ -248,6 +269,15 @@ function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefine
248
269
  return requestedCwd ? path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
249
270
  }
250
271
 
272
+ function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
273
+ const control = state.foregroundControls.get(runId);
274
+ if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
275
+ clearPendingForegroundControlNotices(state, runId);
276
+ state.foregroundControls.delete(runId);
277
+ if (state.lastForegroundControlId === runId) state.lastForegroundControlId = null;
278
+ return true;
279
+ }
280
+
251
281
  function getForegroundControl(state: SubagentState, runId: string | undefined) {
252
282
  if (runId) return state.foregroundControls.get(runId);
253
283
  if (state.lastForegroundControlId) {
@@ -382,7 +412,7 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
382
412
  };
383
413
  }
384
414
 
385
- function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[] }): void {
415
+ function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; checkpoint?: Details["checkpoint"] }): void {
386
416
  state.foregroundRuns ??= new Map();
387
417
  const previous = state.foregroundRuns.get(input.runId);
388
418
  const updatedAt = Date.now();
@@ -392,26 +422,41 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
392
422
  cwd: input.cwd,
393
423
  ...(input.sessionId ? { sessionId: input.sessionId } : {}),
394
424
  updatedAt,
425
+ ...(input.checkpoint ? { checkpoint: input.checkpoint } : {}),
395
426
  children: input.results.map((result, index) => {
396
427
  const child = {
397
428
  agent: result.agent,
398
429
  index,
399
430
  ...(result.context ? { context: result.context } : {}),
400
- status: resolveSubagentResultStatus({ exitCode: result.exitCode, interrupted: result.interrupted, detached: result.detached }),
431
+ status: resolveSubagentResultStatus({
432
+ exitCode: result.exitCode,
433
+ interrupted: result.interrupted,
434
+ detached: result.detached,
435
+ processSignal: result.processSignal,
436
+ timedOut: result.timedOut,
437
+ stopped: result.stopped,
438
+ turnBudgetExceeded: result.turnBudgetExceeded,
439
+ }),
401
440
  ...foregroundChildActivityFromProgress(result.progress),
402
441
  updatedAt,
403
442
  ...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
404
443
  ...(result.error ? { error: result.error } : {}),
405
444
  ...(result.finalOutput ? { finalOutput: result.finalOutput } : {}),
445
+ ...(result.outputState ? { outputState: result.outputState } : {}),
406
446
  ...(result.outputMode ? { outputMode: result.outputMode } : {}),
407
447
  ...(result.savedOutputPath ? { savedOutputPath: result.savedOutputPath } : {}),
408
448
  ...(result.outputSaveError ? { outputSaveError: result.outputSaveError } : {}),
409
449
  ...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
450
+ ...(result.model ? { model: result.model } : {}),
451
+ ...(result.thinking ? { thinking: result.thinking } : {}),
410
452
  ...(result.artifactPaths ? { artifactPaths: result.artifactPaths } : {}),
411
453
  ...(result.transcriptPath ? { transcriptPath: result.transcriptPath } : {}),
412
454
  ...(result.transcriptError ? { transcriptError: result.transcriptError } : {}),
413
455
  ...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
414
456
  ...(result.acceptance ? { acceptance: result.acceptance } : {}),
457
+ ...(result.launchContractDigest ? { launchContractDigest: result.launchContractDigest } : {}),
458
+ ...(result.launchResolvedExtensions ? { launchResolvedExtensions: result.launchResolvedExtensions } : {}),
459
+ ...(result.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: result.runtimeAcknowledgedExtensions } : {}),
415
460
  ...(result.capabilityCeiling ? { capabilityCeiling: result.capabilityCeiling } : {}),
416
461
  ...(result.capabilityAudit ? { capabilityAudit: result.capabilityAudit } : {}),
417
462
  };
@@ -454,37 +499,57 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
454
499
  state.foregroundRuns.set(input.runId, run);
455
500
  }
456
501
  run.updatedAt = updatedAt;
502
+ const terminalStatus = resolveSubagentResultStatus({
503
+ exitCode: input.result.exitCode,
504
+ ...(input.result.acceptance?.status === "rejected" ? { success: false } : {}),
505
+ interrupted: input.result.interrupted,
506
+ detached: false,
507
+ processSignal: input.result.processSignal,
508
+ timedOut: input.result.timedOut,
509
+ stopped: input.result.stopped,
510
+ turnBudgetExceeded: input.result.turnBudgetExceeded,
511
+ });
457
512
  const child = run.children[input.index] ?? { agent: input.result.agent, index: input.index, status: "detached" as const };
458
513
  run.children[input.index] = {
459
514
  ...child,
460
515
  agent: input.result.agent,
461
516
  index: input.index,
462
517
  ...(input.result.context ? { context: input.result.context } : {}),
463
- status: input.result.acceptance?.status === "rejected"
464
- ? "failed"
465
- : resolveSubagentResultStatus({ exitCode: input.result.exitCode, interrupted: input.result.interrupted, detached: false }),
518
+ status: terminalStatus,
466
519
  ...foregroundChildActivityFromProgress(input.result.progress),
467
520
  updatedAt,
468
521
  ...(input.result.exitCode !== undefined ? { exitCode: input.result.exitCode } : {}),
469
522
  ...(input.result.error ? { error: input.result.error } : {}),
470
- ...(input.result.finalOutput ? { finalOutput: input.result.finalOutput } : {}),
523
+ // Reference-first remembered state: when an authoritative saved output path
524
+ // exists, do not retain the full final output in memory; status/transcript
525
+ // views recover it from `savedOutputPath` via `rememberedForegroundChildOutput`
526
+ // (runs/background/run-status.ts) and the TUI via `resultOutputForUi`
527
+ // (tui/render.ts).
528
+ ...(input.result.finalOutput && !input.result.savedOutputPath ? { finalOutput: input.result.finalOutput } : {}),
529
+ outputState: input.result.outputState,
471
530
  outputMode: input.result.outputMode,
472
531
  savedOutputPath: input.result.savedOutputPath,
473
532
  outputSaveError: input.result.outputSaveError,
474
533
  ...(input.result.sessionFile ? { sessionFile: input.result.sessionFile } : {}),
534
+ ...(input.result.model ? { model: input.result.model } : {}),
535
+ ...(input.result.thinking ? { thinking: input.result.thinking } : {}),
475
536
  ...(input.result.artifactPaths ? { artifactPaths: input.result.artifactPaths } : {}),
476
537
  ...(input.result.transcriptPath ? { transcriptPath: input.result.transcriptPath } : {}),
477
538
  ...(input.result.transcriptError ? { transcriptError: input.result.transcriptError } : {}),
478
539
  ...(input.result.detachedReason ? { detachedReason: input.result.detachedReason } : {}),
479
540
  ...(input.result.acceptance ? { acceptance: input.result.acceptance } : {}),
541
+ ...(input.result.launchContractDigest ? { launchContractDigest: input.result.launchContractDigest } : {}),
542
+ ...(input.result.launchResolvedExtensions ? { launchResolvedExtensions: input.result.launchResolvedExtensions } : {}),
543
+ ...(input.result.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: input.result.runtimeAcknowledgedExtensions } : {}),
480
544
  ...(input.result.capabilityCeiling ? { capabilityCeiling: input.result.capabilityCeiling } : {}),
481
545
  ...(input.result.capabilityAudit ? { capabilityAudit: input.result.capabilityAudit } : {}),
482
546
  };
483
547
  trimRememberedForegroundRuns(state);
484
- const output = getSingleResultOutput(input.result).trim();
485
- const success = input.result.exitCode === 0 && input.result.acceptance?.status !== "rejected";
548
+ const reference = input.result.outputReference?.message;
549
+ const output = reference ?? getSingleResultOutput(input.result).trim();
550
+ const success = terminalStatus === "completed";
486
551
  const summary = !success && input.result.error
487
- ? `${input.result.error}${output ? `\n\nOutput:\n${output}` : ""}`
552
+ ? `${input.result.error}${output ? `\n\n${output}` : ""}`
488
553
  : output || input.result.error || "Detached child exited without final output.";
489
554
  // A detached callback may outlive its extension runtime. Stale sessions are
490
555
  // intentionally dropped rather than routed through a replacement runtime.
@@ -498,7 +563,12 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
498
563
  success,
499
564
  summary,
500
565
  exitCode: input.result.exitCode,
501
- state: success ? "complete" : "failed",
566
+ state: terminalStatus === "completed" ? "complete" : terminalStatus,
567
+ ...(input.result.interrupted !== undefined ? { interrupted: input.result.interrupted } : {}),
568
+ ...(input.result.stopped !== undefined ? { stopped: input.result.stopped } : {}),
569
+ ...(input.result.processSignal !== undefined ? { processSignal: input.result.processSignal } : {}),
570
+ ...(input.result.timedOut !== undefined ? { timedOut: input.result.timedOut } : {}),
571
+ ...(input.result.turnBudgetExceeded !== undefined ? { turnBudgetExceeded: input.result.turnBudgetExceeded } : {}),
502
572
  timestamp: updatedAt,
503
573
  cwd: input.cwd,
504
574
  sessionFile: input.result.sessionFile,
@@ -507,7 +577,7 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
507
577
  });
508
578
  }
509
579
 
510
- function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState): { runId: string; mode: "single" | "parallel" | "chain"; state: "complete"; agent: string; index: number; cwd: string; sessionFile: string; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
580
+ function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState): { runId: string; mode: "single" | "parallel" | "chain"; state: "complete"; agent: string; index: number; cwd: string; sessionFile: string; model?: string; thinking?: string; launchContractDigest?: string; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
511
581
  const requested = (params.id ?? params.runId)?.trim();
512
582
  if (!requested || !state.foregroundRuns?.size || !state.currentSessionId) return undefined;
513
583
  const sessionRuns = [...state.foregroundRuns.values()].filter((run) => run.sessionId === state.currentSessionId);
@@ -534,6 +604,9 @@ function resolveForegroundResumeTarget(params: SubagentParamsLike, state: Subage
534
604
  index,
535
605
  cwd: run.cwd,
536
606
  sessionFile,
607
+ ...(child.model ? { model: child.model } : {}),
608
+ ...(child.thinking ? { thinking: child.thinking } : {}),
609
+ ...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
537
610
  ...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
538
611
  };
539
612
  }
@@ -714,50 +787,6 @@ function interruptAsyncRun(
714
787
  }
715
788
  }
716
789
 
717
- function stopAsyncRun(
718
- state: SubagentState,
719
- runId: string | undefined,
720
- kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean,
721
- location?: { asyncDir: string | null; resolvedId?: string },
722
- ): AgentToolResult<Details> | null {
723
- const target = getAsyncInterruptTarget(state, runId, location, { fallbackToNewest: false });
724
- if (!target) return null;
725
- const status = reconcileAsyncRun(target.asyncDir, { kill }).status;
726
- if (state.currentSessionId && status?.sessionId !== state.currentSessionId) {
727
- return {
728
- content: [{ type: "text", text: `Async run '${target.asyncId}' was not found in the active session.` }],
729
- isError: true,
730
- details: { mode: "management", results: [] },
731
- };
732
- }
733
- if (!status || (status.state !== "running" && status.state !== "queued")) {
734
- return {
735
- content: [{ type: "text", text: `No running or queued async run was found for '${runId ?? "current"}'.` }],
736
- isError: true,
737
- details: { mode: "management", results: [] },
738
- };
739
- }
740
- try {
741
- deliverStopRequest({ asyncDir: target.asyncDir, pid: typeof status.pid === "number" ? status.pid : undefined, kill, source: "stop-action" });
742
- const tracked = state.asyncJobs.get(target.asyncId);
743
- if (tracked) {
744
- tracked.activityState = undefined;
745
- tracked.updatedAt = Date.now();
746
- }
747
- return {
748
- content: [{ type: "text", text: `Stop requested for async run ${target.asyncId}.` }],
749
- details: { mode: "management", results: [] },
750
- };
751
- } catch (error) {
752
- const message = error instanceof Error ? error.message : String(error);
753
- return {
754
- content: [{ type: "text", text: `Failed to stop async run ${target.asyncId}: ${message}` }],
755
- isError: true,
756
- details: { mode: "management", results: [] },
757
- };
758
- }
759
- }
760
-
761
790
  function duplicateNames(names: string[]): string[] {
762
791
  const seen = new Set<string>();
763
792
  const duplicates = new Set<string>();
@@ -773,6 +802,7 @@ function appendStepToAsyncChain(input: {
773
802
  requestCwd: string;
774
803
  ctx: ExtensionContext;
775
804
  deps: ExecutorDeps;
805
+ parentModel?: ParentModel;
776
806
  }): AgentToolResult<Details> {
777
807
  const targetRunId = input.params.id ?? input.params.runId;
778
808
  if (!targetRunId) {
@@ -877,13 +907,14 @@ function appendStepToAsyncChain(input: {
877
907
  const contextPolicy = resolveExplicitContextPolicy(input.params);
878
908
  const chainSkillInput = normalizeSkillInput(input.params.skill);
879
909
  const chainSkills = chainSkillInput === false ? [] : (chainSkillInput ?? []);
910
+ const parentModel = input.parentModel;
880
911
  const asyncCtx = {
881
912
  pi: input.deps.pi,
882
913
  cwd: input.ctx.cwd,
883
914
  currentSessionId: resolveCurrentSessionId(input.ctx.sessionManager),
884
915
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
885
- currentModelProvider: input.ctx.model?.provider,
886
- currentModel: input.ctx.model,
916
+ currentModelProvider: parentModel?.provider,
917
+ currentModel: parentModel,
887
918
  modelScope: discoveredForAppend.modelScope,
888
919
  interactive: input.ctx.hasUI,
889
920
  };
@@ -1011,10 +1042,10 @@ function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "neste
1011
1042
  };
1012
1043
  }
1013
1044
 
1014
- async function waitForNestedControlResult(target: ResolvedSubagentRunId & { kind: "nested" }, requestId: string, timeoutMs = 1_000) {
1045
+ async function waitForNestedControlResult(target: ResolvedSubagentRunId & { kind: "nested" }, requestId: string, ignoredFiles: ReadonlySet<string>, timeoutMs = 1_000) {
1015
1046
  const deadline = Date.now() + timeoutMs;
1016
1047
  while (Date.now() < deadline) {
1017
- const result = readNestedControlResults(target.match.route).find((candidate) => candidate.requestId === requestId && candidate.targetRunId === target.match.run.id);
1048
+ const result = findNestedControlResult(target.match.route, requestId, target.match.run.id, ignoredFiles);
1018
1049
  if (result) return result;
1019
1050
  await new Promise((resolve) => setTimeout(resolve, 50));
1020
1051
  }
@@ -1023,14 +1054,16 @@ async function waitForNestedControlResult(target: ResolvedSubagentRunId & { kind
1023
1054
 
1024
1055
  async function sendNestedControlRequest(target: ResolvedSubagentRunId & { kind: "nested" }, action: "interrupt" | "resume", message?: string) {
1025
1056
  const requestId = randomUUID();
1057
+ const ignoredFiles = snapshotNestedEventFiles(target.match.route);
1058
+ const requestedAt = Date.now();
1026
1059
  writeNestedControlRequest(target.match.route, {
1027
- ts: Date.now(),
1060
+ ts: requestedAt,
1028
1061
  requestId,
1029
1062
  targetRunId: target.match.run.id,
1030
1063
  action,
1031
1064
  ...(message ? { message } : {}),
1032
1065
  });
1033
- return waitForNestedControlResult(target, requestId);
1066
+ return waitForNestedControlResult(target, requestId, ignoredFiles);
1034
1067
  }
1035
1068
 
1036
1069
  function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nested" }): AgentToolResult<Details> | undefined {
@@ -1121,6 +1154,7 @@ async function resumeAsyncRun(input: {
1121
1154
  requestCwd: string;
1122
1155
  ctx: ExtensionContext;
1123
1156
  deps: ExecutorDeps;
1157
+ parentModel?: ParentModel;
1124
1158
  absoluteDeadlineAt?: number;
1125
1159
  }): Promise<AgentToolResult<Details>> {
1126
1160
  const followUp = (input.params.message ?? input.params.task ?? "").trim();
@@ -1132,6 +1166,13 @@ async function resumeAsyncRun(input: {
1132
1166
  details: { mode: "management", results: [] },
1133
1167
  };
1134
1168
  }
1169
+ if (input.params.model !== undefined) {
1170
+ return {
1171
+ content: [{ type: "text", text: "action='resume' reuses the persisted child model and does not accept a model override." }],
1172
+ isError: true,
1173
+ details: { mode: "management", results: [] },
1174
+ };
1175
+ }
1135
1176
  const acceptanceErrors = validateExecutionAcceptance(input.params);
1136
1177
  if (acceptanceErrors.length > 0) {
1137
1178
  return {
@@ -1252,13 +1293,14 @@ async function resumeAsyncRun(input: {
1252
1293
  };
1253
1294
  }
1254
1295
  const runId = randomUUID().slice(0, 8);
1255
- const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir };
1296
+ const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts === true, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir };
1256
1297
  const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
1257
1298
  const contextPolicy = resolveExplicitContextPolicy(input.params);
1258
1299
  const workflowTask = (input.params.task ?? followUp) || undefined;
1259
1300
  const goal = resolveAsyncEventGoal(workflowTask, attachChain);
1260
1301
  const chain = wrapChainTasksForFork(attachChain, contextPolicy);
1261
1302
  const normalized = normalizeSkillInput(input.params.skill);
1303
+ const parentModel = input.parentModel;
1262
1304
  const result = executeAsyncChain(runId, {
1263
1305
  chain,
1264
1306
  task: workflowTask,
@@ -1277,8 +1319,8 @@ async function resumeAsyncRun(input: {
1277
1319
  cwd: input.requestCwd,
1278
1320
  currentSessionId: input.deps.state.currentSessionId,
1279
1321
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
1280
- currentModelProvider: input.ctx.model?.provider,
1281
- currentModel: input.ctx.model,
1322
+ currentModelProvider: parentModel?.provider,
1323
+ currentModel: parentModel,
1282
1324
  modelScope,
1283
1325
  interactive: input.ctx.hasUI,
1284
1326
  },
@@ -1317,9 +1359,10 @@ async function resumeAsyncRun(input: {
1317
1359
 
1318
1360
  const runId = randomUUID().slice(0, 8);
1319
1361
  const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(agentConfig, recoveryDescriptor) : agentConfig;
1320
- const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir };
1362
+ const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts === true, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir };
1321
1363
  const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
1322
1364
  const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
1365
+ const parentModel = input.parentModel;
1323
1366
  const result = executeAsyncSingle(runId, {
1324
1367
  agent: target.agent,
1325
1368
  task: buildRevivedAsyncTask(target, followUp),
@@ -1330,8 +1373,8 @@ async function resumeAsyncRun(input: {
1330
1373
  cwd: input.requestCwd,
1331
1374
  currentSessionId: input.deps.state.currentSessionId,
1332
1375
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
1333
- currentModelProvider: input.ctx.model?.provider,
1334
- currentModel: input.ctx.model,
1376
+ currentModelProvider: parentModel?.provider,
1377
+ currentModel: parentModel,
1335
1378
  modelScope,
1336
1379
  interactive: input.ctx.hasUI,
1337
1380
  },
@@ -1349,8 +1392,8 @@ async function resumeAsyncRun(input: {
1349
1392
  sourceRunId: target.runId,
1350
1393
  ...(input.deps.state.currentSessionId ? { parentSessionId: input.deps.state.currentSessionId } : {}),
1351
1394
  },
1352
- modelOverride: input.params.model ?? recoveryDescriptor?.model ?? target.model,
1353
- thinkingOverride: input.params.model ? undefined : recoveryDescriptor?.thinking ?? target.thinking,
1395
+ modelOverride: recoveryDescriptor?.model ?? target.model,
1396
+ thinkingOverride: recoveryDescriptor?.thinking ?? target.thinking,
1354
1397
  outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
1355
1398
  maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
1356
1399
  waitToolEnabled: input.deps.waitToolEnabled,
@@ -1361,8 +1404,8 @@ async function resumeAsyncRun(input: {
1361
1404
  controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
1362
1405
  childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
1363
1406
  availableModels,
1364
- output: recoveryDescriptor?.outputPath,
1365
- outputMode: recoveryDescriptor?.outputMode,
1407
+ output: typeof input.params.output === "string" ? input.params.output : recoveryDescriptor?.outputPath,
1408
+ outputMode: input.params.outputMode ?? recoveryDescriptor?.outputMode,
1366
1409
  ...(recoveryDescriptor?.agentContract ? { agentContract: recoveryDescriptor.agentContract } : {}),
1367
1410
  ...(recoveryDescriptor?.structuredOutputSchema ? { structuredOutputSchema: recoveryDescriptor.structuredOutputSchema } : {}),
1368
1411
  ...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
@@ -1387,18 +1430,92 @@ async function resumeAsyncRun(input: {
1387
1430
  revivedTarget ? `Intercom target: ${revivedTarget} (if registered)` : undefined,
1388
1431
  `Status if needed: subagent({ action: "status", id: "${revivedId}" })`,
1389
1432
  ].filter((line): line is string => Boolean(line));
1390
- return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n"), input.ctx.hasUI) }], details: result.details };
1433
+ return {
1434
+ content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n"), input.ctx.hasUI) }],
1435
+ details: {
1436
+ ...result.details,
1437
+ ...(target.launchContractDigest ? { sourceLaunchContractDigest: target.launchContractDigest } : {}),
1438
+ },
1439
+ };
1391
1440
  }
1392
1441
 
1393
1442
  function resultSummaryForIntercom(result: SingleResult): string {
1394
- const output = getSingleResultOutput(result);
1395
- if (result.exitCode !== 0 && result.error) {
1396
- return output ? `${result.error}\n\nOutput:\n${output}` : result.error;
1443
+ const reference = result.outputReference?.message;
1444
+ if (reference) {
1445
+ return result.exitCode !== 0 && result.error ? `${result.error}\n\n${reference}` : reference;
1446
+ }
1447
+ if (result.savedOutputPath) {
1448
+ try {
1449
+ const ref = formatSavedOutputReference(result.savedOutputPath, fs.readFileSync(result.savedOutputPath, "utf-8"));
1450
+ return result.exitCode !== 0 && result.error ? `${result.error}\n\n${ref.message}` : ref.message;
1451
+ } catch (error) {
1452
+ const fallback = formatBoundedPersistenceFallback({
1453
+ error: `Failed to read saved output: ${error instanceof Error ? error.message : String(error)}`,
1454
+ outputPath: result.savedOutputPath,
1455
+ fullOutput: getSingleResultOutput(result),
1456
+ exitCode: result.exitCode,
1457
+ processError: result.error,
1458
+ });
1459
+ return result.exitCode !== 0 && result.error ? `${result.error}\n\n${fallback}` : fallback;
1460
+ }
1461
+ }
1462
+ if (result.outputSaveError) {
1463
+ return formatBoundedPersistenceFallback({
1464
+ error: result.outputSaveError,
1465
+ outputPath: result.savedOutputPath,
1466
+ fullOutput: getSingleResultOutput(result),
1467
+ exitCode: result.exitCode,
1468
+ processError: result.error,
1469
+ });
1470
+ }
1471
+ // Legacy / `output: false` child without a durable saved path: bounded fallback
1472
+ // only, never the raw full output.
1473
+ return formatBoundedPersistenceFallback({
1474
+ error: "No saved output path is available for this child.",
1475
+ fullOutput: getSingleResultOutput(result),
1476
+ exitCode: result.exitCode,
1477
+ processError: result.error,
1478
+ });
1479
+ }
1480
+
1481
+ /**
1482
+ * Compact terminal per-child line for bridge-off foreground parallel content:
1483
+ * status plus either the saved-output reference (default/file-only), the legacy
1484
+ * inline full output when explicitly requested, or the bounded fallback.
1485
+ */
1486
+ function formatParallelChildResult(result: SingleResult, index: number): string {
1487
+ const status = resolveSubagentResultStatus({
1488
+ exitCode: result.exitCode,
1489
+ interrupted: result.interrupted,
1490
+ detached: result.detached,
1491
+ processSignal: result.processSignal,
1492
+ timedOut: result.timedOut,
1493
+ stopped: result.stopped,
1494
+ turnBudgetExceeded: result.turnBudgetExceeded,
1495
+ });
1496
+ const header = `=== Task ${index + 1}: ${result.agent} [${status}] ===`;
1497
+ if (result.outputMode === "inline") {
1498
+ const output = getSingleResultOutput(result).trim();
1499
+ return `${header}\n${output || "(no output)"}`;
1500
+ }
1501
+ const reference = result.outputReference?.message;
1502
+ if (reference) {
1503
+ const body = result.exitCode !== 0 && result.error ? `${result.error}\n${reference}` : reference;
1504
+ return `${header}\n${body}`;
1397
1505
  }
1398
- return output || result.error || "(no output)";
1506
+ return `${header}\n${formatBoundedPersistenceFallback({
1507
+ error: result.outputSaveError ?? "No saved output path is available for this child.",
1508
+ outputPath: result.savedOutputPath,
1509
+ fullOutput: getSingleResultOutput(result),
1510
+ exitCode: result.exitCode,
1511
+ processError: result.error,
1512
+ })}`;
1399
1513
  }
1400
1514
 
1401
1515
  function formatFailedSingleRunOutput(result: SingleResult, displayOutput: string): string {
1516
+ // Reference-first and persistence-failure fallbacks already carry error/status
1517
+ // and must not be re-wrapped as raw "Output:" text.
1518
+ if (result.savedOutputPath || result.outputSaveError) return displayOutput;
1402
1519
  const error = result.error || "Failed";
1403
1520
  const output = displayOutput.trim();
1404
1521
  const lines = [error];
@@ -1433,14 +1550,19 @@ async function emitForegroundResultIntercom(input: {
1433
1550
  nestedChildren?: NestedRunSummary[];
1434
1551
  parallelHandoff?: Details["parallelHandoff"];
1435
1552
  }): Promise<ReturnType<typeof buildSubagentResultIntercomPayload> | null> {
1436
- if (!input.intercomBridge.active || !input.intercomBridge.orchestratorTarget) return null;
1553
+ if (!input.intercomBridge.active || !input.intercomBridge.resultDelivery || !input.intercomBridge.orchestratorTarget) return null;
1437
1554
  const children = input.results.flatMap((result, index) => result.detached ? [] : [{
1438
1555
  agent: result.agent,
1439
1556
  status: resolveSubagentResultStatus({
1440
1557
  exitCode: result.exitCode,
1441
1558
  interrupted: result.interrupted,
1442
1559
  detached: result.detached,
1560
+ processSignal: result.processSignal,
1561
+ timedOut: result.timedOut,
1562
+ stopped: result.stopped,
1563
+ turnBudgetExceeded: result.turnBudgetExceeded,
1443
1564
  }),
1565
+ outputState: result.outputState ?? "unknown",
1444
1566
  summary: resultSummaryForIntercom(result),
1445
1567
  index,
1446
1568
  artifactPath: result.artifactPaths?.outputPath,
@@ -1487,6 +1609,67 @@ async function maybeBuildForegroundIntercomReceipt(input: {
1487
1609
  };
1488
1610
  }
1489
1611
 
1612
+ function canonicalizeAgentName(name: string, agents: AgentConfig[]): { name?: string; error?: string } {
1613
+ const resolved = resolveAgentName(name, agents);
1614
+ if (resolved.error) return { error: resolved.error };
1615
+ if (!resolved.agent) return { error: `Unknown agent: ${name}` };
1616
+ return { name: resolved.agent.name };
1617
+ }
1618
+
1619
+ function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[]): { params?: SubagentParamsLike; error?: string } {
1620
+ const resolve = (name: string, location?: string): { name?: string; error?: string } => {
1621
+ const result = canonicalizeAgentName(name, agents);
1622
+ return result.error && location ? { error: `${result.error} (${location})` } : result;
1623
+ };
1624
+ if (params.agent) {
1625
+ const result = resolve(params.agent);
1626
+ if (result.error) return { error: result.error };
1627
+ params = { ...params, agent: result.name };
1628
+ }
1629
+ if (params.tasks) {
1630
+ const tasks: TaskParam[] = [];
1631
+ for (let index = 0; index < params.tasks.length; index++) {
1632
+ const task = params.tasks[index]!;
1633
+ const result = resolve(task.agent, `task ${index + 1}`);
1634
+ if (result.error) return { error: result.error };
1635
+ tasks.push({ ...task, agent: result.name! });
1636
+ }
1637
+ params = { ...params, tasks };
1638
+ }
1639
+ if (params.chain) {
1640
+ const chain: ChainStep[] = [];
1641
+ for (let index = 0; index < params.chain.length; index++) {
1642
+ const step = params.chain[index]!;
1643
+ if (isParallelStep(step)) {
1644
+ const parallel: typeof step.parallel = [];
1645
+ for (let taskIndex = 0; taskIndex < step.parallel.length; taskIndex++) {
1646
+ const task = step.parallel[taskIndex]!;
1647
+ const result = resolve(task.agent, `step ${index + 1}, task ${taskIndex + 1}`);
1648
+ if (result.error) return { error: result.error };
1649
+ parallel.push({ ...task, agent: result.name! });
1650
+ }
1651
+ chain.push({ ...step, parallel });
1652
+ continue;
1653
+ }
1654
+ if (isDynamicParallelStep(step)) {
1655
+ const result = resolve(step.parallel.agent, `step ${index + 1}`);
1656
+ if (result.error) return { error: result.error };
1657
+ chain.push({ ...step, parallel: { ...step.parallel, agent: result.name! } });
1658
+ continue;
1659
+ }
1660
+ if ("agent" in step && typeof step.agent === "string") {
1661
+ const result = resolve(step.agent, `step ${index + 1}`);
1662
+ if (result.error) return { error: result.error };
1663
+ chain.push({ ...step, agent: result.name! });
1664
+ continue;
1665
+ }
1666
+ chain.push(step);
1667
+ }
1668
+ params = { ...params, chain };
1669
+ }
1670
+ return { params };
1671
+ }
1672
+
1490
1673
  function validateExecutionInput(
1491
1674
  params: SubagentParamsLike,
1492
1675
  agents: AgentConfig[],
@@ -1699,10 +1882,14 @@ function applySingleAgentLaunchDefaults(params: SubagentParamsLike, agents: Agen
1699
1882
  };
1700
1883
  }
1701
1884
 
1702
- function resolveForegroundTimeout(params: SubagentParamsLike): { timeoutMs?: number; error?: string } {
1885
+ export const DEFAULT_FOREGROUND_TIMEOUT_MS = 30 * 60 * 1000;
1886
+
1887
+ function resolveForegroundTimeout(params: SubagentParamsLike, defaultTimeoutMs?: number): { timeoutMs?: number; error?: string } {
1703
1888
  const rawTimeout = params.timeoutMs;
1704
1889
  const rawMaxRuntime = params.maxRuntimeMs;
1705
- if (rawTimeout === undefined && rawMaxRuntime === undefined) return {};
1890
+ if (rawTimeout === undefined && rawMaxRuntime === undefined) {
1891
+ return defaultTimeoutMs === undefined ? {} : { timeoutMs: defaultTimeoutMs };
1892
+ }
1706
1893
  for (const [name, value] of [["timeoutMs", rawTimeout], ["maxRuntimeMs", rawMaxRuntime]] as const) {
1707
1894
  if (value === undefined) continue;
1708
1895
  if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
@@ -2044,19 +2231,20 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2044
2231
  };
2045
2232
  }
2046
2233
  const id = randomUUID();
2234
+ const parentModel = data.parentModel;
2047
2235
  const asyncCtx = {
2048
2236
  pi: deps.pi,
2049
2237
  cwd: ctx.cwd,
2050
2238
  currentSessionId: deps.state.currentSessionId!,
2051
2239
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
2052
- currentModelProvider: ctx.model?.provider,
2053
- currentModel: ctx.model,
2240
+ currentModelProvider: parentModel?.provider,
2241
+ currentModel: parentModel,
2054
2242
  modelScope: data.modelScope,
2055
2243
  interactive: ctx.hasUI,
2056
2244
  };
2057
2245
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
2058
2246
  const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
2059
- const currentProvider = ctx.model?.provider;
2247
+ const currentProvider = parentModel?.provider;
2060
2248
  const controlIntercomTarget = intercomBridge.active ? intercomBridge.orchestratorTarget : undefined;
2061
2249
  const childIntercomTarget = intercomBridge.active ? (agent: string, index: number) => resolveSubagentIntercomTarget(id, agent, index) : undefined;
2062
2250
 
@@ -2111,6 +2299,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2111
2299
  timeoutMs: data.timeoutMs,
2112
2300
  turnBudget: data.turnBudget,
2113
2301
  toolBudget: data.toolBudget,
2302
+ usageBudget: data.usageBudget,
2114
2303
  configToolBudget: data.configToolBudget,
2115
2304
  capabilityCeiling: data.capabilityCeiling,
2116
2305
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
@@ -2153,6 +2342,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2153
2342
  timeoutMs: data.timeoutMs,
2154
2343
  turnBudget: data.turnBudget,
2155
2344
  toolBudget: data.toolBudget,
2345
+ usageBudget: data.usageBudget,
2156
2346
  configToolBudget: data.configToolBudget,
2157
2347
  capabilityCeiling: data.capabilityCeiling,
2158
2348
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
@@ -2170,11 +2360,10 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2170
2360
  }
2171
2361
  const rawOutput = params.output !== undefined ? params.output : a.output;
2172
2362
  const effectiveOutput = normalizeSingleOutputOverride(rawOutput, a.output);
2173
- const effectiveOutputMode = params.outputMode ?? "inline";
2174
2363
  const normalizedSkills = normalizeSkillInput(params.skill);
2175
2364
  const skills = normalizedSkills === false ? [] : normalizedSkills;
2176
2365
  const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
2177
- const modelOverride = resolveEffectiveSubagentModel(params.model as string | undefined, a.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope });
2366
+ const modelOverride = resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, { scope: data.modelScope });
2178
2367
  return executeAsyncSingle(id, {
2179
2368
  agent: params.agent!,
2180
2369
  task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
@@ -2192,7 +2381,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2192
2381
  context: contextPolicy.contextForAgent(params.agent!),
2193
2382
  skills,
2194
2383
  output: effectiveOutput,
2195
- outputMode: effectiveOutputMode,
2384
+ outputMode: params.outputMode,
2196
2385
  outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
2197
2386
  modelOverride,
2198
2387
  thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
@@ -2211,6 +2400,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2211
2400
  timeoutMs: data.timeoutMs,
2212
2401
  turnBudget: data.turnBudget,
2213
2402
  toolBudget: data.toolBudget,
2403
+ usageBudget: data.usageBudget,
2214
2404
  configToolBudget: data.configToolBudget,
2215
2405
  capabilityCeiling: data.capabilityCeiling,
2216
2406
  });
@@ -2246,11 +2436,12 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
2246
2436
  const chainSkills = normalized === false ? [] : (normalized ?? []);
2247
2437
  const chain = wrapChainTasksForFork(params.chain as ChainStep[], contextPolicy);
2248
2438
  const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
2439
+ const chainCtx = normalizeParentModel(ctx.model) || !data.parentModel ? ctx : { ...ctx, model: data.parentModel };
2249
2440
  const chainResult = await executeChain({
2250
2441
  chain,
2251
2442
  task: params.task,
2252
2443
  agents,
2253
- ctx,
2444
+ ctx: chainCtx,
2254
2445
  modelScope: data.modelScope,
2255
2446
  intercomEvents: deps.pi.events,
2256
2447
  signal,
@@ -2284,8 +2475,18 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
2284
2475
  timeoutMs: data.timeoutMs,
2285
2476
  deadlineAt: data.deadlineAt,
2286
2477
  turnBudget: data.turnBudget,
2287
- onDetachedExit: (index, result) => updateRememberedForegroundChild(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, index, result, events: deps.pi.events }),
2478
+ onDetachedExit: (index, result) => {
2479
+ try {
2480
+ updateRememberedForegroundChild(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, index, result, events: deps.pi.events });
2481
+ } finally {
2482
+ removeForegroundControlIfIdle(deps.state, runId);
2483
+ }
2484
+ },
2485
+ onForegroundChildSettled: () => {
2486
+ removeForegroundControlIfIdle(deps.state, runId);
2487
+ },
2288
2488
  toolBudget: data.toolBudget,
2489
+ usageBudget: data.usageBudget,
2289
2490
  configToolBudget: data.configToolBudget,
2290
2491
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
2291
2492
  capabilityCeiling: data.capabilityCeiling,
@@ -2300,13 +2501,14 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
2300
2501
  };
2301
2502
  }
2302
2503
  const id = randomUUID();
2504
+ const parentModel = data.parentModel;
2303
2505
  const asyncCtx = {
2304
2506
  pi: deps.pi,
2305
2507
  cwd: ctx.cwd,
2306
2508
  currentSessionId: deps.state.currentSessionId!,
2307
2509
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
2308
- currentModelProvider: ctx.model?.provider,
2309
- currentModel: ctx.model,
2510
+ currentModelProvider: parentModel?.provider,
2511
+ currentModel: parentModel,
2310
2512
  modelScope: data.modelScope,
2311
2513
  interactive: ctx.hasUI,
2312
2514
  };
@@ -2344,20 +2546,22 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
2344
2546
  timeoutMs: data.timeoutMs,
2345
2547
  turnBudget: data.turnBudget,
2346
2548
  toolBudget: data.toolBudget,
2549
+ usageBudget: data.usageBudget,
2347
2550
  configToolBudget: data.configToolBudget,
2348
2551
  capabilityCeiling: data.capabilityCeiling,
2349
2552
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
2350
2553
  });
2351
2554
  }
2352
2555
 
2353
- const rawChainDetails = chainResult.details ? { ...chainResult.details, runId } : undefined;
2556
+ const rawChainDetails = chainResult.details ? { ...chainResult.details, runId, timeoutMs: data.timeoutMs } : undefined;
2354
2557
  if (foregroundControl && rawChainDetails) {
2355
2558
  updateForegroundNestedProjection(foregroundControl);
2356
2559
  attachRootChildrenToSteps(runId, rawChainDetails.results, foregroundControl.nestedChildren);
2357
2560
  rawChainDetails.totalCost = sumResultsCost(rawChainDetails.results);
2561
+ rawChainDetails.usageBudget = usageBudgetState(data.usageBudget, rawChainDetails.totalCost);
2358
2562
  }
2359
2563
  const chainDetails = rawChainDetails ? compactForegroundDetails(rawChainDetails) : undefined;
2360
- if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, results: chainDetails.results });
2564
+ if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, results: chainDetails.results, checkpoint: chainDetails.checkpoint });
2361
2565
  const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached)
2362
2566
  ? await maybeBuildForegroundIntercomReceipt({
2363
2567
  pi: deps.pi,
@@ -2406,6 +2610,7 @@ interface ForegroundParallelRunInput {
2406
2610
  waitToolEnabled?: boolean;
2407
2611
  availableModels: ModelInfo[];
2408
2612
  modelScope?: ModelScopeConfig;
2613
+ parentModel?: ParentModel;
2409
2614
  modelOverrides: (string | undefined)[];
2410
2615
  behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
2411
2616
  firstProgressIndex: number;
@@ -2424,6 +2629,7 @@ interface ForegroundParallelRunInput {
2424
2629
  timeoutMs?: number;
2425
2630
  deadlineAt?: number;
2426
2631
  turnBudget?: ResolvedTurnBudget;
2632
+ usageBudget?: UsageBudgetConfig;
2427
2633
  toolBudgets: (ResolvedToolBudget | undefined)[];
2428
2634
  agentContract?: AgentContract;
2429
2635
  }
@@ -2476,6 +2682,19 @@ function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string,
2476
2682
  : path.join(artifactsDir, "outputs", runId);
2477
2683
  }
2478
2684
 
2685
+ /**
2686
+ * Generated per-run durable output path for foreground single runs when the
2687
+ * caller omits `output` (and does not set `output: false`):
2688
+ * `<singleRunOutputBaseDir>/<runId>/result.md`. The base is the configured
2689
+ * `singleRunOutputBaseDir` when present, otherwise `{artifactsDir}/outputs`.
2690
+ */
2691
+ function resolveGeneratedSingleOutputPath(deps: ExecutorDeps, artifactsDir: string, runId: string): string {
2692
+ const base = deps.config.singleRunOutputBaseDir
2693
+ ? path.resolve(deps.expandTilde(deps.config.singleRunOutputBaseDir))
2694
+ : path.join(artifactsDir, "outputs");
2695
+ return path.join(base, runId, "result.md");
2696
+ }
2697
+
2479
2698
  function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string): string | undefined {
2480
2699
  for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) {
2481
2700
  const step = chain[stepIndex]!;
@@ -2530,6 +2749,10 @@ function finalizeParallelWorktreeHandoff(input: {
2530
2749
  interrupted: result.interrupted,
2531
2750
  detached: result.detached,
2532
2751
  state: result.stopped ? "stopped" : undefined,
2752
+ processSignal: result.processSignal,
2753
+ timedOut: result.timedOut,
2754
+ stopped: result.stopped,
2755
+ turnBudgetExceeded: result.turnBudgetExceeded,
2533
2756
  }),
2534
2757
  summary: resultSummaryForIntercom(result),
2535
2758
  ...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
@@ -2578,7 +2801,22 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
2578
2801
  for (let i = 0; i < input.tasks.length; i++) {
2579
2802
  input.sessionFileForTask(input.tasks[i]!.agent, i, input.modelOverrides[i]);
2580
2803
  }
2804
+ const completedResults: SingleResult[] = [];
2805
+ if (input.foregroundControl) retainForegroundSchedulingOwner(input.foregroundControl);
2581
2806
  return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
2807
+ const budgetState = usageBudgetState(input.usageBudget, sumResultsCost(completedResults));
2808
+ if (budgetState?.exhausted) {
2809
+ return {
2810
+ index,
2811
+ agent: task.agent,
2812
+ task: input.taskTexts[index] ?? "(skipped)",
2813
+ exitCode: 1,
2814
+ messages: [],
2815
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 },
2816
+ error: usageBudgetExceededMessage(budgetState),
2817
+ skipped: true,
2818
+ } as SingleResult;
2819
+ }
2582
2820
  const behavior = input.behaviors[index];
2583
2821
  const effectiveSkills = behavior?.skills;
2584
2822
  const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
@@ -2597,10 +2835,14 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
2597
2835
  );
2598
2836
  const interruptController = new AbortController();
2599
2837
  if (input.foregroundControl) {
2838
+ const model = input.modelOverrides[index];
2839
+ const thinking = resolveEffectiveThinking(model, input.thinkingOverrideForTask(task.agent, index, model));
2600
2840
  beginForegroundChild(input.foregroundControl, {
2601
2841
  index,
2602
2842
  agent: task.agent,
2603
2843
  description: input.taskDescriptions[index],
2844
+ ...(model ? { model } : {}),
2845
+ ...(thinking ? { thinking } : {}),
2604
2846
  interrupt: () => {
2605
2847
  if (interruptController.signal.aborted) return false;
2606
2848
  interruptController.abort();
@@ -2611,7 +2853,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
2611
2853
  const structuredRuntime = task.outputSchema
2612
2854
  ? createStructuredOutputRuntime(task.outputSchema, path.join(input.artifactsDir, "structured-output", input.runId))
2613
2855
  : undefined;
2614
- return runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
2856
+ let detachedReceipt = false;
2857
+ const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
2615
2858
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
2616
2859
  context: input.contextPolicy.contextForAgent(task.agent),
2617
2860
  cwd: taskCwd,
@@ -2634,14 +2877,24 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
2634
2877
  capabilityCeiling: input.capabilityCeiling,
2635
2878
  controlConfig: input.controlConfig,
2636
2879
  onControlEvent: input.onControlEvent,
2637
- onDetachedExit: (result) => updateRememberedForegroundChild(input.state, { runId: input.runId, mode: "parallel", cwd: taskCwd, sessionId: input.parentSessionId, index, result, events: input.intercomEvents }),
2880
+ onDetachedExit: (result) => {
2881
+ try {
2882
+ updateRememberedForegroundChild(input.state, { runId: input.runId, mode: "parallel", cwd: taskCwd, sessionId: input.parentSessionId, index, result, events: input.intercomEvents });
2883
+ } finally {
2884
+ try {
2885
+ if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
2886
+ } finally {
2887
+ removeForegroundControlIfIdle(input.state, input.runId);
2888
+ }
2889
+ }
2890
+ },
2638
2891
  intercomSessionName: input.childIntercomTarget?.(task.agent, index),
2639
2892
  orchestratorIntercomTarget: input.orchestratorIntercomTarget,
2640
2893
  nestedRoute: input.foregroundControl?.nestedRoute,
2641
2894
  modelOverride: input.modelOverrides[index],
2642
2895
  thinkingOverride: input.thinkingOverrideForTask(task.agent, index, input.modelOverrides[index]),
2643
2896
  availableModels: input.availableModels,
2644
- preferredModelProvider: input.ctx.model?.provider,
2897
+ preferredModelProvider: input.parentModel?.provider,
2645
2898
  modelScope: input.modelScope,
2646
2899
  skills: effectiveSkills === false ? [] : effectiveSkills,
2647
2900
  structuredOutput: structuredRuntime,
@@ -2675,10 +2928,24 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
2675
2928
  });
2676
2929
  }
2677
2930
  : undefined,
2931
+ }).then((result) => {
2932
+ detachedReceipt = result.detached === true;
2933
+ return result;
2678
2934
  }).finally(() => {
2679
- if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
2935
+ // mapConcurrent rejects before siblings settle, so every attached child
2936
+ // attempts idle removal after releasing its own control. Detached receipts
2937
+ // transfer both responsibilities to the authoritative exit callback.
2938
+ if (!detachedReceipt) {
2939
+ if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
2940
+ removeForegroundControlIfIdle(input.state, input.runId);
2941
+ }
2680
2942
  });
2681
- }, input.globalSemaphore);
2943
+ completedResults.push(result);
2944
+ return result;
2945
+ }, input.globalSemaphore, () => {
2946
+ if (input.foregroundControl) settleForegroundSchedulingOwner(input.foregroundControl);
2947
+ removeForegroundControlIfIdle(input.state, input.runId);
2948
+ });
2682
2949
  }
2683
2950
 
2684
2951
  async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
@@ -2746,7 +3013,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2746
3013
  if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
2747
3014
  }
2748
3015
 
2749
- const currentProvider = ctx.model?.provider;
3016
+ const parentModel = data.parentModel;
3017
+ const currentProvider = parentModel?.provider;
2750
3018
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
2751
3019
  let taskTexts = tasks.map((t) => t.task);
2752
3020
  const skillOverrides: (string[] | false | undefined)[] = tasks.map((t) =>
@@ -2761,7 +3029,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2761
3029
  ...(task.model !== undefined ? { model: task.model } : {}),
2762
3030
  }));
2763
3031
  const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
2764
- resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope }),
3032
+ resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, { scope: data.modelScope }),
2765
3033
  );
2766
3034
 
2767
3035
  if (params.clarify === true && ctx.hasUI) {
@@ -2796,7 +3064,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2796
3064
  for (let i = 0; i < result.behaviorOverrides.length; i++) {
2797
3065
  const override = result.behaviorOverrides[i];
2798
3066
  if (override?.model !== undefined) {
2799
- modelOverrides[i] = resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope });
3067
+ modelOverrides[i] = resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, { scope: data.modelScope });
2800
3068
  behaviorOverrides[i]!.model = override.model;
2801
3069
  }
2802
3070
  if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
@@ -2822,8 +3090,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2822
3090
  cwd: ctx.cwd,
2823
3091
  currentSessionId: deps.state.currentSessionId!,
2824
3092
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
2825
- currentModelProvider: ctx.model?.provider,
2826
- currentModel: ctx.model,
3093
+ currentModelProvider: parentModel?.provider,
3094
+ currentModel: parentModel,
2827
3095
  modelScope: data.modelScope,
2828
3096
  interactive: ctx.hasUI,
2829
3097
  };
@@ -2875,6 +3143,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2875
3143
  nestedRoute: data.nestedRoute,
2876
3144
  timeoutMs: data.timeoutMs,
2877
3145
  turnBudget: data.turnBudget,
3146
+ usageBudget: data.usageBudget,
2878
3147
  toolBudget: data.toolBudget,
2879
3148
  configToolBudget: data.configToolBudget,
2880
3149
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
@@ -2884,8 +3153,19 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2884
3153
 
2885
3154
  const behaviors = tasks.map((task, index) => {
2886
3155
  let behavior = suppressProgressForReadOnlyTask(resolveStepBehavior(agentConfigs[index]!, behaviorOverrides[index]!), taskTexts[index]);
2887
- if (behaviorOverrides[index]?.output === undefined && typeof behavior.output === "string" && !path.isAbsolute(behavior.output)) {
2888
- behavior = { ...behavior, output: path.join("parallel-0", `${index}-${task.agent}`, behavior.output) };
3156
+ if (behaviorOverrides[index]?.output === undefined) {
3157
+ if (typeof behavior.output === "string" && !path.isAbsolute(behavior.output)) {
3158
+ behavior = { ...behavior, output: path.join("parallel-0", `${index}-${task.agent}`, behavior.output) };
3159
+ } else if (behavior.output === false && behaviorOverrides[index]?.output !== false && behaviorOverrides[index]?.output !== "false") {
3160
+ // Omitted output: generate a collision-safe per-task durable path in the
3161
+ // existing per-task namespace (parallel-0/<index>-<agent>/result.md).
3162
+ behavior = { ...behavior, output: path.join("parallel-0", `${index}-${task.agent}`, "result.md") };
3163
+ }
3164
+ }
3165
+ // Omitted outputMode resolves to file-only whenever an output path is active;
3166
+ // explicit `outputMode: "inline"` is preserved exactly.
3167
+ if (behaviorOverrides[index]?.outputMode === undefined && typeof behavior.output === "string") {
3168
+ behavior = { ...behavior, outputMode: "file-only" };
2889
3169
  }
2890
3170
  return behavior;
2891
3171
  });
@@ -2958,6 +3238,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2958
3238
  progressDir: parallelProgressDir,
2959
3239
  availableModels,
2960
3240
  modelScope: data.modelScope,
3241
+ parentModel,
2961
3242
  modelOverrides,
2962
3243
  behaviors,
2963
3244
  firstProgressIndex: parallelProgressPrecreated ? -1 : firstProgressIndex,
@@ -2978,6 +3259,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2978
3259
  timeoutMs: data.timeoutMs,
2979
3260
  deadlineAt,
2980
3261
  turnBudget: data.turnBudget,
3262
+ usageBudget: data.usageBudget,
2981
3263
  toolBudgets,
2982
3264
  agentContract: params.agentContract,
2983
3265
  });
@@ -3001,14 +3283,17 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3001
3283
  handoff = finalizeParallelWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: effectiveCwd, tasks, results });
3002
3284
  }
3003
3285
  const interrupted = results.find((result) => result.interrupted);
3286
+ const totalCost = sumResultsCost(results);
3004
3287
  const details = compactForegroundDetails({
3005
3288
  mode: "parallel",
3006
3289
  runId,
3290
+ timeoutMs: data.timeoutMs,
3007
3291
  results,
3008
3292
  progress: params.includeProgress ? allProgress : undefined,
3009
3293
  artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
3010
3294
  totalChildUsage: sumResultsUsage(results),
3011
- totalCost: sumResultsCost(results),
3295
+ totalCost,
3296
+ usageBudget: usageBudgetState(data.usageBudget, totalCost),
3012
3297
  ...(handoff?.reference ? { parallelHandoff: handoff.reference } : {}),
3013
3298
  });
3014
3299
  rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
@@ -3046,21 +3331,12 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3046
3331
  const worktreeSuffix = handoff?.suffix ?? "";
3047
3332
  const ok = results.filter((result) => result.exitCode === 0).length;
3048
3333
  const downgradeNote = backgroundRequestedWhileClarifying ? " (background requested, but clarify kept this run foreground)" : "";
3049
- const aggregatedOutput = aggregateParallelOutputs(
3050
- results.map((result) => ({
3051
- agent: result.agent,
3052
- output: result.truncation?.text || getSingleResultOutput(result),
3053
- exitCode: result.exitCode,
3054
- error: result.error,
3055
- timedOut: result.timedOut,
3056
- })),
3057
- (i, agent) => `=== Task ${i + 1}: ${agent} ===`,
3058
- );
3334
+ const perChildResults = results.map((result, i) => formatParallelChildResult(result, i)).join("\n\n");
3059
3335
 
3060
3336
  const summary = `${ok}/${results.length} succeeded${downgradeNote}`;
3061
3337
  const fullContent = worktreeSuffix
3062
- ? `${summary}\n\n${aggregatedOutput}\n\n${worktreeSuffix}`
3063
- : `${summary}\n\n${aggregatedOutput}`;
3338
+ ? `${summary}\n\n${perChildResults}\n\n${worktreeSuffix}`
3339
+ : `${summary}\n\n${perChildResults}`;
3064
3340
 
3065
3341
  return {
3066
3342
  content: [{ type: "text", text: fullContent }],
@@ -3105,13 +3381,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3105
3381
  const effectiveToolBudget = resolveEffectiveToolBudget({ runBudget: data.toolBudget, agentBudget: agentConfig.toolBudget, configBudget: data.configToolBudget });
3106
3382
  if (effectiveToolBudget.error) return toExecutionErrorResult(params, new Error(effectiveToolBudget.error), data.contextPolicy.contextSummary);
3107
3383
 
3108
- const currentProvider = ctx.model?.provider;
3384
+ const parentModel = data.parentModel;
3385
+ const currentProvider = parentModel?.provider;
3109
3386
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
3110
3387
  let task = params.task ?? "";
3111
3388
  let modelOverride: string | undefined = resolveEffectiveSubagentModel(
3112
3389
  params.model as string | undefined,
3113
3390
  agentConfig.model,
3114
- ctx.model,
3391
+ parentModel,
3115
3392
  availableModels,
3116
3393
  currentProvider,
3117
3394
  { scope: data.modelScope },
@@ -3119,7 +3396,6 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3119
3396
  let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
3120
3397
  const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
3121
3398
  let effectiveOutput = normalizeSingleOutputOverride(rawOutput, agentConfig.output);
3122
- const effectiveOutputMode = params.outputMode ?? "inline";
3123
3399
  const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
3124
3400
  const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
3125
3401
 
@@ -3151,7 +3427,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3151
3427
 
3152
3428
  task = result.templates[0]!;
3153
3429
  const override = result.behaviorOverrides[0];
3154
- if (override?.model !== undefined) modelOverride = resolveEffectiveSubagentModel(override.model, agentConfig.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope });
3430
+ if (override?.model !== undefined) modelOverride = resolveEffectiveSubagentModel(override.model, agentConfig.model, parentModel, availableModels, currentProvider, { scope: data.modelScope });
3155
3431
  if (override?.output !== undefined) effectiveOutput = normalizeSingleOutputOverride(override.output, agentConfig.output);
3156
3432
  if (override?.skills !== undefined) skillOverride = override.skills;
3157
3433
 
@@ -3169,8 +3445,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3169
3445
  cwd: ctx.cwd,
3170
3446
  currentSessionId: deps.state.currentSessionId!,
3171
3447
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
3172
- currentModelProvider: ctx.model?.provider,
3173
- currentModel: ctx.model,
3448
+ currentModelProvider: parentModel?.provider,
3449
+ currentModel: parentModel,
3174
3450
  modelScope: data.modelScope,
3175
3451
  interactive: ctx.hasUI,
3176
3452
  };
@@ -3191,7 +3467,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3191
3467
  context: contextPolicy.contextForAgent(params.agent!),
3192
3468
  skills: skillOverride === false ? [] : skillOverride,
3193
3469
  output: effectiveOutput,
3194
- outputMode: effectiveOutputMode,
3470
+ outputMode: params.outputMode,
3195
3471
  outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
3196
3472
  modelOverride,
3197
3473
  thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
@@ -3210,6 +3486,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3210
3486
  timeoutMs: data.timeoutMs,
3211
3487
  turnBudget: data.turnBudget,
3212
3488
  toolBudget: effectiveToolBudget.toolBudget,
3489
+ usageBudget: data.usageBudget,
3213
3490
  allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
3214
3491
  });
3215
3492
  }
@@ -3219,7 +3496,16 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3219
3496
  task = wrapForkTask(task);
3220
3497
  }
3221
3498
  const cleanTask = task;
3222
- const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, effectiveCwd, resolveSingleRunOutputBaseDir(deps, artifactsDir, runId));
3499
+ // Omitted output resolves to a generated per-run durable path
3500
+ // (`<singleRunOutputBaseDir>/<runId>/result.md`); omitted outputMode resolves to
3501
+ // file-only whenever an output path is active. Explicit paths, `output: false`,
3502
+ // and explicit `outputMode: "inline"` are preserved exactly.
3503
+ const outputPath = typeof effectiveOutput === "string"
3504
+ ? resolveSingleOutputPath(effectiveOutput, ctx.cwd, effectiveCwd, resolveSingleRunOutputBaseDir(deps, artifactsDir, runId))
3505
+ : effectiveOutput === false
3506
+ ? undefined
3507
+ : resolveGeneratedSingleOutputPath(deps, artifactsDir, runId);
3508
+ const effectiveOutputMode = params.outputMode ?? (outputPath ? "file-only" : "inline");
3223
3509
  const validationError = validateFileOnlyOutputMode(effectiveOutputMode, outputPath, `Single run (${params.agent})`);
3224
3510
  if (validationError) {
3225
3511
  return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
@@ -3236,17 +3522,22 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3236
3522
  effectiveSkills = skillOverride;
3237
3523
  }
3238
3524
  const interruptController = new AbortController();
3525
+ let detachForeground: ((reason?: string) => boolean) | undefined;
3239
3526
  const foregroundControl = deps.state.foregroundControls.get(runId);
3240
3527
  if (foregroundControl) {
3528
+ const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride));
3241
3529
  beginForegroundChild(foregroundControl, {
3242
3530
  index: 0,
3243
3531
  agent: params.agent!,
3244
3532
  description: foregroundControl.description,
3533
+ ...(modelOverride ? { model: modelOverride } : {}),
3534
+ ...(thinking ? { thinking } : {}),
3245
3535
  interrupt: () => {
3246
3536
  if (interruptController.signal.aborted) return false;
3247
3537
  interruptController.abort();
3248
3538
  return true;
3249
3539
  },
3540
+ detach: () => detachForeground?.("user request") === true,
3250
3541
  });
3251
3542
  }
3252
3543
 
@@ -3296,7 +3587,29 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3296
3587
  agentContract: params.agentContract,
3297
3588
  acceptance: params.acceptance,
3298
3589
  acceptanceContext: { mode: "single" },
3299
- onDetachedExit: (result) => updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events }),
3590
+ onDetachReady: (detach) => {
3591
+ detachForeground = detach;
3592
+ },
3593
+ onDetachedExit: (result) => {
3594
+ try {
3595
+ try {
3596
+ updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events });
3597
+ } catch {
3598
+ // Remembered foreground state is best-effort; run history and cleanup must still complete.
3599
+ }
3600
+ } finally {
3601
+ try {
3602
+ if (!artifactConfig.enabled) cleanupStructuredOutputRuntime(structuredRuntime);
3603
+ } finally {
3604
+ try {
3605
+ if (foregroundControl) finishForegroundChild(foregroundControl, 0);
3606
+ } finally {
3607
+ removeForegroundControlIfIdle(deps.state, runId);
3608
+ }
3609
+ }
3610
+ }
3611
+ recordRun(params.agent!, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0);
3612
+ },
3300
3613
  timeoutMs: data.timeoutMs,
3301
3614
  deadlineAt,
3302
3615
  turnBudget: data.turnBudget,
@@ -3306,10 +3619,17 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3306
3619
  allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
3307
3620
  });
3308
3621
  } finally {
3309
- if (!artifactConfig.enabled) cleanupStructuredOutputRuntime(structuredRuntime);
3622
+ // An attached runSync rejection still owns its child and structured runtime.
3623
+ // A successful detached receipt transfers both to onDetachedExit while the
3624
+ // authoritative completion remains live.
3625
+ if (!r?.detached) {
3626
+ if (!artifactConfig.enabled) cleanupStructuredOutputRuntime(structuredRuntime);
3627
+ if (foregroundControl) finishForegroundChild(foregroundControl, 0);
3628
+ }
3629
+ }
3630
+ if (!r.detached) {
3631
+ recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
3310
3632
  }
3311
- if (foregroundControl) finishForegroundChild(foregroundControl, 0);
3312
- recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
3313
3633
 
3314
3634
  if (r.progress) allProgress.push(r.progress);
3315
3635
  if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
@@ -3324,14 +3644,17 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3324
3644
  savedPath: r.savedOutputPath,
3325
3645
  outputReference: r.outputReference,
3326
3646
  saveError: r.outputSaveError,
3647
+ error: r.error,
3327
3648
  });
3328
3649
  if (foregroundControl) {
3329
3650
  updateForegroundNestedProjection(foregroundControl);
3330
3651
  attachRootChildrenToSteps(runId, [r], foregroundControl.nestedChildren);
3331
3652
  }
3653
+ const totalCost = sumResultsCost([r]);
3332
3654
  const details = compactForegroundDetails({
3333
3655
  mode: "single",
3334
3656
  runId,
3657
+ timeoutMs: data.timeoutMs,
3335
3658
  results: [r],
3336
3659
  ...(data.turnBudget ? { turnBudget: data.turnBudget } : {}),
3337
3660
  ...(effectiveToolBudget.toolBudget ? { toolBudget: effectiveToolBudget.toolBudget } : {}),
@@ -3339,7 +3662,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3339
3662
  artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
3340
3663
  truncation: r.truncation,
3341
3664
  totalChildUsage: sumResultsUsage([r]),
3342
- totalCost: sumResultsCost([r]),
3665
+ totalCost,
3666
+ usageBudget: usageBudgetState(data.usageBudget, totalCost),
3343
3667
  });
3344
3668
  rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
3345
3669
 
@@ -3460,6 +3784,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3460
3784
  const requestCwd = resolveRequestedCwd(ctx.cwd, requestParams.cwd);
3461
3785
  const paramsWithResolvedCwd = requestParams.cwd === undefined ? requestParams : { ...requestParams, cwd: requestCwd };
3462
3786
  const action = paramsWithResolvedCwd.action;
3787
+ let requestParentModel: ParentModel | undefined;
3788
+ try {
3789
+ requestParentModel = rememberParentModel(deps.state, resolveCurrentSessionId(ctx.sessionManager), ctx.model);
3790
+ } catch (error) {
3791
+ if (action?.toLowerCase() !== "doctor") throw error;
3792
+ requestParentModel = normalizeParentModel(ctx.model);
3793
+ }
3463
3794
  if (action) {
3464
3795
  if ((WATCHDOG_TOOL_ACTIONS as readonly string[]).includes(action)) {
3465
3796
  if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
@@ -3616,8 +3947,61 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3616
3947
  }
3617
3948
  return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots }));
3618
3949
  }
3950
+
3951
+ if (action === "approve-checkpoint" || action === "reject-checkpoint") {
3952
+ deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
3953
+ const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
3954
+ if (!targetRunId && !paramsWithResolvedCwd.dir) {
3955
+ return { content: [{ type: "text", text: `action='${action}' requires id or dir.` }], isError: true, details: { mode: "management", results: [] } };
3956
+ }
3957
+ try {
3958
+ const resolved = targetRunId ? resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }) : undefined;
3959
+ const decision = action === "approve-checkpoint" ? "approved" : "rejected";
3960
+ if (resolved?.kind === "foreground") {
3961
+ const run = deps.state.foregroundRuns?.get(resolved.id);
3962
+ if (!run?.checkpoint || run.checkpoint.status !== "pending") {
3963
+ return { content: [{ type: "text", text: `Run '${resolved.id}' is not paused at an approval checkpoint.` }], isError: true, details: { mode: "management", results: [] } };
3964
+ }
3965
+ if (deps.state.currentSessionId && run.sessionId !== deps.state.currentSessionId) {
3966
+ return { content: [{ type: "text", text: `Run '${resolved.id}' was not found in the active session.` }], isError: true, details: { mode: "management", results: [] } };
3967
+ }
3968
+ const decidedAt = Date.now();
3969
+ const checkpoint = { ...run.checkpoint, status: decision, ...(decision === "approved" ? { approvedAt: decidedAt } : { rejectedAt: decidedAt }) };
3970
+ run.checkpoint = checkpoint;
3971
+ run.updatedAt = decidedAt;
3972
+ return {
3973
+ content: [{ type: "text", text: `Checkpoint '${checkpoint.name}' ${decision} for foreground run ${resolved.id}.` }],
3974
+ details: { mode: "management", results: [], checkpoint },
3975
+ };
3976
+ }
3977
+ const location = paramsWithResolvedCwd.dir
3978
+ ? resolveAsyncRunLocation(paramsWithResolvedCwd, ASYNC_DIR, RESULTS_DIR)
3979
+ : resolved?.kind === "async"
3980
+ ? resolved.location
3981
+ : undefined;
3982
+ if (!location?.asyncDir) {
3983
+ return { content: [{ type: "text", text: `action='${action}' targets paused foreground or async checkpoints. No matching run found.` }], isError: true, details: { mode: "management", results: [] } };
3984
+ }
3985
+ const status = readStatus(location.asyncDir);
3986
+ const runId = status?.runId ?? location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir);
3987
+ if (!status?.checkpoint || status.state !== "paused") {
3988
+ return { content: [{ type: "text", text: `Run '${runId}' is not paused at an approval checkpoint.` }], isError: true, details: { mode: "management", results: [] } };
3989
+ }
3990
+ if (deps.state.currentSessionId && status.sessionId !== deps.state.currentSessionId) {
3991
+ return { content: [{ type: "text", text: `Run '${runId}' was not found in the active session.` }], isError: true, details: { mode: "management", results: [] } };
3992
+ }
3993
+ deliverCheckpointDecisionRequest({ asyncDir: location.asyncDir, decision, source: "subagent-action", ...(paramsWithResolvedCwd.message ? { reason: paramsWithResolvedCwd.message } : {}) });
3994
+ return {
3995
+ content: [{ type: "text", text: `Checkpoint '${status.checkpoint.name}' ${decision} for run ${runId}.` }],
3996
+ details: { mode: "management", results: [], checkpoint: { ...status.checkpoint, status: decision } },
3997
+ };
3998
+ } catch (error) {
3999
+ const text = error instanceof Error ? error.message : String(error);
4000
+ return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
4001
+ }
4002
+ }
3619
4003
  if (action === "resume") {
3620
- return resumeAsyncRun({ params: paramsWithResolvedCwd, requestCwd, ctx, deps });
4004
+ return resumeAsyncRun({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel });
3621
4005
  }
3622
4006
  if (action === "steer") {
3623
4007
  deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
@@ -3640,7 +4024,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3640
4024
  ? {}
3641
4025
  : {
3642
4026
  recover: ({ absoluteDeadlineAt, ...limits }) =>
3643
- resumeAsyncRun({ params: { ...limits, action: "resume", id: runId, message }, requestCwd, ctx, deps, absoluteDeadlineAt }),
4027
+ resumeAsyncRun({ params: { ...limits, action: "resume", id: runId, message }, requestCwd, ctx, deps, parentModel: requestParentModel, absoluteDeadlineAt }),
3644
4028
  }
3645
4029
  ),
3646
4030
  });
@@ -3677,6 +4061,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3677
4061
  requestCwd,
3678
4062
  ctx,
3679
4063
  deps,
4064
+ parentModel: requestParentModel,
3680
4065
  absoluteDeadlineAt,
3681
4066
  }),
3682
4067
  }
@@ -3684,7 +4069,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3684
4069
  });
3685
4070
  }
3686
4071
  if (action === "append-step") {
3687
- return appendStepToAsyncChain({ params: paramsWithResolvedCwd, requestCwd, ctx, deps });
4072
+ return appendStepToAsyncChain({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel });
3688
4073
  }
3689
4074
  if (action === "schedule" || action === "schedule-list" || action === "schedule-status" || action === "schedule-cancel") {
3690
4075
  if (!deps.handleScheduledRunAction) {
@@ -3790,6 +4175,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3790
4175
  ...ctx,
3791
4176
  cwd: requestCwd,
3792
4177
  config: deps.config,
4178
+ currentSessionId: deps.state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined,
3793
4179
  });
3794
4180
  }
3795
4181
 
@@ -3827,17 +4213,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3827
4213
  if (runToolBudget.error) return buildRequestedModeError(effectiveParams, runToolBudget.error);
3828
4214
  const configToolBudget = resolveToolBudget(deps.config.toolBudget, "config.toolBudget");
3829
4215
  if (configToolBudget.error) return buildRequestedModeError(effectiveParams, configToolBudget.error);
4216
+ const usageBudget = validateUsageBudgetConfig(effectiveParams.usageBudget ?? deps.config.usageBudget, effectiveParams.usageBudget ? "usageBudget" : "config.usageBudget");
4217
+ if (usageBudget.error) return buildRequestedModeError(effectiveParams, usageBudget.error);
3830
4218
 
3831
4219
  const scope: AgentScope = resolveExecutionAgentScope(effectiveParams.agentScope);
3832
4220
  const effectiveCwd = effectiveParams.cwd ?? ctx.cwd;
3833
4221
  const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
3834
- deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
3835
4222
  const discovered = deps.discoverAgents(effectiveCwd, scope);
3836
4223
  const discoveredAgents = discovered.agents;
4224
+ const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents);
4225
+ if (canonicalParams.error) return buildRequestedModeError(effectiveParams, canonicalParams.error);
4226
+ effectiveParams = canonicalParams.params!;
3837
4227
  const modelScope = discovered.modelScope;
3838
4228
  effectiveParams = applySingleAgentLaunchDefaults(effectiveParams, discoveredAgents);
3839
- const foregroundTimeout = resolveForegroundTimeout(effectiveParams);
3840
- if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
3841
4229
  const turnBudget = resolveTurnBudgetConfig(effectiveParams.turnBudget ?? deps.config.turnBudget);
3842
4230
  if (turnBudget.error) return buildRequestedModeError(effectiveParams, turnBudget.error);
3843
4231
  const contextPolicy = resolveAgentDefaultContextPolicy(effectiveParams, discoveredAgents);
@@ -3891,25 +4279,26 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3891
4279
  const forkThinkingDowngrades = new Map<number, string>();
3892
4280
  try {
3893
4281
  const forkAvailableModels = contextPolicy.usesFork ? ctx.modelRegistry.getAvailable().map(toModelInfo) : [];
4282
+ const parentModel = requestParentModel;
3894
4283
  prepareForkThinking = (agentName, index, modelOverride) => {
3895
4284
  const agentConfig = agents.find((agent) => agent.name === agentName);
3896
4285
  const primaryModel = resolveEffectiveSubagentModel(
3897
4286
  modelOverride,
3898
4287
  agentConfig?.model,
3899
- ctx.model,
4288
+ parentModel,
3900
4289
  forkAvailableModels,
3901
- ctx.model?.provider,
4290
+ parentModel?.provider,
3902
4291
  );
3903
4292
  const candidates = buildModelCandidates(
3904
4293
  primaryModel,
3905
4294
  agentConfig?.fallbackModels,
3906
4295
  forkAvailableModels,
3907
- ctx.model?.provider,
4296
+ parentModel?.provider,
3908
4297
  );
3909
4298
  forkThinkingRequirements.set(
3910
4299
  index,
3911
4300
  candidates.length === 0
3912
- || candidates.some((candidate) => forkedChildRequiresThinkingOff(candidate, forkAvailableModels, ctx.model?.provider)),
4301
+ || candidates.some((candidate) => forkedChildRequiresThinkingOff(candidate, forkAvailableModels, parentModel?.provider)),
3913
4302
  );
3914
4303
  };
3915
4304
  const forkContextResolver = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined, {
@@ -3923,11 +4312,16 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3923
4312
  const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
3924
4313
  const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
3925
4314
  const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
4315
+ const foregroundTimeout = resolveForegroundTimeout(
4316
+ effectiveParams,
4317
+ effectiveAsync ? undefined : DEFAULT_FOREGROUND_TIMEOUT_MS,
4318
+ );
4319
+ if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
3926
4320
  const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
3927
4321
 
3928
4322
  const artifactConfig: ArtifactConfig = {
3929
4323
  ...DEFAULT_ARTIFACT_CONFIG,
3930
- enabled: effectiveParams.artifacts !== false,
4324
+ enabled: effectiveParams.artifacts === true,
3931
4325
  dir: deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir,
3932
4326
  };
3933
4327
  const artifactsDir = getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
@@ -3980,7 +4374,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3980
4374
  if (chainBindingsError) return withResolvedContext(chainBindingsError, contextPolicy.contextSummary);
3981
4375
 
3982
4376
  const onUpdateWithContext = onUpdate
3983
- ? (r: AgentToolResult<Details>) => onUpdate(withResolvedContext(r, contextPolicy.contextSummary))
4377
+ ? (r: AgentToolResult<Details>) => onUpdate(withResolvedContext({
4378
+ ...r,
4379
+ details: { ...r.details, runId },
4380
+ }, contextPolicy.contextSummary))
3984
4381
  : undefined;
3985
4382
 
3986
4383
  const reservation = reserveSpawnBudget(
@@ -4015,10 +4412,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4015
4412
  timeoutMs: foregroundTimeout.timeoutMs,
4016
4413
  turnBudget: turnBudget.turnBudget,
4017
4414
  toolBudget: runToolBudget.toolBudget,
4415
+ usageBudget: usageBudget.budget,
4018
4416
  allowZeroToolBudget,
4019
4417
  configToolBudget: configToolBudget.toolBudget,
4020
4418
  contextPolicy,
4021
4419
  modelScope,
4420
+ parentModel: requestParentModel,
4022
4421
  parentSessionId: deps.state.currentSessionId,
4023
4422
  capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(deps.state.currentSessionId ?? undefined),
4024
4423
  };
@@ -4030,6 +4429,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4030
4429
  ? undefined
4031
4430
  : {
4032
4431
  runId,
4432
+ ...(deps.state.currentSessionId ? { sessionId: deps.state.currentSessionId } : {}),
4033
4433
  mode: foregroundMode,
4034
4434
  startedAt: Date.now(),
4035
4435
  updatedAt: Date.now(),
@@ -4039,6 +4439,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4039
4439
  description: foregroundDescription,
4040
4440
  currentActivityState: undefined,
4041
4441
  activeChildren: new Map(),
4442
+ // The outer executor owns scheduling until its finally block settles.
4443
+ schedulingOwners: 1,
4042
4444
  nestedRoute,
4043
4445
  interrupt: undefined,
4044
4446
  };
@@ -4136,11 +4538,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4136
4538
  return errorResult;
4137
4539
  } finally {
4138
4540
  if (foregroundControl) {
4139
- clearPendingForegroundControlNotices(deps.state, runId);
4140
- deps.state.foregroundControls.delete(runId);
4141
- if (deps.state.lastForegroundControlId === runId) {
4142
- deps.state.lastForegroundControlId = null;
4143
- }
4541
+ settleForegroundSchedulingOwner(foregroundControl);
4542
+ removeForegroundControlIfIdle(deps.state, runId);
4144
4543
  }
4145
4544
  }
4146
4545