@selesai/code 0.5.27 → 0.5.29

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 (310) hide show
  1. package/dist/cli/args.d.ts.map +1 -1
  2. package/dist/cli/args.js +9 -1
  3. package/dist/cli/args.js.map +1 -1
  4. package/dist/cli/config-selector.d.ts.map +1 -1
  5. package/dist/cli/config-selector.js +1 -1
  6. package/dist/cli/config-selector.js.map +1 -1
  7. package/dist/cli/credential-print.d.ts +23 -0
  8. package/dist/cli/credential-print.d.ts.map +1 -0
  9. package/dist/cli/credential-print.js +117 -0
  10. package/dist/cli/credential-print.js.map +1 -0
  11. package/dist/cli/startup-ui.d.ts.map +1 -1
  12. package/dist/cli/startup-ui.js +1 -1
  13. package/dist/cli/startup-ui.js.map +1 -1
  14. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  15. package/dist/core/agent-session-runtime.js +3 -0
  16. package/dist/core/agent-session-runtime.js.map +1 -1
  17. package/dist/core/agent-session.d.ts +12 -1
  18. package/dist/core/agent-session.d.ts.map +1 -1
  19. package/dist/core/agent-session.js +20 -14
  20. package/dist/core/agent-session.js.map +1 -1
  21. package/dist/core/compaction/compaction.d.ts.map +1 -1
  22. package/dist/core/compaction/compaction.js +11 -3
  23. package/dist/core/compaction/compaction.js.map +1 -1
  24. package/dist/core/extensions/runner.d.ts +1 -0
  25. package/dist/core/extensions/runner.d.ts.map +1 -1
  26. package/dist/core/extensions/runner.js +11 -0
  27. package/dist/core/extensions/runner.js.map +1 -1
  28. package/dist/core/extensions/types.d.ts +14 -1
  29. package/dist/core/extensions/types.d.ts.map +1 -1
  30. package/dist/core/extensions/types.js.map +1 -1
  31. package/dist/core/footer-data-provider.d.ts +10 -0
  32. package/dist/core/footer-data-provider.d.ts.map +1 -1
  33. package/dist/core/footer-data-provider.js +1 -1
  34. package/dist/core/footer-data-provider.js.map +1 -1
  35. package/dist/core/llama/provider.d.ts.map +1 -1
  36. package/dist/core/llama/provider.js +8 -3
  37. package/dist/core/llama/provider.js.map +1 -1
  38. package/dist/core/model-config.d.ts +30 -0
  39. package/dist/core/model-config.d.ts.map +1 -1
  40. package/dist/core/model-config.js +6 -0
  41. package/dist/core/model-config.js.map +1 -1
  42. package/dist/core/model-registry.d.ts.map +1 -1
  43. package/dist/core/model-registry.js +2 -2
  44. package/dist/core/model-registry.js.map +1 -1
  45. package/dist/core/model-resolver.d.ts +1 -0
  46. package/dist/core/model-resolver.d.ts.map +1 -1
  47. package/dist/core/model-resolver.js +20 -3
  48. package/dist/core/model-resolver.js.map +1 -1
  49. package/dist/core/model-runtime.d.ts +2 -0
  50. package/dist/core/model-runtime.d.ts.map +1 -1
  51. package/dist/core/model-runtime.js +5 -4
  52. package/dist/core/model-runtime.js.map +1 -1
  53. package/dist/core/package-manager.d.ts.map +1 -1
  54. package/dist/core/package-manager.js +13 -6
  55. package/dist/core/package-manager.js.map +1 -1
  56. package/dist/core/remote-catalog-provider.d.ts +1 -1
  57. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  58. package/dist/core/remote-catalog-provider.js +24 -11
  59. package/dist/core/remote-catalog-provider.js.map +1 -1
  60. package/dist/core/resource-loader.d.ts +15 -0
  61. package/dist/core/resource-loader.d.ts.map +1 -1
  62. package/dist/core/resource-loader.js +66 -9
  63. package/dist/core/resource-loader.js.map +1 -1
  64. package/dist/core/settings-manager.d.ts +1 -1
  65. package/dist/core/settings-manager.d.ts.map +1 -1
  66. package/dist/core/settings-manager.js.map +1 -1
  67. package/dist/core/system-prompt.d.ts.map +1 -1
  68. package/dist/core/system-prompt.js +1 -1
  69. package/dist/core/system-prompt.js.map +1 -1
  70. package/dist/core/tools/bash.d.ts +2 -0
  71. package/dist/core/tools/bash.d.ts.map +1 -1
  72. package/dist/core/tools/bash.js +34 -5
  73. package/dist/core/tools/bash.js.map +1 -1
  74. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  75. package/dist/core/tools/tool-definition-wrapper.js +3 -1
  76. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  77. package/dist/extensions/pi-intercom/CHANGELOG.md +249 -0
  78. package/dist/extensions/pi-intercom/README.md +102 -39
  79. package/dist/extensions/pi-intercom/broker/broker.ts +1233 -36
  80. package/dist/extensions/pi-intercom/broker/client.test.ts +83 -0
  81. package/dist/extensions/pi-intercom/broker/client.ts +315 -12
  82. package/dist/extensions/pi-intercom/broker/extension-state.ts +186 -0
  83. package/dist/extensions/pi-intercom/broker/extension.test.ts +387 -0
  84. package/dist/extensions/pi-intercom/broker/framing.test.ts +114 -0
  85. package/dist/extensions/pi-intercom/broker/framing.ts +82 -24
  86. package/dist/extensions/pi-intercom/broker/paths.test.ts +153 -0
  87. package/dist/extensions/pi-intercom/broker/paths.ts +117 -8
  88. package/dist/extensions/pi-intercom/broker/runtime-claim.test.ts +34 -0
  89. package/dist/extensions/pi-intercom/broker/runtime-claim.ts +21 -0
  90. package/dist/extensions/pi-intercom/broker/spawn.test.ts +160 -23
  91. package/dist/extensions/pi-intercom/broker/spawn.ts +113 -27
  92. package/dist/extensions/pi-intercom/config.test.ts +93 -0
  93. package/dist/extensions/pi-intercom/config.ts +55 -6
  94. package/dist/extensions/pi-intercom/cwd.test.ts +40 -0
  95. package/dist/extensions/pi-intercom/cwd.ts +31 -0
  96. package/dist/extensions/pi-intercom/extension-api.ts +44 -0
  97. package/dist/extensions/pi-intercom/format-context.test.ts +31 -0
  98. package/dist/extensions/pi-intercom/format-context.ts +32 -0
  99. package/dist/extensions/pi-intercom/index.ts +742 -145
  100. package/dist/extensions/pi-intercom/intercom.integration.test.ts +2646 -0
  101. package/dist/extensions/pi-intercom/package.json +15 -5
  102. package/dist/extensions/pi-intercom/reply-tracker.test.ts +134 -0
  103. package/dist/extensions/pi-intercom/reply-tracker.ts +31 -13
  104. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +13 -11
  105. package/dist/extensions/pi-intercom/test/inline-message.test.ts +184 -0
  106. package/dist/extensions/pi-intercom/test/overlay-width.test.ts +66 -0
  107. package/dist/extensions/pi-intercom/types.ts +94 -4
  108. package/dist/extensions/pi-intercom/ui/compose.ts +8 -4
  109. package/dist/extensions/pi-intercom/ui/inline-message.ts +61 -25
  110. package/dist/extensions/pi-intercom/ui/session-list.ts +7 -3
  111. package/dist/extensions/pi-subagents/CHANGELOG.md +85 -0
  112. package/dist/extensions/pi-subagents/LICENSE +21 -0
  113. package/dist/extensions/pi-subagents/README.md +146 -52
  114. package/dist/extensions/pi-subagents/agents/architect.md +1 -0
  115. package/dist/extensions/pi-subagents/agents/builder.md +1 -0
  116. package/dist/extensions/pi-subagents/package-lock.json +2 -2
  117. package/dist/extensions/pi-subagents/package.json +3 -3
  118. package/dist/extensions/pi-subagents/prompts/review-loop.md +1 -1
  119. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +21 -989
  120. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +256 -0
  121. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +430 -0
  122. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +144 -0
  123. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +282 -0
  124. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +71 -30
  125. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +4 -0
  126. package/dist/extensions/pi-subagents/src/agents/agents.ts +118 -9
  127. package/dist/extensions/pi-subagents/src/agents/skills.ts +14 -12
  128. package/dist/extensions/pi-subagents/src/api/delegation.ts +3 -0
  129. package/dist/extensions/pi-subagents/src/api/preflight.ts +16 -12
  130. package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +17 -1
  131. package/dist/extensions/pi-subagents/src/extension/index.ts +17 -7
  132. package/dist/extensions/pi-subagents/src/extension/rpc.ts +248 -6
  133. package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -6
  134. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +8 -7
  135. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +9 -4
  136. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +33 -4
  137. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +63 -16
  138. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +24 -13
  139. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +7 -5
  140. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
  141. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +48 -5
  142. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +68 -1
  143. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +29 -4
  144. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +29 -6
  145. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +29 -3
  146. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +372 -28
  147. package/dist/extensions/pi-subagents/src/runs/foreground/async-stop-action.ts +65 -0
  148. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +3 -3
  149. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +143 -43
  150. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +527 -247
  151. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +42 -0
  152. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +426 -123
  153. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +15 -7
  154. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +51 -19
  155. package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +3 -1
  156. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  157. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +44 -11
  158. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +8 -0
  159. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +97 -20
  160. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +43 -12
  161. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +52 -4
  162. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +19 -0
  163. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +45 -9
  164. package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +71 -0
  165. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +31 -1
  166. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +101 -0
  167. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +1 -1
  168. package/dist/extensions/pi-subagents/src/runs/shared/usage-budget.ts +65 -0
  169. package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +26 -1
  170. package/dist/extensions/pi-subagents/src/shared/settings.ts +17 -1
  171. package/dist/extensions/pi-subagents/src/shared/types.ts +155 -10
  172. package/dist/extensions/pi-subagents/src/shared/utils.ts +73 -4
  173. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +5 -0
  174. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +46 -4
  175. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +5 -3
  176. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +87 -24
  177. package/dist/extensions/pi-subagents/src/tui/fleet.ts +182 -9
  178. package/dist/extensions/pi-subagents/src/tui/render.ts +27 -28
  179. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +14 -7
  180. package/dist/extensions/pi-subagents/src/watchdog/review.ts +5 -4
  181. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +170 -17
  182. package/dist/extensions/pi-subagents/src/watchdog/scope.ts +62 -0
  183. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +41 -1
  184. package/dist/extensions/pi-subagents/src/watchdog/types.ts +10 -0
  185. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +2 -2
  186. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +348 -7
  187. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +84 -0
  188. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +4 -2
  189. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +51 -2
  190. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +26 -1
  191. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +48 -0
  192. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +3 -3
  193. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +213 -13
  194. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +34 -0
  195. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +44 -0
  196. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +118 -12
  197. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +850 -7
  198. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +112 -23
  199. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -5
  200. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +2 -0
  201. package/dist/extensions/pi-subagents/test/support/register-loader.mjs +3 -3
  202. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +3 -1
  203. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +128 -0
  204. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +47 -2
  205. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +60 -0
  206. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +8 -0
  207. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +102 -0
  208. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +1 -1
  209. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +6 -0
  210. package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +1 -1
  211. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +48 -48
  212. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +23 -0
  213. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +27 -0
  214. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +4 -2
  215. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +1 -0
  216. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +176 -8
  217. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +164 -5
  218. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +41 -1
  219. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +11 -11
  220. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +18 -2
  221. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +20 -1
  222. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +30 -2
  223. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +10 -2
  224. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +22 -0
  225. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +103 -0
  226. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +18 -0
  227. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +46 -0
  228. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +13 -1
  229. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +29 -4
  230. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +223 -4
  231. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +30 -0
  232. package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +52 -0
  233. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +34 -2
  234. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
  235. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +1 -1
  236. package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +78 -0
  237. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +41 -0
  238. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +77 -0
  239. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +3 -0
  240. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +1 -1
  241. package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +1 -0
  242. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +210 -1
  243. package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +33 -0
  244. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +32 -0
  245. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +1 -1
  246. package/dist/extensions/question/index.ts +14 -1
  247. package/dist/extensions/question/tests/wizard.test.ts +11 -8
  248. package/dist/main.d.ts.map +1 -1
  249. package/dist/main.js +43 -0
  250. package/dist/main.js.map +1 -1
  251. package/dist/modes/interactive/components/custom-message.d.ts +3 -1
  252. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  253. package/dist/modes/interactive/components/custom-message.js +10 -2
  254. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  255. package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
  256. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  257. package/dist/modes/interactive/components/extension-editor.js +16 -46
  258. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  259. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  260. package/dist/modes/interactive/components/model-selector.js +4 -1
  261. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  262. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  263. package/dist/modes/interactive/components/scoped-models-selector.js +22 -13
  264. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  265. package/dist/modes/interactive/external-editor.d.ts +12 -0
  266. package/dist/modes/interactive/external-editor.d.ts.map +1 -0
  267. package/dist/modes/interactive/external-editor.js +37 -0
  268. package/dist/modes/interactive/external-editor.js.map +1 -0
  269. package/dist/modes/interactive/interactive-mode.d.ts +1 -1
  270. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  271. package/dist/modes/interactive/interactive-mode.js +72 -64
  272. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  273. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  274. package/dist/modes/rpc/rpc-mode.js +14 -0
  275. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  276. package/dist/skills/batch-grill-me/SKILL.md +3 -1
  277. package/dist/utils/clipboard.d.ts.map +1 -1
  278. package/dist/utils/clipboard.js +19 -8
  279. package/dist/utils/clipboard.js.map +1 -1
  280. package/dist/utils/version-check.d.ts.map +1 -1
  281. package/dist/utils/version-check.js +1 -1
  282. package/dist/utils/version-check.js.map +1 -1
  283. package/docs/compaction.md +1 -1
  284. package/docs/custom-provider.md +14 -5
  285. package/docs/environment-variables.md +86 -0
  286. package/docs/extensions.md +15 -5
  287. package/docs/index.md +1 -0
  288. package/docs/models.md +9 -2
  289. package/docs/providers.md +21 -2
  290. package/docs/rpc.md +23 -6
  291. package/docs/session-format.md +2 -0
  292. package/docs/settings.md +1 -1
  293. package/docs/usage.md +0 -13
  294. package/examples/extensions/custom-compaction.ts +5 -2
  295. package/examples/extensions/custom-provider-anthropic/index.ts +9 -3
  296. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  297. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  298. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  299. package/examples/extensions/gondolin/package-lock.json +2 -2
  300. package/examples/extensions/gondolin/package.json +1 -1
  301. package/examples/extensions/handoff.ts +11 -3
  302. package/examples/extensions/message-renderer.ts +3 -3
  303. package/examples/extensions/sandbox/package-lock.json +2 -2
  304. package/examples/extensions/sandbox/package.json +1 -1
  305. package/examples/extensions/summarize.ts +5 -2
  306. package/examples/extensions/with-deps/package-lock.json +2 -2
  307. package/examples/extensions/with-deps/package.json +1 -1
  308. package/examples/sdk/12-full-control.ts +3 -1
  309. package/package.json +15 -6
  310. package/dist/extensions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
