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
@@ -18,12 +18,13 @@ import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLau
18
18
  import { assertClaudeCodeModelScope, isClaudeCodeAdapterId, resolveClaudeCodeOverride } from "../shared/claude-code-adapter.js";
19
19
  import { injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.js";
20
20
  import { applyWatchdogLaunchRules, sendRuleViolationWarning } from "../../watchdog/rules.js";
21
- import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveExistingReadInstructionPaths, resolveExistingReadPaths, writeInitialProgressFile } from "../../shared/settings.js";
21
+ import { buildChainInstructions, getStepAgents, isDynamicParallelStep, isParallelStep, resolveExistingReadInstructionPaths, resolveExistingReadPaths, writeInitialProgressFile } from "../../shared/settings.js";
22
22
  import { isDynamicRunnerGroup, isParallelGroup } from "../shared/parallel-utils.js";
23
23
  import { PI_CODING_AGENT_PACKAGE, resolveBunPiExecutable, resolveInstalledPiPackageRoot, resolvePiPackageRoot } from "../shared/pi-spawn.js";
24
24
  import { JITI_ALIAS_ENV, resolveHostPeerAliases } from "./runner-aliases.js";
25
25
  import { preflightLaunchCwd } from "../shared/launch-cwd.js";
26
26
  import { resolveNodeExecutable } from "../../shared/node-executable.js";
27
+ import { resolveRuntimeModulePath } from "../../shared/runtime-module-path.js";
27
28
  import { backgroundProcessOptions } from "../shared/background-process-options.js";
28
29
  import { normalizeSkillInput, resolveSkillsWithFallback } from "../../agents/skills.js";
29
30
  import { PI_CODING_AGENT_PACKAGE_ROOT_ENV, PROMPT_REDACTED, resolveChildCwd } from "../../shared/utils.js";
