@selesai/code 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. package/dist/config.d.ts +4 -15
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +5 -45
  4. package/dist/config.js.map +1 -1
  5. package/dist/core/agent-session.d.ts +1 -0
  6. package/dist/core/agent-session.d.ts.map +1 -1
  7. package/dist/core/agent-session.js +8 -0
  8. package/dist/core/agent-session.js.map +1 -1
  9. package/dist/core/diagnostics.d.ts +1 -0
  10. package/dist/core/diagnostics.d.ts.map +1 -1
  11. package/dist/core/diagnostics.js.map +1 -1
  12. package/dist/core/model-registry.d.ts +4 -1
  13. package/dist/core/model-registry.d.ts.map +1 -1
  14. package/dist/core/model-registry.js +101 -51
  15. package/dist/core/model-registry.js.map +1 -1
  16. package/dist/core/package-manager.d.ts +10 -0
  17. package/dist/core/package-manager.d.ts.map +1 -1
  18. package/dist/core/package-manager.js +93 -8
  19. package/dist/core/package-manager.js.map +1 -1
  20. package/dist/core/package-manager.test.d.ts +2 -0
  21. package/dist/core/package-manager.test.d.ts.map +1 -0
  22. package/dist/core/package-manager.test.js +20 -0
  23. package/dist/core/package-manager.test.js.map +1 -0
  24. package/dist/core/resource-loader.d.ts +3 -0
  25. package/dist/core/resource-loader.d.ts.map +1 -1
  26. package/dist/core/resource-loader.js +10 -0
  27. package/dist/core/resource-loader.js.map +1 -1
  28. package/dist/core/settings-manager.d.ts +1 -0
  29. package/dist/core/settings-manager.d.ts.map +1 -1
  30. package/dist/core/settings-manager.js.map +1 -1
  31. package/dist/defaults/models.json +64 -113
  32. package/dist/extensions/caveman/caveman-instructions.cjs +39 -0
  33. package/dist/extensions/caveman/index.js +116 -0
  34. package/dist/extensions/caveman/package.json +8 -0
  35. package/dist/extensions/caveman/test/extension.test.js +185 -0
  36. package/dist/extensions/caveman/test/helpers.test.js +67 -0
  37. package/dist/extensions/copy-turn.ts +2 -2
  38. package/dist/extensions/pi-intercom/broker/broker.ts +2 -3
  39. package/dist/extensions/pi-intercom/broker/paths.ts +10 -5
  40. package/dist/extensions/pi-intercom/broker/spawn.ts +2 -3
  41. package/dist/extensions/pi-intercom/config.ts +2 -2
  42. package/dist/extensions/pi-intercom/index.ts +2 -2
  43. package/dist/extensions/pi-intercom/package.json +2 -2
  44. package/dist/extensions/pi-intercom/ui/compose.ts +3 -3
  45. package/dist/extensions/pi-intercom/ui/inline-message.ts +3 -3
  46. package/dist/extensions/pi-intercom/ui/session-list.ts +3 -3
  47. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +2 -2
  48. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +2 -1
  49. package/dist/extensions/pi-powerline-footer/index.ts +40 -47
  50. package/dist/extensions/pi-powerline-footer/package.json +2 -2
  51. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +10 -10
  52. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +1 -1
  53. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +2 -2
  54. package/dist/extensions/pi-powerline-footer/theme.ts +6 -6
  55. package/dist/extensions/pi-powerline-footer/tps.ts +1 -1
  56. package/dist/extensions/pi-powerline-footer/types.ts +7 -7
  57. package/dist/extensions/pi-powerline-footer/welcome.ts +23 -16
  58. package/dist/extensions/pi-powerline-footer/working-vibes.ts +52 -59
  59. package/dist/extensions/pi-rewind-hook/index.ts +1 -1
  60. package/dist/extensions/pi-rewind-hook/package.json +1 -1
  61. package/dist/extensions/pi-subagents/README.md +224 -52
  62. package/dist/extensions/pi-subagents/agents/architect.md +188 -0
  63. package/dist/extensions/pi-subagents/agents/builder.md +120 -0
  64. package/dist/extensions/pi-subagents/agents/commentator.md +131 -0
  65. package/dist/extensions/pi-subagents/agents/explorer.md +51 -0
  66. package/dist/extensions/pi-subagents/agents/recapper.md +21 -0
  67. package/dist/extensions/pi-subagents/install.mjs +93 -0
  68. package/dist/extensions/pi-subagents/package.json +4 -5
  69. package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +1 -1
  70. package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +1 -1
  71. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +76 -13
  72. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +179 -2
  73. package/dist/extensions/pi-subagents/src/agents/agent-memory.ts +254 -0
  74. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +11 -0
  75. package/dist/extensions/pi-subagents/src/agents/agents.ts +193 -19
  76. package/dist/extensions/pi-subagents/src/agents/chain-serializer.ts +27 -2
  77. package/dist/extensions/pi-subagents/src/extension/config.ts +27 -4
  78. package/dist/extensions/pi-subagents/src/extension/doctor.ts +1 -7
  79. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -2
  80. package/dist/extensions/pi-subagents/src/extension/index.ts +73 -71
  81. package/dist/extensions/pi-subagents/src/extension/rpc.ts +369 -0
  82. package/dist/extensions/pi-subagents/src/extension/schemas.ts +54 -10
  83. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +200 -0
  84. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +21 -253
  85. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +519 -0
  86. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +1 -1
  87. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +195 -105
  88. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +88 -2
  89. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +67 -10
  90. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +34 -4
  91. package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +166 -0
  92. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +156 -1
  93. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +515 -0
  94. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +161 -44
  95. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +1 -2
  96. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +3 -2
  97. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +167 -6
  98. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +514 -0
  99. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +28 -1
  100. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +873 -204
  101. package/dist/extensions/pi-subagents/src/runs/background/wait.ts +353 -0
  102. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +123 -27
  103. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +174 -70
  104. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +569 -81
  105. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +45 -22
  106. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +3 -1
  107. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +2 -2
  108. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +172 -21
  109. package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +128 -0
  110. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +89 -0
  111. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +50 -1
  112. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +35 -4
  113. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +63 -45
  114. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +2 -0
  115. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +125 -5
  116. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +74 -0
  117. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +52 -0
  118. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +28 -5
  119. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +16 -1
  120. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +15 -2
  121. package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +212 -0
  122. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +133 -22
  123. package/dist/extensions/pi-subagents/src/shared/settings.ts +3 -1
  124. package/dist/extensions/pi-subagents/src/shared/types.ts +197 -4
  125. package/dist/extensions/pi-subagents/src/shared/utils.ts +108 -17
  126. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +330 -0
  127. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +135 -4
  128. package/dist/extensions/pi-subagents/src/tui/render.ts +32 -12
  129. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +94 -0
  130. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2772 -0
  131. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +1107 -0
  132. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +463 -0
  133. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +274 -0
  134. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1535 -0
  135. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +242 -0
  136. package/dist/extensions/pi-subagents/test/integration/doctor-executor.test.ts +128 -0
  137. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +252 -0
  138. package/dist/extensions/pi-subagents/test/integration/foreground-result-size.test.ts +188 -0
  139. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +1306 -0
  140. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +1033 -0
  141. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +397 -0
  142. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +583 -0
  143. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +691 -0
  144. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +892 -0
  145. package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +62 -0
  146. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1824 -0
  147. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1284 -0
  148. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +127 -0
  149. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +299 -0
  150. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +37 -0
  151. package/dist/extensions/pi-subagents/test/support/helpers.ts +189 -0
  152. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +335 -0
  153. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +129 -0
  154. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +184 -0
  155. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +285 -0
  156. package/dist/extensions/pi-subagents/test/support/register-loader.mjs +15 -0
  157. package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +108 -0
  158. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +421 -0
  159. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +213 -0
  160. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +343 -0
  161. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +1199 -0
  162. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +556 -0
  163. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +420 -0
  164. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +579 -0
  165. package/dist/extensions/pi-subagents/test/unit/agent-scope.test.ts +20 -0
  166. package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +88 -0
  167. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +24 -0
  168. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +86 -0
  169. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +221 -0
  170. package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +98 -0
  171. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +285 -0
  172. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +112 -0
  173. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +206 -0
  174. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +159 -0
  175. package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +229 -0
  176. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +225 -0
  177. package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +101 -0
  178. package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +228 -0
  179. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +36 -0
  180. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +152 -0
  181. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +72 -0
  182. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +327 -0
  183. package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +161 -0
  184. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +159 -0
  185. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +217 -0
  186. package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +88 -0
  187. package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +68 -0
  188. package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +78 -0
  189. package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +415 -0
  190. package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +126 -0
  191. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +271 -0
  192. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +173 -0
  193. package/dist/extensions/pi-subagents/test/unit/jsonl-writer.test.ts +119 -0
  194. package/dist/extensions/pi-subagents/test/unit/live-async-resume.test.ts +170 -0
  195. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +346 -0
  196. package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +62 -0
  197. package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +87 -0
  198. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +145 -0
  199. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +162 -0
  200. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +470 -0
  201. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +329 -0
  202. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +319 -0
  203. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +70 -0
  204. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +247 -0
  205. package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +113 -0
  206. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +94 -0
  207. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +803 -0
  208. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +209 -0
  209. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +238 -0
  210. package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +118 -0
  211. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +299 -0
  212. package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +408 -0
  213. package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +134 -0
  214. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +257 -0
  215. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +209 -0
  216. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +226 -0
  217. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +294 -0
  218. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +147 -0
  219. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +997 -0
  220. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +478 -0
  221. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +562 -0
  222. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +213 -0
  223. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +438 -0
  224. package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +54 -0
  225. package/dist/extensions/pi-subagents/test/unit/slash-chain-groups.test.ts +464 -0
  226. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +255 -0
  227. package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +19 -0
  228. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +245 -0
  229. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +517 -0
  230. package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +72 -0
  231. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +52 -0
  232. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +237 -0
  233. package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +60 -0
  234. package/dist/extensions/pi-subagents/test/unit/ts-loader.test.ts +33 -0
  235. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +202 -0
  236. package/dist/extensions/pi-subagents/test/unit/types-fork-preamble.test.ts +23 -0
  237. package/dist/extensions/pi-subagents/test/unit/wait.test.ts +398 -0
  238. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +88 -0
  239. package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +26 -0
  240. package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +134 -0
  241. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +479 -0
  242. package/dist/extensions/pi-web-agent/package.json +2 -2
  243. package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +2 -2
  244. package/dist/extensions/pi-web-agent/src/changelog-notice.ts +5 -2
  245. package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +1 -1
  246. package/dist/extensions/pi-web-agent/src/extension.ts +1 -1
  247. package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +6 -4
  248. package/dist/extensions/question/helpers.ts +3 -3
  249. package/dist/extensions/question/index.ts +2 -2
  250. package/dist/extensions/question/navigation.ts +2 -2
  251. package/dist/extensions/question/package.json +2 -2
  252. package/dist/extensions/question/question-list.ts +2 -2
  253. package/dist/extensions/question/tests/question-list.test.ts +2 -2
  254. package/dist/extensions/question/tui-adapter.ts +2 -2
  255. package/dist/extensions/question/types.ts +2 -2
  256. package/dist/extensions/question/ui-protocol.ts +2 -2
  257. package/dist/extensions/rtk.ts +2 -2
  258. package/dist/extensions/tokenin-onboarding.ts +2 -2
  259. package/dist/extensions/undo.ts +1 -1
  260. package/dist/extensions/web-agent-onboarding.ts +3 -3
  261. package/dist/extensions/workflow/adapter.ts +16 -8
  262. package/dist/extensions/workflow/extension.ts +1 -1
  263. package/dist/extensions/workflow/package.json +1 -1
  264. package/dist/main.d.ts.map +1 -1
  265. package/dist/main.js +4 -0
  266. package/dist/main.js.map +1 -1
  267. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  268. package/dist/modes/interactive/components/assistant-message.js +2 -0
  269. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  270. package/dist/modes/interactive/components/first-time-setup.d.ts +1 -1
  271. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
  272. package/dist/modes/interactive/components/first-time-setup.js +19 -5
  273. package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
  274. package/dist/skills/caveman/SKILL.md +49 -0
  275. package/dist/skills/handoff-text/SKILL.md +13 -0
  276. package/dist/utils/thinking-tags.d.ts +7 -0
  277. package/dist/utils/thinking-tags.d.ts.map +1 -0
  278. package/dist/utils/thinking-tags.js +63 -0
  279. package/dist/utils/thinking-tags.js.map +1 -0
  280. package/docs/docs.json +4 -0
  281. package/docs/extensions.md +2 -0
  282. package/docs/settings.md +15 -0
  283. package/docs/shared-host-extensions.md +109 -0
  284. package/package.json +2 -2