@@ -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,7 +23,7 @@ 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
28
  import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
22
29
  import { recordRun } from "../shared/run-history.ts";
@@ -47,6 +54,7 @@ import { formatControlIntercomMessage, formatControlNoticeMessage, resolveContro
47
54
  import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
48
55
  import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.ts";
49
56
  import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
57
+ import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
50
58
  import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
51
59
  import { isAgentContractV1 } from "../shared/agent-contract.ts";
52
60
  import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
@@ -64,12 +72,13 @@ import {
64
72
  stripDetailsOutputsForIntercomReceipt,
65
73
  } from "../../intercom/result-intercom.ts";
66
74
  import { applySteeringRecoveryAgentConfig, buildRevivedAsyncTask, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
67
- import { deliverInterruptRequest, deliverStopRequest, requestAsyncSteer } from "../background/control-channel.ts";
75
+ import { deliverCheckpointDecisionRequest, deliverInterruptRequest, requestAsyncSteer } from "../background/control-channel.ts";
68
76
  import { waitForSteeringAction } from "../background/steering.ts";
69
77
  import { steerAsyncRun } from "./async-steering-action.ts";
78
+ import { stopAsyncRun } from "./async-stop-action.ts";
70
79
  import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
71
80
  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";
81
+ import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
73
82
  import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
74
83
  import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
75
84
  import { inspectSubagentStatus } from "../background/run-status.ts";
@@ -106,6 +115,7 @@ import {
106
115
  type SingleResult,
107
116
  type ToolBudgetConfig,
108
117
  type TurnBudgetConfig,
118
+ type UsageBudgetConfig,
109
119
  type SubagentRunMode,
110
120
  type SubagentState,
111
121
  ASYNC_DIR,
@@ -167,6 +177,7 @@ export interface SubagentParamsLike {
167
177
  /** Internal-only strict turn-boundary enforcement for versioned foreground delegation. */
168
178
  enforceHardTurnLimit?: boolean;
169
179
  toolBudget?: ToolBudgetConfig;
180
+ usageBudget?: UsageBudgetConfig;
170
181
  clarify?: boolean;
171
182
  share?: boolean;
172
183
  control?: ControlConfig;
@@ -192,6 +203,15 @@ export interface SubagentParamsLike {
192
203
  additional?: number;
193
204
  }
194
205
 
206
+ function rememberParentModel(state: { currentSessionId?: string | null; lastParentModel?: ParentModel }, sessionId: string | null, model: unknown): ParentModel | undefined {
207
+ if (state.currentSessionId !== sessionId) state.lastParentModel = undefined;
208
+ state.currentSessionId = sessionId;
209
+ const parentModel = normalizeParentModel(model);
210
+ if (!sessionId) return parentModel;
211
+ if (parentModel) state.lastParentModel = parentModel;
212
+ return parentModel ?? state.lastParentModel;
213
+ }
214
+
195
215
  interface ExecutorDeps {
196
216
  pi: ExtensionAPI;
197
217
  state: SubagentState;
@@ -236,10 +256,12 @@ interface ExecutionContextData {
236
256
  deadlineAt?: number;
237
257
  turnBudget?: ResolvedTurnBudget;
238
258
  toolBudget?: ResolvedToolBudget;
259
+ usageBudget?: UsageBudgetConfig;
239
260
  allowZeroToolBudget?: boolean;
240
261
  configToolBudget?: ResolvedToolBudget;
241
262
  contextPolicy: AgentDefaultContextPolicy;
242
263
  modelScope?: ModelScopeConfig;
264
+ parentModel?: ParentModel;
243
265
  parentSessionId: string | null;
244
266
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
245
267
  }
@@ -248,6 +270,15 @@ function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefine
248
270
  return requestedCwd ? path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
249
271
  }
250
272
 
273
+ function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
274
+ const control = state.foregroundControls.get(runId);
275
+ if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
276
+ clearPendingForegroundControlNotices(state, runId);
277
+ state.foregroundControls.delete(runId);
278
+ if (state.lastForegroundControlId === runId) state.lastForegroundControlId = null;
279
+ return true;
280
+ }
281
+
251
282
  function getForegroundControl(state: SubagentState, runId: string | undefined) {
252
283
  if (runId) return state.foregroundControls.get(runId);
253
284
  if (state.lastForegroundControlId) {
@@ -382,7 +413,7 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
382
413
  };
383
414
  }
384
415
 
385
- function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[] }): void {
416
+ function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; checkpoint?: Details["checkpoint"] }): void {
386
417
  state.foregroundRuns ??= new Map();
387
418
  const previous = state.foregroundRuns.get(input.runId);
388
419
  const updatedAt = Date.now();
@@ -392,26 +423,41 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
392
423
  cwd: input.cwd,
393
424
  ...(input.sessionId ? { sessionId: input.sessionId } : {}),
394
425
  updatedAt,
426
+ ...(input.checkpoint ? { checkpoint: input.checkpoint } : {}),
395
427
  children: input.results.map((result, index) => {
396
428
  const child = {
397
429
  agent: result.agent,
398
430
  index,
399
431
  ...(result.context ? { context: result.context } : {}),
400
- status: resolveSubagentResultStatus({ exitCode: result.exitCode, interrupted: result.interrupted, detached: result.detached }),
432
+ status: resolveSubagentResultStatus({
433
+ exitCode: result.exitCode,
434
+ interrupted: result.interrupted,
435
+ detached: result.detached,
436
+ processSignal: result.processSignal,
437
+ timedOut: result.timedOut,
438
+ stopped: result.stopped,
439
+ turnBudgetExceeded: result.turnBudgetExceeded,
440
+ }),
401
441
  ...foregroundChildActivityFromProgress(result.progress),
402
442
  updatedAt,
403
443
  ...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
404
444
  ...(result.error ? { error: result.error } : {}),
405
445
  ...(result.finalOutput ? { finalOutput: result.finalOutput } : {}),
446
+ ...(result.outputState ? { outputState: result.outputState } : {}),
406
447
  ...(result.outputMode ? { outputMode: result.outputMode } : {}),
407
448
  ...(result.savedOutputPath ? { savedOutputPath: result.savedOutputPath } : {}),
408
449
  ...(result.outputSaveError ? { outputSaveError: result.outputSaveError } : {}),
409
450
  ...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
451
+ ...(result.model ? { model: result.model } : {}),
452
+ ...(result.thinking ? { thinking: result.thinking } : {}),
410
453
  ...(result.artifactPaths ? { artifactPaths: result.artifactPaths } : {}),
411
454
  ...(result.transcriptPath ? { transcriptPath: result.transcriptPath } : {}),
412
455
  ...(result.transcriptError ? { transcriptError: result.transcriptError } : {}),
413
456
  ...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
414
457
  ...(result.acceptance ? { acceptance: result.acceptance } : {}),
458
+ ...(result.launchContractDigest ? { launchContractDigest: result.launchContractDigest } : {}),
459
+ ...(result.launchResolvedExtensions ? { launchResolvedExtensions: result.launchResolvedExtensions } : {}),
460
+ ...(result.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: result.runtimeAcknowledgedExtensions } : {}),
415
461
  ...(result.capabilityCeiling ? { capabilityCeiling: result.capabilityCeiling } : {}),
416
462
  ...(result.capabilityAudit ? { capabilityAudit: result.capabilityAudit } : {}),
417
463
  };
@@ -454,35 +500,49 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
454
500
  state.foregroundRuns.set(input.runId, run);
455
501
  }
456
502
  run.updatedAt = updatedAt;
503
+ const terminalStatus = resolveSubagentResultStatus({
504
+ exitCode: input.result.exitCode,
505
+ ...(input.result.acceptance?.status === "rejected" ? { success: false } : {}),
506
+ interrupted: input.result.interrupted,
507
+ detached: false,
508
+ processSignal: input.result.processSignal,
509
+ timedOut: input.result.timedOut,
510
+ stopped: input.result.stopped,
511
+ turnBudgetExceeded: input.result.turnBudgetExceeded,
512
+ });
457
513
  const child = run.children[input.index] ?? { agent: input.result.agent, index: input.index, status: "detached" as const };
458
514
  run.children[input.index] = {
459
515
  ...child,
460
516
  agent: input.result.agent,
461
517
  index: input.index,
462
518
  ...(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 }),
519
+ status: terminalStatus,
466
520
  ...foregroundChildActivityFromProgress(input.result.progress),
467
521
  updatedAt,
468
522
  ...(input.result.exitCode !== undefined ? { exitCode: input.result.exitCode } : {}),
469
523
  ...(input.result.error ? { error: input.result.error } : {}),
470
524
  ...(input.result.finalOutput ? { finalOutput: input.result.finalOutput } : {}),
525
+ outputState: input.result.outputState,
471
526
  outputMode: input.result.outputMode,
472
527
  savedOutputPath: input.result.savedOutputPath,
473
528
  outputSaveError: input.result.outputSaveError,
474
529
  ...(input.result.sessionFile ? { sessionFile: input.result.sessionFile } : {}),
530
+ ...(input.result.model ? { model: input.result.model } : {}),
531
+ ...(input.result.thinking ? { thinking: input.result.thinking } : {}),
475
532
  ...(input.result.artifactPaths ? { artifactPaths: input.result.artifactPaths } : {}),
476
533
  ...(input.result.transcriptPath ? { transcriptPath: input.result.transcriptPath } : {}),
477
534
  ...(input.result.transcriptError ? { transcriptError: input.result.transcriptError } : {}),
478
535
  ...(input.result.detachedReason ? { detachedReason: input.result.detachedReason } : {}),
479
536
  ...(input.result.acceptance ? { acceptance: input.result.acceptance } : {}),
537
+ ...(input.result.launchContractDigest ? { launchContractDigest: input.result.launchContractDigest } : {}),
538
+ ...(input.result.launchResolvedExtensions ? { launchResolvedExtensions: input.result.launchResolvedExtensions } : {}),
539
+ ...(input.result.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: input.result.runtimeAcknowledgedExtensions } : {}),
480
540
  ...(input.result.capabilityCeiling ? { capabilityCeiling: input.result.capabilityCeiling } : {}),
