@selesai/code 0.5.19 → 0.5.21

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 (265) hide show
  1. package/dist/extensions/handoff-new.test.ts +8 -9
  2. package/dist/extensions/handoff-new.ts +6 -8
  3. package/dist/extensions/pi-intercom/broker/spawn.test.ts +31 -0
  4. package/dist/extensions/pi-intercom/broker/spawn.ts +5 -3
  5. package/dist/extensions/pi-subagents/CHANGELOG.md +1367 -0
  6. package/dist/extensions/pi-subagents/README.md +602 -220
  7. package/dist/extensions/pi-subagents/banner.png +0 -0
  8. package/dist/extensions/pi-subagents/index.ts +1 -0
  9. package/dist/extensions/pi-subagents/install.mjs +3 -3
  10. package/dist/extensions/pi-subagents/package-lock.json +3405 -0
  11. package/dist/extensions/pi-subagents/package.json +26 -14
  12. package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
  13. package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
  14. package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +3 -3
  15. package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +7 -7
  16. package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
  17. package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
  18. package/dist/extensions/pi-subagents/prompts/review-loop.md +13 -11
  19. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +267 -176
  20. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +89 -16
  21. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +19 -0
  22. package/dist/extensions/pi-subagents/src/agents/agents.ts +218 -120
  23. package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +67 -13
  24. package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
  25. package/dist/extensions/pi-subagents/src/agents/skills.ts +25 -12
  26. package/dist/extensions/pi-subagents/src/api/background-work.ts +197 -0
  27. package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +17 -0
  28. package/dist/extensions/pi-subagents/src/api/delegation.ts +285 -0
  29. package/dist/extensions/pi-subagents/src/api/preflight.ts +399 -0
  30. package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +165 -0
  31. package/dist/extensions/pi-subagents/src/extension/config.ts +7 -1
  32. package/dist/extensions/pi-subagents/src/extension/doctor.ts +15 -0
  33. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -1
  34. package/dist/extensions/pi-subagents/src/extension/index.ts +113 -157
  35. package/dist/extensions/pi-subagents/src/extension/rpc.ts +41 -4
  36. package/dist/extensions/pi-subagents/src/extension/schemas.ts +45 -15
  37. package/dist/extensions/pi-subagents/src/extension/steering-notices.ts +35 -0
  38. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -15
  39. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -3
  40. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +225 -29
  41. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +11 -0
  42. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -10
  43. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +452 -62
  44. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +60 -9
  45. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +198 -52
  46. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +119 -21
  47. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +67 -0
  48. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +2 -0
  49. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +16 -8
  50. package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +6 -4
  51. package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +2 -11
  52. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +260 -13
  53. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +32 -6
  54. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +171 -90
  55. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +280 -0
  56. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +149 -88
  57. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +14 -2
  58. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +33 -17
  59. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +9 -1
  60. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +38 -10
  61. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +237 -0
  62. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +1297 -307
  63. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +610 -0
  64. package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +2 -1
  65. package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +36 -0
  66. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +26 -0
  67. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +230 -0
  68. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +22 -6
  69. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +228 -140
  70. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +452 -138
  71. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +90 -0
  72. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1001 -416
  73. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +443 -132
  74. package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +38 -0
  75. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +178 -0
  76. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +121 -0
  77. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +41 -93
  78. package/dist/extensions/pi-subagents/src/runs/shared/context-mode.ts +44 -0
  79. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +11 -9
  80. package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +4 -0
  81. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
  82. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +36 -0
  83. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +44 -7
  84. package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +4 -1
  85. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +154 -0
  86. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +18 -0
  87. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +174 -54
  88. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +38 -31
  89. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +90 -5
  90. package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +299 -0
  91. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +61 -6
  92. package/dist/extensions/pi-subagents/src/runs/shared/spawn-budget.ts +128 -0
  93. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +112 -7
  94. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -6
  95. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +149 -36
  96. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +181 -0
  97. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +83 -0
  98. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +11 -5
  99. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +50 -4
  100. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +63 -14
  101. package/dist/extensions/pi-subagents/src/shared/accessible-dir.ts +25 -0
  102. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +37 -7
  103. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +18 -43
  104. package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +52 -0
  105. package/dist/extensions/pi-subagents/src/shared/env.ts +16 -0
  106. package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +47 -0
  107. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +28 -3
  108. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +123 -0
  109. package/dist/extensions/pi-subagents/src/shared/model-info.ts +8 -5
  110. package/dist/extensions/pi-subagents/src/shared/settings.ts +9 -1
  111. package/dist/extensions/pi-subagents/src/shared/status-format.ts +7 -1
  112. package/dist/extensions/pi-subagents/src/shared/types.ts +522 -51
  113. package/dist/extensions/pi-subagents/src/shared/utils.ts +59 -53
  114. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +604 -0
  115. package/dist/extensions/pi-subagents/src/slash/delegation-json.ts +108 -0
  116. package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +249 -0
  117. package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +353 -345
  118. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +2 -2
  119. package/dist/extensions/pi-subagents/src/slash/selector.ts +147 -0
  120. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +265 -23
  121. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +2 -2
  122. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +428 -0
  123. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +362 -0
  124. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +472 -0
  125. package/dist/extensions/pi-subagents/src/tui/fleet.ts +664 -0
  126. package/dist/extensions/pi-subagents/src/tui/render.ts +115 -31
  127. package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +220 -0
  128. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +205 -0
  129. package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +123 -0
  130. package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +532 -0
  131. package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +167 -0
  132. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +117 -0
  133. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +433 -0
  134. package/dist/extensions/pi-subagents/src/watchdog/render.ts +54 -0
  135. package/dist/extensions/pi-subagents/src/watchdog/review.ts +298 -0
  136. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +714 -0
  137. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +528 -0
  138. package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +155 -0
  139. package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +161 -0
  140. package/dist/extensions/pi-subagents/src/watchdog/types.ts +188 -0
  141. package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +73 -0
  142. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +130 -2
  143. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +439 -0
  144. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1632 -61
  145. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +54 -1
  146. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +112 -0
  147. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +98 -1
  148. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +179 -9
  149. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +16 -9
  150. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +6 -5
  151. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +589 -9
  152. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +528 -21
  153. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +227 -3
  154. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +235 -7
  155. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +27 -0
  156. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +172 -2
  157. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1502 -18
  158. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +993 -4
  159. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +27 -0
  160. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +7 -1
  161. package/dist/extensions/pi-subagents/test/support/helpers.ts +32 -0
  162. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +59 -2
  163. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +32 -6
  164. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +25 -24
  165. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +59 -35
  166. package/dist/extensions/pi-subagents/test/support/session-lease-child.mjs +42 -0
  167. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +703 -69
  168. package/dist/extensions/pi-subagents/test/unit/accessible-dir.test.ts +82 -0
  169. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
  170. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
  171. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +478 -19
  172. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +228 -7
  173. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +1 -1
  174. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +198 -78
  175. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +15 -1
  176. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +102 -7
  177. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +439 -6
  178. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +41 -1
  179. package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +79 -0
  180. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +285 -0
  181. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +84 -0
  182. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +68 -0
  183. package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +326 -0
  184. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +82 -0
  185. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +46 -4
  186. package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +3 -1
  187. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +9 -17
  188. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +175 -3
  189. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +10 -5
  190. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +126 -6
  191. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +219 -0
  192. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +913 -0
  193. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +13 -2
  194. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -3
  195. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +351 -0
  196. package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +280 -0
  197. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +575 -0
  198. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +65 -0
  199. package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +94 -1
  200. package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +23 -0
  201. package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +157 -0
  202. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +367 -5
  203. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +11 -0
  204. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +36 -0
  205. package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +25 -3
  206. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +1 -1
  207. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +341 -10
  208. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +16 -0
  209. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +52 -0
  210. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +168 -7
  211. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +85 -7
  212. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +175 -0
  213. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +1 -1
  214. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +243 -16
  215. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +79 -36
  216. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +174 -56
  217. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +322 -0
  218. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +204 -0
  219. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +20 -12
  220. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +117 -4
  221. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +40 -7
  222. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +48 -1
  223. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +21 -3
  224. package/dist/extensions/pi-subagents/test/unit/selector.test.ts +57 -0
  225. package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +326 -0
  226. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +101 -2
  227. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +111 -1
  228. package/dist/extensions/pi-subagents/test/unit/spawn-budget.test.ts +124 -0
  229. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +6 -4
  230. package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +7 -0
  231. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +397 -0
  232. package/dist/extensions/pi-subagents/test/unit/steering-notices.test.ts +58 -0
  233. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +173 -0
  234. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +25 -4
  235. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +390 -25
  236. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +837 -0
  237. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +86 -0
  238. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +15 -0
  239. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +47 -1
  240. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +56 -10
  241. package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +387 -0
  242. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +110 -0
  243. package/dist/extensions/pi-subagents/test/unit/watchdog-emission-guard.test.ts +62 -0
  244. package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +144 -0
  245. package/dist/extensions/pi-subagents/test/unit/watchdog-model-selection.test.ts +121 -0
  246. package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +76 -0
  247. package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +327 -0
  248. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +863 -0
  249. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +290 -0
  250. package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +91 -0
  251. package/dist/extensions/pi-subagents/test/unit/watchdog-turn-delta.test.ts +130 -0
  252. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +32 -4
  253. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +5 -1
  254. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +30 -0
  255. package/dist/extensions/question/batch.ts +39 -0
  256. package/dist/extensions/question/dialog-adapter.ts +23 -1
  257. package/dist/extensions/question/index.ts +237 -7
  258. package/dist/extensions/question/schemas.ts +28 -15
  259. package/dist/extensions/question/tests/batch.test.ts +26 -0
  260. package/dist/extensions/question/tests/ui-protocol.test.ts +23 -3
  261. package/dist/extensions/question/tui-adapter.ts +2 -3
  262. package/dist/extensions/question/types.ts +37 -0
  263. package/dist/extensions/question/ui-protocol.ts +3 -3
  264. package/dist/skills/batch-grill-me/SKILL.md +3 -1
  265. package/package.json +2 -1
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { spawn } from "node:child_process";
6
+ import { randomUUID } from "node:crypto";
6
7
  import * as fs from "node:fs";