@@ -4,7 +4,7 @@ import * as path from "node:path";
4
4
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
5
5
  import type { ExtensionAPI, ExtensionContext } from "@selesai/code";
6
6
  import { type AgentConfig, type AgentScope } from "../../agents/agents.ts";
7
- import { getArtifactsDir } from "../../shared/artifacts.ts";
7
+ import { getArtifactsDir, getProjectChainRunsDir } from "../../shared/artifacts.ts";
8
8
  import { ChainClarifyComponent, type ChainClarifyResult } from "./chain-clarify.ts";
9
9
  import { toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
10
10
  import { executeChain } from "./chain-execution.ts";
@@ -14,6 +14,7 @@ import { buildDoctorReport } from "../../extension/doctor.ts";
14
14
  import { clearPendingForegroundControlNotices } from "../../extension/control-notices.ts";
15
15
  import { runSync } from "./execution.ts";
16
16
  import { resolveModelCandidate, resolveSubagentModelOverride } from "../shared/model-fallback.ts";
17
+ import type { ModelScopeConfig } from "../shared/model-scope.ts";
17
18
  import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
18
19
  import { recordRun } from "../shared/run-history.ts";
19
20
  import {
@@ -32,6 +33,7 @@ import {
32
33
  } from "../../shared/settings.ts";
33
34
  import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
34
35
  import { buildAsyncRunnerSteps, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable } from "../background/async-execution.ts";
36
+ import type { ScheduledRunAction } from "../background/scheduled-runs.ts";
35
37
  import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
36
38
  import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
37
39
  import { validateAcceptanceInput } from "../shared/acceptance.ts";
@@ -39,8 +41,11 @@ import { createForkContextResolver } from "../../shared/fork-context.ts";
39
41
  import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
40
42
  import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
41
43
  import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
44
+ import { DEFAULT_TURN_BUDGET_GRACE_TURNS } from "../shared/turn-budget.ts";
45
+ import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
42
46
  import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
43
- import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd } from "../../shared/utils.ts";
47
+ import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
48
+ import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
44
49
  import {
45
50
  attachNestedChildrenToResultChildren,
46
51
  buildSubagentResultIntercomPayload,
@@ -50,11 +55,11 @@ import {
50
55
  resolveSubagentResultStatus,
51
56
  stripDetailsOutputsForIntercomReceipt,
52
57
  } from "../../intercom/result-intercom.ts";
53
- import { buildRevivedAsyncTask, interruptLiveAsyncResumeTarget, resolveAsyncResumeTarget } from "../background/async-resume.ts";
54
- import { deliverInterruptRequest } from "../background/control-channel.ts";
58
+ import { buildRevivedAsyncTask, interruptLiveAsyncResumeTarget, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
59
+ import { deliverInterruptRequest, requestAsyncSteer } from "../background/control-channel.ts";
55
60
  import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
56
61
  import { resolveAsyncRootResultPath } from "../background/chain-root-attachment.ts";
57
- import { createNestedRoute, readNestedControlResults, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
62
+ import { attachRootChildrenToSteps, createNestedRoute, readNestedControlResults, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
58
63
  import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
59
64
  import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
60
65
  import { inspectSubagentStatus } from "../background/run-status.ts";
@@ -82,7 +87,11 @@ import {
82
87
  type NestedRouteInfo,
83
88
  type NestedRunSummary,
84
89
  type ResolvedControlConfig,
90
+ type ResolvedTurnBudget,
91
+ type ResolvedToolBudget,
85
92
  type SingleResult,
93
+ type ToolBudgetConfig,
94
+ type TurnBudgetConfig,
86
95
  type SubagentRunMode,
87
96
  type SubagentState,
88
97
  ASYNC_DIR,
@@ -92,6 +101,7 @@ import {
92
101
  SUBAGENT_CONTROL_EVENT,
93
102
  SUBAGENT_CONTROL_INTERCOM_EVENT,
94
103
  checkSubagentDepth,
104
+ resolveMaxSubagentSpawnsPerSession,
95
105
  resolveTopLevelParallelConcurrency,
96
106
  resolveTopLevelParallelMaxTasks,
97
107
  resolveChildMaxSubagentDepth,
@@ -99,7 +109,7 @@ import {
99
109
  wrapForkTask,
100
110
  } from "../../shared/types.ts";
101
111
 
102
- const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete"]);
112
+ const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset"]);
103
113
 
104
114
  interface TaskParam {
105
115
  agent: string;
@@ -113,6 +123,7 @@ interface TaskParam {
113
123
  model?: string;
114
124
  skill?: string | string[] | boolean;
115
125
  acceptance?: AcceptanceInput;
126
+ toolBudget?: ToolBudgetConfig;
116
127
  }
117
128
 
118
129
  export interface SubagentParamsLike {
@@ -121,6 +132,8 @@ export interface SubagentParamsLike {
121
132
  runId?: string;
122
133
  dir?: string;
123
134
  index?: number;
135
+ view?: "fleet" | "transcript";
136
+ lines?: number;
124
137
  agent?: string;
125
138
  task?: string;
126
139
  message?: string;
@@ -132,6 +145,8 @@ export interface SubagentParamsLike {
132
145
  async?: boolean;
133
146
  timeoutMs?: number;
134
147
  maxRuntimeMs?: number;
148
+ turnBudget?: TurnBudgetConfig;
149
+ toolBudget?: ToolBudgetConfig;
135
150
  clarify?: boolean;
136
151
  share?: boolean;
137
152
  control?: ControlConfig;
@@ -147,6 +162,8 @@ export interface SubagentParamsLike {
147
162
  agentScope?: unknown;
148
163
  chainDir?: string;
149
164
  acceptance?: AcceptanceInput;
165
+ schedule?: string;
166
+ scheduleName?: string;
150
167
  }
151
168
 
152
169
  interface ExecutorDeps {
@@ -154,10 +171,11 @@ interface ExecutorDeps {
154
171
  state: SubagentState;
155
172
  config: ExtensionConfig;
156
173
  asyncByDefault: boolean;
174
+ handleScheduledRunAction?: (params: SubagentParamsLike, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>;
157
175
  tempArtifactsDir: string;
158
176
  getSubagentSessionRoot: (parentSessionFile: string | null) => string;
159
177
  expandTilde: (p: string) => string;
160
- discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
178
+ discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[]; modelScope?: ModelScopeConfig };
161
179
  allowMutatingManagementActions?: boolean;
162
180
  kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
163
181
  }
@@ -175,6 +193,7 @@ interface ExecutionContextData {
175
193
  sessionDirForIndex: (idx?: number) => string;
176
194
  sessionFileForIndex: (idx?: number) => string | undefined;
177
195
  sessionFileForTask: (agentName: string, idx?: number) => string | undefined;
196
+ thinkingOverrideForTask: (agentName: string, idx?: number) => AgentConfig["thinking"] | undefined;
178
197
  artifactConfig: ArtifactConfig;
179
198
  artifactsDir: string;
180
199
  backgroundRequestedWhileClarifying: boolean;
@@ -184,7 +203,11 @@ interface ExecutionContextData {
184
203
  nestedRoute?: NestedRouteInfo;
185
204
  timeoutMs?: number;
186
205
  deadlineAt?: number;
206
+ turnBudget?: ResolvedTurnBudget;
207
+ toolBudget?: ResolvedToolBudget;
208
+ configToolBudget?: ResolvedToolBudget;
187
209
  contextPolicy: AgentDefaultContextPolicy;
210
+ modelScope?: ModelScopeConfig;
188
211
  }
189
212
 
190
213
  function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
@@ -233,6 +256,42 @@ function nestedResolutionScopeForExecutor(deps: ExecutorDeps): NestedRunResoluti
233
256
  };
234
257
  }
235
258
 
259
+ function trustedSessionRootsForStatus(ctx: ExtensionContext, deps: ExecutorDeps): string[] {
260
+ const roots = deps.config.defaultSessionDir ? [path.resolve(deps.expandTilde(deps.config.defaultSessionDir))] : [];
261
+ const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
262
+ if (parentSessionFile) roots.push(deps.getSubagentSessionRoot(parentSessionFile));
263
+ return [...new Set(roots)];
264
+ }
265
+
266
+ function reserveSubagentSpawns(input: { state: SubagentState; config: ExtensionConfig; sessionId: string | null; requested: number; mode: "single" | "parallel" | "chain" }): AgentToolResult<Details> | undefined {
267
+ if (input.requested <= 0) return undefined;
268
+ if (input.state.subagentSpawns?.sessionId !== input.sessionId) {
269
+ input.state.subagentSpawns = { sessionId: input.sessionId, count: 0 };
270
+ }
271
+ const maxSpawns = resolveMaxSubagentSpawnsPerSession(input.config.maxSubagentSpawnsPerSession);
272
+ const used = input.state.subagentSpawns.count;
273
+ if (used + input.requested > maxSpawns) {
274
+ return {
275
+ content: [{ type: "text", text: `Subagent spawn limit reached for this session (${used}/${maxSpawns} used, ${input.requested} requested). Complete the work directly or start a new session.` }],
276
+ isError: true,
277
+ details: { mode: input.mode, results: [] },
278
+ };
279
+ }
280
+ input.state.subagentSpawns.count = used + input.requested;
281
+ return undefined;
282
+ }
283
+
284
+ function countRequestedSubagentSpawns(params: SubagentParamsLike, config: ExtensionConfig): number {
285
+ if (params.tasks) return params.tasks.length;
286
+ if (params.chain) {
287
+ return params.chain.reduce((total, step) => {
288
+ if (isDynamicParallelStep(step)) return total + (step.expand.maxItems ?? config.chain?.dynamicFanout?.maxItems ?? 0);
289
+ return total + getStepAgents(step).length;
290
+ }, 0);
291
+ }
292
+ return params.agent ? 1 : 0;
293
+ }
294
+
236
295
  function foregroundStatusResult(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): AgentToolResult<Details> {
237
296
  let nestedWarning: string | undefined;
238
297
  try {
@@ -253,25 +312,70 @@ function foregroundStatusResult(control: SubagentState["foregroundControls"] ext
253
312
  return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "management", results: [] } };
254
313
  }
255
314
 
315
+ function trimRememberedForegroundRuns(state: SubagentState): void {
316
+ if (!state.foregroundRuns) return;
317
+ while (state.foregroundRuns.size > 50) {
318
+ const oldest = [...state.foregroundRuns.values()].sort((left, right) => left.updatedAt - right.updatedAt)[0];
319
+ if (!oldest) break;
320
+ state.foregroundRuns.delete(oldest.runId);
321
+ }
322
+ }
323
+
256
324
  function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; results: SingleResult[] }): void {
257
325
  state.foregroundRuns ??= new Map();
326
+ const previous = state.foregroundRuns.get(input.runId);
327
+ const updatedAt = Date.now();
258
328
  state.foregroundRuns.set(input.runId, {
259
329
  runId: input.runId,
260
330
  mode: input.mode,
261
331
  cwd: input.cwd,
262
- updatedAt: Date.now(),
263
- children: input.results.map((result, index) => ({
264
- agent: result.agent,
265
- index,
266
- status: resolveSubagentResultStatus({ exitCode: result.exitCode, interrupted: result.interrupted, detached: result.detached }),
267
- ...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
268
- })),
332
+ updatedAt,
333
+ children: input.results.map((result, index) => {
334
+ const child = {
335
+ agent: result.agent,
336
+ index,
337
+ status: resolveSubagentResultStatus({ exitCode: result.exitCode, interrupted: result.interrupted, detached: result.detached }),
338
+ updatedAt,
339
+ ...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
340
+ ...(result.finalOutput ? { finalOutput: result.finalOutput } : {}),
341
+ ...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
342
+ ...(result.artifactPaths ? { artifactPaths: result.artifactPaths } : {}),
343
+ ...(result.transcriptPath ? { transcriptPath: result.transcriptPath } : {}),
344
+ ...(result.transcriptError ? { transcriptError: result.transcriptError } : {}),
345
+ ...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
346
+ };
347
+ const recovered = previous?.children[index];
348
+ return child.status === "detached" && recovered && recovered.status !== "detached" ? recovered : child;
349
+ }),
269
350
  });
270
- while (state.foregroundRuns.size > 50) {
271
- const oldest = [...state.foregroundRuns.values()].sort((left, right) => left.updatedAt - right.updatedAt)[0];
272
- if (!oldest) break;
273
- state.foregroundRuns.delete(oldest.runId);
274
- }
351
+ trimRememberedForegroundRuns(state);
352
+ }
353
+
354
+ function updateRememberedForegroundChild(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; index: number; result: SingleResult }): void {
355
+ state.foregroundRuns ??= new Map();
356
+ const updatedAt = Date.now();
357
+ let run = state.foregroundRuns.get(input.runId);
358
+ if (!run) {
359
+ run = { runId: input.runId, mode: input.mode, cwd: input.cwd, updatedAt, children: [] };
360
+ state.foregroundRuns.set(input.runId, run);
361
+ }
362
+ run.updatedAt = updatedAt;
363
+ const child = run.children[input.index] ?? { agent: input.result.agent, index: input.index, status: "detached" as const };
364
+ run.children[input.index] = {
365
+ ...child,
366
+ agent: input.result.agent,
367
+ index: input.index,
368
+ status: resolveSubagentResultStatus({ exitCode: input.result.exitCode, interrupted: input.result.interrupted, detached: false }),
369
+ updatedAt,
370
+ ...(input.result.exitCode !== undefined ? { exitCode: input.result.exitCode } : {}),
371
+ ...(input.result.finalOutput ? { finalOutput: input.result.finalOutput } : {}),
372
+ ...(input.result.sessionFile ? { sessionFile: input.result.sessionFile } : {}),
373
+ ...(input.result.artifactPaths ? { artifactPaths: input.result.artifactPaths } : {}),
374
+ ...(input.result.transcriptPath ? { transcriptPath: input.result.transcriptPath } : {}),
375
+ ...(input.result.transcriptError ? { transcriptError: input.result.transcriptError } : {}),
376
+ ...(input.result.detachedReason ? { detachedReason: input.result.detachedReason } : {}),
377
+ };
378
+ trimRememberedForegroundRuns(state);
275
379
  }
276
380
 
277
381
  function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState): { runId: string; mode: "single" | "parallel" | "chain"; state: "complete"; agent: string; index: number; intercomTarget: string; cwd: string; sessionFile: string } | undefined {
@@ -463,6 +567,66 @@ function interruptAsyncRun(
463
567
  }
464
568
  }
465
569
 
570
+ function steerAsyncRun(input: {
571
+ state: SubagentState;
572
+ runId: string;
573
+ message: string;
574
+ index?: number;
575
+ kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
576
+ location: { asyncDir: string | null; resolvedId?: string };
577
+ }): AgentToolResult<Details> {
578
+ if (!input.location.asyncDir) {
579
+ return {
580
+ content: [{ type: "text", text: `Async run '${input.runId}' has no live run directory to steer.` }],
581
+ isError: true,
582
+ details: { mode: "management", results: [] },
583
+ };
584
+ }
585
+ const status = reconcileAsyncRun(input.location.asyncDir, { kill: input.kill }).status;
586
+ if (!status || (status.state !== "running" && status.state !== "queued")) {
587
+ return {
588
+ content: [{ type: "text", text: `Async run '${input.runId}' is not running or queued and cannot be steered.` }],
589
+ isError: true,
590
+ details: { mode: "management", results: [] },
591
+ };
592
+ }
593
+ const steps = status.steps ?? [];
594
+ if (input.index !== undefined) {
595
+ if (input.index < 0 || input.index >= steps.length) {
596
+ return {
597
+ content: [{ type: "text", text: `Async run '${status.runId}' has ${steps.length} children. Index ${input.index} is out of range.` }],
598
+ isError: true,
599
+ details: { mode: "management", results: [] },
600
+ };
601
+ }
602
+ const targetStep = steps[input.index];
603
+ if (targetStep && targetStep.status !== "running" && targetStep.status !== "pending") {
604
+ return {
605
+ content: [{ type: "text", text: `Async run '${status.runId}' child ${input.index} is ${targetStep.status} and cannot be steered.` }],
606
+ isError: true,
607
+ details: { mode: "management", results: [] },
608
+ };
609
+ }
610
+ } else {
611
+ const running = steps.filter((step) => step.status === "running");
612
+ if (running.length === 0 && steps.length > 1) {
613
+ return {
614
+ content: [{ type: "text", text: `Async run '${status.runId}' has no running child yet. Provide index to steer a queued child.` }],
615
+ isError: true,
616
+ details: { mode: "management", results: [] },
617
+ };
618
+ }
619
+ }
620
+ requestAsyncSteer(input.location.asyncDir, { message: input.message, targetIndex: input.index, source: "steer-action" });
621
+ const tracked = input.state.asyncJobs.get(status.runId);
622
+ if (tracked) tracked.updatedAt = Date.now();
623
+ const childText = input.index !== undefined ? ` child ${input.index}` : " running child";
624
+ return {
625
+ content: [{ type: "text", text: `Steering queued for async run ${status.runId}${childText}. Delivery requires a live Pi child session that supports mid-run steering.` }],
626
+ details: { mode: "management", results: [] },
627
+ };
628
+ }
629
+
466
630
  function duplicateNames(names: string[]): string[] {
467
631
  const seen = new Set<string>();
468
632
  const duplicates = new Set<string>();
@@ -577,7 +741,8 @@ function appendStepToAsyncChain(input: {
577
741
  }
578
742
 
579
743
  const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
580
- const agents = input.deps.discoverAgents(input.requestCwd, scope).agents;
744
+ const discoveredForAppend = input.deps.discoverAgents(input.requestCwd, scope);
745
+ const agents = discoveredForAppend.agents;
581
746
  const contextPolicy = resolveExplicitContextPolicy(input.params);
582
747
  const chainSkillInput = normalizeSkillInput(input.params.skill);
583
748
  const chainSkills = chainSkillInput === false ? [] : (chainSkillInput ?? []);
@@ -588,6 +753,7 @@ function appendStepToAsyncChain(input: {
588
753
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
589
754
  currentModelProvider: input.ctx.model?.provider,
590
755
  currentModel: input.ctx.model,
756
+ modelScope: discoveredForAppend.modelScope,
591
757
  };
592
758
  const built = buildAsyncRunnerSteps(resolved.id, {
593
759
  chain: wrapChainTasksForFork(input.params.chain, contextPolicy),
@@ -747,6 +913,22 @@ function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nes
747
913
  }
748
914
  }
749
915
 
916
+ function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number }): AgentToolResult<Details> | undefined {
917
+ const run = input.target.match.run;
918
+ const asyncDir = resolveNestedAsyncDir(input.target.match.rootRunId, run);
919
+ if (!asyncDir) return undefined;
920
+ const status = reconcileAsyncRun(asyncDir, { resultsDir: path.join(RESULTS_DIR, "nested", input.target.match.rootRunId) }).status;
921
+ if (!status || (status.state !== "running" && status.state !== "queued")) return undefined;
922
+ const steps = status.steps ?? [];
923
+ if (input.index !== undefined) {
924
+ if (input.index < 0 || input.index >= steps.length) return { content: [{ type: "text", text: `Nested async run ${run.id} has ${steps.length} children. Index ${input.index} is out of range.` }], isError: true, details: { mode: "management", results: [] } };
925
+ const step = steps[input.index];
926
+ if (step && step.status !== "running" && step.status !== "pending") return { content: [{ type: "text", text: `Nested async run ${run.id} child ${input.index} is ${step.status} and cannot be steered.` }], isError: true, details: { mode: "management", results: [] } };
927
+ }
928
+ requestAsyncSteer(asyncDir, { message: input.message, targetIndex: input.index, source: "nested-steer" });
929
+ return { content: [{ type: "text", text: `Steering queued for nested async run ${run.id}. Delivery requires a live Pi child session that supports mid-run steering.` }], details: { mode: "management", results: [] } };
930
+ }
931
+
750
932
  async function interruptNestedRun(target: ResolvedSubagentRunId & { kind: "nested" }): Promise<AgentToolResult<Details>> {
751
933
  const run = target.match.run;
752
934
  if (run.state === "complete") return { content: [{ type: "text", text: `Nested run ${run.id} is already complete and cannot be interrupted.` }], isError: true, details: { mode: "management", results: [] } };
@@ -766,6 +948,14 @@ async function resumeLiveNestedRun(input: { target: ResolvedSubagentRunId & { ki
766
948
  return { content: [{ type: "text", text: `Nested run ${run.id} appears live but its owner route is not reachable. Wait for completion, then retry action='resume'.` }], isError: true, details: { mode: "management", results: [] } };
767
949
  }
768
950
 
951
+ function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number }): AgentToolResult<Details> {
952
+ const run = input.target.match.run;
953
+ if (run.state !== "running" && run.state !== "queued") return { content: [{ type: "text", text: `Nested run ${run.id} is ${run.state} and cannot be steered.` }], isError: true, details: { mode: "management", results: [] } };
954
+ const direct = directNestedAsyncSteer(input);
955
+ if (direct) return direct;
956
+ return { content: [{ type: "text", text: `Nested run ${run.id} is not a live async Pi child session with a steering inbox. action='steer' cannot target foreground nested runs.` }], isError: true, details: { mode: "management", results: [] } };
957
+ }
958
+
769
959
  async function resumeAsyncRun(input: {
770
960
  params: SubagentParamsLike;
771
961
  requestCwd: string;
@@ -786,7 +976,14 @@ async function resumeAsyncRun(input: {
786
976
  const parentSessionFile = input.ctx.sessionManager.getSessionFile() ?? null;
787
977
  try {
788
978
  const requestedId = input.params.id ?? input.params.runId;
789
- const resolved = requestedId ? resolveSubagentRunId(requestedId, { state: input.deps.state, nested: nestedResolutionScopeForExecutor(input.deps) }) : undefined;
979
+ let resolved: ResolvedSubagentRunId | undefined;
980
+ try {
981
+ resolved = requestedId ? resolveSubagentRunId(requestedId, { state: input.deps.state, nested: nestedResolutionScopeForExecutor(input.deps) }) : undefined;
982
+ } catch (error) {
983
+ const message = error instanceof Error ? error.message : "";
984
+ const asyncMatches = message.match(/async:/g)?.length ?? 0;
985
+ if (!isResumeAmbiguity(error) || !message.includes("foreground:") || asyncMatches !== 1) throw error;
986
+ }
790
987
  if (resolved?.kind === "nested") {
791
988
  if (attachChain) {
792
989
  return {
@@ -857,13 +1054,14 @@ async function resumeAsyncRun(input: {
857
1054
  input.deps.state.currentSessionId = resolveCurrentSessionId(input.ctx.sessionManager);
858
1055
  const effectiveCwd = target.cwd ?? input.requestCwd;
859
1056
  const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
860
- const discoveredAgents = input.deps.discoverAgents(effectiveCwd, scope).agents;
1057
+ const discovered = input.deps.discoverAgents(effectiveCwd, scope);
1058
+ const discoveredAgents = discovered.agents;
1059
+ const modelScope = discovered.modelScope;
861
1060
  const sessionName = resolveIntercomSessionTarget(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
862
1061
  const intercomBridge = resolveIntercomBridge({
863
1062
  config: input.deps.config.intercomBridge,
864
1063
  context: input.params.context,
865
1064
  orchestratorTarget: sessionName,
866
- cwd: effectiveCwd,
867
1065
  });
868
1066
  const agents = intercomBridge.active
869
1067
  ? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
@@ -917,11 +1115,12 @@ async function resumeAsyncRun(input: {
917
1115
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
918
1116
  currentModelProvider: input.ctx.model?.provider,
919
1117
  currentModel: input.ctx.model,
1118
+ modelScope,
920
1119
  },
921
1120
  availableModels,
922
1121
  cwd: effectiveCwd,
923
1122
  maxOutput: input.params.maxOutput,
924
- artifactsDir: input.deps.tempArtifactsDir,
1123
+ artifactsDir: getArtifactsDir(parentSessionFile, effectiveCwd),
925
1124
  artifactConfig,
926
1125
  shareEnabled: input.params.share === true,
927
1126
  sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
@@ -930,9 +1129,11 @@ async function resumeAsyncRun(input: {
930
1129
  maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
931
1130
  worktreeSetupHook: input.deps.config.worktreeSetupHook,
932
1131
  worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
1132
+ worktreeBaseDir: input.deps.config.worktreeBaseDir,
933
1133
  controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
934
1134
  controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
935
1135
  childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
1136
+ globalConcurrencyLimit: input.deps.config.globalConcurrencyLimit,
936
1137
  });
937
1138
  if (result.isError) return result;
938
1139
  const attachedId = result.details.asyncId ?? runId;
@@ -948,6 +1149,7 @@ async function resumeAsyncRun(input: {
948
1149
 
949
1150
  const runId = randomUUID().slice(0, 8);
950
1151
  const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false };
1152
+ const artifactsDir = getArtifactsDir(parentSessionFile, effectiveCwd);
951
1153
  const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
952
1154
  const result = executeAsyncSingle(runId, {
953
1155
  agent: target.agent,
@@ -960,17 +1162,20 @@ async function resumeAsyncRun(input: {
960
1162
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
961
1163
  currentModelProvider: input.ctx.model?.provider,
962
1164
  currentModel: input.ctx.model,
1165
+ modelScope,
963
1166
  },
964
1167
  cwd: effectiveCwd,
965
1168
  maxOutput: input.params.maxOutput,
966
- artifactsDir: input.deps.tempArtifactsDir,
1169
+ artifactsDir,
967
1170
  artifactConfig,
968
1171
  shareEnabled: input.params.share === true,
969
1172
  sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
970
1173
  sessionFile: target.sessionFile,
1174
+ outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
971
1175
  maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
972
1176
  worktreeSetupHook: input.deps.config.worktreeSetupHook,
973
1177
  worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
1178
+ worktreeBaseDir: input.deps.config.worktreeBaseDir,
974
1179
  controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
975
1180
  controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
976
1181
  childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
@@ -1303,6 +1508,32 @@ function resolveForegroundTimeout(params: SubagentParamsLike): { timeoutMs?: num
1303
1508
  return { timeoutMs: rawTimeout ?? rawMaxRuntime };
1304
1509
  }
1305
1510
 
1511
+ function resolveTurnBudget(params: SubagentParamsLike, config: ExtensionConfig): { turnBudget?: ResolvedTurnBudget; error?: string } {
1512
+ const raw = params.turnBudget ?? config.turnBudget;
1513
+ if (raw === undefined) return {};
1514
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return { error: "turnBudget must be an object with maxTurns and optional graceTurns." };
1515
+ if (typeof raw.maxTurns !== "number" || !Number.isInteger(raw.maxTurns) || raw.maxTurns < 1) {
1516
+ return { error: "turnBudget.maxTurns must be an integer >= 1." };
1517
+ }
1518
+ const graceTurns = raw.graceTurns ?? DEFAULT_TURN_BUDGET_GRACE_TURNS;
1519
+ if (typeof graceTurns !== "number" || !Number.isInteger(graceTurns) || graceTurns < 0) {
1520
+ return { error: "turnBudget.graceTurns must be an integer >= 0." };
1521
+ }
1522
+ return { turnBudget: { maxTurns: raw.maxTurns, graceTurns } };
1523
+ }
1524
+
1525
+ function resolveToolBudget(raw: unknown, label = "toolBudget"): { toolBudget?: ResolvedToolBudget; error?: string } {
1526
+ const resolved = validateToolBudgetConfig(raw, label);
1527
+ return { toolBudget: resolved.budget, error: resolved.error };
1528
+ }
1529
+
1530
+ function resolveEffectiveToolBudget(input: { stepBudget?: ToolBudgetConfig; runBudget?: ResolvedToolBudget; agentBudget?: ToolBudgetConfig; configBudget?: ToolBudgetConfig }): { toolBudget?: ResolvedToolBudget; error?: string } {
1531
+ if (input.stepBudget !== undefined) return resolveToolBudget(input.stepBudget, "toolBudget");
1532
+ if (input.runBudget !== undefined) return { toolBudget: input.runBudget };
1533
+ if (input.agentBudget !== undefined) return resolveToolBudget(input.agentBudget, "agent.toolBudget");
1534
+ return resolveToolBudget(input.configBudget, "config.toolBudget");
1535
+ }
1536
+
1306
1537
  function expandTopLevelTaskCounts(tasks: TaskParam[]): { tasks?: TaskParam[]; error?: string } {
1307
1538
  const expanded: TaskParam[] = [];
1308
1539
  for (let taskIndex = 0; taskIndex < tasks.length; taskIndex++) {
@@ -1391,6 +1622,7 @@ function toExecutionErrorResult(params: SubagentParamsLike, error: unknown): Age
1391
1622
  function collectChainSessionFiles(
1392
1623
  chain: ChainStep[],
1393
1624
  sessionFileForTask: (agentName: string, idx?: number) => string | undefined,
1625
+ dynamicFanoutMaxItems?: number,
1394
1626
  ): (string | undefined)[] {
1395
1627
  const sessionFiles: (string | undefined)[] = [];
1396
1628
  let flatIndex = 0;
@@ -1403,7 +1635,11 @@ function collectChainSessionFiles(
1403
1635
  continue;
1404
1636
  }
1405
1637
  if (isDynamicParallelStep(step)) {
1406
- sessionFiles.push(undefined);
1638
+ const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
1639
+ for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) {
1640
+ sessionFiles.push(sessionFileForTask(step.parallel.agent, flatIndex));
1641
+ flatIndex++;
1642
+ }
1407
1643
  continue;
1408
1644
  }
1409
1645
  sessionFiles.push(sessionFileForTask((step as SequentialStep).agent, flatIndex));
@@ -1412,6 +1648,35 @@ function collectChainSessionFiles(
1412
1648
  return sessionFiles;
1413
1649
  }
1414
1650
 
1651
+ function collectChainThinkingOverrides(
1652
+ chain: ChainStep[],
1653
+ thinkingOverrideForTask: (agentName: string, idx?: number) => AgentConfig["thinking"] | undefined,
1654
+ dynamicFanoutMaxItems?: number,
1655
+ ): (AgentConfig["thinking"] | undefined)[] {
1656
+ const thinkingOverrides: (AgentConfig["thinking"] | undefined)[] = [];
1657
+ let flatIndex = 0;
1658
+ for (const step of chain) {
1659
+ if (isParallelStep(step)) {
1660
+ for (const task of step.parallel) {
1661
+ thinkingOverrides.push(thinkingOverrideForTask(task.agent, flatIndex));
1662
+ flatIndex++;
1663
+ }
1664
+ continue;
1665
+ }
1666
+ if (isDynamicParallelStep(step)) {
1667
+ const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
1668
+ for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) {
1669
+ thinkingOverrides.push(thinkingOverrideForTask(step.parallel.agent, flatIndex));
1670
+ flatIndex++;
1671
+ }
1672
+ continue;
1673
+ }
1674
+ thinkingOverrides.push(thinkingOverrideForTask((step as SequentialStep).agent, flatIndex));
1675
+ flatIndex++;
1676
+ }
1677
+ return thinkingOverrides;
1678
+ }
1679
+
1415
1680
  function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultContextPolicy): ChainStep[] {
1416
1681
  return chain.map((step, stepIndex) => {
1417
1682
  if (isParallelStep(step)) {
@@ -1450,6 +1715,7 @@ function preflightForkSessionsForStaticTasks(
1450
1715
  params: SubagentParamsLike,
1451
1716
  contextPolicy: AgentDefaultContextPolicy,
1452
1717
  sessionFileForTask: (agentName: string, idx?: number) => string | undefined,
1718
+ dynamicFanoutMaxItems?: number,
1453
1719
  ): void {
1454
1720
  if (!contextPolicy.usesFork) return;
1455
1721
  if (params.agent) {
@@ -1473,8 +1739,11 @@ function preflightForkSessionsForStaticTasks(
1473
1739
  continue;
1474
1740
  }
1475
1741
  if (isDynamicParallelStep(step)) {
1476
- if (shouldForkAgent(contextPolicy, step.parallel.agent)) sessionFileForTask(step.parallel.agent, flatIndex);
1477
- flatIndex++;
1742
+ const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
1743
+ if (shouldForkAgent(contextPolicy, step.parallel.agent)) {
1744
+ for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) sessionFileForTask(step.parallel.agent, flatIndex + itemIndex);
1745
+ }
1746
+ flatIndex += maxItems;
1478
1747
  continue;
1479
1748
  }
1480
1749
  const sequential = step as SequentialStep;
@@ -1493,6 +1762,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1493
1762
  sessionRoot,
1494
1763
  sessionFileForIndex,
1495
1764
  sessionFileForTask,
1765
+ thinkingOverrideForTask,
1496
1766
  artifactConfig,
1497
1767
  artifactsDir,
1498
1768
  effectiveAsync,
@@ -1543,6 +1813,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1543
1813
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
1544
1814
  currentModelProvider: ctx.model?.provider,
1545
1815
  currentModel: ctx.model,
1816
+ modelScope: data.modelScope,
1546
1817
  };
1547
1818
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
1548
1819
  const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
@@ -1553,7 +1824,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1553
1824
  if (hasTasks && params.tasks) {
1554
1825
  const agentConfigs = params.tasks.map((task) => agents.find((agent) => agent.name === task.agent));
1555
1826
  const modelOverrides = params.tasks.map((task, index) =>
1556
- resolveSubagentModelOverride(task.model ?? agentConfigs[index]?.model, ctx.model, availableModels, currentProvider),
1827
+ resolveSubagentModelOverride(task.model ?? agentConfigs[index]?.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope, source: task.model ? "explicit" : "inherited" }),
1557
1828
  );
1558
1829
  const skillOverrides = params.tasks.map((task) => normalizeSkillInput(task.skill));
1559
1830
  const parallelTasks = params.tasks.map((task, index) => ({
@@ -1566,6 +1837,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1566
1837
  ...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
1567
1838
  ...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
1568
1839
  ...(task.progress !== undefined ? { progress: task.progress } : {}),
1840
+ ...(task.toolBudget !== undefined ? { toolBudget: task.toolBudget } : {}),
1569
1841
  ...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
1570
1842
  }));
1571
1843
  return executeAsyncChain(id, {
@@ -1586,13 +1858,20 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1586
1858
  sessionRoot,
1587
1859
  chainSkills: [],
1588
1860
  sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index)),
1861
+ thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index)),
1589
1862
  maxSubagentDepth: currentMaxSubagentDepth,
1590
1863
  worktreeSetupHook: deps.config.worktreeSetupHook,
1591
1864
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
1865
+ worktreeBaseDir: deps.config.worktreeBaseDir,
1592
1866
  controlConfig,
1593
1867
  controlIntercomTarget,
1594
1868
  childIntercomTarget,
1595
1869
  nestedRoute,
1870
+ timeoutMs: data.timeoutMs,
1871
+ turnBudget: data.turnBudget,
1872
+ toolBudget: data.toolBudget,
1873
+ configToolBudget: data.configToolBudget,
1874
+ globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
1596
1875
  });
1597
1876
  }
1598
1877
 
@@ -1613,15 +1892,22 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1613
1892
  shareEnabled,
1614
1893
  sessionRoot,
1615
1894
  chainSkills,
1616
- sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask),
1895
+ sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
1896
+ thinkingOverridesByFlatIndex: collectChainThinkingOverrides(chain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
1617
1897
  dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
1618
1898
  maxSubagentDepth: currentMaxSubagentDepth,
1619
1899
  worktreeSetupHook: deps.config.worktreeSetupHook,
1620
1900
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
1901
+ worktreeBaseDir: deps.config.worktreeBaseDir,
1621
1902
  controlConfig,
1622
1903
  controlIntercomTarget,
1623
1904
  childIntercomTarget,
1624
1905
  nestedRoute,
1906
+ timeoutMs: data.timeoutMs,
1907
+ turnBudget: data.turnBudget,
1908
+ toolBudget: data.toolBudget,
1909
+ configToolBudget: data.configToolBudget,
1910
+ globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
1625
1911
  });
1626
1912
  }
1627
1913
 
@@ -1640,7 +1926,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1640
1926
  const normalizedSkills = normalizeSkillInput(params.skill);
1641
1927
  const skills = normalizedSkills === false ? [] : normalizedSkills;
1642
1928
  const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
1643
- const modelOverride = resolveSubagentModelOverride((params.model as string | undefined) ?? a.model, ctx.model, availableModels, currentProvider);
1929
+ const modelOverride = resolveSubagentModelOverride((params.model as string | undefined) ?? a.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope, source: (params.model as string | undefined) ? "explicit" : "inherited" });
1644
1930
  return executeAsyncSingle(id, {
1645
1931
  agent: params.agent!,
1646
1932
  task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
@@ -1657,15 +1943,22 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1657
1943
  skills,
1658
1944
  output: effectiveOutput,
1659
1945
  outputMode: effectiveOutputMode,
1946
+ outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
1660
1947
  modelOverride,
1948
+ thinkingOverride: thinkingOverrideForTask(params.agent!, 0),
1661
1949
  maxSubagentDepth,
1662
1950
  worktreeSetupHook: deps.config.worktreeSetupHook,
1663
1951
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
1952
+ worktreeBaseDir: deps.config.worktreeBaseDir,
1664
1953
  controlConfig,
1665
1954
  controlIntercomTarget,
1666
1955
  childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
1667
1956
  nestedRoute,
1668
1957
  acceptance: params.acceptance,
1958
+ timeoutMs: data.timeoutMs,
1959
+ turnBudget: data.turnBudget,
1960
+ toolBudget: data.toolBudget,
1961
+ configToolBudget: data.configToolBudget,
1669
1962
  });
1670
1963
  }
1671
1964
 
@@ -1684,6 +1977,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
1684
1977
  sessionDirForIndex,
1685
1978
  sessionFileForIndex,
1686
1979
  sessionFileForTask,
1980
+ thinkingOverrideForTask,
1687
1981
  artifactsDir,
1688
1982
  artifactConfig,
1689
1983
  onUpdate,
@@ -1703,6 +1997,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
1703
1997
  task: params.task,
1704
1998
  agents,
1705
1999
  ctx,
2000
+ modelScope: data.modelScope,
1706
2001
  intercomEvents: deps.pi.events,
1707
2002
  signal,
1708
2003
  runId,
@@ -1711,6 +2006,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
1711
2006
  sessionDirForIndex,
1712
2007
  sessionFileForIndex,
1713
2008
  sessionFileForTask,
2009
+ thinkingOverrideForTask,
1714
2010
  artifactsDir,
1715
2011
  artifactConfig,
1716
2012
  includeProgress: params.includeProgress,
@@ -1723,19 +2019,22 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
1723
2019
  foregroundControl,
1724
2020
  nestedRoute: foregroundControl?.nestedRoute,
1725
2021
  chainSkills,
1726
- chainDir: params.chainDir,
2022
+ chainDir: params.chainDir ?? getProjectChainRunsDir(effectiveCwd),
1727
2023
  dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
1728
2024
  maxSubagentDepth: currentMaxSubagentDepth,
1729
2025
  worktreeSetupHook: deps.config.worktreeSetupHook,
1730
2026
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
2027
+ worktreeBaseDir: deps.config.worktreeBaseDir,
1731
2028
  timeoutMs: data.timeoutMs,
1732
2029
  deadlineAt: data.deadlineAt,
2030
+ turnBudget: data.turnBudget,
2031
+ onDetachedExit: (index, result) => updateRememberedForegroundChild(deps.state, { runId, mode: "chain", cwd: effectiveCwd, index, result }),
2032
+ toolBudget: data.toolBudget,
2033
+ configToolBudget: data.configToolBudget,
2034
+ globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
1733
2035
  });
1734
2036
 
1735
2037
  if (chainResult.requestedAsync) {
1736
- if (data.timeoutMs !== undefined) {
1737
- return buildRequestedModeError(params, "timeoutMs/maxRuntimeMs are only supported for foreground runs; background launch from clarify cannot preserve the timeout.");
1738
- }
1739
2038
  if (!isAsyncAvailable()) {
1740
2039
  return {
1741
2040
  content: [{ type: "text", text: "Background mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the pi-subagents package dependencies are installed." }],
@@ -1751,6 +2050,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
1751
2050
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
1752
2051
  currentModelProvider: ctx.model?.provider,
1753
2052
  currentModel: ctx.model,
2053
+ modelScope: data.modelScope,
1754
2054
  };
1755
2055
  const asyncChain = wrapChainTasksForFork(chainResult.requestedAsync.chain, contextPolicy);
1756
2056
  return executeAsyncChain(id, {
@@ -1766,20 +2066,32 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
1766
2066
  shareEnabled,
1767
2067
  sessionRoot,
1768
2068
  chainSkills: chainResult.requestedAsync.chainSkills,
1769
- sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask),
2069
+ sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
2070
+ thinkingOverridesByFlatIndex: collectChainThinkingOverrides(asyncChain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
1770
2071
  dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
1771
2072
  maxSubagentDepth: currentMaxSubagentDepth,
1772
2073
  worktreeSetupHook: deps.config.worktreeSetupHook,
1773
2074
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
2075
+ worktreeBaseDir: deps.config.worktreeBaseDir,
1774
2076
  controlConfig,
1775
2077
  controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
1776
2078
  childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
1777
2079
  nestedRoute: data.nestedRoute,
2080
+ timeoutMs: data.timeoutMs,
2081
+ turnBudget: data.turnBudget,
2082
+ toolBudget: data.toolBudget,
2083
+ configToolBudget: data.configToolBudget,
2084
+ globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
1778
2085
  });
1779
2086
  }
1780
2087
 
1781
- const chainDetails = chainResult.details ? compactForegroundDetails({ ...chainResult.details, runId }) : undefined;
1782
- if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
2088
+ const rawChainDetails = chainResult.details ? { ...chainResult.details, runId } : undefined;
2089
+ if (foregroundControl && rawChainDetails) {
2090
+ updateForegroundNestedProjection(foregroundControl);
2091
+ attachRootChildrenToSteps(runId, rawChainDetails.results, foregroundControl.nestedChildren);
2092
+ rawChainDetails.totalCost = sumResultsCost(rawChainDetails.results);
2093
+ }
2094
+ const chainDetails = rawChainDetails ? compactForegroundDetails(rawChainDetails) : undefined;
1783
2095
  if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, results: chainDetails.results });
1784
2096
  const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached)
1785
2097
  ? await maybeBuildForegroundIntercomReceipt({
@@ -1807,20 +2119,24 @@ interface ForegroundParallelRunInput {
1807
2119
  taskTexts: string[];
1808
2120
  agents: AgentConfig[];
1809
2121
  ctx: ExtensionContext;
2122
+ state: SubagentState;
1810
2123
  intercomEvents: IntercomEventBus;
1811
2124
  signal: AbortSignal;
1812
2125
  runId: string;
1813
2126
  sessionDirForIndex: (idx?: number) => string | undefined;
1814
2127
  sessionFileForIndex: (idx?: number) => string | undefined;
1815
2128
  sessionFileForTask: (agentName: string, idx?: number) => string | undefined;
2129
+ thinkingOverrideForTask: (agentName: string, idx?: number) => AgentConfig["thinking"] | undefined;
1816
2130
  shareEnabled: boolean;
1817
2131
  artifactConfig: ArtifactConfig;
1818
2132
  artifactsDir: string;
2133
+ outputBaseDir: string;
1819
2134
  maxOutput?: MaxOutputConfig;
1820
2135
  paramsCwd: string;
1821
2136
  progressDir: string;
1822
2137
  maxSubagentDepths: number[];
1823
2138
  availableModels: ModelInfo[];
2139
+ modelScope?: ModelScopeConfig;
1824
2140
  modelOverrides: (string | undefined)[];
1825
2141
  behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
1826
2142
  firstProgressIndex: number;
@@ -1830,12 +2146,15 @@ interface ForegroundParallelRunInput {
1830
2146
  orchestratorIntercomTarget?: string;
1831
2147
  foregroundControl?: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never;
1832
2148
  concurrencyLimit: number;
2149
+ globalSemaphore?: Semaphore;
1833
2150
  liveResults: (SingleResult | undefined)[];
1834
2151
  liveProgress: (AgentProgress | undefined)[];
1835
2152
  onUpdate?: (r: AgentToolResult<Details>) => void;
1836
2153
  worktreeSetup?: WorktreeSetup;
1837
2154
  timeoutMs?: number;
1838
2155
  deadlineAt?: number;
2156
+ turnBudget?: ResolvedTurnBudget;
2157
+ toolBudgets: (ResolvedToolBudget | undefined)[];
1839
2158
  }
1840
2159
 
1841
2160
  function buildParallelModeError(message: string): AgentToolResult<Details> {
@@ -1853,6 +2172,7 @@ function createParallelWorktreeSetup(
1853
2172
  tasks: TaskParam[],
1854
2173
  setupHook: ExtensionConfig["worktreeSetupHook"],
1855
2174
  setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
2175
+ baseDir: ExtensionConfig["worktreeBaseDir"],
1856
2176
  ): { setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> } {
1857
2177
  if (!enabled) return {};
1858
2178
  try {
@@ -1862,6 +2182,7 @@ function createParallelWorktreeSetup(
1862
2182
  setupHook: setupHook
1863
2183
  ? { hookPath: setupHook, timeoutMs: setupHookTimeoutMs }
1864
2184
  : undefined,
2185
+ baseDir,
1865
2186
  }),
1866
2187
  };
1867
2188
  } catch (error) {
@@ -1879,6 +2200,12 @@ function buildParallelWorktreeTaskCwdError(
1879
2200
  return formatWorktreeTaskCwdConflict(conflict, sharedCwd);
1880
2201
  }
1881
2202
 
2203
+ function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string, runId: string): string {
2204
+ return deps.config.singleRunOutputBaseDir
2205
+ ? path.resolve(deps.expandTilde(deps.config.singleRunOutputBaseDir))
2206
+ : path.join(artifactsDir, "outputs", runId);
2207
+ }
2208
+
1882
2209
  function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string): string | undefined {
1883
2210
  for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) {
1884
2211
  const step = chain[stepIndex]!;
@@ -1918,6 +2245,7 @@ function findDuplicateParallelOutputPath(input: {
1918
2245
  behaviors: ResolvedStepBehavior[];
1919
2246
  paramsCwd: string;
1920
2247
  ctxCwd: string;
2248
+ outputBaseDir: string;
1921
2249
  worktreeSetup?: WorktreeSetup;
1922
2250
  }): string | undefined {
1923
2251
  const seen = new Map<string, { index: number; agent: string }>();
@@ -1926,7 +2254,7 @@ function findDuplicateParallelOutputPath(input: {
1926
2254
  if (!behavior?.output) continue;
1927
2255
  const task = input.tasks[index]!;
1928
2256
  const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
1929
- const outputPath = resolveSingleOutputPath(behavior.output, input.ctxCwd, taskCwd);
2257
+ const outputPath = resolveSingleOutputPath(behavior.output, input.ctxCwd, taskCwd, input.outputBaseDir);
1930
2258
  if (!outputPath) continue;
1931
2259
  const previous = seen.get(outputPath);
1932
2260
  if (previous) {
@@ -1938,6 +2266,12 @@ function findDuplicateParallelOutputPath(input: {
1938
2266
  }
1939
2267
 
1940
2268
  async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Promise<SingleResult[]> {
2269
+ // Pre-warm fork session files sequentially before concurrent dispatch to avoid
2270
+ // races where multiple workers simultaneously try to branch the same parent session.
2271
+ // sessionFileForIndex caches results, so these calls return instantly inside mapConcurrent.
2272
+ for (let i = 0; i < input.tasks.length; i++) {
2273
+ input.sessionFileForIndex(i);
2274
+ }
1941
2275
  return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
1942
2276
  const behavior = input.behaviors[index];
1943
2277
  const effectiveSkills = behavior?.skills;
@@ -1948,7 +2282,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
1948
2282
  const progressInstructions = behavior
1949
2283
  ? buildChainInstructions({ ...behavior, output: false, reads: false }, input.progressDir, index === input.firstProgressIndex)
1950
2284
  : { prefix: "", suffix: "" };
1951
- const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd);
2285
+ const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd, input.outputBaseDir);
1952
2286
  const taskText = injectSingleOutputInstruction(
1953
2287
  `${readInstructions.prefix}${input.taskTexts[index]!}${progressInstructions.suffix}`,
1954
2288
  outputPath,
@@ -1988,17 +2322,22 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
1988
2322
  maxSubagentDepth: input.maxSubagentDepths[index],
1989
2323
  controlConfig: input.controlConfig,
1990
2324
  onControlEvent: input.onControlEvent,
2325
+ onDetachedExit: (result) => updateRememberedForegroundChild(input.state, { runId: input.runId, mode: "parallel", cwd: taskCwd, index, result }),
1991
2326
  intercomSessionName: input.childIntercomTarget?.(task.agent, index),
1992
2327
  orchestratorIntercomTarget: input.orchestratorIntercomTarget,
1993
2328
  nestedRoute: input.foregroundControl?.nestedRoute,
1994
2329
  modelOverride: input.modelOverrides[index],
2330
+ thinkingOverride: input.thinkingOverrideForTask(task.agent, index),
1995
2331
  availableModels: input.availableModels,
1996
2332
  preferredModelProvider: input.ctx.model?.provider,
2333
+ modelScope: input.modelScope,
1997
2334
  skills: effectiveSkills === false ? [] : effectiveSkills,
1998
2335
  acceptance: task.acceptance,
1999
2336
  acceptanceContext: { mode: "parallel" },
2000
2337
  timeoutMs: input.timeoutMs,
2001
2338
  deadlineAt: input.deadlineAt,
2339
+ turnBudget: input.turnBudget,
2340
+ toolBudget: input.toolBudgets[index],
2002
2341
  onUpdate: input.onUpdate
2003
2342
  ? (progressUpdate) => {
2004
2343
  const stepResults = progressUpdate.details?.results || [];
@@ -2039,7 +2378,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
2039
2378
  input.foregroundControl.updatedAt = Date.now();
2040
2379
  }
2041
2380
  });
2042
- });
2381
+ }, input.globalSemaphore);
2043
2382
  }
2044
2383
 
2045
2384
  async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
@@ -2053,6 +2392,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2053
2392
  sessionDirForIndex,
2054
2393
  sessionFileForIndex,
2055
2394
  sessionFileForTask,
2395
+ thinkingOverrideForTask,
2056
2396
  shareEnabled,
2057
2397
  artifactConfig,
2058
2398
  artifactsDir,
@@ -2094,6 +2434,12 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2094
2434
  const maxSubagentDepths = agentConfigs.map((config) =>
2095
2435
  resolveChildMaxSubagentDepth(currentMaxSubagentDepth, config.maxSubagentDepth),
2096
2436
  );
2437
+ const toolBudgets: (ResolvedToolBudget | undefined)[] = [];
2438
+ for (let index = 0; index < tasks.length; index++) {
2439
+ const resolved = resolveEffectiveToolBudget({ stepBudget: tasks[index]?.toolBudget, runBudget: data.toolBudget, agentBudget: agentConfigs[index]?.toolBudget, configBudget: data.configToolBudget });
2440
+ if (resolved.error) return buildParallelModeError(resolved.error);
2441
+ toolBudgets.push(resolved.toolBudget);
2442
+ }
2097
2443
 
2098
2444
  if (params.worktree) {
2099
2445
  const worktreeTaskCwdError = buildParallelWorktreeTaskCwdError(tasks, effectiveCwd);
@@ -2115,7 +2461,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2115
2461
  ...(task.model ? { model: task.model } : {}),
2116
2462
  }));
2117
2463
  const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
2118
- resolveSubagentModelOverride(behaviorOverrides[i]?.model ?? agentConfigs[i]?.model, ctx.model, availableModels, currentProvider),
2464
+ resolveSubagentModelOverride(behaviorOverrides[i]?.model ?? agentConfigs[i]?.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope, source: behaviorOverrides[i]?.model ? "explicit" : "inherited" }),
2119
2465
  );
2120
2466
 
2121
2467
  if (params.clarify === true && ctx.hasUI) {
@@ -2150,7 +2496,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2150
2496
  for (let i = 0; i < result.behaviorOverrides.length; i++) {
2151
2497
  const override = result.behaviorOverrides[i];
2152
2498
  if (override?.model) {
2153
- modelOverrides[i] = override.model;
2499
+ modelOverrides[i] = resolveSubagentModelOverride(override.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope, source: "explicit" });
2154
2500
  behaviorOverrides[i]!.model = override.model;
2155
2501
  }
2156
2502
  if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
@@ -2163,9 +2509,6 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2163
2509
  }
2164
2510
 
2165
2511
  if (result.runInBackground) {
2166
- if (data.timeoutMs !== undefined) {
2167
- return buildRequestedModeError(params, "timeoutMs/maxRuntimeMs are only supported for foreground runs; background launch from clarify cannot preserve the timeout.");
2168
- }
2169
2512
  if (!isAsyncAvailable()) {
2170
2513
  return {
2171
2514
  content: [{ type: "text", text: "Background mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the pi-subagents package dependencies are installed." }],
@@ -2181,6 +2524,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2181
2524
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
2182
2525
  currentModelProvider: ctx.model?.provider,
2183
2526
  currentModel: ctx.model,
2527
+ modelScope: data.modelScope,
2184
2528
  };
2185
2529
  const parallelTasks = tasks.map((t, i) => {
2186
2530
  const taskText = shouldForkAgent(contextPolicy, t.agent) ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!;
@@ -2195,6 +2539,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2195
2539
  ...(behaviorOverrides[i]?.outputMode !== undefined ? { outputMode: behaviorOverrides[i]!.outputMode } : {}),
2196
2540
  ...(behaviorOverrides[i]?.reads !== undefined ? { reads: behaviorOverrides[i]!.reads } : {}),
2197
2541
  ...(progress !== undefined ? { progress } : {}),
2542
+ ...(t.toolBudget !== undefined ? { toolBudget: t.toolBudget } : {}),
2198
2543
  ...(t.acceptance !== undefined ? { acceptance: t.acceptance } : {}),
2199
2544
  };
2200
2545
  });
@@ -2212,12 +2557,17 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2212
2557
  sessionRoot,
2213
2558
  chainSkills: [],
2214
2559
  sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index)),
2560
+ thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index)),
2215
2561
  maxSubagentDepth: currentMaxSubagentDepth,
2216
2562
  worktreeSetupHook: deps.config.worktreeSetupHook,
2217
2563
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
2564
+ worktreeBaseDir: deps.config.worktreeBaseDir,
2218
2565
  controlConfig,
2219
2566
  controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
2220
2567
  childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
2568
+ timeoutMs: data.timeoutMs,
2569
+ turnBudget: data.turnBudget,
2570
+ globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
2221
2571
  });
2222
2572
  }
2223
2573
  }
@@ -2234,21 +2584,24 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2234
2584
  tasks,
2235
2585
  deps.config.worktreeSetupHook,
2236
2586
  deps.config.worktreeSetupHookTimeoutMs,
2587
+ deps.config.worktreeBaseDir,
2237
2588
  );
2238
2589
  if (errorResult) return errorResult;
2239
2590
 
2240
2591
  try {
2592
+ const outputBaseDir = path.join(artifactsDir, "outputs", runId);
2241
2593
  const duplicateOutputError = findDuplicateParallelOutputPath({
2242
2594
  tasks,
2243
2595
  behaviors,
2244
2596
  paramsCwd: effectiveCwd,
2245
2597
  ctxCwd: ctx.cwd,
2598
+ outputBaseDir,
2246
2599
  worktreeSetup,
2247
2600
  });
2248
2601
  if (duplicateOutputError) return buildParallelModeError(duplicateOutputError);
2249
2602
  for (let index = 0; index < tasks.length; index++) {
2250
2603
  const taskCwd = resolveParallelTaskCwd(tasks[index]!, effectiveCwd, worktreeSetup, index);
2251
- const outputPath = resolveSingleOutputPath(behaviors[index]?.output, ctx.cwd, taskCwd);
2604
+ const outputPath = resolveSingleOutputPath(behaviors[index]?.output, ctx.cwd, taskCwd, outputBaseDir);
2252
2605
  const validationError = validateFileOnlyOutputMode(behaviors[index]?.outputMode, outputPath, `Parallel task ${index + 1} (${tasks[index]!.agent})`);
2253
2606
  if (validationError) return buildParallelModeError(validationError);
2254
2607
  }
@@ -2267,19 +2620,23 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2267
2620
  taskTexts,
2268
2621
  agents,
2269
2622
  ctx,
2623
+ state: deps.state,
2270
2624
  intercomEvents: deps.pi.events,
2271
2625
  signal,
2272
2626
  runId,
2273
2627
  sessionDirForIndex,
2274
2628
  sessionFileForIndex,
2275
2629
  sessionFileForTask,
2630
+ thinkingOverrideForTask,
2276
2631
  shareEnabled,
2277
2632
  artifactConfig,
2278
2633
  artifactsDir,
2634
+ outputBaseDir,
2279
2635
  maxOutput: params.maxOutput,
2280
2636
  paramsCwd: effectiveCwd,
2281
2637
  progressDir: parallelProgressDir,
2282
2638
  availableModels,
2639
+ modelScope: data.modelScope,
2283
2640
  modelOverrides,
2284
2641
  behaviors,
2285
2642
  firstProgressIndex: parallelProgressPrecreated ? -1 : firstProgressIndex,
@@ -2289,6 +2646,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2289
2646
  orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
2290
2647
  foregroundControl,
2291
2648
  concurrencyLimit: parallelConcurrency,
2649
+ globalSemaphore: new Semaphore(deps.config.globalConcurrencyLimit ?? DEFAULT_GLOBAL_CONCURRENCY_LIMIT),
2292
2650
  maxSubagentDepths,
2293
2651
  liveResults,
2294
2652
  liveProgress,
@@ -2296,6 +2654,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2296
2654
  worktreeSetup,
2297
2655
  timeoutMs: data.timeoutMs,
2298
2656
  deadlineAt,
2657
+ turnBudget: data.turnBudget,
2658
+ toolBudgets,
2299
2659
  });
2300
2660
  for (let i = 0; i < results.length; i++) {
2301
2661
  const run = results[i]!;
@@ -2307,6 +2667,10 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2307
2667
  if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
2308
2668
  }
2309
2669
 
2670
+ if (foregroundControl) {
2671
+ updateForegroundNestedProjection(foregroundControl);
2672
+ attachRootChildrenToSteps(runId, results, foregroundControl.nestedChildren);
2673
+ }
2310
2674
  const interrupted = results.find((result) => result.interrupted);
2311
2675
  const details = compactForegroundDetails({
2312
2676
  mode: "parallel",
@@ -2314,6 +2678,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
2314
2678
  results,
2315
2679
  progress: params.includeProgress ? allProgress : undefined,
2316
2680
  artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
2681
+ totalChildUsage: sumResultsUsage(results),
2682
+ totalCost: sumResultsCost(results),
2317
2683
  });
2318
2684
  rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, results: details.results });
2319
2685
  if (interrupted) {
@@ -2385,6 +2751,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
2385
2751
  runId,
2386
2752
  sessionDirForIndex,
2387
2753
  sessionFileForTask,
2754
+ thinkingOverrideForTask,
2388
2755
  shareEnabled,
2389
2756
  artifactConfig,
2390
2757
  artifactsDir,
@@ -2405,6 +2772,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
2405
2772
  details: { mode: "single", results: [] },
2406
2773
  };
2407
2774
  }