481
541
  ...(input.result.capabilityAudit ? { capabilityAudit: input.result.capabilityAudit } : {}),
482
542
  };
483
543
  trimRememberedForegroundRuns(state);
484
544
  const output = getSingleResultOutput(input.result).trim();
485
- const success = input.result.exitCode === 0 && input.result.acceptance?.status !== "rejected";
545
+ const success = terminalStatus === "completed";
486
546
  const summary = !success && input.result.error
487
547
  ? `${input.result.error}${output ? `\n\nOutput:\n${output}` : ""}`
488
548
  : output || input.result.error || "Detached child exited without final output.";
@@ -498,7 +558,12 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
498
558
  success,
499
559
  summary,
500
560
  exitCode: input.result.exitCode,
501
- state: success ? "complete" : "failed",
561
+ state: terminalStatus === "completed" ? "complete" : terminalStatus,
562
+ ...(input.result.interrupted !== undefined ? { interrupted: input.result.interrupted } : {}),
563
+ ...(input.result.stopped !== undefined ? { stopped: input.result.stopped } : {}),
564
+ ...(input.result.processSignal !== undefined ? { processSignal: input.result.processSignal } : {}),
565
+ ...(input.result.timedOut !== undefined ? { timedOut: input.result.timedOut } : {}),
566
+ ...(input.result.turnBudgetExceeded !== undefined ? { turnBudgetExceeded: input.result.turnBudgetExceeded } : {}),
502
567
  timestamp: updatedAt,
503
568
  cwd: input.cwd,
504
569
  sessionFile: input.result.sessionFile,
@@ -507,7 +572,7 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
507
572
  });
508
573
  }
509
574
 
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 {
575
+ 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
576
  const requested = (params.id ?? params.runId)?.trim();
512
577
  if (!requested || !state.foregroundRuns?.size || !state.currentSessionId) return undefined;
513
578
  const sessionRuns = [...state.foregroundRuns.values()].filter((run) => run.sessionId === state.currentSessionId);
@@ -534,6 +599,9 @@ function resolveForegroundResumeTarget(params: SubagentParamsLike, state: Subage
534
599
  index,
535
600
  cwd: run.cwd,
536
601
  sessionFile,
602
+ ...(child.model ? { model: child.model } : {}),
603
+ ...(child.thinking ? { thinking: child.thinking } : {}),
604
+ ...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
537
605
  ...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
538
606
  };
539
607
  }
@@ -714,50 +782,6 @@ function interruptAsyncRun(
714
782
  }
715
783
  }