@@ -51,6 +52,8 @@ import { finalizeProcessTerminal, initializeProcessTerminal, readProcessTerminal
51
52
  import { persistRunnerStartupFailure } from "./runner-startup-failure.js";
52
53
  import { statusStepDescription } from "./chain-append.js";
53
54
  import { currentPidNamespaceScope } from "./pid-namespace.js";
55
+ import { checkPidLiveness } from "./stale-run-reconciler.js";
56
+ import { lookupRunnerLauncher, resolveAgentRunnerLauncher, runnerLauncherPlacementError } from "../shared/runner-launcher.js";
54
57
  import { SUBAGENT_PROCESS_TERMINAL_EVENT } from "../../shared/types.js";
55
58
  import { assertAgentAllowedByCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling } from "../shared/capability-ceiling.js";
56
59
  import { resolveLaunchBinding } from "../../shared/launch-contract.js";
@@ -122,28 +125,29 @@ function resolveJitiCliPath() {
122
125
  return undefined;
123
126
  }
124
127
  const jitiCliPath = resolveJitiCliPath();
125
- const asyncRunnerSourcePath = path.join(path.dirname(fileURLToPath(import.meta.url)), `subagent-runner-bootstrap${path.extname(fileURLToPath(import.meta.url))}`);
126
- const sourceUnderNodeModules = asyncRunnerSourcePath.split(path.sep).some((segment) => segment.toLowerCase() === "node_modules");
128
+ const asyncRunnerModuleDir = path.dirname(fileURLToPath(import.meta.url));
129
+ // Resolved per launch: an in-place package update can swap a sibling's .ts for .js.
130
+ function asyncRunnerSourcePath() {
131
+ return resolveRuntimeModulePath(asyncRunnerModuleDir, "subagent-runner-bootstrap");
132
+ }
133
+ function sourceUnderNodeModules() {
134
+ return asyncRunnerSourcePath().split(path.sep).some((segment) => segment.toLowerCase() === "node_modules");
135
+ }
127
136
  function supportsNativeRunner(nodeExecutable) {
128
137
  return Boolean(process.features.typescript)
129
138
  && typeof nodeModule.registerHooks === "function"
130
139
  && nodeExecutable === process.execPath
131
- && !sourceUnderNodeModules;
140
+ && !sourceUnderNodeModules();
132
141
  }
133
142
  export const DEFAULT_ASYNC_TIMEOUT_MS = 30 * 60 * 1000;
134
143
  export function formatAsyncStartedMessage(headline, interactive) {
135
144
  const guidance = interactive
136
145
  ? [
137
- "The async run is detached and running in the background.",
138
- "You are in an interactive session. Return control to the user now; Pi will wake you through the native completion notification when this subagent completes or needs attention. Do not run sleep/polling loops to wait for this async subagent; it does not need a wait call.",
139
- "Use bg_wait only for provider, detached, or other background work that lacks a native completion notification.",
140
- "If the current turn must receive results from work without a native notification before it ends, call blocking bg_wait(); ordinary async subagent runs do not need a wait call because their completion is delivered natively.",
141
- "Otherwise, continue any independent work or return control to the user. Use subagent({ action: \"status\", id: \"...\" }) for a one-shot status/result or to inspect a blocked/stale run, never as a wait loop.",
146
+ "Detached. Return control now: native completion wakes you, so do not sleep, poll, or call bg_wait for it.",
147
+ "One-shot check: subagent({ action: \"status\", id: \"...\" }).",
142
148
  ]
143
149
  : [
144
- "The async run is detached. Do not run sleep timers or polling loops just to wait for it.",
145
- "This is a non-interactive run: Pi auto-drains current-session subagent work at agent_end so detached children are not abandoned. Use bg_wait only when this turn must receive provider, detached, or other background-work results that have no native completion notification.",
146
- "Use subagent({ action: \"status\", id: \"...\" }) when you need a one-shot status/result or to inspect a blocked/stale run; do not poll in a loop.",
150
+ "Detached non-interactive run: Pi auto-drains current-session subagent work at agent_end. Do not run sleep timers or polling loops. Use bg_wait only for results this turn needs from work without native notification, and subagent({ action: \"status\", id: \"...\" }) for a one-shot check.",
147
151
  ];
148
152
  return [headline, "", ...guidance].join("\n");
149
153
  }
@@ -191,7 +195,7 @@ function readRunnerStartup(startupPath, expectedState, expectedToken) {
191
195
  if (typeof payload.token !== "string" || (expectedToken !== undefined && payload.token !== expectedToken)) {
192
196
  return { ok: false, error: `Async runner wrote an invalid ${expectedState} startup handshake: ${startupPath}`, startupDidNotProceed: true };
193
197
  }
194
- return { ok: true, token: payload.token };
198
+ return { ok: true, token: payload.token, pid: payload.pid, pidNamespaceScope: payload.pidNamespaceScope };
195
199
  }
196
200
  catch (error) {
197
201
  return { ok: false, error: `Failed to read async runner startup handshake '${startupPath}': ${error instanceof Error ? error.message : String(error)}`, startupDidNotProceed: true };
@@ -276,7 +280,7 @@ function terminateRunnerBeforeProceed(pid) {
276
280
  }
277
281
  return !runnerIsAlive(pid);
278
282
  }
279
- async function terminateRunnerBeforeProceedAsync(proc, processClosed) {
283
+ async function terminateRunnerBeforeProceedAsyncDefault(proc, processClosed) {
280
284
  for (const signal of ["SIGTERM", "SIGKILL"]) {
281
285
  if (proc.pid === undefined || !runnerIsAlive(proc.pid))
282
286
  return true;
@@ -293,9 +297,17 @@ async function terminateRunnerBeforeProceedAsync(proc, processClosed) {
293
297
  }
294
298
  return proc.pid === undefined || !runnerIsAlive(proc.pid);
295
299
  }
296
- async function completeRunnerStartupHandshake(startupPath, startupAckPath, startupConfirmPath, startupProceedPath, proc, processClosed, getObservedProcessExit, runnerProcessInstanceId, persistStartupFailure) {
300
+ async function completeRunnerStartupHandshake(startupPath, startupAckPath, startupConfirmPath, startupProceedPath, proc, processClosed, getObservedProcessExit, runnerProcessInstanceId, persistStartupFailure,
301
+ // Behind a launcher the spawned pid is only the wrapper's: adopt the runner-reported pid and never signal the wrapper.
302
+ launcherIdentity) {
303
+ const terminateRunnerBeforeProceedAsync = async (proc, processClosed) => launcherIdentity ? launcherIdentity.terminate() : terminateRunnerBeforeProceedAsyncDefault(proc, processClosed);
297
304
  try {
298
305
  const ready = await waitForRunnerStartup(startupPath, "ready", RUNNER_STARTUP_TIMEOUT_MS, undefined, processClosed);
306
+ const identityError = ready.ok && launcherIdentity ? launcherIdentity.adopt(ready.pid, ready.pidNamespaceScope) : undefined;
307
+ if (identityError) {
308
+ persistStartupFailure(identityError);
309
+ return { runnerProcessInstanceId, error: identityError, terminationObserved: await terminateRunnerBeforeProceedAsync(proc, processClosed), startupDidNotProceed: true };
310
+ }
299
311
  if (ready.ok === false) {
300
312
  persistStartupFailure(ready.error);
301
313
  const terminationObserved = await terminateRunnerBeforeProceedAsync(proc, processClosed);
@@ -401,13 +413,13 @@ function spawnRunner(cfg, suffix, cwd, initialStatus, initialStatusPath, launchP
401
413
  const binaryHost = resolveBunPiExecutable();
402
414
  const nodeExecutable = resolveNodeExecutable();
403
415
  const nativeRunnerSupported = supportsNativeRunner(nodeExecutable);
404
- const runner = asyncRunnerSourcePath;
416
+ const runner = asyncRunnerSourcePath();
405
417
  const runnerIsJavaScript = path.extname(runner) === ".js";
406
- const bootstrap = path.join(path.dirname(runner), `binary-bootstrap${path.extname(fileURLToPath(import.meta.url))}`);
418
+ const bootstrap = resolveRuntimeModulePath(path.dirname(runner), "binary-bootstrap");
407
419
  if (binaryHost && !fs.existsSync(bootstrap))
408
420
  return { error: `Background runner bootstrap not found: ${bootstrap}` };
409
421
  if (!binaryHost && !runnerIsJavaScript && !nativeRunnerSupported && !jitiCliPath) {
410
- return { error: sourceUnderNodeModules
422
+ return { error: sourceUnderNodeModules()
411
423
  ? "Background runner source is installed under node_modules, so upstream jiti is required for TypeScript execution."
412
424
  : "Background runner requires enabled native TypeScript with synchronous module hooks, or installed upstream jiti for TypeScript execution." };
413
425
  }
@@ -447,6 +459,30 @@ function spawnRunner(cfg, suffix, cwd, initialStatus, initialStatusPath, launchP
447
459
  fs.rmSync(startupConfirmPath, { force: true });
448
460
  if (startupProceedPath)
449
461
  fs.rmSync(startupProceedPath, { force: true });
462
+ // A broker launcher may start the runner outside its own process, so the spawned pid is only the
463
+ // wrapper's. The runner reports its own identity before it is allowed to proceed.
464
+ const launcher = launchConfig.launcher;
465
+ const identityPath = launcher && launchBarrierToken && launchAsyncDir ? path.join(launchAsyncDir, "runner-startup.json") : undefined;
466
+ if (identityPath)
467
+ fs.rmSync(identityPath, { force: true });
468
+ let runnerIdentity;
469
+ // Wrapper exit is not runner exit, so only a reported runner in this namespace may be signalled.
470
+ const terminateBeforeProceed = (pid) => !launcher
471
+ ? terminateRunnerBeforeProceed(pid)
472
+ : runnerIdentity !== undefined && runnerIdentity.pidNamespaceScope === currentPidNamespaceScope() && terminateRunnerBeforeProceed(runnerIdentity.pid);
473
+ const adoptRunnerIdentity = (pid, pidNamespaceScope) => {
474
+ if (typeof pid !== "number" || !Number.isSafeInteger(pid) || pid < 1)
475
+ return `Async runner reported an invalid process identity in '${launchAsyncDir}'.`;
476
+ runnerIdentity = { pid, ...(typeof pidNamespaceScope === "string" ? { pidNamespaceScope } : {}) };
477
+ try {
478
+ const status = JSON.parse(fs.readFileSync(initialStatusPath, "utf-8"));
479
+ writePrivateAtomicJson(initialStatusPath, { ...status, pid, ...(runnerIdentity.pidNamespaceScope !== undefined ? { pidNamespaceScope: runnerIdentity.pidNamespaceScope } : {}) });
480
+ return undefined;
481
+ }
482
+ catch (error) {
483
+ return `Failed to record async runner identity: ${error instanceof Error ? error.message : String(error)}`;
484
+ }
485
+ };
450
486
  const logPaths = resolveAsyncRunnerLogPaths(launchConfig);
451
487
  let stdoutFd;
452
488
  let stderrFd;
@@ -481,7 +517,8 @@ function spawnRunner(cfg, suffix, cwd, initialStatus, initialStatusPath, launchP
481
517
  delete runnerEnv[SUBAGENT_PARENT_SESSION_ENV];
482
518
  else
483
519
  runnerEnv[SUBAGENT_PARENT_SESSION_ENV] = launchParentSessionId;
484
- const proc = spawn(command, args, {
520
+ const [spawnCommand, ...spawnArgs] = launcher ? [...launcher.argv, command, ...args] : [command, ...args];
521
+ const proc = spawn(spawnCommand, spawnArgs, {
485
522
  cwd,
486
523
  ...backgroundProcessOptions(),
487
524
  stdio: ["ignore", stdoutFd ?? "ignore", stderrFd ?? "ignore"],
@@ -507,6 +544,18 @@ function spawnRunner(cfg, suffix, cwd, initialStatus, initialStatusPath, launchP
507
544
  const runId = launch.id;
508
545
  if (typeof asyncDir !== "string" || typeof runId !== "string")
509
546
  return;
547
+ if (launcher) {
548
+ try {
549
+ writePrivateAtomicJson(path.join(asyncDir, "launcher-close.json"), { version: 1, runId, runnerProcessInstanceId, closeObservedAt: Date.now(), exitCode, signal, ...(runnerIdentity ? { runnerPid: runnerIdentity.pid } : {}) });
550
+ }
551
+ catch {
552
+ // Diagnostic only; process-terminal proof stays authoritative.
553
+ }
554
+ // The runner may outlive its wrapper; only a confirmed-dead runner in this pid namespace finalizes here,
555
+ // otherwise status-pid reconciliation does it later.
556
+ if (!runnerIdentity || runnerIdentity.pidNamespaceScope !== currentPidNamespaceScope() || checkPidLiveness(runnerIdentity.pid, process.kill, runnerIdentity.pidNamespaceScope !== undefined) !== "dead")
557
+ return;
558
+ }
510
559
  finalizeProcessTerminal(asyncDir, runId, {
511
560
  processInstanceId: runnerProcessInstanceId,
512
561
  closeObservedAt: Date.now(),
@@ -566,8 +615,7 @@ function spawnRunner(cfg, suffix, cwd, initialStatus, initialStatusPath, launchP
566
615
  try {
567
616
  writePrivateAtomicJson(initialStatusPath, {
568
617
  ...initialStatus,
569
- pid: proc.pid,
570
- pidNamespaceScope: currentPidNamespaceScope(),
618
+ ...(launcher ? {} : { pid: proc.pid, pidNamespaceScope: currentPidNamespaceScope() }),
571
619
  processTerminal: { version: 1, state: "pending", runId: initialStatus.runId, runnerProcessInstanceId },
572
620
  });
573
621
  // Aggregate waits must see the launch before the runner's first status update.
@@ -577,7 +625,7 @@ function spawnRunner(cfg, suffix, cwd, initialStatus, initialStatusPath, launchP
577
625
  const message = `Failed to persist initial async status: ${error instanceof Error ? error.message : String(error)}`;
578
626
  if (launchAsyncDir)
579
627
  persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
580
- const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
628
+ const terminationObserved = terminateBeforeProceed(proc.pid);
581
629
  return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
582
630
  }
583
631
  try {
@@ -589,7 +637,7 @@ function spawnRunner(cfg, suffix, cwd, initialStatus, initialStatusPath, launchP
589
637
  const message = `Failed to establish async runner lifecycle sidecar: ${error instanceof Error ? error.message : String(error)}`;
590
638
  if (launchAsyncDir)
591
639
  persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
592
- const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
640
+ const terminationObserved = terminateBeforeProceed(proc.pid);
593
641
  return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
594
642
  }
595
643
  try {
@@ -599,9 +647,31 @@ function spawnRunner(cfg, suffix, cwd, initialStatus, initialStatusPath, launchP
599
647
  const message = `Failed to establish async runner capacity ownership: ${error instanceof Error ? error.message : String(error)}`;
600
648
  if (launchAsyncDir)
601
649
  persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
602
- const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
650
+ const terminationObserved = terminateBeforeProceed(proc.pid);
603
651
  return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
604
652
  }
653
+ if (identityPath && launchBarrierToken && startupProceedPath) {
654
+ proc.unref();
655
+ const fail = (message) => {
656
+ if (launchAsyncDir)
657
+ persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
658
+ return { ...(runnerIdentity ? { pid: runnerIdentity.pid } : {}), runnerProcessInstanceId, error: message, terminationObserved: terminateBeforeProceed(proc.pid), startupDidNotProceed: true };
659
+ };
660
+ return waitForRunnerStartup(identityPath, "identified", RUNNER_STARTUP_TIMEOUT_MS, launchBarrierToken, processClosed).then((identified) => {
661
+ if (identified.ok === false)
662
+ return fail(identified.error);
663
+ const identityError = adoptRunnerIdentity(identified.pid, identified.pidNamespaceScope);
664
+ if (identityError)
665
+ return fail(identityError);
666
+ try {
667
+ writeRunnerStartupControl(startupProceedPath, { action: "proceed", token: launchBarrierToken });
668
+ }
669
+ catch (error) {
670
+ return fail(`Failed to authorize async runner startup: ${error instanceof Error ? error.message : String(error)}`);
671
+ }
672
+ return { pid: identified.pid, runnerProcessInstanceId };
673
+ });
674
+ }
605
675
  if (launchBarrierToken && startupProceedPath) {
606
676
  try {
607
677
  writeRunnerStartupControl(startupProceedPath, { action: "proceed", token: launchBarrierToken });
@@ -610,7 +680,7 @@ function spawnRunner(cfg, suffix, cwd, initialStatus, initialStatusPath, launchP
610
680
  const message = `Failed to authorize async runner startup: ${error instanceof Error ? error.message : String(error)}`;
611
681
  if (launchAsyncDir)
612
682
  persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
613
- const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
683
+ const terminationObserved = terminateBeforeProceed(proc.pid);
614
684
  return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
615
685
  }
616
686
  }
@@ -620,7 +690,10 @@ function spawnRunner(cfg, suffix, cwd, initialStatus, initialStatusPath, launchP
620
690
  if (launchAsyncDir)
621
691
  persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
622
692
  };
623
- return completeRunnerStartupHandshake(startupPath, startupAckPath, startupConfirmPath, startupProceedPath, proc, processClosed, () => observedProcessExit ?? (proc.exitCode !== null || proc.signalCode !== null ? { exitCode: proc.exitCode, signal: proc.signalCode } : undefined), runnerProcessInstanceId, persistStartupFailure);
693
+ const handshake = completeRunnerStartupHandshake(startupPath, startupAckPath, startupConfirmPath, startupProceedPath, proc, processClosed, () => observedProcessExit ?? (proc.exitCode !== null || proc.signalCode !== null ? { exitCode: proc.exitCode, signal: proc.signalCode } : undefined), runnerProcessInstanceId, persistStartupFailure, launcher ? { adopt: adoptRunnerIdentity, terminate: () => terminateBeforeProceed(proc.pid) } : undefined);
694
+ if (!launcher)
695
+ return handshake;
696
+ return handshake.then(({ pid: _wrapperPid, ...result }) => ({ ...result, ...(runnerIdentity ? { pid: runnerIdentity.pid } : {}) }));
624
697
  }
625
698
  return { pid: proc.pid, runnerProcessInstanceId };
626
699
  }
@@ -660,6 +733,14 @@ class AsyncStartValidationError extends Error {
660
733
  }
661
734
  export function buildAsyncRunnerSteps(id, params) {
662
735
  const { chain, agents, ctx, cwd, sessionFilesByFlatIndex, thinkingOverridesByFlatIndex, maxSubagentDepth, worktreeBaseDir, worktreeProvider, worktreeBranchPrefix, asyncDir, } = params;
736
+ // One runner hosts every new step, so they share one launcher or none. Covers initial chains and
737
+ // appends to a running runner; the attached root runs in its own runner.
738
+ const stepLaunchers = [...new Set(chain.flatMap(getStepAgents))].map((name) => ({ name, launcher: agents.find((agent) => agent.name === name)?.launcher }));
739
+ const launcherNames = new Set(stepLaunchers.map(({ launcher }) => launcher));
740
+ if (launcherNames.size > 1) {
741
+ return { error: `Every agent in one background runner must use the same launcher or none: ${stepLaunchers.map(({ name, launcher }) => `${name} (${launcher ?? "none"})`).join(", ")}.` };
742
+ }
743
+ const launcher = [...launcherNames][0];
663
744
  const launchParentSessionId = ctx.parentSessionId ?? ctx.currentSessionId;
664
745
  const outputBaseDir = params.outputBaseDir;
665
746
  const resultMode = params.resultMode ?? "chain";
@@ -742,6 +823,9 @@ export function buildAsyncRunnerSteps(id, params) {
742
823
  const machineUnsupported = formatHerdrMachineRunnerUnsupported({ machine: requestedMachine, agentName: a.name, runnerType: a.runner?.type, adapter: a.runner?.type === "external-cli" ? a.runner.adapter : undefined, worktree: s.worktree });
743
824
  if (machineUnsupported)
744
825
  throw new AsyncStartValidationError(machineUnsupported);
826
+ const launcherError = runnerLauncherPlacementError(a, requestedMachine);
827
+ if (launcherError)
828
+ throw new AsyncStartValidationError(launcherError);
745
829
  const registeredExtensions = resolveRequiredChildExtensions(ctx.parentSessionId ?? ctx.currentSessionId ?? undefined);
746
830
  try {
747
831
  assertRequiredChildExtensionsAdmitted([params.requiredExtensions, ctx.childRuntime?.requiredExtensions, registeredExtensions], { agent: a.name, runnerType: a.runner?.type, machine: requestedMachine });
@@ -917,6 +1001,7 @@ export function buildAsyncRunnerSteps(id, params) {
917
1001
  }
918
1002
  return {
919
1003
  parentSessionId: launchParentSessionId,
1004
+ ...(ctx.parentSessionFile ? { parentSessionFile: ctx.parentSessionFile } : {}),
920
1005
  permissionRules,
921
1006
  ...(params.capabilityCeiling ? { capabilityCeiling: params.capabilityCeiling } : {}),
922
1007
  ...(runFanoutPath ? { runFanoutPath } : {}),
@@ -1092,6 +1177,7 @@ export function buildAsyncRunnerSteps(id, params) {
1092
1177
  const steps = params.attachRoot
1093
1178
  ? [{
1094
1179
  parentSessionId: launchParentSessionId,
1180
+ ...(ctx.parentSessionFile ? { parentSessionFile: ctx.parentSessionFile } : {}),
1095
1181
  agent: params.attachRoot.agent,
1096
1182
  task: "",
1097
1183
  label: params.attachRoot.label ?? `Attached root ${params.attachRoot.runId}`,
@@ -1122,7 +1208,7 @@ export function buildAsyncRunnerSteps(id, params) {
1122
1208
  seen.set(task.outputPath, { index, agent: task.agent });
1123
1209
  }
1124
1210
  }
1125
- return { steps: steps, runnerCwd, workflowGraph, eventChain: graphChain, ...(originalTask !== undefined ? { originalTask } : {}) };
1211
+ return { steps: steps, runnerCwd, workflowGraph, eventChain: graphChain, ...(originalTask !== undefined ? { originalTask } : {}), ...(launcher !== undefined ? { launcher } : {}) };
1126
1212
  }
1127
1213
  catch (error) {
1128
1214
  if (error instanceof UnavailableSubagentSkillError || error instanceof AsyncStartValidationError)
@@ -1141,6 +1227,11 @@ export function executeAsyncChain(id, params) {
1141
1227
  });
1142
1228
  if (acceptanceErrors.length > 0)
1143
1229
  return formatAsyncStartError(resultMode, acceptanceErrors.join(" "));
1230
+ // Refuse an undefined launcher before the run directory exists and before step building can write progress files.
1231
+ const undefinedLauncher = chain.flatMap(getStepAgents).map((name) => agents.find((agent) => agent.name === name)?.launcher)
1232
+ .find((name) => name !== undefined && !lookupRunnerLauncher(ctx.runnerLaunchers, name));
1233
+ if (undefinedLauncher !== undefined)
1234
+ return formatAsyncStartError(resultMode, `Launcher '${undefinedLauncher}' used by this ${resultMode} is not defined in runnerLaunchers in the user subagent config.`);
1144
1235
  const capabilityCeiling = params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId);
1145
1236
  const inheritedNestedRoute = inheritedNestedRouteOf(ctx.childRuntime);
1146
1237
  const nestedAddress = inheritedNestedRoute ? inheritedNestedParentAddressOf(ctx.childRuntime) : undefined;
@@ -1208,6 +1299,7 @@ export function executeAsyncChain(id, params) {
1208
1299
  return formatAsyncStartError(resultMode, built.error);
1209
1300
  }
1210
1301
  const { steps, runnerCwd, workflowGraph, eventChain } = built;
1302
+ const launcher = built.launcher === undefined ? undefined : lookupRunnerLauncher(ctx.runnerLaunchers, built.launcher);
1211
1303
  const deadlineAt = params.timeoutMs !== undefined ? Date.now() + params.timeoutMs : undefined;
1212
1304
  const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
1213
1305
  let childTargetIndex = 0;
@@ -1250,9 +1342,9 @@ export function executeAsyncChain(id, params) {
1250
1342
  fs.rmSync(asyncDir, { recursive: true, force: true });
1251
1343
  return formatAsyncStartError(resultMode, `Failed to record required child extensions: ${error instanceof Error ? error.message : String(error)}`);
1252
1344
  }
1253
- let spawnResult = {};
1345
+ let spawnResultOrPromise = {};
1254
1346
  try {
1255
- spawnResult = spawnRunner({
1347
+ spawnResultOrPromise = spawnRunner({
1256
1348
  id,
1257
1349
  steps,
1258
1350
  resultPath: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : resultFilePath(DIRS.results, id),
@@ -1298,6 +1390,7 @@ export function executeAsyncChain(id, params) {
1298
1390
  depth: nestedAddress.depth,
1299
1391
  path: nestedAddress.path,
1300
1392
  } : undefined,
1393
+ ...(launcher ? { launcher } : {}),
1301
1394
  }, id, runnerCwd, {
1302
1395
  lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
1303
1396
  runId: id,
@@ -1310,6 +1403,7 @@ export function executeAsyncChain(id, params) {
1310
1403
  currentStep: 0,
1311
1404
  chainStepCount: eventChain.length,
1312
1405
  ...(initialParallelGroups.length ? { parallelGroups: initialParallelGroups } : {}),
1406
+ ...(launcher ? { launcher } : {}),
1313
1407
  steps: initialStatusSteps,
1314
1408
  }, path.join(asyncDir, "status.json"), launchParentSessionId, (proof) => emitProcessTerminalEvent(ctx, proof), (runnerProcessInstanceId) => params.activeAsyncCapacity?.markStarted(runnerProcessInstanceId));
1315
1409
  }
@@ -1318,127 +1412,131 @@ export function executeAsyncChain(id, params) {
1318
1412
  const message = error instanceof Error ? error.message : String(error);
1319
1413
  return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': ${message}`);
1320
1414
  }
1321
- if (spawnResult.error) {
1322
- if (spawnResult.startupDidNotProceed) {
1323
- if (!spawnResult.runnerProcessInstanceId || params.activeAsyncCapacity?.rollbackBeforeRunnerProceed(spawnResult.runnerProcessInstanceId) !== true)
1415
+ // A launcher run's spawn resolves once the runner reports its identity.
1416
+ const finishSpawnResult = (spawnResult) => {
1417
+ if (spawnResult.error) {
1418
+ if (spawnResult.startupDidNotProceed) {
1419
+ if (!spawnResult.runnerProcessInstanceId || params.activeAsyncCapacity?.rollbackBeforeRunnerProceed(spawnResult.runnerProcessInstanceId) !== true)
1420
+ params.activeAsyncCapacity?.rollback();
1421
+ }
1422
+ else if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId)
1324
1423
  params.activeAsyncCapacity?.rollback();
1424
+ else
1425
+ params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
1426
+ return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': ${spawnResult.error}`);
1325
1427
  }
1326
- else if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId)
1428
+ if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId) {
1327
1429
  params.activeAsyncCapacity?.rollback();
1328
- else
1329
- params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
1330
- return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': ${spawnResult.error}`);
1331
- }
1332
- if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId) {
1333
- params.activeAsyncCapacity?.rollback();
1334
- return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': runner identity unavailable`);
1335
- }
1336
- if (spawnResult.pid) {
1337
- const eventFirstStep = eventChain[0];
1338
- if (!eventFirstStep) {
1339
- return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': event chain has no steps`);
1340
- }
1341
- const firstAgents = isParallelStep(eventFirstStep)
1342
- ? eventFirstStep.parallel.map((t) => t.agent)
1343
- : isDynamicParallelStep(eventFirstStep)
1344
- ? [eventFirstStep.parallel.agent]
1345
- : [eventFirstStep.agent];
1346
- const firstTask = isParallelStep(eventFirstStep)
1347
- ? eventFirstStep.parallel[0]?.task
1348
- : isDynamicParallelStep(eventFirstStep)
1349
- ? eventFirstStep.parallel.task
1350
- : eventFirstStep.task;
1351
- const workflowGoal = params.goal ?? (params.task?.trim() || firstTask);
1352
- const parallelGroups = [];
1353
- const flatAgents = [];
1354
- let flatStepStart = 0;
1355
- for (let stepIndex = 0; stepIndex < eventChain.length; stepIndex++) {
1356
- const step = eventChain[stepIndex];
1357
- if (isParallelStep(step)) {
1358
- parallelGroups.push({ start: flatStepStart, count: step.parallel.length, stepIndex });
1359
- flatAgents.push(...step.parallel.map((task) => task.agent));
1360
- flatStepStart += step.parallel.length;
1361
- }
1362
- else if (isDynamicParallelStep(step)) {
1363
- parallelGroups.push({ start: flatStepStart, count: 1, stepIndex });
1364
- flatAgents.push(step.parallel.agent);
1365
- flatStepStart++;
1430
+ return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': runner identity unavailable`);
1431
+ }
1432
+ if (spawnResult.pid) {
1433
+ const eventFirstStep = eventChain[0];
1434
+ if (!eventFirstStep) {
1435
+ return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': event chain has no steps`);
1366
1436
  }
1367
- else {
1368
- flatAgents.push(step.agent);
1369
- flatStepStart++;
1437
+ const firstAgents = isParallelStep(eventFirstStep)
1438
+ ? eventFirstStep.parallel.map((t) => t.agent)
1439
+ : isDynamicParallelStep(eventFirstStep)
1440
+ ? [eventFirstStep.parallel.agent]
1441
+ : [eventFirstStep.agent];
1442
+ const firstTask = isParallelStep(eventFirstStep)
1443
+ ? eventFirstStep.parallel[0]?.task
1444
+ : isDynamicParallelStep(eventFirstStep)
1445
+ ? eventFirstStep.parallel.task
1446
+ : eventFirstStep.task;
1447
+ const workflowGoal = params.goal ?? (params.task?.trim() || firstTask);
1448
+ const parallelGroups = [];
1449
+ const flatAgents = [];
1450
+ let flatStepStart = 0;
1451
+ for (let stepIndex = 0; stepIndex < eventChain.length; stepIndex++) {
1452
+ const step = eventChain[stepIndex];
1453
+ if (isParallelStep(step)) {
1454
+ parallelGroups.push({ start: flatStepStart, count: step.parallel.length, stepIndex });
1455
+ flatAgents.push(...step.parallel.map((task) => task.agent));
1456
+ flatStepStart += step.parallel.length;
1457
+ }
1458
+ else if (isDynamicParallelStep(step)) {
1459
+ parallelGroups.push({ start: flatStepStart, count: 1, stepIndex });
1460
+ flatAgents.push(step.parallel.agent);
1461
+ flatStepStart++;
1462
+ }
1463
+ else {
1464
+ flatAgents.push(step.agent);
1465
+ flatStepStart++;
1466
+ }
1370
1467
  }
1371
- }
1372
- if (inheritedNestedRoute && nestedAddress) {
1373
- const now = Date.now();
1374
- try {
1375
- writeNestedEvent(inheritedNestedRoute, {
1376
- type: "subagent.nested.started",
1377
- ts: now,
1378
- parentRunId: nestedAddress.parentRunId,
1379
- parentStepIndex: nestedAddress.parentStepIndex,
1380
- child: {
1381
- id,
1468
+ if (inheritedNestedRoute && nestedAddress) {
1469
+ const now = Date.now();
1470
+ try {
1471
+ writeNestedEvent(inheritedNestedRoute, {
1472
+ type: "subagent.nested.started",
1473
+ ts: now,
1382
1474
  parentRunId: nestedAddress.parentRunId,
1383
1475
  parentStepIndex: nestedAddress.parentStepIndex,
1384
- depth: nestedAddress.depth,
1385
- path: nestedAddress.path,
1386
- asyncDir,
1387
- pid: spawnResult.pid,
1388
- ownerIntercomTarget: ctx.childRuntime?.intercomSessionName,
1389
- leafIntercomTarget: childIntercomTargets?.[0],
1390
- intercomTarget: childIntercomTargets?.[0],
1391
- ownerState: "live",
1392
- mode: resultMode,
1393
- state: "running",
1394
- agent: firstAgents[0],
1395
- agents: flatAgents,
1396
- chainStepCount: eventChain.length,
1397
- parallelGroups,
1398
- ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}),
1399
- startedAt: now,
1400
- lastUpdate: now,
1401
- ...(capabilityCeiling ? { capabilityCeiling } : {}),
1402
- },
1403
- });
1404
- }
1405
- catch (error) {
1406
- console.error("Failed to emit nested async start event:", error);
1476
+ child: {
1477
+ id,
1478
+ parentRunId: nestedAddress.parentRunId,
1479
+ parentStepIndex: nestedAddress.parentStepIndex,
1480
+ depth: nestedAddress.depth,
1481
+ path: nestedAddress.path,
1482
+ asyncDir,
1483
+ pid: spawnResult.pid,
1484
+ ownerIntercomTarget: ctx.childRuntime?.intercomSessionName,
1485
+ leafIntercomTarget: childIntercomTargets?.[0],
1486
+ intercomTarget: childIntercomTargets?.[0],
1487
+ ownerState: "live",
1488
+ mode: resultMode,
1489
+ state: "running",
1490
+ agent: firstAgents[0],
1491
+ agents: flatAgents,
1492
+ chainStepCount: eventChain.length,
1493
+ parallelGroups,
1494
+ ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}),
1495
+ startedAt: now,
1496
+ lastUpdate: now,
1497
+ ...(capabilityCeiling ? { capabilityCeiling } : {}),
1498
+ },
1499
+ });
1500
+ }
1501
+ catch (error) {
1502
+ console.error("Failed to emit nested async start event:", error);
1503
+ }
1407
1504
  }
1505
+ ctx.pi.events.emit(SUBAGENT_ASYNC_STARTED_EVENT, {
1506
+ lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
1507
+ id,
1508
+ pid: spawnResult.pid,
1509
+ sessionId: ctx.currentSessionId,
1510
+ completionOwnerId: ctx.completionOwnerId ?? currentCompletionOwnerId(),
1511
+ mode: resultMode,
1512
+ agent: firstAgents[0],
1513
+ agents: flatAgents,
1514
+ task: firstTask?.trim() ? PROMPT_REDACTED : undefined,
1515
+ goal: workflowGoal?.trim() ? PROMPT_REDACTED : undefined,
1516
+ chain: eventChain.map((s) => isParallelStep(s) ? `[${s.parallel.map((t) => t.agent).join("+")}]` : isDynamicParallelStep(s) ? `expand:${s.parallel.agent}` : s.agent),
1517
+ chainStepCount: eventChain.length,
1518
+ parallelGroups,
1519
+ workflowGraph,
1520
+ cwd: runnerCwd,
1521
+ asyncDir,
1522
+ ...(sessionRoot ? { sessionRoot } : {}),
1523
+ ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}),
1524
+ ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}),
1525
+ ...(capabilityCeiling ? { capabilityCeiling } : {}),
1526
+ ...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}),
1527
+ ...(params.workflowKey ? { workflowKey: params.workflowKey } : {}),
1528
+ nestedRoute,
1529
+ });
1408
1530
  }
1409
- ctx.pi.events.emit(SUBAGENT_ASYNC_STARTED_EVENT, {
1410
- lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
1411
- id,
1412
- pid: spawnResult.pid,
1413
- sessionId: ctx.currentSessionId,
1414
- completionOwnerId: ctx.completionOwnerId ?? currentCompletionOwnerId(),
1415
- mode: resultMode,
1416
- agent: firstAgents[0],
1417
- agents: flatAgents,
1418
- task: firstTask?.trim() ? PROMPT_REDACTED : undefined,
1419
- goal: workflowGoal?.trim() ? PROMPT_REDACTED : undefined,
1420
- chain: eventChain.map((s) => isParallelStep(s) ? `[${s.parallel.map((t) => t.agent).join("+")}]` : isDynamicParallelStep(s) ? `expand:${s.parallel.agent}` : s.agent),
1421
- chainStepCount: eventChain.length,
1422
- parallelGroups,
1423
- workflowGraph,
1424
- cwd: runnerCwd,
1425
- asyncDir,
1426
- ...(sessionRoot ? { sessionRoot } : {}),
1427
- ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}),
1428
- ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}),
1429
- ...(capabilityCeiling ? { capabilityCeiling } : {}),
1430
- ...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}),
1431
- ...(params.workflowKey ? { workflowKey: params.workflowKey } : {}),
1432
- nestedRoute,
1433
- });
1434
- }
1435
- const chainDesc = chain
1436
- .map((s) => isParallelStep(s) ? `[${s.parallel.map((t) => t.agent).join("+")}]` : isDynamicParallelStep(s) ? `expand:${s.parallel.agent}` : s.agent)
1437
- .join(" -> ");
1438
- return {
1439
- content: [{ type: "text", text: formatAsyncStartedMessage(`Async ${resultMode}: ${chainDesc} [${id}]`, ctx.interactive === true) }],
1440
- details: { mode: resultMode, runId: id, results: [], asyncId: id, asyncDir, workflowGraph, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}), ...(params.workflowKey ? { workflowKey: params.workflowKey } : {}), ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}), ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}) },
1531
+ const chainDesc = chain
1532
+ .map((s) => isParallelStep(s) ? `[${s.parallel.map((t) => t.agent).join("+")}]` : isDynamicParallelStep(s) ? `expand:${s.parallel.agent}` : s.agent)
1533
+ .join(" -> ");
1534
+ return {
1535
+ content: [{ type: "text", text: formatAsyncStartedMessage(`Async ${resultMode}: ${chainDesc} [${id}]`, ctx.interactive === true) }],
1536
+ details: { mode: resultMode, runId: id, results: [], asyncId: id, asyncDir, workflowGraph, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.parentWorkflowRunId ? { parentWorkflowRunId: params.parentWorkflowRunId } : {}), ...(params.workflowKey ? { workflowKey: params.workflowKey } : {}), ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}), ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}) },
1537
+ };
1441
1538
  };
1539
+ return spawnResultOrPromise instanceof Promise ? spawnResultOrPromise.then(finishSpawnResult) : finishSpawnResult(spawnResultOrPromise);
1442
1540
  }
1443
1541
  /**
1444
1542
  * Execute a single agent asynchronously
@@ -1510,6 +1608,10 @@ export function executeAsyncSingle(id, params) {
1510
1608
  const machineUnsupported = formatHerdrMachineRunnerUnsupported({ machine: requestedMachine, agentName: agentConfig.name, runnerType: agentConfig.runner?.type, adapter: agentConfig.runner?.type === "external-cli" ? agentConfig.runner.adapter : undefined, worktree: params.worktree });
1511
1609
  if (machineUnsupported)
1512
1610
  return formatAsyncStartError("single", machineUnsupported);
1611
+ const resolvedLauncher = resolveAgentRunnerLauncher(agentConfig, ctx.runnerLaunchers, requestedMachine);
1612
+ if (resolvedLauncher.error)
1613
+ return formatAsyncStartError("single", resolvedLauncher.error);
1614
+ const launcher = resolvedLauncher.launcher;
1513
1615
  const registeredExtensions = resolveRequiredChildExtensions(ctx.parentSessionId ?? ctx.currentSessionId ?? undefined);
1514
1616
  try {
1515
1617
  assertRequiredChildExtensionsAdmitted([params.requiredExtensions, ctx.childRuntime?.requiredExtensions, registeredExtensions], { agent: agentConfig.name, runnerType: agentConfig.runner?.type, machine: requestedMachine });
@@ -1774,6 +1876,7 @@ export function executeAsyncSingle(id, params) {
1774
1876
  ...(params.context ? { context: params.context } : {}),
1775
1877
  ...(params.intercomBridge !== undefined ? { intercomBridge: params.intercomBridge } : {}),
1776
1878
  ...(params.baseRef !== undefined ? { baseRef: params.baseRef } : {}),
1879
+ ...(launcher ? { launcher: launcher.name } : {}),
1777
1880
  ...(deadlineAt !== undefined ? { absoluteDeadlineAt: deadlineAt } : {}),
1778
1881
  ...(resolvedToolBudget.budget ? { initialToolBudget: resolvedToolBudget.budget } : {}),
1779
1882
  maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, recoveryAgentConfig.maxSubagentDepth),
@@ -1799,9 +1902,11 @@ export function executeAsyncSingle(id, params) {
1799
1902
  try {
1800
1903
  spawnResultOrPromise = spawnRunner({
1801
1904
  id,
1905
+ ...(params.toolCallId ? { toolCallId: params.toolCallId } : {}),
1802
1906
  steps: [
1803
1907
  {
1804
1908
  parentSessionId: launchParentSessionId,
1909
+ ...(ctx.parentSessionFile ? { parentSessionFile: ctx.parentSessionFile } : {}),
1805
1910
  permissionRules,
1806
1911
  ...(capabilityCeiling ? { capabilityCeiling } : {}),
1807
1912
  agent,
@@ -1904,6 +2009,7 @@ export function executeAsyncSingle(id, params) {
1904
2009
  ...(lane ? { lane } : {}),
1905
2010
  ...(params.revivalLease ? { revivalLease: params.revivalLease } : {}),
1906
2011
  nestedRoute: nestedRoute ?? inheritedNestedRoute,
2012
+ ...(launcher ? { launcher } : {}),
1907
2013
  nestedSelf: inheritedNestedRoute && nestedAddress ? {
1908
2014
  parentRunId: nestedAddress.parentRunId,
1909
2015
  parentStepIndex: nestedAddress.parentStepIndex,
@@ -1916,12 +2022,14 @@ export function executeAsyncSingle(id, params) {
1916
2022
  ...(ctx.currentSessionId ? { sessionId: ctx.currentSessionId } : {}),
1917
2023
  ...(initialCompletionOwnerId ? { completionOwnerId: initialCompletionOwnerId } : {}),
1918
2024
  mode: "single",
2025
+ ...(params.toolCallId ? { toolCallId: params.toolCallId } : {}),
1919
2026
  state: "running",
1920
2027
  startedAt: initialStatusAt,
1921
2028
  lastUpdate: initialStatusAt,
1922
2029
  currentStep: 0,
1923
2030
  chainStepCount: 1,
1924
2031
  ...(lane ? { lane } : {}),
2032
+ ...(launcher ? { launcher } : {}),
1925
2033
  steps: [{ agent, status: "pending", ...(lane ? { lane } : {}), ...(model ? { model } : {}), ...(contextLimit !== undefined ? { contextLimit } : {}) }],
1926
2034
  }, path.join(asyncDir, "status.json"), launchParentSessionId, (proof) => emitProcessTerminalEvent(ctx, proof), (runnerProcessInstanceId) => params.activeAsyncCapacity?.markStarted(runnerProcessInstanceId), params.requestedCwd ?? runnerCwd);
1927
2035
  }
@@ -1990,6 +2098,7 @@ export function executeAsyncSingle(id, params) {
1990
2098
  sessionId: ctx.currentSessionId,
1991
2099
  completionOwnerId: ctx.completionOwnerId ?? currentCompletionOwnerId(),
1992
2100
  mode: "single",
2101
+ ...(params.toolCallId ? { toolCallId: params.toolCallId } : {}),
1993
2102
  agent,
1994
2103
  task: task?.trim() ? PROMPT_REDACTED : undefined,
1995
2104
  goal: (params.goal ?? task).trim() ? PROMPT_REDACTED : undefined,
@@ -2008,7 +2117,7 @@ export function executeAsyncSingle(id, params) {
2008
2117
  }
2009
2118
  return {
2010
2119
  content: [{ type: "text", text: formatAsyncStartedMessage(`Async: ${agent} [${id}]`, ctx.interactive === true) }],
2011
- details: { mode: "single", runId: id, results: [], asyncId: id, asyncDir, launchContractDigest, launchResolvedExtensions, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.context ? { context: params.context } : {}), ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}), ...(params.toolBudget ? { toolBudget: resolvedToolBudget.budget ?? params.toolBudget } : {}), ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}) },
2120
+ details: { mode: "single", runId: id, ...(params.toolCallId ? { toolCallId: params.toolCallId } : {}), results: [], asyncId: id, asyncDir, launchContractDigest, launchResolvedExtensions, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.context ? { context: params.context } : {}), ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}), ...(params.toolBudget ? { toolBudget: resolvedToolBudget.budget ?? params.toolBudget } : {}), ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}) },
2012
2121
  };
2013
2122
  };
2014
2123
  return spawnResultOrPromise instanceof Promise ? spawnResultOrPromise.then(finishSpawnResult) : finishSpawnResult(spawnResultOrPromise);