2775
+ const effectiveToolBudget = resolveEffectiveToolBudget({ runBudget: data.toolBudget, agentBudget: agentConfig.toolBudget, configBudget: data.configToolBudget });
2776
+ if (effectiveToolBudget.error) return toExecutionErrorResult(params, new Error(effectiveToolBudget.error));
2408
2777
 
2409
2778
  const currentProvider = ctx.model?.provider;
2410
2779
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
@@ -2414,6 +2783,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
2414
2783
  ctx.model,
2415
2784
  availableModels,
2416
2785
  currentProvider,
2786
+ { scope: data.modelScope, source: (params.model as string | undefined) ? "explicit" : "inherited" },
2417
2787
  );
2418
2788
  let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
2419
2789
  const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
@@ -2450,14 +2820,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
2450
2820
 
2451
2821
  task = result.templates[0]!;
2452
2822
  const override = result.behaviorOverrides[0];
2453
- if (override?.model) modelOverride = override.model;
2823
+ if (override?.model) modelOverride = resolveSubagentModelOverride(override.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope, source: "explicit" });
2454
2824
  if (override?.output !== undefined) effectiveOutput = normalizeSingleOutputOverride(override.output, agentConfig.output);
2455
2825
  if (override?.skills !== undefined) skillOverride = override.skills;