716
784
 
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
785
  function duplicateNames(names: string[]): string[] {
762
786
  const seen = new Set<string>();
763
787
  const duplicates = new Set<string>();
@@ -773,6 +797,7 @@ function appendStepToAsyncChain(input: {
773
797
  requestCwd: string;
774
798
  ctx: ExtensionContext;
775
799
  deps: ExecutorDeps;
800
+ parentModel?: ParentModel;
776
801
  }): AgentToolResult<Details> {
777
802
  const targetRunId = input.params.id ?? input.params.runId;
778
803
  if (!targetRunId) {
@@ -877,13 +902,14 @@ function appendStepToAsyncChain(input: {
877
902
  const contextPolicy = resolveExplicitContextPolicy(input.params);
878
903
  const chainSkillInput = normalizeSkillInput(input.params.skill);
879
904
  const chainSkills = chainSkillInput === false ? [] : (chainSkillInput ?? []);
905
+ const parentModel = input.parentModel;
880
906
  const asyncCtx = {
881
907
  pi: input.deps.pi,
882
908
  cwd: input.ctx.cwd,
883
909
  currentSessionId: resolveCurrentSessionId(input.ctx.sessionManager),
884
910
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
885
- currentModelProvider: input.ctx.model?.provider,
886
- currentModel: input.ctx.model,
911
+ currentModelProvider: parentModel?.provider,
912
+ currentModel: parentModel,
887
913
  modelScope: discoveredForAppend.modelScope,
888
914
  interactive: input.ctx.hasUI,
889
915
  };
@@ -1011,10 +1037,10 @@ function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "neste
1011
1037
  };
1012
1038
  }
1013
1039
 
1014
- async function waitForNestedControlResult(target: ResolvedSubagentRunId & { kind: "nested" }, requestId: string, timeoutMs = 1_000) {
1040
+ async function waitForNestedControlResult(target: ResolvedSubagentRunId & { kind: "nested" }, requestId: string, ignoredFiles: ReadonlySet<string>, timeoutMs = 1_000) {
1015
1041
  const deadline = Date.now() + timeoutMs;
1016
1042
  while (Date.now() < deadline) {
1017
- const result = readNestedControlResults(target.match.route).find((candidate) => candidate.requestId === requestId && candidate.targetRunId === target.match.run.id);
1043
+ const result = findNestedControlResult(target.match.route, requestId, target.match.run.id, ignoredFiles);
1018
1044
  if (result) return result;
1019
1045
  await new Promise((resolve) => setTimeout(resolve, 50));
1020
1046
  }
@@ -1023,14 +1049,16 @@ async function waitForNestedControlResult(target: ResolvedSubagentRunId & { kind
1023
1049
 
1024
1050
  async function sendNestedControlRequest(target: ResolvedSubagentRunId & { kind: "nested" }, action: "interrupt" | "resume", message?: string) {
1025
1051
  const requestId = randomUUID();
1052
+ const ignoredFiles = snapshotNestedEventFiles(target.match.route);
1053
+ const requestedAt = Date.now();
1026
1054
  writeNestedControlRequest(target.match.route, {
1027
- ts: Date.now(),
1055
+ ts: requestedAt,
1028
1056
  requestId,
1029
1057
  targetRunId: target.match.run.id,
1030
1058
  action,
1031
1059
  ...(message ? { message } : {}),
1032
1060
  });
1033
- return waitForNestedControlResult(target, requestId);
1061
+ return waitForNestedControlResult(target, requestId, ignoredFiles);
1034
1062
  }
1035
1063
 
1036
1064
  function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nested" }): AgentToolResult<Details> | undefined {
@@ -1121,6 +1149,7 @@ async function resumeAsyncRun(input: {
1121
1149
  requestCwd: string;
1122
1150
  ctx: ExtensionContext;
1123
1151
  deps: ExecutorDeps;
1152
+ parentModel?: ParentModel;
1124
1153
  absoluteDeadlineAt?: number;
1125
1154
  }): Promise<AgentToolResult<Details>> {
1126
1155
  const followUp = (input.params.message ?? input.params.task ?? "").trim();
@@ -1132,6 +1161,13 @@ async function resumeAsyncRun(input: {
1132
1161
  details: { mode: "management", results: [] },
1133
1162
  };
1134
1163
  }
1164
+ if (input.params.model !== undefined) {
1165
+ return {
1166
+ content: [{ type: "text", text: "action='resume' reuses the persisted child model and does not accept a model override." }],
1167
+ isError: true,
1168
+ details: { mode: "management", results: [] },
1169
+ };
1170
+ }
1135
1171
  const acceptanceErrors = validateExecutionAcceptance(input.params);
1136
1172
  if (acceptanceErrors.length > 0) {
1137
1173
  return {
@@ -1259,6 +1295,7 @@ async function resumeAsyncRun(input: {
1259
1295
  const goal = resolveAsyncEventGoal(workflowTask, attachChain);
1260
1296
  const chain = wrapChainTasksForFork(attachChain, contextPolicy);
1261
1297
  const normalized = normalizeSkillInput(input.params.skill);
1298
+ const parentModel = input.parentModel;
1262
1299
  const result = executeAsyncChain(runId, {
1263
1300
  chain,
1264
1301
  task: workflowTask,
@@ -1277,8 +1314,8 @@ async function resumeAsyncRun(input: {
1277
1314
  cwd: input.requestCwd,
1278
1315
  currentSessionId: input.deps.state.currentSessionId,
1279
1316
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
1280
- currentModelProvider: input.ctx.model?.provider,
1281
- currentModel: input.ctx.model,
1317
+ currentModelProvider: parentModel?.provider,
1318
+ currentModel: parentModel,
1282
1319
  modelScope,
1283
1320
  interactive: input.ctx.hasUI,
1284
1321
  },
@@ -1320,6 +1357,7 @@ async function resumeAsyncRun(input: {
1320
1357
  const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir };
1321
1358
  const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
1322
1359
  const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
1360
+ const parentModel = input.parentModel;
1323
1361
  const result = executeAsyncSingle(runId, {
1324
1362
  agent: target.agent,
1325
1363
  task: buildRevivedAsyncTask(target, followUp),
@@ -1330,8 +1368,8 @@ async function resumeAsyncRun(input: {
1330
1368
  cwd: input.requestCwd,
1331
1369
  currentSessionId: input.deps.state.currentSessionId,
1332
1370
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
1333
- currentModelProvider: input.ctx.model?.provider,
1334
- currentModel: input.ctx.model,
1371
+ currentModelProvider: parentModel?.provider,
1372
+ currentModel: parentModel,
1335
1373
  modelScope,
1336
1374
  interactive: input.ctx.hasUI,
1337
1375
  },
@@ -1349,8 +1387,8 @@ async function resumeAsyncRun(input: {
1349
1387
  sourceRunId: target.runId,
1350
1388
  ...(input.deps.state.currentSessionId ? { parentSessionId: input.deps.state.currentSessionId } : {}),
1351
1389
  },
1352
- modelOverride: input.params.model ?? recoveryDescriptor?.model ?? target.model,
1353
- thinkingOverride: input.params.model ? undefined : recoveryDescriptor?.thinking ?? target.thinking,
1390
+ modelOverride: recoveryDescriptor?.model ?? target.model,
1391
+ thinkingOverride: recoveryDescriptor?.thinking ?? target.thinking,
1354
1392
  outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
1355
1393
  maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
1356
1394
  waitToolEnabled: input.deps.waitToolEnabled,
@@ -1361,8 +1399,8 @@ async function resumeAsyncRun(input: {
1361
1399
  controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
1362
1400
  childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
1363
1401
  availableModels,
1364
- output: recoveryDescriptor?.outputPath,
1365
- outputMode: recoveryDescriptor?.outputMode,
1402
+ output: typeof input.params.output === "string" ? input.params.output : recoveryDescriptor?.outputPath,
1403
+ outputMode: input.params.outputMode ?? recoveryDescriptor?.outputMode,
1366
1404
  ...(recoveryDescriptor?.agentContract ? { agentContract: recoveryDescriptor.agentContract } : {}),
1367
1405
  ...(recoveryDescriptor?.structuredOutputSchema ? { structuredOutputSchema: recoveryDescriptor.structuredOutputSchema } : {}),
1368
1406
  ...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
@@ -1387,7 +1425,13 @@ async function resumeAsyncRun(input: {
1387
1425
  revivedTarget ? `Intercom target: ${revivedTarget} (if registered)` : undefined,
1388
1426
  `Status if needed: subagent({ action: "status", id: "${revivedId}" })`,
1389
1427
  ].filter((line): line is string => Boolean(line));
1390
- return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n"), input.ctx.hasUI) }], details: result.details };
1428
+ return {
1429
+ content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n"), input.ctx.hasUI) }],
1430
+ details: {
1431
+ ...result.details,
1432
+ ...(target.launchContractDigest ? { sourceLaunchContractDigest: target.launchContractDigest } : {}),
1433
+ },
1434
+ };
1391
1435
  }
1392
1436
 
1393
1437
  function resultSummaryForIntercom(result: SingleResult): string {
@@ -1433,14 +1477,19 @@ async function emitForegroundResultIntercom(input: {
1433
1477
  nestedChildren?: NestedRunSummary[];
1434
1478
  parallelHandoff?: Details["parallelHandoff"];
1435
1479
  }): Promise<ReturnType<typeof buildSubagentResultIntercomPayload> | null> {
1436
- if (!input.intercomBridge.active || !input.intercomBridge.orchestratorTarget) return null;
1480
+ if (!input.intercomBridge.active || !input.intercomBridge.resultDelivery || !input.intercomBridge.orchestratorTarget) return null;
1437
1481
  const children = input.results.flatMap((result, index) => result.detached ? [] : [{
1438
1482
  agent: result.agent,
1439
1483
  status: resolveSubagentResultStatus({
1440
1484
  exitCode: result.exitCode,
1441
1485
  interrupted: result.interrupted,
1442
1486
  detached: result.detached,
1487
+ processSignal: result.processSignal,
1488
+ timedOut: result.timedOut,
1489
+ stopped: result.stopped,
1490
+ turnBudgetExceeded: result.turnBudgetExceeded,
1443
1491
  }),
1492
+ outputState: result.outputState ?? "unknown",
1444
1493
  summary: resultSummaryForIntercom(result),
1445
1494
  index,
1446
1495
  artifactPath: result.artifactPaths?.outputPath,
@@ -1487,6 +1536,67 @@ async function maybeBuildForegroundIntercomReceipt(input: {
1487
1536
  };
1488
1537
  }
1489
1538
 
1539
+ function canonicalizeAgentName(name: string, agents: AgentConfig[]): { name?: string; error?: string } {
1540
+ const resolved = resolveAgentName(name, agents);
1541
+ if (resolved.error) return { error: resolved.error };
1542
+ if (!resolved.agent) return { error: `Unknown agent: ${name}` };
1543
+ return { name: resolved.agent.name };
1544
+ }
1545
+
1546
+ function canonicalizeExecutionParams(params: SubagentParamsLike, agents: AgentConfig[]): { params?: SubagentParamsLike; error?: string } {
1547
+ const resolve = (name: string, location?: string): { name?: string; error?: string } => {
1548
+ const result = canonicalizeAgentName(name, agents);
1549
+ return result.error && location ? { error: `${result.error} (${location})` } : result;
1550
+ };
1551
+ if (params.agent) {
1552
+ const result = resolve(params.agent);
1553
+ if (result.error) return { error: result.error };
1554
+ params = { ...params, agent: result.name };
1555
+ }
1556
+ if (params.tasks) {
1557
+ const tasks: TaskParam[] = [];
1558
+ for (let index = 0; index < params.tasks.length; index++) {
1559
+ const task = params.tasks[index]!;
1560
+ const result = resolve(task.agent, `task ${index + 1}`);
1561
+ if (result.error) return { error: result.error };
1562
+ tasks.push({ ...task, agent: result.name! });
1563
+ }
1564
+ params = { ...params, tasks };
1565
+ }
1566
+ if (params.chain) {
1567
+ const chain: ChainStep[] = [];
1568
+ for (let index = 0; index < params.chain.length; index++) {
1569
+ const step = params.chain[index]!;
1570
+ if (isParallelStep(step)) {
1571
+ const parallel: typeof step.parallel = [];
1572
+ for (let taskIndex = 0; taskIndex < step.parallel.length; taskIndex++) {
1573
+ const task = step.parallel[taskIndex]!;
1574
+ const result = resolve(task.agent, `step ${index + 1}, task ${taskIndex + 1}`);
1575
+ if (result.error) return { error: result.error };
1576
+ parallel.push({ ...task, agent: result.name! });
1577
+ }
1578
+ chain.push({ ...step, parallel });
1579
+ continue;
1580
+ }
1581
+ if (isDynamicParallelStep(step)) {
1582
+ const result = resolve(step.parallel.agent, `step ${index + 1}`);
1583
+ if (result.error) return { error: result.error };
1584
+ chain.push({ ...step, parallel: { ...step.parallel, agent: result.name! } });
1585
+ continue;
1586
+ }
1587
+ if ("agent" in step && typeof step.agent === "string") {
1588
+ const result = resolve(step.agent, `step ${index + 1}`);
1589
+ if (result.error) return { error: result.error };
1590
+ chain.push({ ...step, agent: result.name! });
1591
+ continue;
1592
+ }
1593
+ chain.push(step);
1594
+ }
1595
+ params = { ...params, chain };
1596
+ }
1597
+ return { params };
1598
+ }
1599
+
1490
1600
  function validateExecutionInput(
1491
1601
  params: SubagentParamsLike,
1492
1602
  agents: AgentConfig[],
@@ -1699,10 +1809,14 @@ function applySingleAgentLaunchDefaults(params: SubagentParamsLike, agents: Agen
1699
1809
  };
1700
1810
  }
1701
1811
 
1702
- function resolveForegroundTimeout(params: SubagentParamsLike): { timeoutMs?: number; error?: string } {
1812
+ export const DEFAULT_FOREGROUND_TIMEOUT_MS = 30 * 60 * 1000;
1813
+
1814
+ function resolveForegroundTimeout(params: SubagentParamsLike, defaultTimeoutMs?: number): { timeoutMs?: number; error?: string } {
1703
1815
  const rawTimeout = params.timeoutMs;
1704
1816
  const rawMaxRuntime = params.maxRuntimeMs;
1705
- if (rawTimeout === undefined && rawMaxRuntime === undefined) return {};
1817
+ if (rawTimeout === undefined && rawMaxRuntime === undefined) {
1818
+ return defaultTimeoutMs === undefined ? {} : { timeoutMs: defaultTimeoutMs };
1819
+ }
1706
1820
  for (const [name, value] of [["timeoutMs", rawTimeout], ["maxRuntimeMs", rawMaxRuntime]] as const) {
1707
1821
  if (value === undefined) continue;
1708
1822
  if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
@@ -2044,19 +2158,20 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2044
2158
  };
2045
2159
  }
2046
2160
  const id = randomUUID();
2161
+ const parentModel = data.parentModel;
2047
2162
  const asyncCtx = {
2048
2163
  pi: deps.pi,
2049
2164
  cwd: ctx.cwd,
2050
2165
  currentSessionId: deps.state.currentSessionId!,
2051
2166
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
2052
- currentModelProvider: ctx.model?.provider,
2053
- currentModel: ctx.model,
2167
+ currentModelProvider: parentModel?.provider,
2168
+ currentModel: parentModel,
2054
2169
  modelScope: data.modelScope,
2055
2170
  interactive: ctx.hasUI,
2056
2171
  };
2057
2172
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
2058
2173
  const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
2059
- const currentProvider = ctx.model?.provider;
2174
+ const currentProvider = parentModel?.provider;
2060
2175
  const controlIntercomTarget = intercomBridge.active ? intercomBridge.orchestratorTarget : undefined;
2061
2176
  const childIntercomTarget = intercomBridge.active ? (agent: string, index: number) => resolveSubagentIntercomTarget(id, agent, index) : undefined;
2062
2177
 
@@ -2111,6 +2226,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2111
2226
  timeoutMs: data.timeoutMs,
2112
2227
  turnBudget: data.turnBudget,
2113
2228
  toolBudget: data.toolBudget,
2229
+ usageBudget: data.usageBudget,
2114
2230
  configToolBudget: data.configToolBudget,
2115
2231
  capabilityCeiling: data.capabilityCeiling,
2116
2232
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
@@ -2153,6 +2269,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2153
2269
  timeoutMs: data.timeoutMs,
2154
2270
  turnBudget: data.turnBudget,
2155
2271
  toolBudget: data.toolBudget,
2272
+ usageBudget: data.usageBudget,
2156
2273
  configToolBudget: data.configToolBudget,
2157
2274
  capabilityCeiling: data.capabilityCeiling,
2158
2275
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
@@ -2174,7 +2291,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2174
2291
  const normalizedSkills = normalizeSkillInput(params.skill);
2175
2292
  const skills = normalizedSkills === false ? [] : normalizedSkills;
2176
2293
  const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
2177
- const modelOverride = resolveEffectiveSubagentModel(params.model as string | undefined, a.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope });
2294
+ const modelOverride = resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, { scope: data.modelScope });
2178
2295
  return executeAsyncSingle(id, {
2179
2296
  agent: params.agent!,
2180
2297
  task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
@@ -2211,6 +2328,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
2211
2328
  timeoutMs: data.timeoutMs,
2212
2329
  turnBudget: data.turnBudget,
2213
2330
  toolBudget: data.toolBudget,
2331
+ usageBudget: data.usageBudget,
2214
2332
  configToolBudget: data.configToolBudget,
2215
2333
  capabilityCeiling: data.capabilityCeiling,
2216
2334
  });
@@ -2246,11 +2364,12 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
2246
2364
  const chainSkills = normalized === false ? [] : (normalized ?? []);
2247
2365
  const chain = wrapChainTasksForFork(params.chain as ChainStep[], contextPolicy);
2248
2366
  const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
2367
+ const chainCtx = normalizeParentModel(ctx.model) || !data.parentModel ? ctx : { ...ctx, model: data.parentModel };
2249
2368
  const chainResult = await executeChain({
2250
2369
  chain,
2251
2370
  task: params.task,
2252
2371
  agents,
2253
- ctx,
2372
+ ctx: chainCtx,
2254
2373
  modelScope: data.modelScope,
2255
2374
  intercomEvents: deps.pi.events,
2256
2375
  signal,
@@ -2284,8 +2403,18 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
2284
2403
  timeoutMs: data.timeoutMs,
2285
2404
  deadlineAt: data.deadlineAt,
2286
2405
  turnBudget: data.turnBudget,
2287
- onDetachedExit: (index, result) => updateRememberedForegroundChild(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, index, result, events: deps.pi.events }),
2406
+ onDetachedExit: (index, result) => {
2407
+ try {
2408
+ updateRememberedForegroundChild(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, index, result, events: deps.pi.events });
2409
+ } finally {
2410
+ removeForegroundControlIfIdle(deps.state, runId);
2411
+ }
2412
+ },
2413
+ onForegroundChildSettled: () => {
2414
+ removeForegroundControlIfIdle(deps.state, runId);
2415
+ },
2288
2416
  toolBudget: data.toolBudget,
2417
+ usageBudget: data.usageBudget,
2289
2418
  configToolBudget: data.configToolBudget,
2290
2419
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
2291
2420
  capabilityCeiling: data.capabilityCeiling,
@@ -2300,13 +2429,14 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
2300
2429
  };
2301
2430
  }
2302
2431
  const id = randomUUID();
2432
+ const parentModel = data.parentModel;
2303
2433
  const asyncCtx = {
2304
2434
  pi: deps.pi,
2305
2435
  cwd: ctx.cwd,
2306
2436
  currentSessionId: deps.state.currentSessionId!,
2307
2437
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
2308
- currentModelProvider: ctx.model?.provider,
2309
- currentModel: ctx.model,
2438
+ currentModelProvider: parentModel?.provider,
2439
+ currentModel: parentModel,
2310
2440
  modelScope: data.modelScope,
2311
2441
  interactive: ctx.hasUI,
2312
2442
  };
@@ -2344,20 +2474,22 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
2344
2474
  timeoutMs: data.timeoutMs,
2345
2475
  turnBudget: data.turnBudget,
2346
2476
  toolBudget: data.toolBudget,
2477
+ usageBudget: data.usageBudget,
2347
2478
  configToolBudget: data.configToolBudget,
2348
2479
  capabilityCeiling: data.capabilityCeiling,
2349
2480
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
2350
2481
  });
2351
2482
  }
2352
2483
 
2353
- const rawChainDetails = chainResult.details ? { ...chainResult.details, runId } : undefined;
2484
+ const rawChainDetails = chainResult.details ? { ...chainResult.details, runId, timeoutMs: data.timeoutMs } : undefined;
2354
2485
  if (foregroundControl && rawChainDetails) {
2355
2486
  updateForegroundNestedProjection(foregroundControl);
2356
2487
  attachRootChildrenToSteps(runId, rawChainDetails.results, foregroundControl.nestedChildren);
2357
2488
  rawChainDetails.totalCost = sumResultsCost(rawChainDetails.results);
2489
+ rawChainDetails.usageBudget = usageBudgetState(data.usageBudget, rawChainDetails.totalCost);
2358
2490
  }
2359
2491
  const chainDetails = rawChainDetails ? compactForegroundDetails(rawChainDetails) : undefined;
2360
- if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, results: chainDetails.results });
2492
+ if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, results: chainDetails.results, checkpoint: chainDetails.checkpoint });
2361
2493
  const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached)
2362
2494
  ? await maybeBuildForegroundIntercomReceipt({
2363
2495
  pi: deps.pi,
@@ -2406,6 +2538,7 @@ interface ForegroundParallelRunInput {
2406
2538
  waitToolEnabled?: boolean;
2407
2539
  availableModels: ModelInfo[];
2408
2540
  modelScope?: ModelScopeConfig;
2541
+ parentModel?: ParentModel;
2409
2542
  modelOverrides: (string | undefined)[];
2410
2543
  behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
2411
2544
  firstProgressIndex: number;
@@ -2424,6 +2557,7 @@ interface ForegroundParallelRunInput {
2424
2557
  timeoutMs?: number;
2425
2558
  deadlineAt?: number;
2426
2559
  turnBudget?: ResolvedTurnBudget;
2560
+ usageBudget?: UsageBudgetConfig;
2427
2561
  toolBudgets: (ResolvedToolBudget | undefined)[];
2428
2562
  agentContract?: AgentContract;
2429
2563
  }
@@ -2530,6 +2664,10 @@ function finalizeParallelWorktreeHandoff(input: {
2530
2664
  interrupted: result.interrupted,
2531
2665
  detached: result.detached,
2532
2666
  state: result.stopped ? "stopped" : undefined,
2667
+ processSignal: result.processSignal,
2668
+ timedOut: result.timedOut,
2669
+ stopped: result.stopped,
2670
+ turnBudgetExceeded: result.turnBudgetExceeded,
2533
2671
  }),
2534
2672
  summary: resultSummaryForIntercom(result),
2535
2673
  ...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
@@ -2578,7 +2716,22 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
2578
2716
  for (let i = 0; i < input.tasks.length; i++) {
2579
2717
  input.sessionFileForTask(input.tasks[i]!.agent, i, input.modelOverrides[i]);
2580
2718
  }
2719
+ const completedResults: SingleResult[] = [];
2720
+ if (input.foregroundControl) retainForegroundSchedulingOwner(input.foregroundControl);
2581
2721
  return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
2722
+ const budgetState = usageBudgetState(input.usageBudget, sumResultsCost(completedResults));
2723
+ if (budgetState?.exhausted) {
2724
+ return {
2725
+ index,
2726
+ agent: task.agent,
2727
+ task: input.taskTexts[index] ?? "(skipped)",
2728
+ exitCode: 1,
2729
+ messages: [],
2730
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 },
2731
+ error: usageBudgetExceededMessage(budgetState),
2732
+ skipped: true,
2733
+ } as SingleResult;
2734
+ }
2582
2735
  const behavior = input.behaviors[index];
2583
2736
  const effectiveSkills = behavior?.skills;
2584
2737
  const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
@@ -2597,10 +2750,14 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
2597
2750
  );
2598
2751
  const interruptController = new AbortController();
2599
2752
  if (input.foregroundControl) {
2753
+ const model = input.modelOverrides[index];
2754
+ const thinking = resolveEffectiveThinking(model, input.thinkingOverrideForTask(task.agent, index, model));
2600
2755
  beginForegroundChild(input.foregroundControl, {
2601
2756
  index,
2602
2757
  agent: task.agent,
2603
2758
  description: input.taskDescriptions[index],
2759
+ ...(model ? { model } : {}),
2760
+ ...(thinking ? { thinking } : {}),
2604
2761
  interrupt: () => {
2605
2762
  if (interruptController.signal.aborted) return false;
2606
2763
  interruptController.abort();
@@ -2611,7 +2768,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
2611
2768
  const structuredRuntime = task.outputSchema
2612
2769
  ? createStructuredOutputRuntime(task.outputSchema, path.join(input.artifactsDir, "structured-output", input.runId))
2613
2770
  : undefined;
2614
- return runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
2771
+ let detachedReceipt = false;
2772
+ const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
2615
2773
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
2616
2774
  context: input.contextPolicy.contextForAgent(task.agent),
2617
2775
  cwd: taskCwd,
@@ -2634,14 +2792,24 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
2634
2792
  capabilityCeiling: input.capabilityCeiling,
2635
2793
  controlConfig: input.controlConfig,
2636
2794
  onControlEvent: input.onControlEvent,
2637
- onDetachedExit: (result) => updateRememberedForegroundChild(input.state, { runId: input.runId, mode: "parallel", cwd: taskCwd, sessionId: input.parentSessionId, index, result, events: input.intercomEvents }),
2795
+ onDetachedExit: (result) => {
2796
+ try {
2797
+ updateRememberedForegroundChild(input.state, { runId: input.runId, mode: "parallel", cwd: taskCwd, sessionId: input.parentSessionId, index, result, events: input.intercomEvents });
2798
+ } finally {
2799
+ try {
2800
+ if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
2801
+ } finally {
2802
+ removeForegroundControlIfIdle(input.state, input.runId);
2803
+ }
2804
+ }
2805
+ },
2638
2806
  intercomSessionName: input.childIntercomTarget?.(task.agent, index),
2639
2807
  orchestratorIntercomTarget: input.orchestratorIntercomTarget,
2640
2808
  nestedRoute: input.foregroundControl?.nestedRoute,
2641
2809
  modelOverride: input.modelOverrides[index],
2642
2810
  thinkingOverride: input.thinkingOverrideForTask(task.agent, index, input.modelOverrides[index]),
2643
2811
  availableModels: input.availableModels,
2644
- preferredModelProvider: input.ctx.model?.provider,
2812
+ preferredModelProvider: input.parentModel?.provider,
2645
2813
  modelScope: input.modelScope,
2646
2814
  skills: effectiveSkills === false ? [] : effectiveSkills,
2647
2815
  structuredOutput: structuredRuntime,
@@ -2675,10 +2843,24 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
2675
2843
  });
2676
2844
  }
2677
2845
  : undefined,
2846
+ }).then((result) => {
2847
+ detachedReceipt = result.detached === true;
2848
+ return result;
2678
2849
  }).finally(() => {
2679
- if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
2850
+ // mapConcurrent rejects before siblings settle, so every attached child
2851
+ // attempts idle removal after releasing its own control. Detached receipts
2852
+ // transfer both responsibilities to the authoritative exit callback.
2853
+ if (!detachedReceipt) {
2854
+ if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
2855
+ removeForegroundControlIfIdle(input.state, input.runId);
2856
+ }
2680
2857
  });
2681
- }, input.globalSemaphore);
2858
+ completedResults.push(result);
2859
+ return result;
2860
+ }, input.globalSemaphore, () => {
2861
+ if (input.foregroundControl) settleForegroundSchedulingOwner(input.foregroundControl);
2862
+ removeForegroundControlIfIdle(input.state, input.runId);
2863
+ });
2682
2864
  }
2683
2865
 
2684
2866
  async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
@@ -2746,7 +2928,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2746
2928
  if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
2747
2929
  }
2748
2930
 
2749
- const currentProvider = ctx.model?.provider;
2931
+ const parentModel = data.parentModel;
2932
+ const currentProvider = parentModel?.provider;
2750
2933
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
2751
2934
  let taskTexts = tasks.map((t) => t.task);
2752
2935
  const skillOverrides: (string[] | false | undefined)[] = tasks.map((t) =>
@@ -2761,7 +2944,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2761
2944
  ...(task.model !== undefined ? { model: task.model } : {}),
2762
2945
  }));
2763
2946
  const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
2764
- resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope }),
2947
+ resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, { scope: data.modelScope }),
2765
2948
  );
2766
2949
 
2767
2950
  if (params.clarify === true && ctx.hasUI) {
@@ -2796,7 +2979,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2796
2979
  for (let i = 0; i < result.behaviorOverrides.length; i++) {
2797
2980
  const override = result.behaviorOverrides[i];
2798
2981
  if (override?.model !== undefined) {
2799
- modelOverrides[i] = resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope });
2982
+ modelOverrides[i] = resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, parentModel, availableModels, currentProvider, { scope: data.modelScope });
2800
2983
  behaviorOverrides[i]!.model = override.model;
2801
2984
  }
2802
2985
  if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
@@ -2822,8 +3005,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2822
3005
  cwd: ctx.cwd,
2823
3006
  currentSessionId: deps.state.currentSessionId!,
2824
3007
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
2825
- currentModelProvider: ctx.model?.provider,
2826
- currentModel: ctx.model,
3008
+ currentModelProvider: parentModel?.provider,
3009
+ currentModel: parentModel,
2827
3010
  modelScope: data.modelScope,
2828
3011
  interactive: ctx.hasUI,
2829
3012
  };
@@ -2875,6 +3058,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2875
3058
  nestedRoute: data.nestedRoute,
2876
3059
  timeoutMs: data.timeoutMs,
2877
3060
  turnBudget: data.turnBudget,
3061
+ usageBudget: data.usageBudget,
2878
3062
  toolBudget: data.toolBudget,
2879
3063
  configToolBudget: data.configToolBudget,
2880
3064
  globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
@@ -2958,6 +3142,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2958
3142
  progressDir: parallelProgressDir,
2959
3143
  availableModels,
2960
3144
  modelScope: data.modelScope,
3145
+ parentModel,
2961
3146
  modelOverrides,
2962
3147
  behaviors,
2963
3148
  firstProgressIndex: parallelProgressPrecreated ? -1 : firstProgressIndex,
@@ -2978,6 +3163,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2978
3163
  timeoutMs: data.timeoutMs,
2979
3164
  deadlineAt,
2980
3165
  turnBudget: data.turnBudget,
3166
+ usageBudget: data.usageBudget,
2981
3167
  toolBudgets,
2982
3168
  agentContract: params.agentContract,
2983
3169
  });