7
8
  import * as os from "node:os";
8
9
  import * as path from "node:path";
@@ -10,15 +11,17 @@ import { fileURLToPath } from "node:url";
10
11
  import { createRequire } from "node:module";
11
12
  import type { ExtensionAPI } from "@selesai/code";
12
13
  import type { AgentConfig } from "../../agents/agents.ts";
13
- import { applyThinkingSuffix } from "../shared/pi-args.ts";
14
+ import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
15
+ import { applyThinkingSuffix, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
14
16
  import { injectOutputPathSystemPrompt, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
15
17
  import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveStepBehavior, suppressProgressForReadOnlyTask, writeInitialProgressFile, type ChainStep, type ResolvedStepBehavior, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
16
18
  import type { RunnerStep } from "../shared/parallel-utils.ts";
17
- import { resolveSelesaiPackageRoot } from "../shared/pi-spawn.ts";
19
+ import type { ContextMode } from "../shared/context-mode.ts";
20
+ import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
18
21
  import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../../agents/skills.ts";
19
22
  import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
20
23
  import { SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV, resolveChildCwd } from "../../shared/utils.ts";
21
- import { buildModelCandidates, resolveModelCandidate, resolveSubagentModelOverride, type AvailableModelInfo, type ParentModel } from "../shared/model-fallback.ts";
24
+ import { buildModelCandidates, resolveEffectiveSubagentModel, resolveModelCandidate, resolveSubagentModelOverride, type AvailableModelInfo, type ParentModel } from "../shared/model-fallback.ts";
22
25
  import type { ModelScopeConfig } from "../shared/model-scope.ts";
23
26
  import { resolveEffectiveThinking } from "../../shared/model-info.ts";
24
27
  import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
@@ -28,14 +31,17 @@ import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
28
31
  import { resolveEffectiveAcceptance } from "../shared/acceptance.ts";
29
32
  import {
30
33
  type AcceptanceInput,
34
+ type AgentContract,
31
35
  type ArtifactConfig,
32
36
  type Details,
37
+ type JsonSchemaObject,
33
38
  type MaxOutputConfig,
34
39
  type NestedRouteInfo,
35
40
  type ResolvedControlConfig,
36
41
  type ResolvedTurnBudget,
37
42
  type ResolvedToolBudget,
38
43
  type SubagentRunMode,
44
+ type SteeringRecoveryDescriptor,
39
45
  ASYNC_DIR,
40
46
  RESULTS_DIR,
41
47
  SUBAGENT_ASYNC_STARTED_EVENT,
@@ -44,13 +50,18 @@ import {
44
50
  getAsyncConfigPath,
45
51
  resolveChildMaxSubagentDepth,
46
52
  } from "../../shared/types.ts";
47
- import { nestedResultsPath, resolveInheritedNestedRouteFromEnv, resolveNestedParentAddressFromEnv, writeNestedEvent } from "../shared/nested-events.ts";
48
- import { initialTurnBudgetState } from "../shared/turn-budget.ts";
53
+ import { nestedResultsPath, nestedSummaryFromAsyncStatus, resolveInheritedNestedRouteFromEnv, resolveNestedParentAddressFromEnv, writeNestedEvent } from "../shared/nested-events.ts";
54
+ import { appendTurnBudgetSystemPrompt, initialTurnBudgetState } from "../shared/turn-budget.ts";
49
55
  import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
50
56
  import type { ImportedAsyncRoot } from "./chain-root-attachment.ts";
57
+ import type { SessionLeaseRequest } from "../shared/session-lease.ts";
58
+ import { finalizeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
59
+ import { SUBAGENT_PROCESS_TERMINAL_EVENT } from "../../shared/types.ts";
60
+ import { decodeSubagentCapabilityCeiling, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
61
+ import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
51
62
 
52
63
  const require = createRequire(import.meta.url);
53
- const selesaiPackageRoot = resolveSelesaiPackageRoot();
64
+ const piPackageRoot = resolvePiPackageRoot();
54
65
 
55
66
  function resolveJitiCliFromPackageJson(packageJsonPath: string): string | undefined {
56
67
  if (!fs.existsSync(packageJsonPath)) return undefined;
@@ -73,15 +84,15 @@ function resolveJitiCliFromPackageJson(packageJsonPath: string): string | undefi
73
84
  function resolveJitiCliPath(): string | undefined {
74
85
  const candidates: Array<() => string | undefined> = [
75
86
  () => require.resolve("jiti/package.json"),
76
- () => selesaiPackageRoot
77
- ? createRequire(path.join(selesaiPackageRoot, "package.json")).resolve("jiti/package.json")
87
+ () => piPackageRoot
88
+ ? createRequire(path.join(piPackageRoot, "package.json")).resolve("jiti/package.json")
78
89
  : undefined,
79
90
  () => {
80
91
  if (!process.argv[1]) return undefined;
81
92
  const piEntry = fs.realpathSync(process.argv[1]);
82
93
  return createRequire(piEntry).resolve("jiti/package.json");
83
94
  },
84
- () => selesaiPackageRoot ? path.join(selesaiPackageRoot, "node_modules", "jiti", "package.json") : undefined,
95
+ () => piPackageRoot ? path.join(piPackageRoot, "node_modules", "jiti", "package.json") : undefined,
85
96
  ];
86
97
  for (const candidate of candidates) {
87
98
  try {
@@ -108,11 +119,15 @@ interface AsyncExecutionContext {
108
119
  currentModel?: ParentModel;
109
120
  /** Optional model-scope enforcement resolved from subagent settings. */
110
121
  modelScope?: ModelScopeConfig;
122
+ /** Whether the parent session has an interactive UI. */
123
+ interactive?: boolean;
111
124
  }
112
125
 
113
126
  interface AsyncChainParams {
114
127
  chain: ChainStep[];
115
128
  task?: string;
129
+ /** Raw caller-facing goal used only by the started event. */
130
+ goal?: string;
116
131
  attachRoot?: ImportedAsyncRoot & { agent: string; outputName?: string; label?: string };
117
132
  resultMode?: Exclude<SubagentRunMode, "single">;
118
133
  agents: AgentConfig[];
@@ -124,12 +139,15 @@ interface AsyncChainParams {
124
139
  artifactConfig: ArtifactConfig;
125
140
  shareEnabled: boolean;
126
141
  sessionRoot?: string;
142
+ agentContract?: AgentContract;
127
143
  chainSkills?: string[];
128
144
  sessionFilesByFlatIndex?: (string | undefined)[];
129
145
  thinkingOverridesByFlatIndex?: (AgentConfig["thinking"] | undefined)[];
146
+ contextForAgent?: (agentName: string) => ContextMode;
130
147
  progressDir?: string;
131
148
  dynamicFanoutMaxItems?: number;
132
149
  maxSubagentDepth: number;
150
+ waitToolEnabled?: boolean;
133
151
  worktreeSetupHook?: string;
134
152
  worktreeSetupHookTimeoutMs?: number;
135
153
  worktreeBaseDir?: string;
@@ -144,11 +162,14 @@ interface AsyncChainParams {
144
162
  configToolBudget?: ResolvedToolBudget;
145
163
  /** Global cap on simultaneously-running subagent tasks within the async run. */
146
164
  globalConcurrencyLimit?: number;
165
+ capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
147
166
  }
148
167
 
149
168
  interface AsyncSingleParams {
150
169
  agent: string;
151
170
  task?: string;
171
+ /** Raw caller-facing goal used only by the started event. */
172
+ goal?: string;
152
173
  agentConfig: AgentConfig;
153
174
  ctx: AsyncExecutionContext;
154
175
  cwd?: string;
@@ -157,15 +178,21 @@ interface AsyncSingleParams {
157
178
  artifactConfig: ArtifactConfig;
158
179
  shareEnabled: boolean;
159
180
  sessionRoot?: string;
181
+ sessionDir?: string;
160
182
  sessionFile?: string;
183
+ revivalLease?: SessionLeaseRequest;
184
+ context?: ContextMode;
161
185
  skills?: string[];
162
186
  output?: string | boolean;
163
187
  outputMode?: "inline" | "file-only";
164
188
  outputBaseDir?: string;
189
+ agentContract?: AgentContract;
190
+ structuredOutputSchema?: JsonSchemaObject;
165
191
  modelOverride?: string;
166
192
  thinkingOverride?: AgentConfig["thinking"];
167
193
  availableModels?: AvailableModelInfo[];
168
194
  maxSubagentDepth: number;
195
+ waitToolEnabled?: boolean;
169
196
  worktreeSetupHook?: string;
170
197
  worktreeSetupHookTimeoutMs?: number;
171
198
  worktreeBaseDir?: string;
@@ -175,9 +202,11 @@ interface AsyncSingleParams {
175
202
  nestedRoute?: NestedRouteInfo;
176
203
  acceptance?: AcceptanceInput;
177
204
  timeoutMs?: number;
205
+ absoluteDeadlineAt?: number;
178
206
  turnBudget?: ResolvedTurnBudget;
179
207
  toolBudget?: ResolvedToolBudget;
180
208
  configToolBudget?: ResolvedToolBudget;
209
+ capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
181
210
  }
182
211
 
183
212
  interface AsyncExecutionResult {
@@ -198,15 +227,19 @@ export interface AsyncRunnerStepBuildParams {
198
227
  chainSkills?: string[];
199
228
  sessionFilesByFlatIndex?: (string | undefined)[];
200
229
  thinkingOverridesByFlatIndex?: (AgentConfig["thinking"] | undefined)[];
230
+ contextForAgent?: (agentName: string) => ContextMode;
201
231
  progressDir?: string;
232
+ agentContract?: AgentContract;
202
233
  dynamicFanoutMaxItems?: number;
203
234
  maxSubagentDepth: number;
235
+ waitToolEnabled?: boolean;
204
236
  worktreeBaseDir?: string;
205
237
  asyncDir: string;
206
238
  outputBaseDir?: string;
207
239
  validateOutputBindings?: boolean;
208
240
  toolBudget?: ResolvedToolBudget;
209
241
  configToolBudget?: ResolvedToolBudget;
242
+ capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
210
243
  }
211
244
 
212
245
  export type AsyncRunnerStepBuildResult =
@@ -219,14 +252,20 @@ export type AsyncRunnerStepBuildResult =
219
252
  }
220
253
  | { error: string };
221
254
 
222
- export function formatAsyncStartedMessage(headline: string): string {
223
- return [
224
- headline,
225
- "",
226
- "The async run is detached. Do not run sleep timers or polling loops just to wait for it.",
227
- "If you have independent work, continue that work. When you have nothing left to do until the async result arrives, call wait() — it blocks until the run finishes and delivers the completion here. Only if you are certain you will get another turn (an interactive session where the user will prompt you again) can you instead stop and let Pi wake you; inside a skill that must run to completion, or in a non-interactive run, there is no next turn, so use wait().",
228
- "Use subagent({ action: \"status\", id: \"...\" }) when you need a one-shot status/result or to inspect a blocked/stale run. To block until completion, prefer wait(). Do not poll in a loop just to wait.",
229
- ].join("\n");
255
+ export function formatAsyncStartedMessage(headline: string, interactive: boolean): string {
256
+ const guidance = interactive
257
+ ? [
258
+ "The async run is detached and running in the background.",
259
+ "You are in an interactive session. By default, return control to the user now; Pi will wake you on completion when the run finishes or needs attention. Do NOT call subagent_wait() merely to wait, and do not run sleep/polling loops to wait for it.",
260
+ "Override that default and call subagent_wait() before ending the turn only when the current request is run-to-completion — for example, the user asked you to report results back here before continuing, or a skill must finish in one turn. In that case, call subagent_wait() to block until the run completes so its results are delivered in this turn instead of deferred.",
261
+ "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.",
262
+ ]
263
+ : [
264
+ "The async run is detached. Do not run sleep timers or polling loops just to wait for it.",
265
+ "This is a non-interactive run: Pi auto-drains current-session background work at agent_end so detached children are not abandoned; call subagent_wait() when this turn must receive the run's results before it ends, otherwise let the headless auto-drain finish the work.",
266
+ "Use subagent({ action: \"status\", id: \"...\" }) when you need a one-shot status/result or to inspect a blocked/stale run. To block until completion, use subagent_wait() — do not poll in a loop.",
267
+ ];
268
+ return [headline, "", ...guidance].join("\n");
230
269
  }
231
270
 
232
271
  /**
@@ -280,7 +319,93 @@ function closeFd(fd: number | undefined): void {
280
319
  /**
281
320
  * Spawn the async runner process
282
321
  */
283
- function spawnRunner(cfg: object, suffix: string, cwd: string): { pid?: number; error?: string } {
322
+ const RUNNER_STARTUP_TIMEOUT_MS = 10_000;
323
+ const RUNNER_STARTUP_WAIT_BUFFER = typeof SharedArrayBuffer !== "undefined" ? new SharedArrayBuffer(4) : undefined;
324
+ const RUNNER_STARTUP_WAIT_VIEW = RUNNER_STARTUP_WAIT_BUFFER ? new Int32Array(RUNNER_STARTUP_WAIT_BUFFER) : undefined;
325
+
326
+ type RunnerStartupState = "ready" | "acknowledged";
327
+
328
+ type RunnerStartupWaitResult =
329
+ | { ok: true; token: string }
330
+ | { ok: false; error: string };
331
+
332
+ function waitForStartupInterval(delayMs = 20): void {
333
+ if (RUNNER_STARTUP_WAIT_VIEW) {
334
+ Atomics.wait(RUNNER_STARTUP_WAIT_VIEW, 0, 0, delayMs);
335
+ return;
336
+ }
337
+ const waitUntil = Date.now() + delayMs;
338
+ while (Date.now() < waitUntil) {
339
+ // Startup handshakes are synchronous so resume rejects before reporting a run as started.
340
+ }
341
+ }
342
+
343
+ function readRunnerStartup(startupPath: string, expectedState: RunnerStartupState, expectedToken?: string): RunnerStartupWaitResult | undefined {
344
+ if (!fs.existsSync(startupPath)) return undefined;
345
+ try {
346
+ const payload = JSON.parse(fs.readFileSync(startupPath, "utf-8")) as { state?: unknown; token?: unknown; error?: unknown };
347
+ if (payload.state === "error" && typeof payload.error === "string") return { ok: false, error: payload.error };
348
+ if (payload.state !== expectedState) return undefined;
349
+ if (typeof payload.token !== "string" || (expectedToken !== undefined && payload.token !== expectedToken)) {
350
+ return { ok: false, error: `Async runner wrote an invalid ${expectedState} startup handshake: ${startupPath}` };
351
+ }
352
+ return { ok: true, token: payload.token };
353
+ } catch (error) {
354
+ return { ok: false, error: `Failed to read async runner startup handshake '${startupPath}': ${error instanceof Error ? error.message : String(error)}` };
355
+ }
356
+ }
357
+
358
+ function waitForRunnerStartup(startupPath: string, expectedState: RunnerStartupState, timeoutMs: number, expectedToken?: string): RunnerStartupWaitResult {
359
+ const deadline = Date.now() + timeoutMs;
360
+ for (;;) {
361
+ const result = readRunnerStartup(startupPath, expectedState, expectedToken);
362
+ if (result) return result;
363
+ if (Date.now() >= deadline) break;
364
+ waitForStartupInterval(Math.min(20, Math.max(1, deadline - Date.now())));
365
+ }
366
+ const finalResult = readRunnerStartup(startupPath, expectedState, expectedToken);
367
+ if (finalResult) return finalResult;
368
+ return { ok: false, error: `Timed out after ${timeoutMs}ms waiting for the async runner startup state '${expectedState}'.` };
369
+ }
370
+
371
+ function writeRunnerStartupControl(filePath: string, payload: { action: "ack" | "proceed"; token: string }): void {
372
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
373
+ try {
374
+ fs.writeFileSync(tempPath, JSON.stringify(payload), { encoding: "utf-8", mode: 0o600 });
375
+ fs.renameSync(tempPath, filePath);
376
+ } catch (error) {
377
+ try {
378
+ fs.rmSync(tempPath, { force: true });
379
+ } catch {
380
+ // Preserve the startup-control write error.
381
+ }
382
+ throw error;
383
+ }
384
+ }
385
+
386
+ function runnerIsAlive(pid: number): boolean {
387
+ try {
388
+ process.kill(pid, 0);
389
+ return true;
390
+ } catch (error) {
391
+ return (error as NodeJS.ErrnoException).code === "EPERM";
392
+ }
393
+ }
394
+
395
+ function terminateRunnerBeforeProceed(pid: number): void {
396
+ for (const signal of ["SIGTERM", "SIGKILL"] as const) {
397
+ if (!runnerIsAlive(pid)) return;
398
+ try {
399
+ process.kill(pid, signal);
400
+ } catch {
401
+ if (!runnerIsAlive(pid)) return;
402
+ }
403
+ const deadline = Date.now() + 1000;
404
+ while (runnerIsAlive(pid) && Date.now() < deadline) waitForStartupInterval();
405
+ }
406
+ }
407
+
408
+ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal?: (proof: unknown) => void): { pid?: number; error?: string } {
284
409
  if (!jitiCliPath) {
285
410
  return { error: "upstream jiti for TypeScript execution could not be found; ensure package dependencies are installed" };
286
411
  }
@@ -296,11 +421,22 @@ function spawnRunner(cfg: object, suffix: string, cwd: string): { pid?: number;
296
421
 
297
422
  fs.mkdirSync(TEMP_ROOT_DIR, { recursive: true });
298
423
  const cfgPath = getAsyncConfigPath(suffix);
299
- fs.writeFileSync(cfgPath, JSON.stringify(cfg));
424
+ const runnerProcessInstanceId = randomUUID();
425
+ const launchConfig = { ...cfg, runnerProcessInstanceId };
426
+ fs.writeFileSync(cfgPath, JSON.stringify(launchConfig));
300
427
  const runner = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-runner.ts");
301
428
  const nodeCommand = resolveAsyncRunnerNodeCommand();
429
+ const startupPath = typeof (launchConfig as { revivalLease?: unknown; asyncDir?: unknown }).revivalLease === "object"
430
+ && typeof (launchConfig as { asyncDir?: unknown }).asyncDir === "string"
431
+ ? path.join((launchConfig as { asyncDir: string }).asyncDir, "runner-startup.json")
432
+ : undefined;
433
+ const startupAckPath = startupPath ? path.join(path.dirname(startupPath), "runner-startup-ack.json") : undefined;
434
+ const startupProceedPath = startupPath ? path.join(path.dirname(startupPath), "runner-startup-proceed.json") : undefined;
435
+ if (startupPath) fs.rmSync(startupPath, { force: true });
436
+ if (startupAckPath) fs.rmSync(startupAckPath, { force: true });
437
+ if (startupProceedPath) fs.rmSync(startupProceedPath, { force: true });
302
438
 
303
- const logPaths = resolveAsyncRunnerLogPaths(cfg);
439
+ const logPaths = resolveAsyncRunnerLogPaths(launchConfig);
304
440
  let stdoutFd: number | undefined;
305
441
  let stderrFd: number | undefined;
306
442
  try {
@@ -316,7 +452,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string): { pid?: number;
316
452
  windowsHide: true,
317
453
  env: {
318
454
  ...process.env,
319
- ...(selesaiPackageRoot ? { [SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV]: selesaiPackageRoot } : {}),
455
+ ...(piPackageRoot ? { [SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV]: piPackageRoot } : {}),
320
456
  },
321
457
  });
322
458
  closeFd(stdoutFd);
@@ -324,10 +460,89 @@ function spawnRunner(cfg: object, suffix: string, cwd: string): { pid?: number;
324
460
  proc.on("error", (error) => {
325
461
  console.error(`[pi-subagents] async spawn failed: ${error.message}`);
326
462
  });
463
+ proc.once("close", (exitCode, signal) => {
464
+ const launch = launchConfig as { asyncDir?: unknown; id?: unknown; nestedRoute?: NestedRouteInfo; nestedSelf?: { parentRunId: string; parentStepIndex?: number; depth: number; path?: Array<{ runId: string; stepIndex?: number; agent?: string }> } };
465
+ const asyncDir = launch.asyncDir;
466
+ const runId = launch.id;
467
+ if (typeof asyncDir !== "string" || typeof runId !== "string") return;
468
+ finalizeProcessTerminal(asyncDir, runId, {
469
+ processInstanceId: runnerProcessInstanceId,
470
+ closeObservedAt: Date.now(),
471
+ exitCode,
472
+ signal,
473
+ });
474
+ const persisted = readProcessTerminal(asyncDir, { runId, runnerProcessInstanceId });
475
+ if (!persisted) return;
476
+ if (launch.nestedRoute && launch.nestedSelf) {
477
+ try {
478
+ let status: import("../../shared/types.ts").AsyncStatus;
479
+ try {
480
+ status = JSON.parse(fs.readFileSync(path.join(asyncDir, "status.json"), "utf-8")) as import("../../shared/types.ts").AsyncStatus;
481
+ status.processTerminal = persisted;
482
+ } catch {
483
+ status = {
484
+ runId,
485
+ mode: "single",
486
+ state: persisted.state === "observed" ? "complete" : "failed",
487
+ startedAt: persisted.observedAt ?? Date.now(),
488
+ lastUpdate: Date.now(),
489
+ processTerminal: persisted,
490
+ };
491
+ }
492
+ writeNestedEvent(launch.nestedRoute, {
493
+ type: "subagent.nested.completed",
494
+ ts: Date.now(),
495
+ parentRunId: launch.nestedSelf.parentRunId,
496
+ parentStepIndex: launch.nestedSelf.parentStepIndex,
497
+ child: nestedSummaryFromAsyncStatus(status, asyncDir, {
498
+ id: runId,
499
+ parentRunId: launch.nestedSelf.parentRunId,
500
+ parentStepIndex: launch.nestedSelf.parentStepIndex,
501
+ depth: launch.nestedSelf.depth,
502
+ path: launch.nestedSelf.path,
503
+ mode: status.mode,
504
+ ts: Date.now(),
505
+ }),
506
+ });
507
+ } catch (error) {
508
+ console.error("Failed to emit final nested process-terminal status:", error);
509
+ }
510
+ }
511
+ onProcessTerminal?.(persisted);
512
+ });
327
513
  if (typeof proc.pid !== "number") {
328
514
  return { error: `async runner did not produce a pid for cwd: ${cwd}` };
329
515
  }
330
516
  proc.unref();
517
+ if (startupPath && startupAckPath && startupProceedPath) {
518
+ const ready = waitForRunnerStartup(startupPath, "ready", RUNNER_STARTUP_TIMEOUT_MS);
519
+ if (!ready.ok) {
520
+ terminateRunnerBeforeProceed(proc.pid);
521
+ return { error: ready.error };
522
+ }
523
+ try {
524
+ writeRunnerStartupControl(startupAckPath, { action: "ack", token: ready.token });
525
+ } catch (error) {
526
+ terminateRunnerBeforeProceed(proc.pid);
527
+ return { error: `Failed to acknowledge async runner startup: ${error instanceof Error ? error.message : String(error)}` };
528
+ }
529
+ const acknowledged = waitForRunnerStartup(startupPath, "acknowledged", RUNNER_STARTUP_TIMEOUT_MS, ready.token);
530
+ if (!acknowledged.ok) {
531
+ terminateRunnerBeforeProceed(proc.pid);
532
+ return { error: acknowledged.error };
533
+ }
534
+ try {
535
+ writeRunnerStartupControl(startupProceedPath, { action: "proceed", token: ready.token });
536
+ } catch (error) {
537
+ terminateRunnerBeforeProceed(proc.pid);
538
+ return { error: `Failed to authorize async runner startup: ${error instanceof Error ? error.message : String(error)}` };
539
+ }
540
+ try {
541
+ fs.rmSync(startupPath, { force: true });
542
+ } catch {
543
+ // Proceed is the commit point; handshake cleanup cannot turn a running revival into a start error.
544
+ }
545
+ }
331
546
  return { pid: proc.pid };
332
547
  } catch (error) {
333
548
  closeFd(stdoutFd);
@@ -415,19 +630,33 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
415
630
  ...(s.reads !== undefined ? { reads: s.reads } : {}),
416
631
  ...(s.progress !== undefined ? { progress: s.progress } : {}),
417
632
  ...(stepSkillInput !== undefined ? { skills: stepSkillInput } : {}),
418
- ...(s.model ? { model: s.model } : {}),
633
+ ...(s.model !== undefined ? { model: s.model } : {}),
419
634
  };
420
635
  };
421
- const buildSeqStep = (s: SequentialStep, sessionFile?: string, behaviorCwd?: string, progressPrecreated = false, resolvedBehavior?: ResolvedStepBehavior, flatIndex?: number) => {
636
+ const buildSeqStep = (s: SequentialStep, sessionFile?: string, behaviorCwd?: string, progressPrecreated = false, resolvedBehavior?: ResolvedStepBehavior, flatIndex?: number, parallelOutputNamespace?: { stepIndex: number; taskIndex?: number }) => {
422
637
  const a = agents.find((x) => x.name === s.agent)!;
423
638
  const toolBudgetInput = s.toolBudget ?? params.toolBudget ?? a.toolBudget ?? params.configToolBudget;
424
639
  const resolvedToolBudget = validateToolBudgetConfig(toolBudgetInput, s.toolBudget ? "toolBudget" : a.toolBudget ? "agent.toolBudget" : "config.toolBudget");
425
640
  if (resolvedToolBudget.error) throw new AsyncStartValidationError(resolvedToolBudget.error);
426
641
  const stepCwd = resolveChildCwd(runnerCwd, s.cwd);
427
642
  const instructionCwd = behaviorCwd ?? stepCwd;
428
- const behavior = suppressProgressForReadOnlyTask(resolvedBehavior ?? resolveStepBehavior(a, buildStepOverrides(s), chainSkills), s.task, originalTask);
643
+ let behavior = suppressProgressForReadOnlyTask(resolvedBehavior ?? resolveStepBehavior(a, buildStepOverrides(s), chainSkills), s.task, originalTask);
644
+ const inheritedRelativeParallelOutput = parallelOutputNamespace && s.output === undefined && typeof behavior.output === "string" && !path.isAbsolute(behavior.output);
645
+ if (inheritedRelativeParallelOutput && parallelOutputNamespace.taskIndex !== undefined) {
646
+ behavior = {
647
+ ...behavior,
648
+ output: path.join(`parallel-${parallelOutputNamespace.stepIndex}`, `${parallelOutputNamespace.taskIndex}-${s.agent}`, behavior.output),
649
+ };
650
+ }
651
+ const namespaceOutputPath = Boolean(inheritedRelativeParallelOutput && parallelOutputNamespace.taskIndex === undefined);
429
652
  const skillNames = behavior.skills === false ? [] : behavior.skills;
430
- const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(skillNames, stepCwd, ctx.cwd);
653
+ const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(
654
+ skillNames,
655
+ stepCwd,
656
+ ctx.cwd,
657
+ a.skillPath,
658
+ a.filePath ? path.dirname(a.filePath) : stepCwd,
659
+ );
431
660
  if (missingSkills.includes("pi-subagents")) throw new UnavailableSubagentSkillError(UNAVAILABLE_SUBAGENT_SKILL_ERROR);
432
661
 
433
662
  let systemPrompt = a.systemPrompt?.trim() ?? "";
@@ -445,23 +674,34 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
445
674
  if (behavior.progress) progressInstructionCreated = true;
446
675
  const progressInstructions = buildChainInstructions({ ...behavior, output: false, reads: false }, progressDir, isFirstProgressAgent);
447
676
  const outputPath = resolveSingleOutputPath(behavior.output, ctx.cwd, instructionCwd, outputBaseDir);
448
- systemPrompt = injectOutputPathSystemPrompt(systemPrompt, outputPath);
677
+ if (!namespaceOutputPath) systemPrompt = injectOutputPathSystemPrompt(systemPrompt, outputPath, a);
449
678
  const validationError = validateFileOnlyOutputMode(behavior.outputMode, outputPath, `Async step (${s.agent})`);
450
679
  if (validationError) throw new AsyncStartValidationError(validationError);
451
680
  let taskTemplate = s.task ?? "{previous}";
452
681
  taskTemplate = taskTemplate.replace(/\{task\}/g, originalTask ?? "");
453
682
  taskTemplate = taskTemplate.replace(/\{chain_dir\}/g, runnerCwd);
454
- const task = injectSingleOutputInstruction(`${readInstructions.prefix}${taskTemplate}${progressInstructions.suffix}`, outputPath);
683
+ const taskText = `${readInstructions.prefix}${taskTemplate}${progressInstructions.suffix}`;
684
+ const task = namespaceOutputPath ? taskText : injectSingleOutputInstruction(taskText, outputPath, a);
455
685
 
456
- const requestedModel = behavior.model ?? a.model;
457
- const primaryModel = resolveSubagentModelOverride(requestedModel, ctx.currentModel, availableModels, ctx.currentModelProvider, { scope: ctx.modelScope, source: behavior.model ? "explicit" : "inherited" });
686
+ const primaryModel = resolveEffectiveSubagentModel(
687
+ s.model,
688
+ a.model,
689
+ ctx.currentModel,
690
+ availableModels,
691
+ ctx.currentModelProvider,
692
+ { scope: ctx.modelScope },
693
+ );
458
694
  const thinkingOverride = flatIndex === undefined ? undefined : thinkingOverridesByFlatIndex?.[flatIndex];
459
695
  const effectiveThinking = thinkingOverride ?? a.thinking;
460
696
  const model = applyThinkingSuffix(primaryModel, effectiveThinking, thinkingOverride !== undefined);
697
+ const agentContract = s.agentContract ?? params.agentContract;
461
698
  return {
462
699
  parentSessionId: ctx.parentSessionId ?? ctx.currentSessionId,
700
+ ...(params.capabilityCeiling ? { capabilityCeiling: params.capabilityCeiling } : {}),
463
701
  agent: s.agent,
464
702
  task,
703
+ ...(params.contextForAgent ? { context: params.contextForAgent(s.agent) } : {}),
704
+ ...(agentContract ? { agentContract } : {}),
465
705
  phase: s.phase,
466
706
  label: s.label,
467
707
  outputName: s.as,
@@ -483,17 +723,24 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
483
723
  inheritSkills: a.inheritSkills,
484
724
  skills: resolvedSkills.map((r) => r.name),
485
725
  outputPath,
726
+ ...(namespaceOutputPath ? { namespaceOutputPath: true } : {}),
486
727
  outputMode: behavior.outputMode,
487
728
  sessionFile,
488
729
  maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, a.maxSubagentDepth),
730
+ waitToolEnabled: params.waitToolEnabled,
489
731
  effectiveAcceptance: resolveEffectiveAcceptance({
490
732
  explicit: s.acceptance,
491
733
  agentName: s.agent,
492
- task: s.task,
734
+ acceptanceRole: a.acceptanceRole,
735
+ task,
493
736
  mode: resultMode,
494
737
  async: true,
495
738
  dynamic: false,
739
+ agentContract,
496
740
  }),
741
+ acceptanceInput: s.acceptance,
742
+ acceptanceRole: a.acceptanceRole,
743
+ ...(s.gateOn ? { gateOn: s.gateOn } : {}),
497
744
  ...(s.outputSchema ? { structuredOutputSchema: s.outputSchema } : {}),
498
745
  ...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output")) } : {}),
499
746
  ...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
@@ -536,7 +783,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
536
783
  }
537
784
  }
538
785
  const staticStep = nextFlatStep();
539
- return buildSeqStep(t, staticStep.sessionFile, behaviorCwd, progressPrecreated, parallelBehaviors[taskIndex], staticStep.index);
786
+ return buildSeqStep({ ...t, agentContract: t.agentContract ?? s.agentContract, gateOn: t.gateOn ?? s.gateOn }, staticStep.sessionFile, behaviorCwd, progressPrecreated, parallelBehaviors[taskIndex], staticStep.index, { stepIndex, taskIndex });
540
787
  }),
541
788
  concurrency: s.concurrency,
542
789
  failFast: s.failFast,
@@ -553,9 +800,10 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
553
800
  }
554
801
  const maxItems = s.expand.maxItems ?? params.dynamicFanoutMaxItems ?? 0;
555
802
  const dynamicFlatSteps = Array.from({ length: maxItems }, () => nextFlatStep());
803
+ const parallel = buildSeqStep({ ...(s.parallel as SequentialStep), agentContract: s.parallel.agentContract ?? s.agentContract, gateOn: s.parallel.gateOn ?? s.gateOn }, undefined, undefined, progressPrecreated, behavior, undefined, { stepIndex });
556
804
  return {
557
805
  expand: s.expand,
558
- parallel: buildSeqStep(s.parallel as SequentialStep, undefined, undefined, progressPrecreated, behavior),
806
+ parallel,
559
807
  collect: s.collect,
560
808
  concurrency: s.concurrency,
561
809
  failFast: s.failFast,
@@ -566,11 +814,17 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
566
814
  effectiveAcceptance: resolveEffectiveAcceptance({
567
815
  explicit: s.acceptance,
568
816
  agentName: s.parallel.agent,
569
- task: s.parallel.task,
817
+ acceptanceRole: agent.acceptanceRole,
818
+ task: parallel.task,
570
819
  mode: resultMode,
571
820
  async: true,
572
821
  dynamicGroup: true,
822
+ agentContract: s.agentContract ?? params.agentContract,
573
823
  }),
824
+ acceptanceInput: s.acceptance,
825
+ acceptanceRole: agent.acceptanceRole,
826
+ ...(s.agentContract ?? params.agentContract ? { agentContract: s.agentContract ?? params.agentContract } : {}),
827
+ ...(s.gateOn ? { gateOn: s.gateOn } : {}),
574
828
  };
575
829
  }
576
830
  const staticStep = nextFlatStep();
@@ -592,6 +846,19 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
592
846
  inheritSkills: false,
593
847
  }, ...builtSteps]
594
848
  : builtSteps;
849
+ for (const step of steps) {
850
+ if (!("parallel" in step) || !Array.isArray(step.parallel)) continue;
851
+ const seen = new Map<string, { index: number; agent: string }>();
852
+ for (let index = 0; index < step.parallel.length; index++) {
853
+ const task = step.parallel[index]!;
854
+ if (!task.outputPath) continue;
855
+ const previous = seen.get(task.outputPath);
856
+ if (previous) {
857
+ throw new AsyncStartValidationError(`Parallel tasks ${previous.index + 1} (${previous.agent}) and ${index + 1} (${task.agent}) resolve output to the same path: ${task.outputPath}. Use distinct output paths.`);
858
+ }
859
+ seen.set(task.outputPath, { index, agent: task.agent });
860
+ }
861
+ }
595
862
  return { steps, runnerCwd, workflowGraph, eventChain: graphChain, ...(originalTask !== undefined ? { originalTask } : {}) };
596
863
  } catch (error) {
597
864
  if (error instanceof UnavailableSubagentSkillError || error instanceof AsyncStartValidationError) return { error: error.message };
@@ -628,6 +895,7 @@ export function executeAsyncChain(
628
895
  nestedRoute,
629
896
  } = params;
630
897
  const resultMode = params.resultMode ?? "chain";
898
+ const capabilityCeiling = params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId);
631
899
  const inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
632
900
  const nestedAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
633
901
  const asyncDir = inheritedNestedRoute
@@ -656,14 +924,18 @@ export function executeAsyncChain(
656
924
  chainSkills: params.chainSkills,
657
925
  sessionFilesByFlatIndex,
658
926
  thinkingOverridesByFlatIndex,
927
+ contextForAgent: params.contextForAgent,
659
928
  progressDir: params.progressDir ?? (artifactsDir ? path.join(artifactsDir, "progress", id) : resultMode === "parallel" ? path.join(asyncDir, "progress") : undefined),
929
+ agentContract: params.agentContract,
660
930
  outputBaseDir: artifactsDir ? path.join(artifactsDir, "outputs", id) : undefined,
661
931
  dynamicFanoutMaxItems: params.dynamicFanoutMaxItems,
662
932
  maxSubagentDepth,
933
+ waitToolEnabled: params.waitToolEnabled,
663
934
  worktreeBaseDir,
664
935
  asyncDir,
665
936
  toolBudget: params.toolBudget,
666
937
  configToolBudget: params.configToolBudget,
938
+ capabilityCeiling,
667
939
  });
668
940
  if ("error" in built) {
669
941
  try {
@@ -708,7 +980,8 @@ export function executeAsyncChain(
708
980
  sessionDir: sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined,
709
981
  asyncDir,
710
982
  sessionId: ctx.currentSessionId,
711
- selesaiPackageRoot,
983
+ ...(capabilityCeiling ? { capabilityCeiling } : {}),
984
+ piPackageRoot,
712
985
  piArgv1: process.argv[1],
713
986
  worktreeSetupHook,
714
987
  worktreeSetupHookTimeoutMs,
@@ -734,6 +1007,7 @@ export function executeAsyncChain(
734
1007
  },
735
1008
  id,
736
1009
  runnerCwd,
1010
+ (proof) => ctx.pi.events.emit(SUBAGENT_PROCESS_TERMINAL_EVENT, proof),
737
1011
  );
738
1012
  } catch (error) {
739
1013
  const message = error instanceof Error ? error.message : String(error);
@@ -751,6 +1025,12 @@ export function executeAsyncChain(
751
1025
  : isDynamicParallelStep(eventFirstStep)
752
1026
  ? [eventFirstStep.parallel.agent]
753
1027
  : [(eventFirstStep as SequentialStep).agent];
1028
+ const firstTask = isParallelStep(eventFirstStep)
1029
+ ? eventFirstStep.parallel[0]?.task
1030
+ : isDynamicParallelStep(eventFirstStep)
1031
+ ? eventFirstStep.parallel.task
1032
+ : (eventFirstStep as SequentialStep).task;
1033
+ const workflowGoal = params.goal ?? (params.task?.trim() || firstTask);
754
1034
  const parallelGroups: Array<{ start: number; count: number; stepIndex: number }> = [];
755
1035
  const flatAgents: string[] = [];
756
1036
  let flatStepStart = 0;
@@ -799,6 +1079,7 @@ export function executeAsyncChain(
799
1079
  ...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
800
1080
  startedAt: now,
801
1081
  lastUpdate: now,
1082
+ ...(capabilityCeiling ? { capabilityCeiling } : {}),
802
1083
  },
803
1084
  });
804
1085
  } catch (error) {
@@ -813,11 +1094,8 @@ export function executeAsyncChain(
813
1094
  mode: resultMode,
814
1095
  agent: firstAgents[0],
815
1096
  agents: flatAgents,
816
- task: isParallelStep(eventFirstStep)
817
- ? eventFirstStep.parallel[0]?.task?.slice(0, 50)
818
- : isDynamicParallelStep(eventFirstStep)
819
- ? eventFirstStep.parallel.task?.slice(0, 50)
820
- : (eventFirstStep as SequentialStep).task?.slice(0, 50),
1097
+ task: firstTask?.slice(0, 50),
1098
+ goal: workflowGoal?.slice(0, 120),
821
1099
  chain: eventChain.map((s) =>
822
1100
  isParallelStep(s) ? `[${s.parallel.map((t) => t.agent).join("+")}]` : isDynamicParallelStep(s) ? `expand:${s.parallel.agent}` : (s as SequentialStep).agent,
823
1101
  ),
@@ -828,6 +1106,7 @@ export function executeAsyncChain(
828
1106
  asyncDir,
829
1107
  ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}),
830
1108
  ...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
1109
+ ...(capabilityCeiling ? { capabilityCeiling } : {}),
831
1110
  nestedRoute,
832
1111
  });
833
1112
  }
@@ -839,8 +1118,8 @@ export function executeAsyncChain(
839
1118
  .join(" -> ");
840
1119
 
841
1120
  return {
842
- content: [{ type: "text", text: formatAsyncStartedMessage(`Async ${resultMode}: ${chainDesc} [${id}]`) }],
843
- details: { mode: resultMode, runId: id, results: [], asyncId: id, asyncDir, workflowGraph, ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}) },
1121
+ content: [{ type: "text", text: formatAsyncStartedMessage(`Async ${resultMode}: ${chainDesc} [${id}]`, ctx.interactive === true) }],
1122
+ details: { mode: resultMode, runId: id, results: [], asyncId: id, asyncDir, workflowGraph, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}) },
844
1123
  };
845
1124
  }