2456
2826
 
2457
2827
  if (result.runInBackground) {
2458
- if (data.timeoutMs !== undefined) {
2459
- return buildRequestedModeError(params, "timeoutMs/maxRuntimeMs are only supported for foreground runs; background launch from clarify cannot preserve the timeout.");
2460
- }
2461
2828
  if (!isAsyncAvailable()) {
2462
2829
  return {
2463
2830
  content: [{ type: "text", text: "Background mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the pi-subagents package dependencies are installed." }],
@@ -2473,6 +2840,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
2473
2840
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
2474
2841
  currentModelProvider: ctx.model?.provider,
2475
2842
  currentModel: ctx.model,
2843
+ modelScope: data.modelScope,
2476
2844
  };
2477
2845
  return executeAsyncSingle(id, {
2478
2846
  agent: params.agent!,
@@ -2490,13 +2858,19 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
2490
2858
  skills: skillOverride === false ? [] : skillOverride,
2491
2859
  output: effectiveOutput,
2492
2860
  outputMode: effectiveOutputMode,
2861
+ outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
2493
2862
  modelOverride,
2863
+ thinkingOverride: thinkingOverrideForTask(params.agent!, 0),
2494
2864
  maxSubagentDepth,
2495
2865
  worktreeSetupHook: deps.config.worktreeSetupHook,
2496
2866
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
2867
+ worktreeBaseDir: deps.config.worktreeBaseDir,
2497
2868
  controlConfig,
2498
2869
  controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
2499
2870
  childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
2871
+ timeoutMs: data.timeoutMs,
2872
+ turnBudget: data.turnBudget,
2873
+ toolBudget: effectiveToolBudget.toolBudget,
2500
2874
  });
2501
2875
  }
2502
2876
  }