@@ -3001,14 +3187,17 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
3001
3187
  handoff = finalizeParallelWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: effectiveCwd, tasks, results });
3002
3188
  }
3003
3189
  const interrupted = results.find((result) => result.interrupted);
3190
+ const totalCost = sumResultsCost(results);
3004
3191
  const details = compactForegroundDetails({
3005
3192
  mode: "parallel",
3006
3193
  runId,
3194
+ timeoutMs: data.timeoutMs,
3007
3195
  results,
3008
3196
  progress: params.includeProgress ? allProgress : undefined,
3009
3197
  artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
3010
3198
  totalChildUsage: sumResultsUsage(results),
3011
- totalCost: sumResultsCost(results),
3199
+ totalCost,
3200
+ usageBudget: usageBudgetState(data.usageBudget, totalCost),
3012
3201
  ...(handoff?.reference ? { parallelHandoff: handoff.reference } : {}),
3013
3202
  });
3014
3203
  rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
@@ -3105,13 +3294,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3105
3294
  const effectiveToolBudget = resolveEffectiveToolBudget({ runBudget: data.toolBudget, agentBudget: agentConfig.toolBudget, configBudget: data.configToolBudget });
3106
3295
  if (effectiveToolBudget.error) return toExecutionErrorResult(params, new Error(effectiveToolBudget.error), data.contextPolicy.contextSummary);