846
1125
 
@@ -872,10 +1151,17 @@ export function executeAsyncSingle(
872
1151
  nestedRoute,
873
1152
  } = params;
874
1153
  const task = params.task ?? "";
1154
+ const capabilityCeiling = params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId);
875
1155
  const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
876
1156
  const skillNames = params.skills ?? agentConfig.skills ?? [];
877
1157
  const availableModels = params.availableModels;
878
- const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(skillNames, runnerCwd, ctx.cwd);
1158
+ const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(
1159
+ skillNames,
1160
+ runnerCwd,
1161
+ ctx.cwd,
1162
+ agentConfig.skillPath,
1163
+ agentConfig.filePath ? path.dirname(agentConfig.filePath) : runnerCwd,
1164
+ );
879
1165
  if (missingSkills.includes("pi-subagents")) return formatAsyncStartError("single", UNAVAILABLE_SUBAGENT_SKILL_ERROR);
880
1166
  let systemPrompt = agentConfig.systemPrompt?.trim() ?? "";
881
1167
  if (resolvedSkills.length > 0) {
@@ -905,11 +1191,11 @@ export function executeAsyncSingle(
905
1191
 
906
1192
  const effectiveOutput = normalizeSingleOutputOverride(params.output, agentConfig.output);
907
1193
  const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, runnerCwd, params.outputBaseDir ?? (artifactsDir ? path.join(artifactsDir, "outputs", id) : undefined));
908
- systemPrompt = injectOutputPathSystemPrompt(systemPrompt, outputPath);
1194
+ systemPrompt = injectOutputPathSystemPrompt(systemPrompt, outputPath, agentConfig);
909
1195
  const outputMode = params.outputMode ?? "inline";
910
1196
  const validationError = validateFileOnlyOutputMode(outputMode, outputPath, `Async single run (${agent})`);
911
1197
  if (validationError) return formatAsyncStartError("single", validationError);
912
- const taskWithOutputInstruction = injectSingleOutputInstruction(task, outputPath);
1198
+ const taskWithOutputInstruction = injectSingleOutputInstruction(task, outputPath, agentConfig);
913
1199
  const primaryModel = resolveSubagentModelOverride(
914
1200
  params.modelOverride ?? agentConfig.model,
915
1201
  ctx.currentModel,
@@ -921,8 +1207,103 @@ export function executeAsyncSingle(
921
1207
  const toolBudgetInput = params.toolBudget ?? agentConfig.toolBudget ?? params.configToolBudget;
922
1208
  const resolvedToolBudget = validateToolBudgetConfig(toolBudgetInput, params.toolBudget ? "toolBudget" : agentConfig.toolBudget ? "agent.toolBudget" : "config.toolBudget");
923
1209
  if (resolvedToolBudget.error) return formatAsyncStartError("single", resolvedToolBudget.error);
924
- const deadlineAt = params.timeoutMs !== undefined ? Date.now() + params.timeoutMs : undefined;
1210
+ const deadlineAt = params.absoluteDeadlineAt ?? (params.timeoutMs !== undefined ? Date.now() + params.timeoutMs : undefined);
1211
+ const timeoutMs = params.absoluteDeadlineAt !== undefined && deadlineAt !== undefined
1212
+ ? deadlineAt - Date.now()
1213
+ : params.timeoutMs;
1214
+ if (timeoutMs !== undefined && timeoutMs <= 0) return formatAsyncStartError("single", "The source run's absolute deadline expired before recovery could launch.");
925
1215
  const initialTurnBudget = params.turnBudget ? initialTurnBudgetState(params.turnBudget) : undefined;
1216
+ const resolvedSessionDir = params.sessionDir ?? (sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined);
1217
+ const structuredOutput = params.structuredOutputSchema
1218
+ ? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"))
1219
+ : undefined;
1220
+ const modelCandidates = buildModelCandidates(primaryModel, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider, { scope: ctx.modelScope }).map((candidate) =>
1221
+ applyThinkingSuffix(candidate, effectiveThinking, params.thinkingOverride !== undefined),
1222
+ );
1223
+ const effectiveSystemPrompt = appendTurnBudgetSystemPrompt(systemPrompt, params.turnBudget);
1224
+ const toolPlan = resolvePiLaunchToolPlan({
1225
+ tools: agentConfig.tools,
1226
+ extensions: agentConfig.extensions,
1227
+ subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
1228
+ mcpDirectTools: agentConfig.mcpDirectTools,
1229
+ cwd: runnerCwd,
1230
+ requireReadTool: Boolean(resolvedSkills.length),
1231
+ structuredOutput: Boolean(params.structuredOutputSchema),
1232
+ capabilityCeiling,
1233
+ inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
1234
+ });
1235
+ const launchContractDigest = launchBindingDigest({
1236
+ definitionDigest: agentDefinitionDigest(agentConfig),
1237
+ task,
1238
+ ...(model ? { model } : {}),
1239
+ modelCandidates,
1240
+ ...(resolveEffectiveThinking(model, effectiveThinking) ? { thinking: resolveEffectiveThinking(model, effectiveThinking) } : {}),
1241
+ systemPrompt: effectiveSystemPrompt,
1242
+ systemPromptMode: agentConfig.systemPromptMode,
1243
+ inheritProjectContext: agentConfig.inheritProjectContext,
1244
+ inheritSkills: agentConfig.inheritSkills,
1245
+ skills: resolvedSkills.map((skill) => skill.name),
1246
+ tools: toolPlan.effectiveToolAllowlist,
1247
+ extensions: toolPlan.extensionArgs,
1248
+ mcpDirectTools: toolPlan.effectiveMcpTools,
1249
+ ...(outputPath ? { outputPath } : {}),
1250
+ outputMode,
1251
+ ...(params.structuredOutputSchema ? { structuredOutputSchema: params.structuredOutputSchema } : {}),
1252
+ });
1253
+ const resolvedAcceptance = resolveEffectiveAcceptance({
1254
+ explicit: params.acceptance,
1255
+ agentName: agent,
1256
+ acceptanceRole: agentConfig.acceptanceRole,
1257
+ task,
1258
+ mode: "single",
1259
+ async: true,
1260
+ agentContract: params.agentContract,
1261
+ });
1262
+ const recoveryDescriptor: SteeringRecoveryDescriptor = {
1263
+ version: 1,
1264
+ launchContractDigest,
1265
+ sourceRunId: id,
1266
+ ...(params.agentContract ? { agentContract: params.agentContract } : {}),
1267
+ agent,
1268
+ ...(sessionFile ? { sessionFile } : {}),
1269
+ cwd: runnerCwd,
1270
+ ...(model ? { model } : {}),
1271
+ ...(agentConfig.fallbackModels ? { fallbackModels: [...agentConfig.fallbackModels] } : {}),
1272
+ ...(effectiveThinking ? { thinking: resolveEffectiveThinking(model, effectiveThinking) } : {}),
1273
+ ...(agentConfig.tools ? { tools: [...agentConfig.tools] } : {}),
1274
+ ...(agentConfig.extensions ? { extensions: [...agentConfig.extensions] } : {}),
1275
+ ...(agentConfig.subagentOnlyExtensions ? { subagentOnlyExtensions: [...agentConfig.subagentOnlyExtensions] } : {}),
1276
+ ...(agentConfig.mcpDirectTools ? { mcpDirectTools: [...agentConfig.mcpDirectTools] } : {}),
1277
+ ...(agentConfig.systemPrompt ? { systemPrompt: agentConfig.systemPrompt } : {}),
1278
+ systemPromptMode: agentConfig.systemPromptMode,
1279
+ inheritProjectContext: agentConfig.inheritProjectContext,
1280
+ inheritSkills: agentConfig.inheritSkills,
1281
+ ...(resolvedSkills.length ? { skills: resolvedSkills.map((skill) => skill.name) } : {}),
1282
+ ...(agentConfig.skillPath ? { skillPath: [...agentConfig.skillPath] } : {}),
1283
+ ...(agentConfig.filePath ? { agentFilePath: agentConfig.filePath } : {}),
1284
+ ...(agentConfig.completionGuard !== undefined ? { completionGuard: agentConfig.completionGuard } : {}),
1285
+ ...(agentConfig.memory ? { memory: { ...agentConfig.memory } } : {}),
1286
+ ...(outputPath ? { outputPath } : {}),
1287
+ outputMode,
1288
+ ...(params.structuredOutputSchema ? { structuredOutputSchema: params.structuredOutputSchema } : {}),
1289
+ ...(params.acceptance !== undefined ? { acceptance: params.acceptance } : {}),
1290
+ ...(controlConfig ? { controlConfig } : {}),
1291
+ ...(deadlineAt !== undefined ? { absoluteDeadlineAt: deadlineAt } : {}),
1292
+ ...(params.turnBudget ? { initialTurnBudget: params.turnBudget } : {}),
1293
+ ...(resolvedToolBudget.budget ? { initialToolBudget: resolvedToolBudget.budget } : {}),
1294
+ maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, agentConfig.maxSubagentDepth),
1295
+ ...(maxOutput ? { maxOutput } : {}),
1296
+ share: shareEnabled,
1297
+ ...(resolvedSessionDir ? { sessionDir: resolvedSessionDir } : {}),
1298
+ ...(artifactsDir ? { artifactsDir } : {}),
1299
+ artifactConfig,
1300
+ ...(capabilityCeiling ? { capabilityCeiling } : {}),
1301
+ };
1302
+ try {
1303
+ writePrivateAtomicJson(path.join(asyncDir, "recovery-descriptor.json"), recoveryDescriptor);
1304
+ } catch (error) {
1305
+ return formatAsyncStartError("single", `Failed to persist async recovery descriptor for '${id}': ${error instanceof Error ? error.message : String(error)}`);
1306
+ }
926
1307
  let spawnResult: { pid?: number; error?: string } = {};
927
1308
  try {
928
1309
  spawnResult = spawnRunner(
@@ -931,14 +1312,14 @@ export function executeAsyncSingle(
931
1312
  steps: [
932
1313
  {
933
1314
  parentSessionId: ctx.parentSessionId ?? ctx.currentSessionId,
1315
+ ...(capabilityCeiling ? { capabilityCeiling } : {}),
934
1316
  agent,
935
1317
  task: taskWithOutputInstruction,
1318
+ ...(params.context ? { context: params.context } : {}),
936
1319
  cwd: runnerCwd,
937
1320
  model,
938
1321
  thinking: resolveEffectiveThinking(model, effectiveThinking),
939
- modelCandidates: buildModelCandidates(primaryModel, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider, { scope: ctx.modelScope }).map((candidate) =>
940
- applyThinkingSuffix(candidate, effectiveThinking, params.thinkingOverride !== undefined),
941
- ),
1322
+ modelCandidates,
942
1323
  tools: agentConfig.tools,
943
1324
  extensions: agentConfig.extensions,
944
1325
  subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
@@ -953,13 +1334,14 @@ export function executeAsyncSingle(
953
1334
  outputMode,
954
1335
  sessionFile,
955
1336
  maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, agentConfig.maxSubagentDepth),
956
- effectiveAcceptance: resolveEffectiveAcceptance({
957
- explicit: params.acceptance,
958
- agentName: agent,
959
- task,
960
- mode: "single",
961
- async: true,
962
- }),
1337
+ waitToolEnabled: params.waitToolEnabled,
1338
+ ...(params.agentContract ? { agentContract: params.agentContract } : {}),
1339
+ definitionDigest: agentDefinitionDigest(agentConfig),
1340
+ launchBindingTask: task,
1341
+ launchContractDigest,
1342
+ effectiveAcceptance: resolvedAcceptance,
1343
+ ...(structuredOutput ? { structuredOutput } : {}),
1344
+ ...(params.structuredOutputSchema ? { structuredOutputSchema: params.structuredOutputSchema } : {}),
963
1345
  ...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
964
1346
  },
965
1347
  ],
@@ -970,22 +1352,25 @@ export function executeAsyncSingle(
970
1352
  artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
971
1353
  artifactConfig,
972
1354
  share: shareEnabled,
973
- sessionDir: sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined,
1355
+ sessionDir: resolvedSessionDir,
974
1356
  asyncDir,
975
1357
  sessionId: ctx.currentSessionId,
976
- selesaiPackageRoot,
1358
+ ...(capabilityCeiling ? { capabilityCeiling } : {}),
1359
+ piPackageRoot,
977
1360
  piArgv1: process.argv[1],
978
1361
  worktreeSetupHook,
979
1362
  worktreeSetupHookTimeoutMs,
980
1363
  worktreeBaseDir,
981
1364
  controlConfig,
982
- timeoutMs: params.timeoutMs,
1365
+ timeoutMs,
983
1366
  deadlineAt,
984
1367
  turnBudget: params.turnBudget,
985
1368
  toolBudget: params.toolBudget,
986
1369
  controlIntercomTarget,
987
1370
  childIntercomTargets: childIntercomTarget ? [childIntercomTarget(agent, 0)] : undefined,
988
1371
  resultMode: "single",
1372
+ launchContractDigest,
1373
+ ...(params.revivalLease ? { revivalLease: params.revivalLease } : {}),
989
1374
  nestedRoute: nestedRoute ?? inheritedNestedRoute,
990
1375
  nestedSelf: inheritedNestedRoute && nestedAddress ? {
991
1376
  parentRunId: nestedAddress.parentRunId,
@@ -996,6 +1381,7 @@ export function executeAsyncSingle(
996
1381
  },
997
1382
  id,
998
1383
  runnerCwd,
1384
+ (proof) => ctx.pi.events.emit(SUBAGENT_PROCESS_TERMINAL_EVENT, proof),
999
1385
  );
1000
1386
  } catch (error) {
1001
1387
  const message = error instanceof Error ? error.message : String(error);
@@ -1032,10 +1418,11 @@ export function executeAsyncSingle(
1032
1418
  agent,
1033
1419
  agents: [agent],
1034
1420
  chainStepCount: 1,
1035
- ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}),
1421
+ ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}),
1036
1422
  ...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
1037
1423
  startedAt: now,
1038
1424
  lastUpdate: now,
1425
+ ...(capabilityCeiling ? { capabilityCeiling } : {}),
1039
1426
  },
1040
1427
  });