@@ -2505,7 +2879,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
2505
2879
  task = wrapForkTask(task);
2506
2880
  }
2507
2881
  const cleanTask = task;
2508
- const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, effectiveCwd);
2882
+ const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, effectiveCwd, resolveSingleRunOutputBaseDir(deps, artifactsDir, runId));
2509
2883
  const validationError = validateFileOnlyOutputMode(effectiveOutputMode, outputPath, `Single run (${params.agent})`);
2510
2884
  if (validationError) {
2511
2885
  return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
@@ -2580,13 +2954,18 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
2580
2954
  nestedRoute: foregroundControl?.nestedRoute,
2581
2955
  index: 0,
2582
2956
  modelOverride,
2957
+ thinkingOverride: thinkingOverrideForTask(params.agent!, 0),
2583
2958
  availableModels,
2584
2959
  preferredModelProvider: currentProvider,
2960
+ modelScope: data.modelScope,
2585
2961
  skills: effectiveSkills,
2586
2962
  acceptance: params.acceptance,
2587
2963
  acceptanceContext: { mode: "single" },
2964
+ onDetachedExit: (result) => updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: effectiveCwd, index: 0, result }),
2588
2965
  timeoutMs: data.timeoutMs,
2589
2966
  deadlineAt,
2967
+ turnBudget: data.turnBudget,
2968
+ toolBudget: effectiveToolBudget.toolBudget,
2590
2969
  });