3107
3296
 
3108
- const currentProvider = ctx.model?.provider;
3297
+ const parentModel = data.parentModel;
3298
+ const currentProvider = parentModel?.provider;
3109
3299
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
3110
3300
  let task = params.task ?? "";
3111
3301
  let modelOverride: string | undefined = resolveEffectiveSubagentModel(
3112
3302
  params.model as string | undefined,
3113
3303
  agentConfig.model,
3114
- ctx.model,
3304
+ parentModel,
3115
3305
  availableModels,
3116
3306
  currentProvider,
3117
3307
  { scope: data.modelScope },
@@ -3151,7 +3341,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3151
3341
 
3152
3342
  task = result.templates[0]!;
3153
3343
  const override = result.behaviorOverrides[0];
3154
- if (override?.model !== undefined) modelOverride = resolveEffectiveSubagentModel(override.model, agentConfig.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope });
3344
+ if (override?.model !== undefined) modelOverride = resolveEffectiveSubagentModel(override.model, agentConfig.model, parentModel, availableModels, currentProvider, { scope: data.modelScope });
3155
3345
  if (override?.output !== undefined) effectiveOutput = normalizeSingleOutputOverride(override.output, agentConfig.output);
3156
3346
  if (override?.skills !== undefined) skillOverride = override.skills;