1041
1428
  } catch (error) {
@@ -1050,16 +1437,19 @@ export function executeAsyncSingle(
1050
1437
  mode: "single",
1051
1438
  agent,
1052
1439
  task: task?.slice(0, 50),
1440
+ goal: (params.goal ?? task).slice(0, 120),
1053
1441
  cwd: runnerCwd,
1054
1442
  asyncDir,
1055
- ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}),
1443
+ launchContractDigest,
1444
+ ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}),
1056
1445
  ...(initialTurnBudget ? { turnBudget: initialTurnBudget } : {}),
1446
+ ...(capabilityCeiling ? { capabilityCeiling } : {}),
1057
1447
  nestedRoute,
1058
1448
  });
1059
1449
  }
1060
1450
 
1061
1451
  return {
1062
- content: [{ type: "text", text: formatAsyncStartedMessage(`Async: ${agent} [${id}]`) }],
1063
- details: { mode: "single", runId: id, results: [], asyncId: id, asyncDir, ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}) },
1452
+ content: [{ type: "text", text: formatAsyncStartedMessage(`Async: ${agent} [${id}]`, ctx.interactive === true) }],
1453
+ details: { mode: "single", runId: id, results: [], asyncId: id, asyncDir, launchContractDigest, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.context ? { context: params.context } : {}), ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}), ...(params.turnBudget ? { turnBudget: params.turnBudget } : {}), ...(params.toolBudget ? { toolBudget: params.toolBudget } : {}) },
1064
1454
  };
1065
1455
  }