2591
2970
  if (foregroundControl?.currentIndex === 0) {
2592
2971
  foregroundControl.interrupt = undefined;
@@ -2616,13 +2995,21 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
2616
2995
  outputReference: r.outputReference,
2617
2996
  saveError: r.outputSaveError,
2618
2997
  });
2998
+ if (foregroundControl) {
2999
+ updateForegroundNestedProjection(foregroundControl);
3000
+ attachRootChildrenToSteps(runId, [r], foregroundControl.nestedChildren);
3001
+ }
2619
3002
  const details = compactForegroundDetails({
2620
3003
  mode: "single",
2621
3004
  runId,
2622
3005
  results: [r],
3006
+ ...(data.turnBudget ? { turnBudget: data.turnBudget } : {}),
3007
+ ...(effectiveToolBudget.toolBudget ? { toolBudget: effectiveToolBudget.toolBudget } : {}),
2623
3008
  progress: params.includeProgress ? allProgress : undefined,
2624
3009
  artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
2625
3010
  truncation: r.truncation,
3011
+ totalChildUsage: sumResultsUsage([r]),
3012
+ totalCost: sumResultsCost([r]),
2626
3013
  });
2627
3014
  rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, results: details.results });
2628
3015
 
@@ -2688,6 +3075,21 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
2688
3075
  };
