pi-subagents 0.76.0 → 0.77.0

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 (347) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/README.md +1 -1
  3. package/agents/oracle.md +8 -19
  4. package/agents/researcher.md +0 -3
  5. package/agents/reviewer.md +17 -61
  6. package/agents/scout.md +1 -4
  7. package/agents/worker.md +8 -25
  8. package/docs/agents.md +70 -3
  9. package/docs/configuration.md +55 -6
  10. package/docs/extension-api.md +36 -11
  11. package/docs/missions.md +24 -17
  12. package/docs/models.md +1 -1
  13. package/docs/observability.md +10 -5
  14. package/docs/tool-reference.md +101 -83
  15. package/docs/watchdog.md +8 -6
  16. package/docs/workflows.md +15 -6
  17. package/index.d.ts.map +1 -1
  18. package/index.js +5 -0
  19. package/index.js.map +1 -1
  20. package/package.json +2 -1
  21. package/prompts/council.md +1 -1
  22. package/skills/pi-subagents/SKILL.md +1 -1
  23. package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  24. package/skills/pi-subagents/references/execution-controls.md +16 -16
  25. package/skills/pi-subagents/references/management-authoring-rpc.md +16 -12
  26. package/skills/pi-subagents/references/prompting-and-roles.md +3 -3
  27. package/src/agents/advertised-agent-prompt.d.ts +5 -1
  28. package/src/agents/advertised-agent-prompt.d.ts.map +1 -1
  29. package/src/agents/advertised-agent-prompt.js +8 -2
  30. package/src/agents/advertised-agent-prompt.js.map +1 -1
  31. package/src/agents/agent-management.d.ts.map +1 -1
  32. package/src/agents/agent-management.js +5 -0
  33. package/src/agents/agent-management.js.map +1 -1
  34. package/src/agents/agent-scope.d.ts +1 -0
  35. package/src/agents/agent-scope.d.ts.map +1 -1
  36. package/src/agents/agent-scope.js +3 -0
  37. package/src/agents/agent-scope.js.map +1 -1
  38. package/src/agents/agent-serializer.d.ts.map +1 -1
  39. package/src/agents/agent-serializer.js +3 -0
  40. package/src/agents/agent-serializer.js.map +1 -1
  41. package/src/agents/agents.d.ts +14 -2
  42. package/src/agents/agents.d.ts.map +1 -1
  43. package/src/agents/agents.js +94 -36
  44. package/src/agents/agents.js.map +1 -1
  45. package/src/api/preflight.d.ts +2 -0
  46. package/src/api/preflight.d.ts.map +1 -1
  47. package/src/api/preflight.js +6 -2
  48. package/src/api/preflight.js.map +1 -1
  49. package/src/extension/config.d.ts.map +1 -1
  50. package/src/extension/config.js +23 -2
  51. package/src/extension/config.js.map +1 -1
  52. package/src/extension/control-notices.d.ts +0 -2
  53. package/src/extension/control-notices.d.ts.map +1 -1
  54. package/src/extension/control-notices.js +1 -7
  55. package/src/extension/control-notices.js.map +1 -1
  56. package/src/extension/declaration-pinning.d.ts +14 -0
  57. package/src/extension/declaration-pinning.d.ts.map +1 -0
  58. package/src/extension/declaration-pinning.js +147 -0
  59. package/src/extension/declaration-pinning.js.map +1 -0
  60. package/src/extension/doctor.d.ts +3 -1
  61. package/src/extension/doctor.d.ts.map +1 -1
  62. package/src/extension/doctor.js +3 -9
  63. package/src/extension/doctor.js.map +1 -1
  64. package/src/extension/fanout-child.d.ts.map +1 -1
  65. package/src/extension/fanout-child.js +6 -8
  66. package/src/extension/fanout-child.js.map +1 -1
  67. package/src/extension/index.d.ts +1 -1
  68. package/src/extension/index.d.ts.map +1 -1
  69. package/src/extension/index.js +68 -116
  70. package/src/extension/index.js.map +1 -1
  71. package/src/extension/rpc.d.ts.map +1 -1
  72. package/src/extension/rpc.js +12 -6
  73. package/src/extension/rpc.js.map +1 -1
  74. package/src/extension/schemas.d.ts +137 -1
  75. package/src/extension/schemas.d.ts.map +1 -1
  76. package/src/extension/schemas.js +22 -22
  77. package/src/extension/schemas.js.map +1 -1
  78. package/src/extension/subagent-guide.d.ts +1 -0
  79. package/src/extension/subagent-guide.d.ts.map +1 -1
  80. package/src/extension/subagent-guide.js +80 -6
  81. package/src/extension/subagent-guide.js.map +1 -1
  82. package/src/extension/subagent-options.d.ts +8 -0
  83. package/src/extension/subagent-options.d.ts.map +1 -0
  84. package/src/extension/subagent-options.js +52 -0
  85. package/src/extension/subagent-options.js.map +1 -0
  86. package/src/extension/tool-activation.d.ts.map +1 -1
  87. package/src/extension/tool-activation.js +3 -2
  88. package/src/extension/tool-activation.js.map +1 -1
  89. package/src/extension/tool-description.d.ts +4 -7
  90. package/src/extension/tool-description.d.ts.map +1 -1
  91. package/src/extension/tool-description.js +45 -59
  92. package/src/extension/tool-description.js.map +1 -1
  93. package/src/inspectors/actions.d.ts +2 -0
  94. package/src/inspectors/actions.d.ts.map +1 -1
  95. package/src/inspectors/actions.js +41 -13
  96. package/src/inspectors/actions.js.map +1 -1
  97. package/src/inspectors/plugins.d.ts.map +1 -1
  98. package/src/inspectors/plugins.js +3 -2
  99. package/src/inspectors/plugins.js.map +1 -1
  100. package/src/inspectors/tmux/actions.d.ts +26 -0
  101. package/src/inspectors/tmux/actions.d.ts.map +1 -0
  102. package/src/inspectors/tmux/actions.js +182 -0
  103. package/src/inspectors/tmux/actions.js.map +1 -0
  104. package/src/inspectors/tmux/client.d.ts +28 -0
  105. package/src/inspectors/tmux/client.d.ts.map +1 -0
  106. package/src/inspectors/tmux/client.js +65 -0
  107. package/src/inspectors/tmux/client.js.map +1 -0
  108. package/src/inspectors/tmux/plugin.d.ts +8 -0
  109. package/src/inspectors/tmux/plugin.d.ts.map +1 -0
  110. package/src/inspectors/tmux/plugin.js +15 -0
  111. package/src/inspectors/tmux/plugin.js.map +1 -0
  112. package/src/integrations/herdr-status.d.ts +0 -1
  113. package/src/integrations/herdr-status.d.ts.map +1 -1
  114. package/src/integrations/herdr-status.js +18 -52
  115. package/src/integrations/herdr-status.js.map +1 -1
  116. package/src/integrations/program-status.d.ts +31 -0
  117. package/src/integrations/program-status.d.ts.map +1 -0
  118. package/src/integrations/program-status.js +189 -0
  119. package/src/integrations/program-status.js.map +1 -0
  120. package/src/intercom/native-supervisor-channel.d.ts +1 -0
  121. package/src/intercom/native-supervisor-channel.d.ts.map +1 -1
  122. package/src/intercom/native-supervisor-channel.js +77 -3
  123. package/src/intercom/native-supervisor-channel.js.map +1 -1
  124. package/src/intercom/result-intercom.js +1 -1
  125. package/src/intercom/result-intercom.js.map +1 -1
  126. package/src/intercom/supervisor-ui.d.ts +2 -12
  127. package/src/intercom/supervisor-ui.d.ts.map +1 -1
  128. package/src/intercom/supervisor-ui.js +2 -154
  129. package/src/intercom/supervisor-ui.js.map +1 -1
  130. package/src/missions/store.d.ts.map +1 -1
  131. package/src/missions/store.js +27 -5
  132. package/src/missions/store.js.map +1 -1
  133. package/src/runs/background/async-execution.d.ts +9 -1
  134. package/src/runs/background/async-execution.d.ts.map +1 -1
  135. package/src/runs/background/async-execution.js +250 -141
  136. package/src/runs/background/async-execution.js.map +1 -1
  137. package/src/runs/background/async-job-tracker.d.ts +4 -1
  138. package/src/runs/background/async-job-tracker.d.ts.map +1 -1
  139. package/src/runs/background/async-job-tracker.js +3 -2
  140. package/src/runs/background/async-job-tracker.js.map +1 -1
  141. package/src/runs/background/async-resume.d.ts.map +1 -1
  142. package/src/runs/background/async-resume.js +6 -2
  143. package/src/runs/background/async-resume.js.map +1 -1
  144. package/src/runs/background/await-async-run.d.ts +8 -4
  145. package/src/runs/background/await-async-run.d.ts.map +1 -1
  146. package/src/runs/background/await-async-run.js +14 -8
  147. package/src/runs/background/await-async-run.js.map +1 -1
  148. package/src/runs/background/binary-bootstrap.d.ts.map +1 -1
  149. package/src/runs/background/binary-bootstrap.js +2 -1
  150. package/src/runs/background/binary-bootstrap.js.map +1 -1
  151. package/src/runs/background/chain-root-attachment.d.ts +2 -0
  152. package/src/runs/background/chain-root-attachment.d.ts.map +1 -1
  153. package/src/runs/background/chain-root-attachment.js +5 -2
  154. package/src/runs/background/chain-root-attachment.js.map +1 -1
  155. package/src/runs/background/fleet-view.js +5 -5
  156. package/src/runs/background/fleet-view.js.map +1 -1
  157. package/src/runs/background/notify.d.ts +12 -2
  158. package/src/runs/background/notify.d.ts.map +1 -1
  159. package/src/runs/background/notify.js +148 -13
  160. package/src/runs/background/notify.js.map +1 -1
  161. package/src/runs/background/result-files.d.ts +29 -0
  162. package/src/runs/background/result-files.d.ts.map +1 -1
  163. package/src/runs/background/result-files.js +104 -6
  164. package/src/runs/background/result-files.js.map +1 -1
  165. package/src/runs/background/result-watcher.d.ts.map +1 -1
  166. package/src/runs/background/result-watcher.js +60 -64
  167. package/src/runs/background/result-watcher.js.map +1 -1
  168. package/src/runs/background/resume-guidance.js +2 -2
  169. package/src/runs/background/resume-guidance.js.map +1 -1
  170. package/src/runs/background/run-child-session.d.ts.map +1 -1
  171. package/src/runs/background/run-child-session.js +2 -1
  172. package/src/runs/background/run-child-session.js.map +1 -1
  173. package/src/runs/background/run-id-resolver.d.ts.map +1 -1
  174. package/src/runs/background/run-id-resolver.js +9 -6
  175. package/src/runs/background/run-id-resolver.js.map +1 -1
  176. package/src/runs/background/run-status.d.ts.map +1 -1
  177. package/src/runs/background/run-status.js +19 -15
  178. package/src/runs/background/run-status.js.map +1 -1
  179. package/src/runs/background/runner-child-launch.d.ts.map +1 -1
  180. package/src/runs/background/runner-child-launch.js +1 -0
  181. package/src/runs/background/runner-child-launch.js.map +1 -1
  182. package/src/runs/background/runner-http-dispatcher.d.ts +3 -0
  183. package/src/runs/background/runner-http-dispatcher.d.ts.map +1 -1
  184. package/src/runs/background/runner-http-dispatcher.js +2 -0
  185. package/src/runs/background/runner-http-dispatcher.js.map +1 -1
  186. package/src/runs/background/scheduled-runs.d.ts +9 -0
  187. package/src/runs/background/scheduled-runs.d.ts.map +1 -1
  188. package/src/runs/background/scheduled-runs.js +124 -36
  189. package/src/runs/background/scheduled-runs.js.map +1 -1
  190. package/src/runs/background/subagent-runner-bootstrap.d.ts.map +1 -1
  191. package/src/runs/background/subagent-runner-bootstrap.js +5 -1
  192. package/src/runs/background/subagent-runner-bootstrap.js.map +1 -1
  193. package/src/runs/background/subagent-runner.d.ts +3 -1
  194. package/src/runs/background/subagent-runner.d.ts.map +1 -1
  195. package/src/runs/background/subagent-runner.js +15 -6
  196. package/src/runs/background/subagent-runner.js.map +1 -1
  197. package/src/runs/background/subagent-wait.d.ts +2 -0
  198. package/src/runs/background/subagent-wait.d.ts.map +1 -1
  199. package/src/runs/background/subagent-wait.js +34 -20
  200. package/src/runs/background/subagent-wait.js.map +1 -1
  201. package/src/runs/background/terminal-run-index.d.ts +1 -0
  202. package/src/runs/background/terminal-run-index.d.ts.map +1 -1
  203. package/src/runs/background/terminal-run-index.js +47 -1
  204. package/src/runs/background/terminal-run-index.js.map +1 -1
  205. package/src/runs/background/wait-tool.d.ts.map +1 -1
  206. package/src/runs/background/wait-tool.js +36 -23
  207. package/src/runs/background/wait-tool.js.map +1 -1
  208. package/src/runs/foreground/async-stop-action.d.ts +7 -1
  209. package/src/runs/foreground/async-stop-action.d.ts.map +1 -1
  210. package/src/runs/foreground/async-stop-action.js +49 -25
  211. package/src/runs/foreground/async-stop-action.js.map +1 -1
  212. package/src/runs/foreground/execution.d.ts.map +1 -1
  213. package/src/runs/foreground/execution.js +50 -24
  214. package/src/runs/foreground/execution.js.map +1 -1
  215. package/src/runs/foreground/subagent-executor.d.ts +5 -1
  216. package/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  217. package/src/runs/foreground/subagent-executor.js +174 -65
  218. package/src/runs/foreground/subagent-executor.js.map +1 -1
  219. package/src/runs/shared/child-hooks.js +6 -5
  220. package/src/runs/shared/child-hooks.js.map +1 -1
  221. package/src/runs/shared/child-launch.d.ts +2 -0
  222. package/src/runs/shared/child-launch.d.ts.map +1 -1
  223. package/src/runs/shared/child-launch.js +1 -0
  224. package/src/runs/shared/child-launch.js.map +1 -1
  225. package/src/runs/shared/child-session.d.ts +4 -0
  226. package/src/runs/shared/child-session.d.ts.map +1 -1
  227. package/src/runs/shared/child-session.js +28 -23
  228. package/src/runs/shared/child-session.js.map +1 -1
  229. package/src/runs/shared/child-tool-plan.d.ts.map +1 -1
  230. package/src/runs/shared/child-tool-plan.js +11 -10
  231. package/src/runs/shared/child-tool-plan.js.map +1 -1
  232. package/src/runs/shared/dynamic-fanout.js +1 -1
  233. package/src/runs/shared/dynamic-fanout.js.map +1 -1
  234. package/src/runs/shared/herdr-placed-run.d.ts +1 -0
  235. package/src/runs/shared/herdr-placed-run.d.ts.map +1 -1
  236. package/src/runs/shared/herdr-placed-run.js +10 -6
  237. package/src/runs/shared/herdr-placed-run.js.map +1 -1
  238. package/src/runs/shared/model-resolution-diagnostic.d.ts +10 -0
  239. package/src/runs/shared/model-resolution-diagnostic.d.ts.map +1 -1
  240. package/src/runs/shared/model-resolution-diagnostic.js +24 -0
  241. package/src/runs/shared/model-resolution-diagnostic.js.map +1 -1
  242. package/src/runs/shared/model-resolution.d.ts.map +1 -1
  243. package/src/runs/shared/model-resolution.js +29 -1
  244. package/src/runs/shared/model-resolution.js.map +1 -1
  245. package/src/runs/shared/nested-events.d.ts +2 -0
  246. package/src/runs/shared/nested-events.d.ts.map +1 -1
  247. package/src/runs/shared/nested-events.js +4 -0
  248. package/src/runs/shared/nested-events.js.map +1 -1
  249. package/src/runs/shared/parallel-handoff.js +2 -2
  250. package/src/runs/shared/parallel-handoff.js.map +1 -1
  251. package/src/runs/shared/parallel-utils.d.ts +2 -0
  252. package/src/runs/shared/parallel-utils.d.ts.map +1 -1
  253. package/src/runs/shared/parallel-utils.js.map +1 -1
  254. package/src/runs/shared/runner-launcher.d.ts +12 -0
  255. package/src/runs/shared/runner-launcher.d.ts.map +1 -0
  256. package/src/runs/shared/runner-launcher.js +25 -0
  257. package/src/runs/shared/runner-launcher.js.map +1 -0
  258. package/src/runs/shared/single-output.d.ts +20 -0
  259. package/src/runs/shared/single-output.d.ts.map +1 -1
  260. package/src/runs/shared/single-output.js +57 -0
  261. package/src/runs/shared/single-output.js.map +1 -1
  262. package/src/runs/shared/structured-output.d.ts.map +1 -1
  263. package/src/runs/shared/structured-output.js +17 -8
  264. package/src/runs/shared/structured-output.js.map +1 -1
  265. package/src/runs/shared/subagent-control.js +3 -3
  266. package/src/runs/shared/subagent-control.js.map +1 -1
  267. package/src/runs/shared/subagent-prompt-runtime.d.ts +8 -2
  268. package/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
  269. package/src/runs/shared/subagent-prompt-runtime.js +43 -20
  270. package/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
  271. package/src/runs/shared/workflow-async-child-guidance.js +1 -1
  272. package/src/runs/shared/workflow-async-child-guidance.js.map +1 -1
  273. package/src/runs/shared/worktree-cleanup-plan.js +1 -1
  274. package/src/runs/shared/worktree-cleanup-plan.js.map +1 -1
  275. package/src/shared/atomic-json.d.ts +4 -3
  276. package/src/shared/atomic-json.d.ts.map +1 -1
  277. package/src/shared/atomic-json.js +3 -1
  278. package/src/shared/atomic-json.js.map +1 -1
  279. package/src/shared/edit-distance.d.ts +5 -0
  280. package/src/shared/edit-distance.d.ts.map +1 -0
  281. package/src/shared/edit-distance.js +33 -0
  282. package/src/shared/edit-distance.js.map +1 -0
  283. package/src/shared/formatters.d.ts.map +1 -1
  284. package/src/shared/formatters.js +1 -6
  285. package/src/shared/formatters.js.map +1 -1
  286. package/src/shared/model-info.d.ts +8 -0
  287. package/src/shared/model-info.d.ts.map +1 -1
  288. package/src/shared/model-info.js +22 -0
  289. package/src/shared/model-info.js.map +1 -1
  290. package/src/shared/package-version.d.ts +3 -0
  291. package/src/shared/package-version.d.ts.map +1 -0
  292. package/src/shared/package-version.js +15 -0
  293. package/src/shared/package-version.js.map +1 -0
  294. package/src/shared/parent-wake.d.ts +18 -0
  295. package/src/shared/parent-wake.d.ts.map +1 -0
  296. package/src/shared/parent-wake.js +60 -0
  297. package/src/shared/parent-wake.js.map +1 -0
  298. package/src/shared/pruned-fork.d.ts +1 -1
  299. package/src/shared/pruned-fork.d.ts.map +1 -1
  300. package/src/shared/pruned-fork.js +6 -0
  301. package/src/shared/pruned-fork.js.map +1 -1
  302. package/src/shared/runtime-module-path.d.ts +11 -0
  303. package/src/shared/runtime-module-path.d.ts.map +1 -0
  304. package/src/shared/runtime-module-path.js +17 -0
  305. package/src/shared/runtime-module-path.js.map +1 -0
  306. package/src/shared/types.d.ts +26 -2
  307. package/src/shared/types.d.ts.map +1 -1
  308. package/src/shared/types.js +3 -0
  309. package/src/shared/types.js.map +1 -1
  310. package/src/shared/utils.d.ts.map +1 -1
  311. package/src/shared/utils.js +8 -7
  312. package/src/shared/utils.js.map +1 -1
  313. package/src/slash/slash-commands.js +3 -3
  314. package/src/slash/slash-commands.js.map +1 -1
  315. package/src/slash/subagents-admin.d.ts.map +1 -1
  316. package/src/slash/subagents-admin.js +2 -1
  317. package/src/slash/subagents-admin.js.map +1 -1
  318. package/src/tui/fleet-transcript.d.ts +2 -0
  319. package/src/tui/fleet-transcript.d.ts.map +1 -1
  320. package/src/tui/fleet-transcript.js +7 -2
  321. package/src/tui/fleet-transcript.js.map +1 -1
  322. package/src/tui/fleet.d.ts.map +1 -1
  323. package/src/tui/fleet.js +9 -4
  324. package/src/tui/fleet.js.map +1 -1
  325. package/src/tui/render.d.ts +2 -2
  326. package/src/tui/render.d.ts.map +1 -1
  327. package/src/tui/render.js +19 -17
  328. package/src/tui/render.js.map +1 -1
  329. package/src/tui/subagent-messages.d.ts +23 -0
  330. package/src/tui/subagent-messages.d.ts.map +1 -0
  331. package/src/tui/subagent-messages.js +270 -0
  332. package/src/tui/subagent-messages.js.map +1 -0
  333. package/src/watchdog/register-main.d.ts.map +1 -1
  334. package/src/watchdog/register-main.js +1 -19
  335. package/src/watchdog/register-main.js.map +1 -1
  336. package/src/watchdog/render.d.ts +2 -10
  337. package/src/watchdog/render.d.ts.map +1 -1
  338. package/src/watchdog/render.js +1 -19
  339. package/src/watchdog/render.js.map +1 -1
  340. package/src/watchdog/tool-actions.js +2 -2
  341. package/src/watchdog/tool-actions.js.map +1 -1
  342. package/src/workflows/scripted-workflow.d.ts.map +1 -1
  343. package/src/workflows/scripted-workflow.js +20 -6
  344. package/src/workflows/scripted-workflow.js.map +1 -1
  345. package/src/workflows/workflow-resources.d.ts.map +1 -1
  346. package/src/workflows/workflow-resources.js +12 -0
  347. package/src/workflows/workflow-resources.js.map +1 -1
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
- import { discoverAgents, findBlockingAgentDiagnostic, formatUnknownAgentError, resolveAgentName, unknownAgentDiagnosticContext } from "../../agents/agents.js";
4
+ import { discoverAgents, findBlockingAgentDiagnostic, formatAvailableAgentLines, formatUnknownAgentError, resolveAgentName, suggestAgentName, unknownAgentDiagnosticContext } from "../../agents/agents.js";
5
5
  import { getArtifactsDir, getProjectArtifactPackagingWarning, getProjectSubagentsDir } from "../../shared/artifacts.js";