3157
3347
 
@@ -3169,8 +3359,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3169
3359
  cwd: ctx.cwd,
3170
3360
  currentSessionId: deps.state.currentSessionId!,
3171
3361
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
3172
- currentModelProvider: ctx.model?.provider,
3173
- currentModel: ctx.model,
3362
+ currentModelProvider: parentModel?.provider,
3363
+ currentModel: parentModel,
3174
3364
  modelScope: data.modelScope,
3175
3365
  interactive: ctx.hasUI,
3176
3366
  };
@@ -3210,6 +3400,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3210
3400
  timeoutMs: data.timeoutMs,
3211
3401
  turnBudget: data.turnBudget,
3212
3402
  toolBudget: effectiveToolBudget.toolBudget,
3403
+ usageBudget: data.usageBudget,
3213
3404
  allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
3214
3405
  });
3215
3406
  }
@@ -3236,17 +3427,22 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3236
3427
  effectiveSkills = skillOverride;
3237
3428
  }
3238
3429
  const interruptController = new AbortController();
3430
+ let detachForeground: ((reason?: string) => boolean) | undefined;
3239
3431
  const foregroundControl = deps.state.foregroundControls.get(runId);
3240
3432
  if (foregroundControl) {
3433
+ const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride));
3241
3434
  beginForegroundChild(foregroundControl, {
3242
3435
  index: 0,
3243
3436
  agent: params.agent!,
3244
3437
  description: foregroundControl.description,
3438
+ ...(modelOverride ? { model: modelOverride } : {}),
3439
+ ...(thinking ? { thinking } : {}),
3245
3440
  interrupt: () => {
3246
3441
  if (interruptController.signal.aborted) return false;
3247
3442
  interruptController.abort();
3248
3443
  return true;
3249
3444
  },
3445
+ detach: () => detachForeground?.("user request") === true,
3250
3446
  });
3251
3447
  }
3252
3448
 
@@ -3296,7 +3492,29 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3296
3492
  agentContract: params.agentContract,
3297
3493
  acceptance: params.acceptance,
3298
3494
  acceptanceContext: { mode: "single" },
3299
- onDetachedExit: (result) => updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events }),
3495
+ onDetachReady: (detach) => {
3496
+ detachForeground = detach;
3497
+ },
3498
+ onDetachedExit: (result) => {
3499
+ try {
3500
+ try {
3501
+ updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events });
3502
+ } catch {
3503
+ // Remembered foreground state is best-effort; run history and cleanup must still complete.
3504
+ }
3505
+ } finally {
3506
+ try {
3507
+ if (!artifactConfig.enabled) cleanupStructuredOutputRuntime(structuredRuntime);
3508
+ } finally {
3509
+ try {
3510
+ if (foregroundControl) finishForegroundChild(foregroundControl, 0);
3511
+ } finally {
3512
+ removeForegroundControlIfIdle(deps.state, runId);
3513
+ }
3514
+ }
3515
+ }
3516
+ recordRun(params.agent!, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0);
3517
+ },
3300
3518
  timeoutMs: data.timeoutMs,
3301
3519
  deadlineAt,
3302
3520
  turnBudget: data.turnBudget,
@@ -3306,10 +3524,17 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3306
3524
  allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
3307
3525
  });
3308
3526
  } finally {
3309
- if (!artifactConfig.enabled) cleanupStructuredOutputRuntime(structuredRuntime);
3527
+ // An attached runSync rejection still owns its child and structured runtime.
3528
+ // A successful detached receipt transfers both to onDetachedExit while the
3529
+ // authoritative completion remains live.
3530
+ if (!r?.detached) {
3531
+ if (!artifactConfig.enabled) cleanupStructuredOutputRuntime(structuredRuntime);
3532
+ if (foregroundControl) finishForegroundChild(foregroundControl, 0);
3533
+ }
3534
+ }
3535
+ if (!r.detached) {
3536
+ recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
3310
3537
  }
3311
- if (foregroundControl) finishForegroundChild(foregroundControl, 0);
3312
- recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
3313
3538
 
3314
3539
  if (r.progress) allProgress.push(r.progress);
3315
3540
  if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
@@ -3329,9 +3554,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3329
3554
  updateForegroundNestedProjection(foregroundControl);
3330
3555
  attachRootChildrenToSteps(runId, [r], foregroundControl.nestedChildren);
3331
3556
  }
3557
+ const totalCost = sumResultsCost([r]);
3332
3558
  const details = compactForegroundDetails({
3333
3559
  mode: "single",
3334
3560
  runId,
3561
+ timeoutMs: data.timeoutMs,
3335
3562
  results: [r],
3336
3563
  ...(data.turnBudget ? { turnBudget: data.turnBudget } : {}),
3337
3564
  ...(effectiveToolBudget.toolBudget ? { toolBudget: effectiveToolBudget.toolBudget } : {}),
@@ -3339,7 +3566,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
3339
3566
  artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
3340
3567
  truncation: r.truncation,
3341
3568
  totalChildUsage: sumResultsUsage([r]),
3342
- totalCost: sumResultsCost([r]),
3569
+ totalCost,
3570
+ usageBudget: usageBudgetState(data.usageBudget, totalCost),
3343
3571
  });
3344
3572
  rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
3345
3573
 
@@ -3460,6 +3688,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3460
3688
  const requestCwd = resolveRequestedCwd(ctx.cwd, requestParams.cwd);
3461
3689
  const paramsWithResolvedCwd = requestParams.cwd === undefined ? requestParams : { ...requestParams, cwd: requestCwd };
3462
3690
  const action = paramsWithResolvedCwd.action;
3691
+ let requestParentModel: ParentModel | undefined;
3692
+ try {
3693
+ requestParentModel = rememberParentModel(deps.state, resolveCurrentSessionId(ctx.sessionManager), ctx.model);
3694
+ } catch (error) {
3695
+ if (action?.toLowerCase() !== "doctor") throw error;
3696
+ requestParentModel = normalizeParentModel(ctx.model);
3697
+ }
3463
3698
  if (action) {
3464
3699
  if ((WATCHDOG_TOOL_ACTIONS as readonly string[]).includes(action)) {
3465
3700
  if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
@@ -3616,8 +3851,61 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3616
3851
  }
3617
3852
  return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots }));
3618
3853
  }
3854
+
3855
+ if (action === "approve-checkpoint" || action === "reject-checkpoint") {
3856
+ deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
3857
+ const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
3858
+ if (!targetRunId && !paramsWithResolvedCwd.dir) {
3859
+ return { content: [{ type: "text", text: `action='${action}' requires id or dir.` }], isError: true, details: { mode: "management", results: [] } };
3860
+ }
3861
+ try {
3862
+ const resolved = targetRunId ? resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }) : undefined;
3863
+ const decision = action === "approve-checkpoint" ? "approved" : "rejected";
3864
+ if (resolved?.kind === "foreground") {
3865
+ const run = deps.state.foregroundRuns?.get(resolved.id);
3866
+ if (!run?.checkpoint || run.checkpoint.status !== "pending") {
3867
+ return { content: [{ type: "text", text: `Run '${resolved.id}' is not paused at an approval checkpoint.` }], isError: true, details: { mode: "management", results: [] } };
3868
+ }
3869
+ if (deps.state.currentSessionId && run.sessionId !== deps.state.currentSessionId) {
3870
+ return { content: [{ type: "text", text: `Run '${resolved.id}' was not found in the active session.` }], isError: true, details: { mode: "management", results: [] } };
3871
+ }
3872
+ const decidedAt = Date.now();
3873
+ const checkpoint = { ...run.checkpoint, status: decision, ...(decision === "approved" ? { approvedAt: decidedAt } : { rejectedAt: decidedAt }) };
3874
+ run.checkpoint = checkpoint;
3875
+ run.updatedAt = decidedAt;
3876
+ return {
3877
+ content: [{ type: "text", text: `Checkpoint '${checkpoint.name}' ${decision} for foreground run ${resolved.id}.` }],
3878
+ details: { mode: "management", results: [], checkpoint },
3879
+ };
3880
+ }
3881
+ const location = paramsWithResolvedCwd.dir
3882
+ ? resolveAsyncRunLocation(paramsWithResolvedCwd, ASYNC_DIR, RESULTS_DIR)
3883
+ : resolved?.kind === "async"
3884
+ ? resolved.location
3885
+ : undefined;
3886
+ if (!location?.asyncDir) {
3887
+ return { content: [{ type: "text", text: `action='${action}' targets paused foreground or async checkpoints. No matching run found.` }], isError: true, details: { mode: "management", results: [] } };
3888
+ }
3889
+ const status = readStatus(location.asyncDir);
3890
+ const runId = status?.runId ?? location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir);
3891
+ if (!status?.checkpoint || status.state !== "paused") {
3892
+ return { content: [{ type: "text", text: `Run '${runId}' is not paused at an approval checkpoint.` }], isError: true, details: { mode: "management", results: [] } };
3893
+ }
3894
+ if (deps.state.currentSessionId && status.sessionId !== deps.state.currentSessionId) {
3895
+ return { content: [{ type: "text", text: `Run '${runId}' was not found in the active session.` }], isError: true, details: { mode: "management", results: [] } };
3896
+ }
3897
+ deliverCheckpointDecisionRequest({ asyncDir: location.asyncDir, decision, source: "subagent-action", ...(paramsWithResolvedCwd.message ? { reason: paramsWithResolvedCwd.message } : {}) });
3898
+ return {
3899
+ content: [{ type: "text", text: `Checkpoint '${status.checkpoint.name}' ${decision} for run ${runId}.` }],
3900
+ details: { mode: "management", results: [], checkpoint: { ...status.checkpoint, status: decision } },
3901
+ };
3902
+ } catch (error) {
3903
+ const text = error instanceof Error ? error.message : String(error);
3904
+ return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
3905
+ }
3906
+ }
3619
3907
  if (action === "resume") {
3620
- return resumeAsyncRun({ params: paramsWithResolvedCwd, requestCwd, ctx, deps });
3908
+ return resumeAsyncRun({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel });
3621
3909
  }