2689
3076
  }
2690
3077
 
3078
+ function omitExecutionModeActionAlias(params: SubagentParamsLike): SubagentParamsLike {
3079
+ const action = params.action?.toLowerCase();
3080
+ if (action === "single" && (params.agent !== undefined || params.task !== undefined)) {
3081
+ const rest = { ...params };
3082
+ delete rest.action;
3083
+ return rest;
3084
+ }
3085
+ if ((action === "parallel" || action === "tasks") && (params.tasks?.length ?? 0) > 0) {
3086
+ const rest = { ...params };
3087
+ delete rest.action;
3088
+ return rest;
3089
+ }
3090
+ return params;
3091
+ }
3092
+
2691
3093
  export function createSubagentExecutor(deps: ExecutorDeps): {
2692
3094
  execute: (
2693
3095
  id: string,
@@ -2708,10 +3110,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
2708
3110
  deps.state.foregroundRuns ??= new Map();
2709
3111
  deps.state.foregroundControls ??= new Map();
2710
3112
  deps.state.lastForegroundControlId ??= null;
2711
- const requestCwd = resolveRequestedCwd(ctx.cwd, params.cwd);
2712
- const paramsWithResolvedCwd = params.cwd === undefined ? params : { ...params, cwd: requestCwd };
2713
- if (params.action) {
2714
- if (params.action === "doctor") {
3113
+ const requestParams = omitExecutionModeActionAlias(params);
3114
+ const requestCwd = resolveRequestedCwd(ctx.cwd, requestParams.cwd);
3115
+ const paramsWithResolvedCwd = requestParams.cwd === undefined ? requestParams : { ...requestParams, cwd: requestCwd };
3116
+ const action = paramsWithResolvedCwd.action;
3117
+ if (action) {
3118
+ if (action === "doctor") {
2715
3119
  let currentSessionFile: string | null = null;
2716
3120
  let currentSessionId = deps.state.currentSessionId;
2717
3121
  let sessionError: string | undefined;
@@ -2746,15 +3150,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
2746
3150
  details: { mode: "management", results: [] },
2747
3151
  };
2748
3152
  }
2749
- if (params.action === "status") {
3153
+ if (action === "status") {
2750
3154
  const targetRunId = paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId;
3155
+ const nestedScope = nestedResolutionScopeForExecutor(deps);
3156
+ const sessionRoots = trustedSessionRootsForStatus(ctx, deps);
3157
+ if (paramsWithResolvedCwd.view === "fleet") {
3158
+ return inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots });
3159
+ }
2751
3160
  if (targetRunId) {
2752
3161
  try {
2753
- const nestedScope = nestedResolutionScopeForExecutor(deps);
2754
3162
  const resolved = resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedScope });
2755
3163
  if (resolved?.kind === "foreground") {
2756
3164
  const foreground = getForegroundControl(deps.state, resolved.id);
2757
- if (foreground) return foregroundStatusResult(foreground);
3165
+ if (foreground) {
3166
+ if (paramsWithResolvedCwd.view === "transcript") {
3167
+ return {
3168
+ content: [{ type: "text", text: "Live foreground transcript is already visible in the expanded running subagent result. Persisted session transcript becomes inspectable after the foreground run completes when sessions are enabled." }],
3169
+ details: { mode: "management", results: [] },
3170
+ };
3171
+ }
3172
+ return foregroundStatusResult(foreground);
3173
+ }
2758
3174
  }
2759
3175
  } catch (error) {
2760
3176
  const message = error instanceof Error ? error.message : String(error);
@@ -2762,17 +3178,60 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
2762
3178
  }
2763
3179
  } else {
2764
3180
  const foreground = getForegroundControl(deps.state, undefined);
2765
- if (foreground) return foregroundStatusResult(foreground);
3181
+ if (foreground && paramsWithResolvedCwd.view !== "transcript") return foregroundStatusResult(foreground);
3182
+ if (foreground && paramsWithResolvedCwd.view === "transcript") {
3183
+ return {
3184
+ content: [{ type: "text", text: "Live foreground transcript is already visible in the expanded running subagent result. Pass an async run id to inspect a background transcript." }],
3185
+ details: { mode: "management", results: [] },
3186
+ };
3187
+ }
2766
3188
  }
2767
- return inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) });
3189
+ return inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots });
2768
3190
  }