6
6
  import { writeAtomicJson } from "../../shared/atomic-json.js";
7
7
  import { createCapacityResilientJsonWriter } from "../../shared/capacity-resilient-json.js";
@@ -10,7 +10,7 @@ import { resolveEffectiveThinking, toModelInfo } from "../../shared/model-info.j
10
10
  import { beginForegroundChild, finishForegroundChild, foregroundSchedulingSettled, settleForegroundSchedulingOwner, updateForegroundChild, } from "./foreground-control.js";
11
11
  import { getLivePromptAudit, rewritePromptWithGuidance, updateLiveEffectivePrompt } from "./prompt-audit.js";
12
12
  import { persistForegroundRunHistory, MAX_REMEMBERED_FOREGROUND_RUNS } from "./foreground-history.js";
13
- import { resolveExecutionAgentScope } from "../../agents/agent-scope.js";
13
+ import { projectScopeRequiresTrustMessage, resolveExecutionAgentScope } from "../../agents/agent-scope.js";
14
14
  import { handleManagementAction } from "../../agents/agent-management.js";
15
15
  import { handleRefinementAction } from "../../agents/agent-refinements.js";
16
16
  import { buildDoctorReport } from "../../extension/doctor.js";
@@ -56,7 +56,7 @@ import { assertAgentAllowedByCapabilityCeiling, intersectSubagentCapabilityCeili
56
56
  import { isAgentContract } from "../shared/agent-contract.js";
57
57
  import { normalizeExtensionBindings } from "../shared/extension-bindings.js";
58
58
  import { assertRequiredChildExtensionsAdmitted, hasMandatoryRequiredChildExtensions, readRetainedRequiredChildExtensions, resolveRequiredChildExtensions } from "../../shared/required-child-extensions.js";
59
- import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, outputPathMappingFromTask, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.js";
59
+ import { capInlineOutput, finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, outputPathMappingFromTask, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.js";
60
60
  import { assertJsonSchemaObject, cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.js";
61
61
  import { compactForegroundDetails, getSingleResultOutput, PROMPT_REDACTED, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage, toAgentToolUsage } from "../../shared/utils.js";
62
62
  import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, protectRetainedWorktreeForResume, formatStoredParallelHandoffCleanup, parallelHandoffPath, readParallelHandoffManifest, recordParallelHandoffMerge, recordParallelHandoffSupersession, writeParallelHandoffGroup, writeWorktreeSetupHandoff } from "../shared/parallel-handoff.js";
@@ -73,8 +73,8 @@ import { promotePausedWorkflowIfSettled, reconcileDetachedWorkflowChildCompletio
73
73
  import { reconcileAsyncRun } from "../background/stale-run-reconciler.js";
74
74
  import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.js";
75
75
  import { awaitExistingAsyncRun, claimWorkflowAwaitedResult } from "../background/await-async-run.js";
76
- import { fallbackResultPayloadPathForSessionRun, removeResultIndex, resultFilePath, writeAsyncResultFile } from "../background/result-files.js";
77
- import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, inheritedNestedParentAddressOf, inheritedNestedRouteOf, nestedRunScope, resolveNestedAsyncDir, retainNestedLookupRoute, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent } from "../shared/nested-events.js";
76
+ import { fallbackResultPayloadPathForSessionRun, resultFilePath, retireResultSnapshot, writeAsyncResultFile } from "../background/result-files.js";
77
+ import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, inheritedNestedParentAddressOf, inheritedNestedRouteOf, nestedRunNotActiveMessage, nestedRunScope, resolveNestedAsyncDir, retainNestedLookupRoute, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent } from "../shared/nested-events.js";
78
78
  import { resolveSubagentRunId } from "../background/run-id-resolver.js";
79
79
  import { formatNestedRunStatusLines } from "../shared/nested-render.js";
80
80
  import { isStoppableAsyncStatusStep, resolveAsyncStatusChild } from "../shared/child-identity.js";
@@ -83,6 +83,7 @@ import { getExternalJobProvider } from "../../api/external-job-provider.js";
83
83
  import { externalJobFollowUpRequestDigest, externalJobFollowUpRequestId, externalJobFollowUpRunId, externalJobPromptDigest, externalJobStableJson } from "../shared/external-job-runner.js";
84
84
  import { externalCliReceiptMetadata, normalizeExternalCliRunnerStatus } from "../shared/external-cli-contract.js";
85
85
  import { formatHerdrMachineRunnerUnsupported, resolveHerdrMachinePlacement } from "../shared/herdr-machine.js";
86
+ import { lookupRunnerLauncher, resolveAgentRunnerLauncher } from "../shared/runner-launcher.js";
86
87
  import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.js";
87
88
  import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.js";
88
89
  import { attachMissionToLaunchResult, prepareMissionLaunch, writeMissionAsyncBinding } from "../../missions/lifecycle.js";
@@ -111,35 +112,12 @@ import { applyReviewedCleanupPlan, formatCleanupReceipt, loadReviewedCleanupPlan
111
112
  import { createWorktreeCleanupPlan, formatWorktreeCleanupPlan } from "../shared/worktree-cleanup-plan.js";
112
113
  import { DIRS, DEFAULT_ARTIFACT_CONFIG, DEFAULT_MAX_OUTPUT, DEFAULT_FORK_PREAMBLE, SUBAGENT_ACTIONS, SUBAGENT_ASYNC_STARTED_EVENT, SUBAGENT_CHILD_STATUS_EVENT, SUBAGENT_CONTROL_EVENT, SUBAGENT_CONTROL_INTERCOM_EVENT, SUBAGENT_FOREGROUND_COMPLETE_EVENT, SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, checkSubagentDepth, resolveChildMaxSubagentDepth, resolveCurrentMaxSubagentDepth, resolveMaxSubagentSpawnsPerRun, truncateOutput, wrapForkTask, } from "../../shared/types.js";
113
114
  import { deriveChildSessionName } from "../../shared/child-session-name.js";
115
+ import { editDistance, hasSingleAdjacentTransposition } from "../../shared/edit-distance.js";
114
116
  const MUTATING_MANAGEMENT_ACTIONS = new Set(["command.yield", "command.cancel", "create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "worktree.cleanup", "lane.recordMerge", "lane.recordSupersession", "refine", "refine.rollback", "dismiss", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
115
117
  const DESTRUCTIVE_MANAGEMENT_ACTIONS = new Set(["command.cancel", "delete", "eject", "disable", "reset", "mission.close", "worktree.discard", "refine.rollback", "inspector.close", "project.close", "stop", "interrupt", "schedule.delete"]);
116
118
  function resolveSteerDeliveryMode(mode) {
117
119
  return mode === "steer" || mode === "follow_up" || mode === "auto" ? mode : undefined;
118
120
  }
119
- function editDistance(left, right) {
120
- const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
121
- for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
122
- let diagonal = previous[0];
123
- previous[0] = leftIndex;
124
- for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
125
- const above = previous[rightIndex];
126
- previous[rightIndex] = left[leftIndex - 1] === right[rightIndex - 1]
127
- ? diagonal
128
- : Math.min(diagonal, above, previous[rightIndex - 1]) + 1;
129
- diagonal = above;
130
- }
131
- }
132
- return previous[right.length];
133
- }
134
- function hasSingleAdjacentTransposition(left, right) {
135
- if (left.length !== right.length)
136
- return false;
137
- const mismatch = [...left].findIndex((character, index) => character !== right[index]);
138
- return mismatch >= 0
139
- && left[mismatch] === right[mismatch + 1]
140
- && left[mismatch + 1] === right[mismatch]
141
- && left.slice(mismatch + 2) === right.slice(mismatch + 2);
142
- }
143
121
  export function unknownSubagentActionMessage(action, disabled) {
144
122
  const requested = action.toLowerCase();
145
123
  const enabledActions = SUBAGENT_ACTIONS.filter((candidate) => !disabled?.actions.has(candidate));
@@ -304,6 +282,21 @@ function trustedSessionRootsForStatus(ctx, deps) {
304
282
  function sessionProjectTrust(ctx) {
305
283
  return typeof ctx.isProjectTrusted === "function" ? ctx.isProjectTrusted() : undefined;
306
284
  }
285
+ /** Agent and settings discovery follows the launching session's trust, like the child itself. */
286
+ function withSessionProjectTrust(deps, ctx) {
287
+ if (sessionProjectTrust(ctx) !== false)
288
+ return deps;
289
+ return { ...deps, discoverAgents: (cwd, scope, preferredModelProvider) => deps.discoverAgents(cwd, scope, preferredModelProvider, { projectTrusted: false }) };
290
+ }
291
+ function projectScopeTrustError(params, ctx) {
292
+ if (sessionProjectTrust(ctx) !== false || resolveExecutionAgentScope(params.agentScope) !== "project")
293
+ return undefined;
294
+ const action = typeof params.action === "string" ? params.action.trim() : undefined;
295
+ // These actions resolve agents in the requested scope; management actions are left to their own handlers.
296
+ if (action !== undefined && action !== "resume" && action !== "validate" && action !== "append-step")
297
+ return undefined;
298
+ return buildRequestedModeError(params, projectScopeRequiresTrustMessage(resolveRequestedCwd(ctx.cwd, params.cwd)));
299
+ }
307
300
  function spawnBudgetErrorResult(message, mode) {
308
301
  return {
309
302
  content: [{ type: "text", text: message }],
@@ -824,7 +817,7 @@ function emitControlNotification(input) {
824
817
  }
825
818
  }
826
819
  function interruptAsyncRun(state, runId, kill, location) {
827
- const target = getAsyncInterruptTarget(state, runId, location);
820
+ const target = getAsyncInterruptTarget(state, runId, location, { fallbackToNewest: runId === undefined });
828
821
  if (!target)
829
822
  return null;
830
823
  const status = reconcileAsyncRun(target.asyncDir, omitUndefinedProperties({ kill })).status;
@@ -1016,6 +1009,7 @@ function appendStepToAsyncChain(input) {
1016
1009
  cwd: input.ctx.cwd,
1017
1010
  currentSessionId: resolveCurrentSessionId(input.ctx.sessionManager),
1018
1011
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
1012
+ parentSessionFile: input.ctx.sessionManager.getSessionFile() ?? undefined,
1019
1013
  currentModelProvider: parentModel?.provider,
1020
1014
  currentModel: parentModel,
1021
1015
  scopedModelIds: scopedModelIdsFromContext(input.ctx),
@@ -1025,6 +1019,17 @@ function appendStepToAsyncChain(input) {
1025
1019
  permissions: input.deps.config.permissions,
1026
1020
  childRuntime: input.deps.childRuntime,
1027
1021
  });
1022
+ // Appended steps run inside the already-running runner, so they must use its launcher (or none).
1023
+ // Checked before building, which can write progress files. A mix is refused by the builder.
1024
+ const appendedLaunchers = new Set(chain.flatMap(getStepAgents).map((name) => agents.find((agent) => agent.name === name)?.launcher));
1025
+ const appendedLauncher = [...appendedLaunchers][0];
1026
+ if (appendedLaunchers.size === 1 && appendedLauncher !== status.launcher?.name) {
1027
+ return {
1028
+ content: [{ type: "text", text: `Cannot append step to run '${resolved.id}': its runner uses ${status.launcher ? `launcher '${status.launcher.name}'` : "no launcher"}, but the appended agents use ${appendedLauncher ? `launcher '${appendedLauncher}'` : "no launcher"}.` }],
1029
+ isError: true,
1030
+ details: { mode: "management", results: [] },
1031
+ };
1032
+ }
1028
1033
  const built = buildAsyncRunnerSteps(resolved.id, compactOptional({
1029
1034
  chain: wrapChainTasksForFork(chain, contextPolicy),
1030
1035
  task: input.params.task,
@@ -1332,11 +1337,18 @@ async function interruptNestedRun(target) {
1332
1337
  if (run.state === "paused")
1333
1338
  return { content: [{ type: "text", text: `Nested run ${run.id} is already paused.` }], isError: true, details: { mode: "management", results: [] } };
1334
1339
  const result = await sendNestedControlRequest(target, "interrupt");
1335
- if (result)
1340
+ // The owner's foreground map cannot address a detached async child. Only
1341
+ // that missing-route response may use the existing authorized async inbox;
1342
+ // other owner refusals must remain authoritative.
1343
+ const missingForegroundRoute = result?.ok === false && result.message === nestedRunNotActiveMessage(run.id);
1344
+ if (result && !missingForegroundRoute)
1336
1345
  return { content: [{ type: "text", text: result.message }], ...(result.ok ? {} : { isError: true }), details: { mode: "management", results: [] } };
1337
1346
  const direct = directNestedAsyncInterrupt(target);
1338
1347
  if (direct)
1339
1348
  return direct;
1349
+ // No async inbox either: the owner's own answer is the accurate one.
1350
+ if (result)
1351
+ return { content: [{ type: "text", text: result.message }], isError: true, details: { mode: "management", results: [] } };
1340
1352
  return { content: [{ type: "text", text: `Nested run ${run.id} owner is not reachable and no safe direct async interrupt fallback is available.` }], isError: true, details: { mode: "management", results: [] } };
1341
1353
  }
1342
1354
  async function resumeLiveNestedRun(input) {
@@ -1467,6 +1479,7 @@ async function resumeExternalJobFollowUp(input) {
1467
1479
  cwd: input.requestCwd,
1468
1480
  currentSessionId,
1469
1481
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
1482
+ parentSessionFile: input.ctx.sessionManager.getSessionFile() ?? undefined,
1470
1483
  currentModelProvider: parentModel?.provider,
1471
1484
  currentModel: parentModel,
1472
1485
  scopedModelIds: scopedModelIdsFromContext(input.ctx),
@@ -1587,7 +1600,7 @@ async function resumeAsyncRun(input) {
1587
1600
  type: "text",
1588
1601
  text: [
1589
1602
  `Async child '${target.runId}' index ${target.index} is still running. action='resume' only revives paused, completed, or failed children.`,
1590
- `Send live input with subagent({ action: "steer", id: "${target.runId}", index: ${target.index}, message: "..." }).`,
1603
+ `Send live input with subagent({ action: "steer", id: "${target.runId}", message: "...", options: { index: ${target.index} } }).`,
1591
1604
  ].join("\n"),
1592
1605
  }],
1593
1606
  isError: true,
@@ -1727,7 +1740,7 @@ async function resumeAsyncRun(input) {
1727
1740
  const chain = wrapChainTasksForFork(attachChain, contextPolicy);
1728
1741
  const normalized = normalizeSkillInput(input.params.skill);
1729
1742
  const parentModel = input.parentModel;
1730
- const result = executeAsyncChain(runId, compactOptional({
1743
+ const result = await executeAsyncChain(runId, compactOptional({
1731
1744
  chain,
1732
1745
  task: workflowTask,
1733
1746
  goal,
@@ -1746,11 +1759,13 @@ async function resumeAsyncRun(input) {
1746
1759
  cwd: input.requestCwd,
1747
1760
  currentSessionId: input.deps.state.currentSessionId,
1748
1761
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
1762
+ parentSessionFile: input.ctx.sessionManager.getSessionFile() ?? undefined,
1749
1763
  currentModelProvider: parentModel?.provider,
1750
1764
  currentModel: parentModel,
1751
1765
  scopedModelIds: scopedModelIdsFromContext(input.ctx),
1752
1766
  modelScope,
1753
1767
  modelResponseAliases: input.deps.config.modelResponseAliases,
1768
+ runnerLaunchers: input.deps.config.runnerLaunchers,
1754
1769
  interactive: input.ctx.hasUI,
1755
1770
  permissions: input.deps.config.permissions,
1756
1771
  childRuntime: input.deps.childRuntime,
@@ -1814,7 +1829,8 @@ async function resumeAsyncRun(input) {
1814
1829
  if (target.source === "nested" && !target.recoveryDescriptor) {
1815
1830
  return { content: [{ type: "text", text: `Nested child '${target.runId}' is missing its required recovery identity. Start a new run instead.` }], isError: true, details: { mode: "management", results: [] } };
1816
1831
  }
1817
- const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : baseAgentConfig;
1832
+ // Runs without a recovery descriptor were never wrapped; a launcher added to the agent file later does not apply.
1833
+ const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : { ...baseAgentConfig, launcher: undefined };
1818
1834
  const agentConfig = intercomBridge.active ? applyIntercomBridgeToAgent(recoveryAgentConfig, intercomBridge) : recoveryAgentConfig;
1819
1835
  const foregroundContract = target.source === "foreground" ? target.resumeContract : undefined;
1820
1836
  const outputSchema = Object.hasOwn(input.params, "outputSchema")
@@ -1831,6 +1847,10 @@ async function resumeAsyncRun(input) {
1831
1847
  if (acceptanceErrors.length > 0) {
1832
1848
  return { content: [{ type: "text", text: `Cannot resume: ${acceptanceErrors.join(" ")}` }], isError: true, details: { mode: "management", results: [] } };
1833
1849
  }
1850
+ // Refuse before capacity moves so the retained run stays resumable once the config is fixed.
1851
+ if (agentConfig.launcher !== undefined && !lookupRunnerLauncher(input.deps.config.runnerLaunchers, agentConfig.launcher)) {
1852
+ return { content: [{ type: "text", text: `Cannot resume: run '${target.runId}' was launched with launcher '${agentConfig.launcher}', which is no longer defined in runnerLaunchers in the user subagent config.` }], isError: true, details: { mode: "management", results: [] } };
1853
+ }
1834
1854
  const runId = randomUUID();
1835
1855
  const topLevelResume = depth === 0 && !inheritedNestedRoute(input.deps) && !input.params.workflowParentRunId;
1836
1856
  let activeAsyncCapacity;
@@ -1873,12 +1893,14 @@ async function resumeAsyncRun(input) {
1873
1893
  cwd: input.requestCwd,
1874
1894
  currentSessionId: input.deps.state.currentSessionId,
1875
1895
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
1896
+ parentSessionFile: input.ctx.sessionManager.getSessionFile() ?? undefined,
1876
1897
  currentModelProvider: parentModel?.provider,
1877
1898
  currentModel: parentModel,
1878
1899
  scopedModelIds: scopedModelIdsFromContext(input.ctx),
1879
1900
  modelScope,
1880
1901
  // Absence in the retained contract is meaningful; never acquire current aliases.
1881
1902
  modelResponseAliases: recoveryDescriptor ? recoveryDescriptor.modelResponseAliases : foregroundContract?.modelResponseAliases,
1903
+ runnerLaunchers: input.deps.config.runnerLaunchers,
1882
1904
  interactive: input.ctx.hasUI,
1883
1905
  permissions: input.deps.config.permissions,
1884
1906
  childRuntime: input.deps.childRuntime,
@@ -2028,10 +2050,12 @@ function formatFailedSingleRunOutput(result, displayOutput) {
2028
2050
  const error = result.error || "Failed";
2029
2051
  const output = displayOutput.trim();
2030
2052
  const lines = [error];
2053
+ let cappedOutput = output;
2031
2054
  if (output && output !== error.trim()) {
2032
- lines.push("", "Output:", output);
2055
+ cappedOutput = capInlineOutput(output, { artifactPath: result.artifactPaths?.outputPath, savedPath: result.savedOutputPath });
2056
+ lines.push("", "Output:", cappedOutput);
2033
2057
  }
2034
- if (result.artifactPaths?.outputPath && fs.existsSync(result.artifactPaths.outputPath)) {
2058
+ if (cappedOutput === output && result.artifactPaths?.outputPath && fs.existsSync(result.artifactPaths.outputPath)) {
2035
2059
  lines.push("", `Output artifact: ${result.artifactPaths.outputPath}`);
2036
2060
  }
2037
2061
  return lines.join("\n");
@@ -2156,6 +2180,8 @@ function workflowValidationOptions(deps, params, runtimeCwd, parentModelProvider
2156
2180
  const cwd = resolveRequestedCwd(runtimeCwd, params.cwd);
2157
2181
  const scope = resolveExecutionAgentScope(params.agentScope);
2158
2182
  let discovered;
2183
+ // Validation reports every bad agent name; only the first one carries the agent list.
2184
+ let listedAgents = false;
2159
2185
  return {
2160
2186
  maxSubagentSpawnsPerRun: params.maxSubagentSpawnsPerRun ?? resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun),
2161
2187
  agentNameError: (name) => {
@@ -2164,12 +2190,13 @@ function workflowValidationOptions(deps, params, runtimeCwd, parentModelProvider
2164
2190
  const resolved = resolveAgentName(name, agents);
2165
2191
  if (resolved.agent || resolved.error)
2166
2192
  return canonicalizeAgentName(name, agents, discovered.agentDiagnostics, diagnosticContextFromDiscovery(discovered, cwd, scope)).error;
2167
- const requested = name.trim().toLowerCase();
2168
- const suggestion = [...new Set(agents.flatMap((agent) => [agent.name, ...(agent.localName ? [agent.localName] : []), ...(agent.aliases ?? [])]))]
2169
- .map((candidate) => ({ candidate, distance: editDistance(requested, candidate.toLowerCase()) }))
2170
- .filter(({ candidate, distance }) => distance <= Math.max(1, Math.floor(candidate.length / 4)) || hasSingleAdjacentTransposition(requested, candidate.toLowerCase()))
2171
- .sort((left, right) => left.distance - right.distance || left.candidate.localeCompare(right.candidate))[0]?.candidate;
2172
- return `Unknown agent '${name}'.${suggestion ? ` Did you mean '${suggestion}'?` : ""} Use subagent({ action: "list" }) to inspect agents.`;
2193
+ const disabled = discovered.disabledAgents?.includes(name.trim()) === true;
2194
+ const suggestion = disabled ? undefined : suggestAgentName(name, agents);
2195
+ const problem = disabled ? `Agent '${name}' is disabled by a settings override.` : `Unknown agent '${name}'.${suggestion ? ` Did you mean '${suggestion}'?` : ""}`;
2196
+ if (listedAgents)
2197
+ return problem;
2198
+ listedAgents = true;
2199
+ return [`${problem} Available agents:`, ...formatAvailableAgentLines(agents)].join("\n");
2173
2200
  },
2174
2201
  };
2175
2202
  }
@@ -2904,12 +2931,12 @@ function importedAsyncRootUsage(completed) {
2904
2931
  }
2905
2932
  function removeWorkflowAwaitedResult(asyncDir, resultPath, runId, publication) {
2906
2933
  try {
2907
- fs.rmSync(resultPath, { force: true });
2934
+ retireResultSnapshot(workflowAwaitedAsyncResultPath(asyncDir), { ...publication, runId }, resultPath);
2908
2935
  }
2909
- catch {
2910
- // Payload cleanup must not replace an already imported delivery.
2936
+ catch (error) {
2937
+ // Cleanup must not replace an already imported delivery.
2938
+ console.error(`Failed to remove imported workflow child result '${resultPath}'; leaving it in place:`, error);
2911
2939
  }
2912
- removeResultIndex(asyncDir, publication.sessionId, runId, publication.toolCallId);
2913
2940
  }
2914
2941
  function emitWorkflowAwaitedChildComplete(pi, state, runId, asyncDir, parentWorkflowRunId, completed) {
2915
2942
  let status;
@@ -3037,11 +3064,13 @@ async function runAsyncPath(data, deps) {
3037
3064
  cwd: ctx.cwd,
3038
3065
  currentSessionId: data.parentSessionId,
3039
3066
  parentSessionId: data.parentPiSessionId,
3067
+ parentSessionFile: ctx.sessionManager.getSessionFile() ?? undefined,
3040
3068
  currentModelProvider: parentModel?.provider,
3041
3069
  currentModel: parentModel,
3042
3070
  scopedModelIds: data.scopedModelIds,
3043
3071
  modelScope: data.modelScope,
3044
3072
  modelResponseAliases: deps.config.modelResponseAliases,
3073
+ runnerLaunchers: deps.config.runnerLaunchers,
3045
3074
  interactive: ctx.hasUI,
3046
3075
  permissions: deps.config.permissions,
3047
3076
  childRuntime: deps.childRuntime,
@@ -3091,6 +3120,7 @@ async function runAsyncPath(data, deps) {
3091
3120
  if (launchRuleError)
3092
3121
  return toExecutionErrorResult(params, new Error(launchRuleError), data.contextPolicy.contextSummary);
3093
3122
  const asyncResult = await executeAsyncSingle(id, compactOptional({
3123
+ toolCallId: data.toolCallId,
3094
3124
  agent: params.agent,
3095
3125
  task: shouldForkAgent(contextPolicy, params.agent) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
3096
3126
  goal: params.task ?? "",
@@ -3295,7 +3325,12 @@ function resolveWorkflowChildOutputPath(input) {
3295
3325
  id: input.params.resume.trim(),
3296
3326
  ...(typeof index === "number" && Number.isInteger(index) ? { index } : {}),
3297
3327
  }, input.state);
3298
- return { path: "recoveryDescriptor" in target ? target.recoveryDescriptor?.outputPath : undefined, inherited: false };
3328
+ const recoveryOutput = "recoveryDescriptor" in target ? target.recoveryDescriptor?.outputPath : undefined;
3329
+ if (hasExplicitOutput)
3330
+ return { path: recoveryOutput, inherited: false };
3331
+ // An output-less foreground resume reuses its recorded output; only an absolute path is known before the new run id exists.
3332
+ const foregroundOutput = target.source === "foreground" ? target.resumeContract?.output : undefined;
3333
+ return { path: recoveryOutput, inherited: false, resumedOutput: recoveryOutput ?? (typeof foregroundOutput === "string" && path.isAbsolute(foregroundOutput) ? foregroundOutput : undefined) };
3299
3334
  }
3300
3335
  const childCwd = resolveWorkflowChildLocalCwd(input);
3301
3336
  let agentOutput;
@@ -3335,7 +3370,9 @@ function workflowChildOutputClaims(input) {
3335
3370
  }
3336
3371
  const overrides = new Map();
3337
3372
  for (const entry of resolvedEntries) {
3338
- if (entry.inherited && entry.path && (paths.get(resolveWorkflowHostOutputClaimPath(entry.path)) ?? 0) > 1) {
3373
+ // An output-less resume keeps its retained path unless an earlier child of this workflow already owns it.
3374
+ const resumeCollides = entry.resumedOutput !== undefined && input.claimedOutputPaths.has(resolveWorkflowHostOutputClaimPath(entry.resumedOutput));
3375
+ if (resumeCollides || (entry.inherited && entry.path && (paths.get(resolveWorkflowHostOutputClaimPath(entry.path)) ?? 0) > 1)) {
3339
3376
  const output = workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, entry.key);
3340
3377
  overrides.set(entry.key, output);
3341
3378
  entry.path = output;
@@ -3353,6 +3390,16 @@ function workflowChildOutputClaims(input) {
3353
3390
  claims.set(claimPath, key);
3354
3391
  childClaims.set(key, claimPath);
3355
3392
  }
3393
+ // Record a discovered output-less resume path for later admissions without adding same-batch duplicate errors.
3394
+ for (const { key, path: resolved, resumedOutput } of resolvedEntries) {
3395
+ if (resolved || !resumedOutput)
3396
+ continue;
3397
+ const claimPath = resolveWorkflowHostOutputClaimPath(resumedOutput);
3398
+ if (claims.has(claimPath))
3399
+ continue;
3400
+ claims.set(claimPath, key);
3401
+ childClaims.set(key, claimPath);
3402
+ }
3356
3403
  return { claims, childClaims, overrides };
3357
3404
  }
3358
3405
  function applyWorkflowChildOutputClaims(target, claims) {
@@ -3363,7 +3410,7 @@ function applyWorkflowChildOutputClaims(target, claims) {
3363
3410
  function prepareWorkflowChildLaunchParams(input) {
3364
3411
  let childParams = input.childParams;
3365
3412
  const usesDefaultOutput = input.childParams.output === undefined && input.childParams.resume === undefined;
3366
- if (usesDefaultOutput && input.outputOverride !== undefined) {
3413
+ if (input.childParams.output === undefined && input.outputOverride !== undefined) {
3367
3414
  childParams = { ...input.childParams, output: input.outputOverride };
3368
3415
  }
3369
3416
  else if (usesDefaultOutput && input.aggregateOutputPath !== undefined) {
@@ -3380,7 +3427,7 @@ function prepareWorkflowChildLaunchParams(input) {
3380
3427
  const agent = typeof childParams.agent === "string"
3381
3428
  ? resolveAgentName(childParams.agent, discoveredAgents).agent ?? resolveAgentName(childParams.agent, input.agents).agent
3382
3429
  : undefined;
3383
- const externalAsyncRequired = agent?.runner?.type === "external-cli" || agent?.runner?.type === "external-job";
3430
+ const externalAsyncRequired = agent?.runner?.type === "external-cli" || agent?.runner?.type === "external-job" || agent?.launcher !== undefined;
3384
3431
  return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, { ...input.options, externalAsyncRequired, outputClaimPath: input.outputClaimPath });
3385
3432
  }
3386
3433
  async function finalizeSingleWorktreeHandoff(input) {
@@ -3604,6 +3651,7 @@ async function runSinglePath(data, deps) {
3604
3651
  const interruptController = new AbortController();
3605
3652
  let detachForeground;
3606
3653
  let childSessionControls;
3654
+ let pendingRuntimeStop;
3607
3655
  const foregroundControl = deps.state.foregroundControls.get(runId);
3608
3656
  const syncHerdrForegroundChild = () => {
3609
3657
  if (!foregroundControl?.parentWorkflowRunId)
@@ -3679,6 +3727,7 @@ async function runSinglePath(data, deps) {
3679
3727
  ? new Promise((resolve) => { resolveDetachedWorkflowChild = resolve; })
3680
3728
  : undefined;
3681
3729
  try {
3730
+ const childSessionFile = sessionFileForTask(params.agent, 0, modelOverride, modelOverrideFromParent, modelOrigin);
3682
3731
  const launched = await runSync(ctx.cwd, agents, params.agent, task, compactOptional({
3683
3732
  machine: foregroundMachine,
3684
3733
  parentProviderRegistry: ctx.modelRegistry,
@@ -3687,9 +3736,14 @@ async function runSinglePath(data, deps) {
3687
3736
  permissions: deps.config.permissions,
3688
3737
  runtimeSnapshotHost: deps.pi,
3689
3738
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
3739
+ parentSessionFile: ctx.sessionManager.getSessionFile() ?? undefined,
3690
3740
  requiredExtensions,
3691
3741
  childRuntime: deps.childRuntime,
3692
- onChildSession: (controls) => { childSessionControls = controls; },
3742
+ onChildSession: (controls) => {
3743
+ childSessionControls = controls;
3744
+ if (pendingRuntimeStop)
3745
+ controls.stop(pendingRuntimeStop);
3746
+ },
3693
3747
  context: data.contextPolicy.contextForAgent(params.agent),
3694
3748
  unknownAgentDiagnosticContext: data.unknownAgentDiagnosticContext,
3695
3749
  runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
@@ -3702,7 +3756,7 @@ async function runSinglePath(data, deps) {
3702
3756
  intercomEvents: deps.pi.events,
3703
3757
  runId,
3704
3758
  sessionDir: sessionDirForIndex(0),
3705
- sessionFile: sessionFileForTask(params.agent, 0, modelOverride, modelOverrideFromParent, modelOrigin),
3759
+ sessionFile: childSessionFile,
3706
3760
  share: shareEnabled,
3707
3761
  artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
3708
3762
  artifactConfig,
@@ -3743,6 +3797,8 @@ async function runSinglePath(data, deps) {
3743
3797
  detachForeground = detach;
3744
3798
  },
3745
3799
  onDetachedExit: async (result) => {
3800
+ if (foregroundControl)
3801
+ foregroundControl.stopForRuntimeReplacement = undefined;
3746
3802
  if (resolveDetachedWorkflowChild) {
3747
3803
  resolveDetachedWorkflowChild(result);
3748
3804
  return;
@@ -3794,6 +3850,20 @@ async function runSinglePath(data, deps) {
3794
3850
  capabilityCeiling: data.capabilityCeiling,
3795
3851
  allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
3796
3852
  }));
3853
+ if (launched.detached && foregroundControl) {
3854
+ // Record the stop before aborting so the next runtime's history restore finds it.
3855
+ foregroundControl.stopForRuntimeReplacement = () => {
3856
+ // The detach receipt predates the session file that resume needs.
3857
+ const sessionFile = childSessionFile && fs.existsSync(childSessionFile) ? childSessionFile : undefined;
3858
+ const stopped = { ...launched, detached: undefined, stopped: true, exitCode: 1, error: RUNTIME_REPLACED_STOP_ERROR, finalOutput: RUNTIME_REPLACED_STOP_ERROR, sessionFile };
3859
+ rememberForegroundRun(deps.state, { modelResponseAliases, runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: [stopped], params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings, requiredExtensions });
3860
+ // A child detached during creation has no controls yet; stop it as soon as they arrive.
3861
+ if (childSessionControls)
3862
+ childSessionControls.stop(RUNTIME_REPLACED_STOP_ERROR);
3863
+ else
3864
+ pendingRuntimeStop = RUNTIME_REPLACED_STOP_ERROR;
3865
+ };
3866
+ }
3797
3867
  r = launched.detached && detachedWorkflowChild ? await detachedWorkflowChild : launched;
3798
3868
  }
3799
3869
  catch (error) {
@@ -3904,7 +3974,7 @@ async function runSinglePath(data, deps) {
3904
3974
  isError: true,
3905
3975
  };
3906
3976
  return {
3907
- content: [{ type: "text", text: `${finalizedOutput.displayOutput || "(no output)"}${worktreeSuffix}` }],
3977
+ content: [{ type: "text", text: `${capInlineOutput(finalizedOutput.displayOutput, { artifactPath: r.artifactPaths?.outputPath, savedPath: finalizedOutput.savedPath }) || "(no output)"}${worktreeSuffix}` }],
3908
3978
  details,
3909
3979
  };
3910
3980
  }
@@ -4107,7 +4177,7 @@ function workflowRunningChildrenSummary(children) {
4107
4177
  function workflowResultChildren(children, status, includeFailureFields) {
4108
4178
  return withWorkflowRevivals(DIRS.async, status.runId, children).map((child) => {
4109
4179
  const sessionName = status.steps?.find((step) => step.workflowKey === child.key)?.sessionName;
4110
- return { workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), ...(child.revival ? { revival: formatWorkflowKeyRevival(child.revival) } : {}), ...(sessionName ? { sessionName } : {}), ...workflowChildAccountingFields(child), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, ...(child.state === "running" ? { state: "running" } : { success: child.ok }), ...(child.asyncDir ? { asyncDir: child.asyncDir } : {}), ...(child.outputReference ? { outputReference: child.outputReference } : {}), ...(includeFailureFields && child.terminalOutcome ? { terminalOutcome: child.terminalOutcome } : {}), ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(includeFailureFields && child.detached && status.state !== "complete" ? { detached: true } : {}), ...(child.outputArtifactPath || child.outputReference ? { artifactPaths: { outputPath: child.outputArtifactPath ?? child.outputReference } } : {}) };
4180
+ return { workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), ...(child.revival ? { revival: formatWorkflowKeyRevival(child.revival) } : {}), ...(sessionName ? { sessionName } : {}), ...workflowChildAccountingFields(child), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, ...(child.state === "running" ? { state: "running" } : { success: child.ok }), ...(!child.ok && child.error ? { error: child.error } : {}), ...(child.asyncDir ? { asyncDir: child.asyncDir } : {}), ...(child.outputReference ? { outputReference: child.outputReference } : {}), ...(includeFailureFields && child.terminalOutcome ? { terminalOutcome: child.terminalOutcome } : {}), ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(includeFailureFields && child.detached && status.state !== "complete" ? { detached: true } : {}), ...(child.outputArtifactPath || child.outputReference ? { artifactPaths: { outputPath: child.outputArtifactPath ?? child.outputReference } } : {}) };
4111
4181
  });
4112
4182
  }
4113
4183
  function workflowOutputPathMappingSummary(children) {
@@ -4141,6 +4211,7 @@ function workflowSteerReceipt(key, result) {
4141
4211
  };
4142
4212
  }
4143
4213
  const CHILD_SESSION_NOT_RUNNING_YET = "Child session is not running yet.";
4214
+ const RUNTIME_REPLACED_STOP_ERROR = "Subagent stopped because the pi-subagents runtime that ran it was replaced (reload or session switch).";
4144
4215
  const MAX_WORKFLOW_RESUME_HINT_BYTES = 1024;
4145
4216
  const MAX_WORKFLOW_CHILD_RUN_ID_BYTES = 256;
4146
4217
  const WORKFLOW_RESUME_HINT_PARENT_STATES = new Set(["complete", "failed", "partial"]);
@@ -4634,6 +4705,7 @@ function createScheduledOwnerState(source, ownerSessionId, ctx) {
4634
4705
  };
4635
4706
  }
4636
4707
  export function createSubagentExecutor(deps) {
4708
+ const executorDeps = deps;
4637
4709
  const delegatedThinkingOverrides = new WeakMap();
4638
4710
  const delegatedZeroToolBudgets = new WeakSet();
4639
4711
  const delegatedExecutions = new WeakSet();
@@ -4657,6 +4729,10 @@ export function createSubagentExecutor(deps) {
4657
4729
  }
4658
4730
  };
4659
4731
  const execute = async (_id, params, signal, onUpdate, ctx, preserveActiveSession = false, parentModelOverride) => {
4732
+ const projectScopeError = projectScopeTrustError(params, ctx);
4733
+ if (projectScopeError)
4734
+ return projectScopeError;
4735
+ const deps = withSessionProjectTrust(executorDeps, ctx);
4660
4736
  const workflowLaunchObserver = workflowLaunchObservers.get(params);
4661
4737
  const inheritedUsageBudget = workflowOwnedUsageBudgets.get(params);
4662
4738
  const retainedRequiredExtensions = workflowRetainedRequiredExtensions.get(params);
@@ -4985,7 +5061,7 @@ export function createSubagentExecutor(deps) {
4985
5061
  if (deps.state.currentSessionId !== currentSessionId || deps.state.completionOwnerId !== completionOwnerId)
4986
5062
  return false;
4987
5063
  try {
4988
- deps.pi.sendMessage({
5064
+ (deps.parentWake ?? deps.pi).sendMessage({
4989
5065
  customType: "subagent-workflow-result-write-failed",
4990
5066
  content: message,
4991
5067
  display: true,
@@ -5529,6 +5605,16 @@ export function createSubagentExecutor(deps) {
5529
5605
  const journalFingerprint = workflowJournalFingerprints.get(notification.childKey);
5530
5606
  if (journalFingerprint)
5531
5607
  appendWorkflowChildJournal(asyncDir, { type: "settle", key: notification.childKey, fingerprint: journalFingerprint, result: notification.result });
5608
+ // Fresh, journal-reused and re-attached children all settle here, so their rows take usage from the same result.
5609
+ const settledResults = notification.result.results ?? [];
5610
+ const settledStep = settledResults.length ? status.steps?.find((candidate) => candidate.workflowKey === notification.childKey) : undefined;
5611
+ if (settledStep) {
5612
+ const usage = sumResultsUsage(settledResults);
5613
+ settledStep.tokens = { input: usage.input, output: usage.output, total: usage.input + usage.output };
5614
+ if (usage.turns > 0)
5615
+ settledStep.turnCount = usage.turns;
5616
+ persist({ tolerateStatusWriteFailure: true });
5617
+ }
5532
5618
  appendWorkflowEvent({
5533
5619
  type: "subagent.workflow.child_settled",
5534
5620
  childKey: notification.childKey,
@@ -5537,10 +5623,12 @@ export function createSubagentExecutor(deps) {
5537
5623
  workflowRunning: notification.workflowRunning,
5538
5624
  });
5539
5625
  try {
5540
- deps.pi.sendMessage({
5626
+ (deps.parentWake ?? deps.pi).sendMessage({
5541
5627
  customType: "subagent-incremental-child-notify",
5542
5628
  content: formatIncrementalChildCompletion(notification),
5543
5629
  display: notification.outcome !== "completed",
5630
+ // Render-only: Pi does not send details to the model.
5631
+ details: { childKey: notification.childKey, outcome: notification.outcome },
5544
5632
  }, { triggerTurn: incrementalChildCompletionTriggersTurn(notification, requestParams.scheduleOrigin) });
5545
5633
  }
5546
5634
  catch (sendError) {
@@ -5600,7 +5688,7 @@ export function createSubagentExecutor(deps) {
5600
5688
  appendWorkflowChildJournal(asyncDir, { type: "start", key, fingerprint: journalFingerprint, runId: priorRunId });
5601
5689
  const stopListener = stopAwaitedAsyncChildOnAbort(workflowSignal, deps.state, priorRunId, priorAsyncDir, deps.kill);
5602
5690
  const reattached = await awaitExistingAsyncRun(priorAsyncDir, priorRunId, workflowSignal).finally(stopListener.remove);
5603
- const claimedPath = reattached.status === "settled" ? claimWorkflowAwaitedResult(priorAsyncDir, workflowRunId) : undefined;
5691
+ const claimedPath = reattached.status === "settled" ? claimWorkflowAwaitedResult(priorAsyncDir, workflowRunId, { ...reattached.result.importedPublication, runId: priorRunId }) : undefined;
5604
5692
  if (reattached.status === "settled" && claimedPath) {
5605
5693
  const imported = importWorkflowAwaitedChildResult(reattached.result, { runId: priorRunId, asyncDir: priorAsyncDir, resultPath: claimedPath, task: typeof childParams.task === "string" ? childParams.task : "", parentWorkflowRunId: workflowReuseSource?.runId, details: { mode: "single", asyncId: priorRunId, asyncDir: priorAsyncDir, results: [] }, state: deps.state, pi: deps.pi });
5606
5694
  const reattachedResult = workflowChildResult(key, imported, childParams, deps.state);
@@ -6408,7 +6496,10 @@ export function createSubagentExecutor(deps) {
6408
6496
  if (action === "guide") {
6409
6497
  try {
6410
6498
  const guide = readSubagentGuide(paramsWithResolvedCwd.topic);
6411
- const notice = paramsWithResolvedCwd.topic === "tool-reference" ? disabledFeatureNotice(disabledFeatures) : undefined;
6499
+ const [topic, section] = paramsWithResolvedCwd.topic?.split("/") ?? [];
6500
+ const fullNotice = topic === "tool-reference" ? disabledFeatureNotice(disabledFeatures) : undefined;
6501
+ // Sections are capped at 8,000 chars, so they get a one-line pointer instead of the full list.
6502
+ const notice = fullNotice && section !== undefined ? "Some options and actions documented here are disabled by config in this session; guide topic tool-reference lists them." : fullNotice;
6412
6503
  return {
6413
6504
  content: [{ type: "text", text: notice ? `${notice}\n\n${guide}` : guide }],
6414
6505
  details: { mode: "management", results: [] },
@@ -6433,10 +6524,13 @@ export function createSubagentExecutor(deps) {
6433
6524
  if (action === "doctor") {
6434
6525
  let currentSessionFile = null;
6435
6526
  let currentSessionId = deps.state.currentSessionId;
6527
+ let budgetSessionId = deps.state.currentSessionId;
6436
6528
  let sessionError;
6437
6529
  try {
6438
6530
  currentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
6439
6531
  currentSessionId = ctx.sessionManager.getSessionId();
6532
+ // Launch admission's key (resolveCurrentSessionId) without its throw for an unbound session.
6533
+ budgetSessionId = currentSessionFile ?? currentSessionId;
6440
6534
  }
6441
6535
  catch (error) {
6442
6536
  sessionError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
@@ -6449,9 +6543,9 @@ export function createSubagentExecutor(deps) {
6449
6543
  if (!sessionError)
6450
6544
  sessionError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
6451
6545
  }
6452
- const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, currentSessionId);
6453
- const activeAsyncCapacity = currentSessionId
6454
- ? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })
6546
+ const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, budgetSessionId);
6547
+ const activeAsyncCapacity = budgetSessionId
6548
+ ? getActiveAsyncCapacitySnapshot(budgetSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []), abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })
6455
6549
  : { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession) ?? 0 };
6456
6550
  deps.state.activeAsyncCapacity = activeAsyncCapacity;
6457
6551
  return {
@@ -6465,6 +6559,8 @@ export function createSubagentExecutor(deps) {
6465
6559
  requestedSessionDir: paramsWithResolvedCwd.sessionDir,
6466
6560
  currentSessionFile,
6467
6561
  currentSessionId,
6562
+ spawnBudget,
6563
+ activeAsyncCapacity,
6468
6564
  orchestratorTarget,
6469
6565
  sessionError,
6470
6566
  expandTilde: deps.expandTilde,
@@ -6965,16 +7061,28 @@ export function createSubagentExecutor(deps) {
6965
7061
  : hasTasks
6966
7062
  ? (effectiveParams.tasks ?? []).map((task) => task.agent)
6967
7063
  : (effectiveParams.chain ?? []).flatMap((step) => getStepAgents(step));
6968
- const externalAgent = selectedAgentNames
6969
- .map((name) => agents.find((agent) => agent.name === name))
6970
- .find((agent) => agent?.runner?.type === "external-cli" || agent?.runner?.type === "external-job");
6971
- const externalAsyncRequired = Boolean(externalAgent) && effectiveParams.async === undefined && effectiveParams.clarify !== true && effectiveParams.foregroundOnly !== true;
7064
+ const selectedAgents = selectedAgentNames.map((name) => agents.find((agent) => agent.name === name));
7065
+ const externalAgent = selectedAgents.find((agent) => agent?.runner?.type === "external-cli" || agent?.runner?.type === "external-job");
7066
+ const launcherAgent = selectedAgents.find((agent) => agent?.launcher !== undefined);
7067
+ // A launcher agent goes to the background whenever the caller omitted async, even if the agent file defaults to foreground.
7068
+ const externalAsyncRequired = effectiveParams.foregroundOnly !== true && ((Boolean(externalAgent) && effectiveParams.async === undefined && effectiveParams.clarify !== true)
7069
+ || (Boolean(launcherAgent) && normalizedParams.async === undefined && normalizedParams.clarify !== true));
6972
7070
  const requestedAsync = externalAsyncRequired ? true : effectiveParams.async ?? deps.asyncByDefault;
6973
7071
  const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
6974
7072
  const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
6975
7073
  if (externalAgent && (!effectiveAsync || effectiveParams.foregroundOnly === true)) {
6976
7074
  return buildRequestedModeError(effectiveParams, `Agent '${externalAgent.name}' uses runner.type='${externalAgent.runner?.type}', which currently supports async/background execution only. Omit async or pass async:true; clarify and foregroundOnly are unsupported.`);
6977
7075
  }
7076
+ // forceTopLevelAsync rewrites async:false and clarify, so check what the caller asked for.
7077
+ if (launcherAgent && (!effectiveAsync || normalizedParams.async === false || normalizedParams.clarify === true || effectiveParams.foregroundOnly === true)) {
7078
+ return buildRequestedModeError(effectiveParams, `Agent '${launcherAgent.name}' uses launcher '${launcherAgent.launcher}', which wraps the background runner only. Foreground children run inside the parent process, so a launcher cannot wrap them. Omit async or pass async:true; clarify and foregroundOnly are unsupported.`);
7079
+ }
7080
+ // Refuse a launcher launch that cannot start before fan-out admission or a session directory exists.
7081
+ for (const agent of selectedAgents) {
7082
+ const launcherError = agent ? resolveAgentRunnerLauncher(agent, deps.config.runnerLaunchers, effectiveParams.machine ?? agent.machine).error : undefined;
7083
+ if (launcherError)
7084
+ return buildRequestedModeError(effectiveParams, launcherError);
7085
+ }
6978
7086
  if (effectiveAsync && hasSingle && effectiveParams.resume === undefined && effectiveParams.worktree === true) {
6979
7087
  try {
6980
7088
  await preflightWorktreeSource(effectiveCwd, { signal });
@@ -7168,6 +7276,7 @@ export function createSubagentExecutor(deps) {
7168
7276
  backgroundRequestedWhileClarifying,
7169
7277
  effectiveAsync,
7170
7278
  asyncRunId,
7279
+ toolCallId: _id,
7171
7280
  controlConfig,
7172
7281
  ...(delegatedExecution ? { suppressUnchangedDelegationUpdates: true } : {}),
7173
7282
  intercomBridge,