3622
3910
  if (action === "steer") {
3623
3911
  deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
@@ -3640,7 +3928,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3640
3928
  ? {}
3641
3929
  : {
3642
3930
  recover: ({ absoluteDeadlineAt, ...limits }) =>
3643
- resumeAsyncRun({ params: { ...limits, action: "resume", id: runId, message }, requestCwd, ctx, deps, absoluteDeadlineAt }),
3931
+ resumeAsyncRun({ params: { ...limits, action: "resume", id: runId, message }, requestCwd, ctx, deps, parentModel: requestParentModel, absoluteDeadlineAt }),
3644
3932
  }
3645
3933
  ),
3646
3934
  });
@@ -3677,6 +3965,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3677
3965
  requestCwd,
3678
3966
  ctx,
3679
3967
  deps,
3968
+ parentModel: requestParentModel,
3680
3969
  absoluteDeadlineAt,
3681
3970
  }),
3682
3971
  }
@@ -3684,7 +3973,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3684
3973
  });
3685
3974
  }
3686
3975
  if (action === "append-step") {
3687
- return appendStepToAsyncChain({ params: paramsWithResolvedCwd, requestCwd, ctx, deps });
3976
+ return appendStepToAsyncChain({ params: paramsWithResolvedCwd, requestCwd, ctx, deps, parentModel: requestParentModel });
3688
3977
  }
3689
3978
  if (action === "schedule" || action === "schedule-list" || action === "schedule-status" || action === "schedule-cancel") {
3690
3979
  if (!deps.handleScheduledRunAction) {
@@ -3790,6 +4079,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3790
4079
  ...ctx,
3791
4080
  cwd: requestCwd,
3792
4081
  config: deps.config,
4082
+ currentSessionId: deps.state.currentSessionId ?? ctx.sessionManager.getSessionId() ?? undefined,
3793
4083
  });
3794
4084
  }
3795
4085
 
@@ -3827,17 +4117,19 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3827
4117
  if (runToolBudget.error) return buildRequestedModeError(effectiveParams, runToolBudget.error);
3828
4118
  const configToolBudget = resolveToolBudget(deps.config.toolBudget, "config.toolBudget");
3829
4119
  if (configToolBudget.error) return buildRequestedModeError(effectiveParams, configToolBudget.error);
4120
+ const usageBudget = validateUsageBudgetConfig(effectiveParams.usageBudget ?? deps.config.usageBudget, effectiveParams.usageBudget ? "usageBudget" : "config.usageBudget");
4121
+ if (usageBudget.error) return buildRequestedModeError(effectiveParams, usageBudget.error);
3830
4122
 
3831
4123
  const scope: AgentScope = resolveExecutionAgentScope(effectiveParams.agentScope);
3832
4124
  const effectiveCwd = effectiveParams.cwd ?? ctx.cwd;
3833
4125
  const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
3834
- deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
3835
4126
  const discovered = deps.discoverAgents(effectiveCwd, scope);
3836
4127
  const discoveredAgents = discovered.agents;
4128
+ const canonicalParams = canonicalizeExecutionParams(effectiveParams, discoveredAgents);
4129
+ if (canonicalParams.error) return buildRequestedModeError(effectiveParams, canonicalParams.error);
4130
+ effectiveParams = canonicalParams.params!;
3837
4131
  const modelScope = discovered.modelScope;
3838
4132
  effectiveParams = applySingleAgentLaunchDefaults(effectiveParams, discoveredAgents);
3839
- const foregroundTimeout = resolveForegroundTimeout(effectiveParams);
3840
- if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
3841
4133
  const turnBudget = resolveTurnBudgetConfig(effectiveParams.turnBudget ?? deps.config.turnBudget);
3842
4134
  if (turnBudget.error) return buildRequestedModeError(effectiveParams, turnBudget.error);
3843
4135
  const contextPolicy = resolveAgentDefaultContextPolicy(effectiveParams, discoveredAgents);
@@ -3891,25 +4183,26 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3891
4183
  const forkThinkingDowngrades = new Map<number, string>();
3892
4184
  try {
3893
4185
  const forkAvailableModels = contextPolicy.usesFork ? ctx.modelRegistry.getAvailable().map(toModelInfo) : [];
4186
+ const parentModel = requestParentModel;
3894
4187
  prepareForkThinking = (agentName, index, modelOverride) => {
3895
4188
  const agentConfig = agents.find((agent) => agent.name === agentName);
3896
4189
  const primaryModel = resolveEffectiveSubagentModel(
3897
4190
  modelOverride,
3898
4191
  agentConfig?.model,
3899
- ctx.model,
4192
+ parentModel,
3900
4193
  forkAvailableModels,
3901
- ctx.model?.provider,
4194
+ parentModel?.provider,
3902
4195
  );
3903
4196
  const candidates = buildModelCandidates(
3904
4197
  primaryModel,
3905
4198
  agentConfig?.fallbackModels,
3906
4199
  forkAvailableModels,
3907
- ctx.model?.provider,
4200
+ parentModel?.provider,
3908
4201
  );
3909
4202
  forkThinkingRequirements.set(
3910
4203
  index,
3911
4204
  candidates.length === 0
3912
- || candidates.some((candidate) => forkedChildRequiresThinkingOff(candidate, forkAvailableModels, ctx.model?.provider)),
4205
+ || candidates.some((candidate) => forkedChildRequiresThinkingOff(candidate, forkAvailableModels, parentModel?.provider)),
3913
4206
  );
3914
4207
  };
3915
4208
  const forkContextResolver = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined, {
@@ -3923,6 +4216,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3923
4216
  const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
3924
4217
  const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
3925
4218
  const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
4219
+ const foregroundTimeout = resolveForegroundTimeout(
4220
+ effectiveParams,
4221
+ effectiveAsync ? undefined : DEFAULT_FOREGROUND_TIMEOUT_MS,
4222
+ );
4223
+ if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
3926
4224
  const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
3927
4225
 
3928
4226
  const artifactConfig: ArtifactConfig = {
@@ -3980,7 +4278,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3980
4278
  if (chainBindingsError) return withResolvedContext(chainBindingsError, contextPolicy.contextSummary);
3981
4279
 
3982
4280
  const onUpdateWithContext = onUpdate
3983
- ? (r: AgentToolResult<Details>) => onUpdate(withResolvedContext(r, contextPolicy.contextSummary))
4281
+ ? (r: AgentToolResult<Details>) => onUpdate(withResolvedContext({
4282
+ ...r,
4283
+ details: { ...r.details, runId },
4284
+ }, contextPolicy.contextSummary))
3984
4285
  : undefined;
3985
4286
 
3986
4287
  const reservation = reserveSpawnBudget(
@@ -4015,10 +4316,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4015
4316
  timeoutMs: foregroundTimeout.timeoutMs,
4016
4317
  turnBudget: turnBudget.turnBudget,
4017
4318
  toolBudget: runToolBudget.toolBudget,
4319
+ usageBudget: usageBudget.budget,
4018
4320
  allowZeroToolBudget,
4019
4321
  configToolBudget: configToolBudget.toolBudget,
4020
4322
  contextPolicy,
4021
4323
  modelScope,
4324
+ parentModel: requestParentModel,
4022
4325
  parentSessionId: deps.state.currentSessionId,
4023
4326
  capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(deps.state.currentSessionId ?? undefined),
4024
4327
  };
@@ -4030,6 +4333,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4030
4333
  ? undefined
4031
4334
  : {
4032
4335
  runId,
4336
+ ...(deps.state.currentSessionId ? { sessionId: deps.state.currentSessionId } : {}),
4033
4337
  mode: foregroundMode,
4034
4338
  startedAt: Date.now(),
4035
4339
  updatedAt: Date.now(),
@@ -4039,6 +4343,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4039
4343
  description: foregroundDescription,
4040
4344
  currentActivityState: undefined,
4041
4345
  activeChildren: new Map(),
4346
+ // The outer executor owns scheduling until its finally block settles.
4347
+ schedulingOwners: 1,
4042
4348
  nestedRoute,
4043
4349
  interrupt: undefined,
4044
4350
  };
@@ -4136,11 +4442,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4136
4442
  return errorResult;
4137
4443
  } finally {
4138
4444
  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
- }
4445
+ settleForegroundSchedulingOwner(foregroundControl);
4446
+ removeForegroundControlIfIdle(deps.state, runId);
4144
4447
  }
4145
4448
  }
4146
4449