2769
- if (params.action === "resume") {
3191
+ if (action === "resume") {
2770
3192
  return resumeAsyncRun({ params: paramsWithResolvedCwd, requestCwd, ctx, deps });
2771
3193
  }
2772
- if (params.action === "append-step") {
3194
+ if (action === "steer") {
3195
+ const message = (paramsWithResolvedCwd.message ?? paramsWithResolvedCwd.task ?? "").trim();
3196
+ if (!message) return { content: [{ type: "text", text: "action='steer' requires message." }], isError: true, details: { mode: "management", results: [] } };
3197
+ const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
3198
+ if (paramsWithResolvedCwd.dir) {
3199
+ try {
3200
+ const location = resolveAsyncRunLocation(paramsWithResolvedCwd, ASYNC_DIR, RESULTS_DIR);
3201
+ const runId = location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir);
3202
+ return steerAsyncRun({ state: deps.state, runId, message, index: paramsWithResolvedCwd.index, kill: deps.kill, location });
3203
+ } catch (error) {
3204
+ const text = error instanceof Error ? error.message : String(error);
3205
+ return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
3206
+ }
3207
+ }
3208
+ if (!targetRunId) return { content: [{ type: "text", text: "action='steer' requires id or dir." }], isError: true, details: { mode: "management", results: [] } };
3209
+ let resolved: ResolvedSubagentRunId | undefined;
3210
+ try {
3211
+ resolved = resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) });
3212
+ } catch (error) {
3213
+ const text = error instanceof Error ? error.message : String(error);
3214
+ return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
3215
+ }
3216
+ if (resolved?.kind === "nested") return steerNestedRun({ target: resolved, message, index: paramsWithResolvedCwd.index });
3217
+ if (resolved?.kind === "foreground") return { content: [{ type: "text", text: "action='steer' currently supports live async Pi child sessions only; use action='interrupt' or action='resume' for foreground runs." }], isError: true, details: { mode: "management", results: [] } };
3218
+ if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
3219
+ return steerAsyncRun({ state: deps.state, runId: resolved.id, message, index: paramsWithResolvedCwd.index, kill: deps.kill, location: resolved.location });
3220
+ }
3221
+ if (action === "append-step") {
2773
3222
  return appendStepToAsyncChain({ params: paramsWithResolvedCwd, requestCwd, ctx, deps });
2774
3223
  }
2775
- if (params.action === "interrupt") {
3224
+ if (action === "schedule" || action === "schedule-list" || action === "schedule-status" || action === "schedule-cancel") {
3225
+ if (!deps.handleScheduledRunAction) {
3226
+ return {
3227
+ content: [{ type: "text", text: `Action '${action}' is not available in this subagent context.` }],
3228
+ isError: true,
3229
+ details: { mode: "management", results: [] },
3230
+ };
3231
+ }
3232
+ return deps.handleScheduledRunAction(paramsWithResolvedCwd, ctx);
3233
+ }
3234
+ if (action === "interrupt") {
2776
3235
  const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
2777
3236
  let resolved: ResolvedSubagentRunId | undefined;
2778
3237
  if (targetRunId) {
@@ -2814,21 +3273,25 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
2814
3273
  details: { mode: "management", results: [] },
2815
3274
  };
2816
3275
  }
2817
- if (!(SUBAGENT_ACTIONS as readonly string[]).includes(params.action)) {
3276
+ if (!(SUBAGENT_ACTIONS as readonly string[]).includes(action)) {
2818
3277
  return {
2819
- content: [{ type: "text", text: `Unknown action: ${params.action}. Valid: ${SUBAGENT_ACTIONS.join(", ")}` }],
3278
+ content: [{ type: "text", text: `Unknown action: ${action}. Valid: ${SUBAGENT_ACTIONS.join(", ")}` }],
2820
3279
  isError: true,
2821
3280
  details: { mode: "management" as const, results: [] },
2822
3281
  };
2823
3282
  }
2824
- if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(params.action)) {
3283
+ if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
2825
3284
  return {
2826
- content: [{ type: "text", text: `Action '${params.action}' is not available from child-safe subagent fanout mode.` }],
3285
+ content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
2827
3286
  isError: true,
2828
3287
  details: { mode: "management" as const, results: [] },
2829
3288
  };
2830
3289
  }
2831
- return handleManagementAction(params.action, paramsWithResolvedCwd, { ...ctx, cwd: requestCwd, config: deps.config });
3290
+ return handleManagementAction(action, paramsWithResolvedCwd, {
3291
+ ...ctx,
3292
+ cwd: requestCwd,
3293
+ config: deps.config,
3294
+ });
2832
3295
  }
2833
3296
 
2834
3297
  const { blocked, depth, maxDepth } = checkSubagentDepth(deps.config.maxSubagentDepth);
@@ -2859,12 +3322,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
2859
3322
  );
2860
3323
  const foregroundTimeout = resolveForegroundTimeout(effectiveParams);
2861
3324
  if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
3325
+ const turnBudget = resolveTurnBudget(effectiveParams, deps.config);
3326
+ if (turnBudget.error) return buildRequestedModeError(effectiveParams, turnBudget.error);
3327
+ const runToolBudget = resolveToolBudget(effectiveParams.toolBudget, "toolBudget");
3328
+ if (runToolBudget.error) return buildRequestedModeError(effectiveParams, runToolBudget.error);
3329
+ const configToolBudget = resolveToolBudget(deps.config.toolBudget, "config.toolBudget");
3330
+ if (configToolBudget.error) return buildRequestedModeError(effectiveParams, configToolBudget.error);
2862
3331
 
2863
3332
  const scope: AgentScope = resolveExecutionAgentScope(effectiveParams.agentScope);
2864
3333
  const effectiveCwd = effectiveParams.cwd ?? ctx.cwd;
2865
3334
  const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
2866
3335
  deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
2867
- const discoveredAgents = deps.discoverAgents(effectiveCwd, scope).agents;
3336
+ const discovered = deps.discoverAgents(effectiveCwd, scope);
3337
+ const discoveredAgents = discovered.agents;
3338
+ const modelScope = discovered.modelScope;
2868
3339
  const contextPolicy = resolveAgentDefaultContextPolicy(effectiveParams, discoveredAgents);
2869
3340
  effectiveParams = contextPolicy.params;
2870
3341
  const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
@@ -2872,7 +3343,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
2872
3343
  config: deps.config.intercomBridge,
2873
3344
  context: effectiveParams.context,
2874
3345
  orchestratorTarget: sessionName,
2875
- cwd: effectiveCwd,
2876
3346
  });
2877
3347
  const agents = intercomBridge.active
2878
3348
  ? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
@@ -2901,24 +3371,24 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
2901
3371
  if (validationError) return validationError;
2902
3372
 
2903
3373
  let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
3374
+ let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
2904
3375
  try {
2905
- forkSessionFileForIndex = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined).sessionFileForIndex;
3376
+ const forkContextResolver = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined);
3377
+ forkSessionFileForIndex = forkContextResolver.sessionFileForIndex;
3378
+ forkThinkingOverrideForIndex = forkContextResolver.thinkingOverrideForIndex;
2906
3379
  } catch (error) {
2907
3380
  return toExecutionErrorResult(effectiveParams, error);
2908
3381
  }
2909
3382
  const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
2910
3383
  const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
2911
3384
  const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
2912
- if (foregroundTimeout.timeoutMs !== undefined && effectiveAsync) {
2913
- return buildRequestedModeError(effectiveParams, "timeoutMs/maxRuntimeMs are only supported for foreground runs; set async: false or omit the timeout for background runs.");
2914
- }
2915
3385
  const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
2916
3386
 
2917
3387
  const artifactConfig: ArtifactConfig = {
2918
3388
  ...DEFAULT_ARTIFACT_CONFIG,
2919
3389
  enabled: effectiveParams.artifacts !== false,
2920
3390
  };
2921
- const artifactsDir = effectiveAsync ? deps.tempArtifactsDir : getArtifactsDir(parentSessionFile);
3391
+ const artifactsDir = getArtifactsDir(parentSessionFile, effectiveCwd);
2922
3392
 
2923
3393
  let sessionRoot: string;
2924
3394
  if (effectiveParams.sessionDir) {
@@ -2942,12 +3412,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
2942
3412
  path.join(sessionRoot, `run-${idx ?? 0}`);
2943
3413
  const forkSessionFileForTask = (agentName: string, idx?: number) =>
2944
3414
  shouldForkAgent(contextPolicy, agentName) ? forkSessionFileForIndex(idx) : undefined;
3415
+ const forkThinkingOverrideForTask = (agentName: string, idx?: number) =>
3416
+ shouldForkAgent(contextPolicy, agentName) ? forkThinkingOverrideForIndex(idx) : undefined;
2945
3417
  const childSessionFileForTask = (agentName: string, idx?: number) =>
2946
3418
  forkSessionFileForTask(agentName, idx) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
2947
3419
  const childSessionFileForIndex = (idx?: number) =>
2948
3420
  path.join(sessionDirForIndex(idx), "session.jsonl");
2949
3421
  try {
2950
- preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask);
3422
+ preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems);
2951
3423
  } catch (error) {
2952
3424
  return toExecutionErrorResult(effectiveParams, error);
2953
3425
  }
@@ -2958,6 +3430,16 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
2958
3430
  ? (r: AgentToolResult<Details>) => onUpdate(withForkContext(r, effectiveParams.context))
2959
3431
  : undefined;
2960
3432
 
3433
+ const foregroundMode: "single" | "parallel" | "chain" = hasChain ? "chain" : hasTasks ? "parallel" : "single";
3434
+ const spawnLimitError = reserveSubagentSpawns({
3435
+ state: deps.state,
3436
+ config: deps.config,
3437
+ sessionId: deps.state.currentSessionId,
3438
+ requested: countRequestedSubagentSpawns(effectiveParams, deps.config),
3439
+ mode: foregroundMode,
3440
+ });
3441
+ if (spawnLimitError) return spawnLimitError;
3442
+
2961
3443
  const execData: ExecutionContextData = {
2962
3444
  params: effectiveParams,
2963
3445
  effectiveCwd,
@@ -2971,6 +3453,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
2971
3453
  sessionDirForIndex,
2972
3454
  sessionFileForIndex: childSessionFileForIndex,
2973
3455
  sessionFileForTask: childSessionFileForTask,
3456
+ thinkingOverrideForTask: forkThinkingOverrideForTask,
2974
3457
  artifactConfig,
2975
3458
  artifactsDir,
2976
3459
  backgroundRequestedWhileClarifying,
@@ -2979,10 +3462,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
2979
3462
  intercomBridge,
2980
3463
  nestedRoute,
2981
3464
  timeoutMs: foregroundTimeout.timeoutMs,
3465
+ turnBudget: turnBudget.turnBudget,
3466
+ toolBudget: runToolBudget.toolBudget,
3467
+ configToolBudget: configToolBudget.toolBudget,
2982
3468
  contextPolicy,
3469
+ modelScope,
2983
3470
  };
2984
3471
 
2985
- const foregroundMode: "single" | "parallel" | "chain" = hasChain ? "chain" : hasTasks ? "parallel" : "single";
2986
3472
  const foregroundControl = effectiveAsync
2987
3473
  ? undefined
2988
3474
  : {
@@ -3046,6 +3532,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3046
3532
  startedAt: foregroundControl?.startedAt ?? now,
3047
3533
  ...(state !== "running" ? { endedAt: now } : {}),
3048
3534
  lastUpdate: now,
3535
+ ...(details?.totalCost ? { totalCost: details.totalCost } : {}),
3049
3536
  ...(errorText ? { error: errorText } : {}),
3050
3537
  ...(details?.results.length ? { steps: details.results.map((child) => ({
3051
3538
  agent: child.agent,
@@ -3111,11 +3598,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
3111
3598
  onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
3112
3599
  ctx: ExtensionContext,
3113
3600
  ): Promise<AgentToolResult<Details>> => {
3114
- if (params.action) return execute(id, params, signal, onUpdate, ctx);
3115
- if (deps.state.subagentInProgress === true) return duplicateSubagentCallResult(params);
3601
+ const requestParams = omitExecutionModeActionAlias(params);
3602
+ if (requestParams.action) return execute(id, requestParams, signal, onUpdate, ctx);
3603
+ if (deps.state.subagentInProgress === true) return duplicateSubagentCallResult(requestParams);
3116
3604
  deps.state.subagentInProgress = true;
3117
3605
  try {
3118
- return await execute(id, params, signal, onUpdate, ctx);
3606
+ return await execute(id, requestParams, signal, onUpdate, ctx);
3119
3607
  } finally {
3120
3608
  deps.state.subagentInProgress = false;
3121
3609
  }