@selesai/code 0.13.15 → 0.13.18

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 (473) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +2 -14
  3. package/dist/bun/cli.d.ts +3 -1
  4. package/dist/bun/cli.js +3 -9
  5. package/dist/bun/{register-bedrock.js → runtime-setup.js} +5 -0
  6. package/dist/bun/sandbox-env-setup.d.ts +1 -0
  7. package/dist/bun/sandbox-env-setup.js +3 -0
  8. package/dist/cli/auth-command.d.ts +23 -0
  9. package/dist/cli/auth-command.js +102 -0
  10. package/dist/cli/config-selector.js +2 -1
  11. package/dist/cli/setup.d.ts +1 -0
  12. package/dist/cli/setup.js +11 -0
  13. package/dist/cli.js +2 -14
  14. package/dist/config.d.ts +3 -2
  15. package/dist/config.js +9 -4
  16. package/dist/core/agent-session-runtime.js +15 -7
  17. package/dist/core/agent-session.d.ts +2 -6
  18. package/dist/core/agent-session.js +15 -4
  19. package/dist/core/compaction/branch-summarization.d.ts +1 -1
  20. package/dist/core/compaction/branch-summarization.js +2 -1
  21. package/dist/core/extensions/loader.js +3 -0
  22. package/dist/core/extensions/types.d.ts +16 -2
  23. package/dist/core/http-dispatcher.js +2 -0
  24. package/dist/core/keybindings.d.ts +5 -0
  25. package/dist/core/keybindings.js +4 -0
  26. package/dist/core/messages.d.ts +1 -1
  27. package/dist/core/model-config.d.ts +15 -0
  28. package/dist/core/model-config.js +3 -0
  29. package/dist/core/model-runtime.d.ts +1 -0
  30. package/dist/core/model-runtime.js +5 -2
  31. package/dist/core/provider-composer.js +8 -2
  32. package/dist/core/session-export.d.ts +3 -0
  33. package/dist/core/session-export.js +31 -0
  34. package/dist/core/session-manager.d.ts +3 -2
  35. package/dist/core/session-manager.js +40 -13
  36. package/dist/core/skills.d.ts +1 -1
  37. package/dist/core/skills.js +4 -2
  38. package/dist/core/system-prompt.js +10 -10
  39. package/dist/core/tools/bash.js +3 -117
  40. package/dist/core/tools/edit.d.ts +1 -12
  41. package/dist/core/tools/edit.js +5 -163
  42. package/dist/core/tools/find.js +4 -55
  43. package/dist/core/tools/grep.js +4 -60
  44. package/dist/core/tools/ls.js +4 -49
  45. package/dist/core/tools/read.d.ts +4 -0
  46. package/dist/core/tools/read.js +10 -122
  47. package/dist/core/tools/renderers/bash.d.ts +11 -0
  48. package/dist/core/tools/renderers/bash.js +129 -0
  49. package/dist/core/tools/renderers/edit.d.ts +22 -0
  50. package/dist/core/tools/renderers/edit.js +166 -0
  51. package/dist/core/tools/renderers/find.d.ts +9 -0
  52. package/dist/core/tools/renderers/find.js +63 -0
  53. package/dist/core/tools/renderers/grep.d.ts +9 -0
  54. package/dist/core/tools/renderers/grep.js +68 -0
  55. package/dist/core/tools/renderers/index.d.ts +27 -0
  56. package/dist/core/tools/renderers/index.js +46 -0
  57. package/dist/core/tools/renderers/ls.d.ts +9 -0
  58. package/dist/core/tools/renderers/ls.js +57 -0
  59. package/dist/core/tools/renderers/read.d.ts +10 -0
  60. package/dist/core/tools/renderers/read.js +128 -0
  61. package/dist/core/tools/renderers/write.d.ts +9 -0
  62. package/dist/core/tools/renderers/write.js +152 -0
  63. package/dist/core/tools/write.js +5 -146
  64. package/dist/extensions/capability-gateway/index.ts +12 -1
  65. package/dist/extensions/capability-gateway/integration.test.ts +23 -0
  66. package/dist/extensions/pi-hermes-memory/CHANGELOG.md +18 -0
  67. package/dist/extensions/pi-hermes-memory/README.md +16 -12
  68. package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +1 -1
  69. package/dist/extensions/pi-hermes-memory/package-lock.json +587 -62
  70. package/dist/extensions/pi-hermes-memory/package.json +4 -4
  71. package/dist/extensions/pi-hermes-memory/src/config.ts +32 -0
  72. package/dist/extensions/pi-hermes-memory/src/constants.ts +12 -2
  73. package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +109 -51
  74. package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +9 -3
  75. package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +257 -98
  76. package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +9 -2
  77. package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +5 -0
  78. package/dist/extensions/pi-hermes-memory/src/index.ts +22 -2
  79. package/dist/extensions/pi-hermes-memory/src/paths.ts +1 -1
  80. package/dist/extensions/pi-hermes-memory/src/store/db.ts +6 -0
  81. package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +58 -1
  82. package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +60 -8
  83. package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +169 -7
  84. package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +7 -21
  85. package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +134 -15
  86. package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +4 -2
  87. package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +24 -9
  88. package/dist/extensions/pi-hermes-memory/src/types.ts +11 -0
  89. package/dist/extensions/pi-hermes-memory/tests/config.test.ts +42 -0
  90. package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +159 -3
  91. package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +452 -0
  92. package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +39 -0
  93. package/dist/extensions/pi-hermes-memory/tests/integration/corruption-recovery-integration.test.ts +91 -0
  94. package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +29 -0
  95. package/dist/extensions/pi-hermes-memory/tests/store/fts-query.test.ts +116 -0
  96. package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +163 -0
  97. package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +233 -0
  98. package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +12 -0
  99. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +175 -0
  100. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +2 -2
  101. package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +22 -0
  102. package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +37 -1
  103. package/dist/extensions/pi-intercom/CHANGELOG.md +13 -0
  104. package/dist/extensions/pi-intercom/README.md +16 -2
  105. package/dist/extensions/pi-intercom/broker/spawn.test.ts +19 -2
  106. package/dist/extensions/pi-intercom/broker/spawn.ts +4 -4
  107. package/dist/extensions/pi-intercom/index.ts +1 -1
  108. package/dist/extensions/pi-intercom/intercom.integration.test.ts +121 -2
  109. package/dist/extensions/pi-intercom/package.json +1 -1
  110. package/dist/extensions/pi-intercom/project-agent.test.ts +1 -1
  111. package/dist/extensions/pi-intercom/project-agent.ts +1 -6
  112. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +7 -6
  113. package/dist/extensions/pi-rewind-hook/index.test.ts +7 -0
  114. package/dist/extensions/pi-subagents/.github/workflows/test.yml +36 -4
  115. package/dist/extensions/pi-subagents/CHANGELOG.md +120 -0
  116. package/dist/extensions/pi-subagents/README.md +3 -3
  117. package/dist/extensions/pi-subagents/agents/researcher.md +23 -13
  118. package/dist/extensions/pi-subagents/agents/reviewer.md +1 -1
  119. package/dist/extensions/pi-subagents/agents/scout.md +1 -1
  120. package/dist/extensions/pi-subagents/docs/agents.md +32 -16
  121. package/dist/extensions/pi-subagents/docs/configuration.md +41 -15
  122. package/dist/extensions/pi-subagents/docs/extension-api.md +109 -6
  123. package/dist/extensions/pi-subagents/docs/missions.md +2 -0
  124. package/dist/extensions/pi-subagents/docs/models.md +58 -1
  125. package/dist/extensions/pi-subagents/docs/observability.md +50 -12
  126. package/dist/extensions/pi-subagents/docs/tool-reference.md +17 -10
  127. package/dist/extensions/pi-subagents/docs/watchdog.md +1 -1
  128. package/dist/extensions/pi-subagents/docs/workflows.md +26 -9
  129. package/dist/extensions/pi-subagents/package-lock.json +747 -177
  130. package/dist/extensions/pi-subagents/package.json +6 -4
  131. package/dist/extensions/pi-subagents/runner-server-preload.mjs +13 -0
  132. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +2 -1
  133. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  134. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +21 -4
  135. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
  136. package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
  137. package/dist/extensions/pi-subagents/src/agents/advertised-agent-prompt.ts +63 -0
  138. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +61 -16
  139. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
  140. package/dist/extensions/pi-subagents/src/agents/agents.ts +8 -0
  141. package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +0 -1
  142. package/dist/extensions/pi-subagents/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
  143. package/dist/extensions/pi-subagents/src/api/preflight.ts +7 -4
  144. package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -2
  145. package/dist/extensions/pi-subagents/src/api/workflow-resources.ts +6 -0
  146. package/dist/extensions/pi-subagents/src/extension/config.ts +4 -2
  147. package/dist/extensions/pi-subagents/src/extension/doctor.ts +2 -10
  148. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +9 -11
  149. package/dist/extensions/pi-subagents/src/extension/index.ts +99 -10
  150. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +13 -0
  151. package/dist/extensions/pi-subagents/src/extension/rpc.ts +7 -23
  152. package/dist/extensions/pi-subagents/src/extension/schemas.ts +11 -8
  153. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +25 -7
  154. package/dist/extensions/pi-subagents/src/integrations/pi-web-session-liveness.ts +73 -0
  155. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +228 -136
  156. package/dist/extensions/pi-subagents/src/intercom/supervisor-ui.ts +244 -0
  157. package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +37 -16
  158. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +18 -18
  159. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +51 -25
  160. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +46 -3
  161. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -3
  162. package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +9 -0
  163. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +10 -12
  164. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -15
  165. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +40 -29
  166. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +8 -0
  167. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +79 -247
  168. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +88 -12
  169. package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +6 -6
  170. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +24 -24
  171. package/dist/extensions/pi-subagents/src/runs/background/retained-nested-route-tracker.ts +96 -0
  172. package/dist/extensions/pi-subagents/src/runs/background/run-child-session.ts +642 -0
  173. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +75 -6
  174. package/dist/extensions/pi-subagents/src/runs/background/runner-aliases.ts +163 -0
  175. package/dist/extensions/pi-subagents/src/runs/background/runner-child-launch.ts +86 -0
  176. package/dist/extensions/pi-subagents/src/runs/background/runner-child-sessions.ts +31 -0
  177. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +18 -4
  178. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +3 -1
  179. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +597 -1092
  180. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +3 -0
  181. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +4 -0
  182. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +20 -17
  183. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +296 -394
  184. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +4 -0
  185. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +3 -1
  186. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +9 -5
  187. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +630 -283
  188. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
  189. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +78 -98
  190. package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +3 -3
  191. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +16 -3
  192. package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +1 -1
  193. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +47 -47
  194. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -2
  195. package/dist/extensions/pi-subagents/src/runs/shared/child-hooks.ts +174 -0
  196. package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +14 -3
  197. package/dist/extensions/pi-subagents/src/runs/shared/child-launch.ts +319 -0
  198. package/dist/extensions/pi-subagents/src/runs/shared/child-lifecycle.ts +25 -0
  199. package/dist/extensions/pi-subagents/src/runs/shared/child-runtime-config.ts +126 -0
  200. package/dist/extensions/pi-subagents/src/runs/shared/child-session.ts +373 -0
  201. package/dist/extensions/pi-subagents/src/runs/shared/child-tool-plan.ts +530 -0
  202. package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +2 -2
  203. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +2 -1
  204. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +16 -0
  205. package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +11 -11
  206. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +30 -20
  207. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
  208. package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +84 -15
  209. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +88 -12
  210. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +37 -53
  211. package/dist/extensions/pi-subagents/src/runs/shared/nested-path.ts +1 -1
  212. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +18 -7
  213. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +57 -12
  214. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +3 -4
  215. package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +0 -11
  216. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +4 -1
  217. package/dist/extensions/pi-subagents/src/runs/shared/readonly-drain-observation.ts +42 -0
  218. package/dist/extensions/pi-subagents/src/runs/shared/readonly-model-continuation.ts +69 -0
  219. package/dist/extensions/pi-subagents/src/runs/shared/readonly-session-evidence.ts +307 -0
  220. package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +8 -21
  221. package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +3 -30
  222. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +17 -4
  223. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +2 -0
  224. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +99 -386
  225. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +18 -62
  226. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +0 -14
  227. package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +31 -9
  228. package/dist/extensions/pi-subagents/src/runs/shared/worktree-setup-command.ts +190 -0
  229. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +506 -226
  230. package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +1 -1
  231. package/dist/extensions/pi-subagents/src/shared/jsonl-writer.ts +11 -0
  232. package/dist/extensions/pi-subagents/src/shared/model-response-aliases.ts +13 -0
  233. package/dist/extensions/pi-subagents/src/shared/thinking-ceiling.ts +0 -6
  234. package/dist/extensions/pi-subagents/src/shared/types.ts +145 -89
  235. package/dist/extensions/pi-subagents/src/shared/utils.ts +13 -6
  236. package/dist/extensions/pi-subagents/src/shared/watch-strategy.ts +2 -0
  237. package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +18 -13
  238. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +0 -6
  239. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +1 -1
  240. package/dist/extensions/pi-subagents/src/tui/fleet.ts +11 -6
  241. package/dist/extensions/pi-subagents/src/tui/render.ts +277 -46
  242. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +0 -1
  243. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +12 -12
  244. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -3
  245. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +116 -15
  246. package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +15 -18
  247. package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +57 -8
  248. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +19 -19
  249. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +3 -3
  250. package/dist/extensions/pi-subagents/src/workflows/workflow-resources.ts +97 -22
  251. package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +3 -0
  252. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +336 -21
  253. package/dist/extensions/pi-subagents/test/integration/async-execution.part-1.test.ts +1181 -0
  254. package/dist/extensions/pi-subagents/test/integration/async-execution.part-2.test.ts +2240 -0
  255. package/dist/extensions/pi-subagents/test/integration/async-execution.part-3.test.ts +2268 -0
  256. package/dist/extensions/pi-subagents/test/integration/async-execution.part-4.test.ts +1512 -0
  257. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +231 -16
  258. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +4 -18
  259. package/dist/extensions/pi-subagents/test/integration/foreign-workflow-steering.test.ts +95 -0
  260. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +9 -15
  261. package/dist/extensions/pi-subagents/test/integration/in-process-child.test.ts +374 -0
  262. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +20 -10
  263. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +18 -6
  264. package/dist/extensions/pi-subagents/test/integration/recovered-assistant-error.test.ts +151 -0
  265. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +42 -1
  266. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +150 -25
  267. package/dist/extensions/pi-subagents/test/integration/result-publication.test.ts +354 -0
  268. package/dist/extensions/pi-subagents/test/integration/shared-cwd-terminal-evidence.test.ts +117 -0
  269. package/dist/extensions/pi-subagents/test/integration/single-execution.part-1.test.ts +3678 -0
  270. package/dist/extensions/pi-subagents/test/integration/{single-execution.test.ts → single-execution.part-2.test.ts} +500 -4207
  271. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -2
  272. package/dist/extensions/pi-subagents/test/integration/workflow-result-publication.test.ts +127 -0
  273. package/dist/extensions/pi-subagents/test/integration/workflow-steer-inbox.test.ts +365 -0
  274. package/dist/extensions/pi-subagents/test/smoke/pi085-child.ts +31 -0
  275. package/dist/extensions/pi-subagents/test/smoke/pi085-clean-install.mjs +74 -0
  276. package/dist/extensions/pi-subagents/test/smoke/pi085-extension.ts +16 -0
  277. package/dist/extensions/pi-subagents/test/support/async-execution-fixture.ts +699 -0
  278. package/dist/extensions/pi-subagents/test/support/fake-child-session.ts +418 -0
  279. package/dist/extensions/pi-subagents/test/support/foreign-workflow-steer-host.mjs +13 -0
  280. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +30 -76
  281. package/dist/extensions/pi-subagents/test/support/notify-diagnostics-fixture.ts +84 -0
  282. package/dist/extensions/pi-subagents/test/support/result-publication-capacity-preload.mjs +70 -0
  283. package/dist/extensions/pi-subagents/test/support/runner-child-session-factory.ts +17 -0
  284. package/dist/extensions/pi-subagents/test/support/single-execution-fixture.ts +390 -0
  285. package/dist/extensions/pi-subagents/test/unit/acceptance-compaction.test.ts +120 -0
  286. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +24 -3
  287. package/dist/extensions/pi-subagents/test/unit/advertised-agent-prompt.test.ts +87 -0
  288. package/dist/extensions/pi-subagents/test/unit/advertised-agent-refresh.test.ts +163 -0
  289. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +70 -31
  290. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +78 -3
  291. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +2 -8
  292. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +1 -1
  293. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +73 -66
  294. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +51 -1
  295. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +49 -1
  296. package/dist/extensions/pi-subagents/test/unit/async-spawn-preload.test.ts +103 -0
  297. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +3 -3
  298. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +9 -8
  299. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +10 -7
  300. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +48 -6
  301. package/dist/extensions/pi-subagents/test/unit/child-runtime-config.test.ts +90 -0
  302. package/dist/extensions/pi-subagents/test/unit/{pi-args-permission-system.test.ts → child-tool-plan-permission-system.test.ts} +48 -86
  303. package/dist/extensions/pi-subagents/test/unit/child-tool-plan.test.ts +33 -0
  304. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +18 -18
  305. package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +9 -9
  306. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +8 -7
  307. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +225 -65
  308. package/dist/extensions/pi-subagents/test/unit/extension-bindings.test.ts +17 -22
  309. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +11 -1
  310. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +47 -3
  311. package/dist/extensions/pi-subagents/test/unit/foreign-workflow-steering.test.ts +74 -0
  312. package/dist/extensions/pi-subagents/test/unit/fork-cache-key.test.ts +23 -29
  313. package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +96 -4
  314. package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +4 -4
  315. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +116 -55
  316. package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +58 -1
  317. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +104 -3
  318. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +10 -1
  319. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +31 -1
  320. package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +102 -51
  321. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +196 -6
  322. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +181 -38
  323. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +62 -70
  324. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +58 -53
  325. package/dist/extensions/pi-subagents/test/unit/notify-diagnostics.test.ts +42 -0
  326. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +84 -0
  327. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +13 -5
  328. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +21 -5
  329. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +5 -4
  330. package/dist/extensions/pi-subagents/test/unit/permissions.test.ts +2 -6
  331. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +28 -2
  332. package/dist/extensions/pi-subagents/test/unit/pi-web-session-liveness.test.ts +209 -0
  333. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +38 -1
  334. package/dist/extensions/pi-subagents/test/unit/prompt-audit-optional-peers.test.ts +43 -0
  335. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +24 -2
  336. package/dist/extensions/pi-subagents/test/unit/readonly-drain-observation.test.ts +174 -0
  337. package/dist/extensions/pi-subagents/test/unit/readonly-model-continuation.test.ts +94 -0
  338. package/dist/extensions/pi-subagents/test/unit/readonly-session-evidence.test.ts +1329 -0
  339. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +29 -80
  340. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +11 -8
  341. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +2 -2
  342. package/dist/extensions/pi-subagents/test/unit/retained-nested-route-tracker.test.ts +175 -0
  343. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +38 -12
  344. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +2 -5
  345. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +89 -1
  346. package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +2 -16
  347. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +27 -3
  348. package/dist/extensions/pi-subagents/test/unit/scheduled-supervisor-demand.test.ts +215 -0
  349. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +15 -11
  350. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +237 -1
  351. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -5
  352. package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +2 -2
  353. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +14 -1
  354. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +130 -739
  355. package/dist/extensions/pi-subagents/test/unit/supervisor-ask-registration.test.ts +682 -0
  356. package/dist/extensions/pi-subagents/test/unit/supervisor-ui.test.ts +137 -0
  357. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +1 -11
  358. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +23 -3
  359. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +8 -1
  360. package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +19 -1
  361. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +72 -4
  362. package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +17 -3
  363. package/dist/extensions/pi-subagents/test/unit/workflow-child-summary.test.ts +34 -0
  364. package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +12 -2
  365. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +46 -6
  366. package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +3 -3
  367. package/dist/extensions/pi-subagents/test/unit/workflow-resources.test.ts +133 -1
  368. package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +53 -2
  369. package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +222 -14
  370. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +526 -85
  371. package/dist/extensions/pi-web-agent/src/backends/config.ts +1 -0
  372. package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +5 -3
  373. package/dist/extensions/pi-web-agent/src/search/tokenin.ts +7 -3
  374. package/dist/extensions/pi-web-agent/src/types.ts +1 -1
  375. package/dist/extensions/pi-zentui/README.md +17 -0
  376. package/dist/extensions/pi-zentui/assets/screenshots/footer-status-hyperlink.png +0 -0
  377. package/dist/extensions/pi-zentui/docs/configuration.md +71 -2
  378. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +11 -0
  379. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +14 -0
  380. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +29 -13
  381. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +5 -4
  382. package/dist/extensions/pi-zentui/extensions/zentui/footer-text.ts +11 -0
  383. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +11 -10
  384. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +77 -3
  385. package/dist/extensions/pi-zentui/extensions/zentui/presets.ts +77 -0
  386. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +103 -2
  387. package/dist/extensions/pi-zentui/extensions/zentui/working-line-extension-segments.ts +115 -0
  388. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +87 -5
  389. package/dist/extensions/pi-zentui/package-lock.json +277 -517
  390. package/dist/extensions/pi-zentui/package.json +7 -4
  391. package/dist/extensions/pi-zentui/test/config-presets.test.ts +238 -0
  392. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +24 -1
  393. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +570 -2
  394. package/dist/extensions/pi-zentui/test/extension-status.test.ts +90 -0
  395. package/dist/extensions/pi-zentui/test/footer-hyperlink.test.ts +243 -0
  396. package/dist/extensions/pi-zentui/test/package-contents.mjs +1 -0
  397. package/dist/extensions/pi-zentui/test/presets.test.ts +121 -0
  398. package/dist/extensions/pi-zentui/test/settings-command.test.ts +205 -1
  399. package/dist/extensions/pi-zentui/test/working-line-extension-segments.test.ts +139 -0
  400. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +237 -3
  401. package/dist/extensions/pi-zentui/test/working-line.test.ts +94 -2
  402. package/dist/index.d.ts +1 -1
  403. package/dist/main.js +4 -1
  404. package/dist/modes/interactive/chat-viewport.d.ts +19 -0
  405. package/dist/modes/interactive/chat-viewport.js +27 -0
  406. package/dist/modes/interactive/components/assistant-message.d.ts +6 -2
  407. package/dist/modes/interactive/components/assistant-message.js +30 -13
  408. package/dist/modes/interactive/components/custom-editor.d.ts +10 -1
  409. package/dist/modes/interactive/components/custom-editor.js +39 -1
  410. package/dist/modes/interactive/components/first-time-setup.js +1 -1
  411. package/dist/modes/interactive/components/index.d.ts +1 -1
  412. package/dist/modes/interactive/components/markdown-transform.d.ts +2 -0
  413. package/dist/modes/interactive/components/markdown-transform.js +18 -0
  414. package/dist/modes/interactive/components/model-selector.js +19 -17
  415. package/dist/modes/interactive/components/scoped-models-selector.js +21 -23
  416. package/dist/modes/interactive/components/settings-selector.d.ts +7 -1
  417. package/dist/modes/interactive/components/settings-selector.js +128 -20
  418. package/dist/modes/interactive/components/settings-submenu.d.ts +71 -0
  419. package/dist/modes/interactive/components/settings-submenu.js +164 -0
  420. package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
  421. package/dist/modes/interactive/components/status-indicator.js +10 -3
  422. package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
  423. package/dist/modes/interactive/components/thinking-selector.js +75 -18
  424. package/dist/modes/interactive/components/tool-execution.d.ts +21 -4
  425. package/dist/modes/interactive/components/tool-execution.js +35 -33
  426. package/dist/modes/interactive/components/trust-selector.js +2 -2
  427. package/dist/modes/interactive/interactive-mode.d.ts +13 -15
  428. package/dist/modes/interactive/interactive-mode.js +144 -80
  429. package/dist/modes/interactive/session-share.d.ts +15 -0
  430. package/dist/modes/interactive/session-share.js +183 -0
  431. package/dist/modes/interactive/theme/dark.json +2 -1
  432. package/dist/modes/interactive/theme/light.json +2 -1
  433. package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
  434. package/dist/modes/interactive/theme/theme-controller.js +5 -0
  435. package/dist/modes/interactive/theme/theme-json.d.ts +83 -0
  436. package/dist/modes/interactive/theme/theme-json.js +129 -0
  437. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  438. package/dist/modes/interactive/theme/theme.d.ts +14 -5
  439. package/dist/modes/interactive/theme/theme.js +18 -119
  440. package/dist/modes/interactive/tui-renderer.d.ts +20 -0
  441. package/dist/modes/interactive/tui-renderer.js +65 -0
  442. package/dist/utils/exif-orientation.js +2 -3
  443. package/dist/utils/management-http.d.ts +24 -0
  444. package/dist/utils/management-http.js +53 -0
  445. package/dist/utils/text.d.ts +7 -0
  446. package/dist/utils/text.js +8 -0
  447. package/dist/utils/tools-manager.d.ts +1 -0
  448. package/dist/utils/tools-manager.js +43 -21
  449. package/docs/docs.json +0 -4
  450. package/package.json +4 -4
  451. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +0 -415
  452. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +0 -1059
  453. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +0 -116
  454. package/dist/extensions/pi-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
  455. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +0 -224
  456. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +0 -6986
  457. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +0 -447
  458. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +0 -185
  459. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +0 -311
  460. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +0 -84
  461. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +0 -202
  462. package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +0 -101
  463. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +0 -2347
  464. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +0 -104
  465. package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +0 -49
  466. package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -1465
  467. package/dist/skills/imagegen-frontend-web/SKILL.md +0 -987
  468. package/dist/skills/implanger/SKILL.md +0 -70
  469. package/dist/skills/output/SKILL.md +0 -49
  470. package/dist/skills/planger/SKILL.md +0 -167
  471. package/dist/skills/workflow/SKILL.md +0 -95
  472. package/docs/workflows.md +0 -18
  473. /package/dist/bun/{register-bedrock.d.ts → runtime-setup.d.ts} +0 -0
@@ -2,8 +2,7 @@
2
2
  * Core execution logic for running subagents
3
3
  */
4
4
 
5
- import { spawn } from "node:child_process";
6
- import { existsSync } from "node:fs";
5
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
7
6
  import * as path from "node:path";
8
7
  import type { Message } from "@earendil-works/pi-ai";
9
8
  import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig } from "../../agents/agents.ts";
@@ -31,7 +30,6 @@ import {
31
30
  type AcceptanceLedger,
32
31
  type ResolvedAcceptanceConfig,
33
32
  truncateOutput,
34
- getSubagentDepthEnv,
35
33
  } from "../../shared/types.ts";
36
34
  import {
37
35
  DEFAULT_CONTROL_CONFIG,
@@ -60,22 +58,22 @@ import { effectiveToolTimeoutMs, formatToolTimeoutMessage, resolveToolTimeoutMs,
60
58
  import { evaluateCompletionMutationGuard, expectsImplementationMutation, hasMutationToolCapability, validateImplementationToolContract } from "../shared/completion-guard.ts";
61
59
  import { planCompletionEvidence } from "../shared/completion-evidence.ts";
62
60
  import { planAbortRecovery } from "../shared/abort-recovery.ts";
61
+ import { planReadonlyModelContinuation, type LogicalRecoveryState } from "../shared/readonly-model-continuation.ts";
62
+ import { getReadonlySessionEvidence, requestReadonlySessionEvidence, type SettledReadonlyEvidence } from "../shared/readonly-session-evidence.ts";
63
+ import { getReadonlyChildModels } from "../shared/child-session.ts";
63
64
  import { arbitrateCompletionGuardRescue } from "../shared/llm-intent-arbiter.ts";
64
- import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
65
65
  import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
66
66
  import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
67
67
  import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
68
- import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
69
68
  import { resolvePermissionRules } from "../shared/permissions.ts";
70
- import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, deriveForkPromptCacheKey, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, type SubagentTaskDelivery } from "../shared/pi-args.ts";
69
+ import { applyThinkingSuffix, deriveForkPromptCacheKey } from "../shared/child-tool-plan.ts";
71
70
  import { deriveChildSessionName } from "../../shared/child-session-name.ts";
72
- import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
73
- import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.ts";
71
+ import { assertAgentAllowedByCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
74
72
  import { resolveEffectiveThinking } from "../../shared/model-info.ts";
75
- import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV } from "../../shared/thinking-ceiling.ts";
76
- import { clearStructuredOutputCaptures, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
77
- import { formatMidToolExitError, formatProcessSignalError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
78
- import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
73
+ import { assertThinkingWithinCeiling, intersectThinkingCeilings } from "../../shared/thinking-ceiling.ts";
74
+ import { MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR, MISSING_STRUCTURED_OUTPUT_CALL_ERROR } from "../shared/structured-output.ts";
75
+ import { formatMidToolExitError, isOrdinaryToolForMidToolExit } from "../shared/process-signal.ts";
76
+ import { formatChildToolDiagnostic } from "../shared/tool-availability.ts";
79
77
  import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
80
78
  import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, hasSingleOutputChangedSinceSnapshot, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
81
79
  import {
@@ -86,14 +84,6 @@ import {
86
84
  isRetryableModelFailureAttempt,
87
85
  recordRetryableModelFailure,
88
86
  } from "../shared/model-fallback.ts";
89
- import {
90
- SUBAGENT_STARTUP_RETRY_DELAYS_MS,
91
- formatSubagentExtensionConflictError,
92
- formatSubagentStartupRetryExhaustedError,
93
- formatSubagentStartupRetryNote,
94
- isRetryableSubagentStartupFailure,
95
- waitForSubagentStartupRetry,
96
- } from "../shared/subagent-startup-retry.ts";
97
87
  import {
98
88
  createMutatingFailureState,
99
89
  didMutatingToolFail,
@@ -107,12 +97,12 @@ import {
107
97
  } from "../shared/long-running-guard.ts";
108
98
  import { acceptanceFailureMessage, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport, validateAcceptanceInput } from "../shared/acceptance.ts";
109
99
  import { PROMPT_REDACTED } from "../../shared/utils.ts";
110
- import { attachContractProjections, isAgentContractV1 } from "../shared/agent-contract.ts";
100
+ import { attachContractProjections, isAgentContract } from "../shared/agent-contract.ts";
111
101
  import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
112
102
  import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
113
103
  import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
114
104
  import { consumeWorkflowChildPermit } from "../../shared/workflow-child-permit.ts";
115
- import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ChildLifecycleState, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
105
+ import { projectChildLifecycle, type ChildLifecycleAction, type ChildLifecycleState } from "../shared/child-lifecycle.ts";
116
106
  import {
117
107
  acceptChildWatchdogEvent,
118
108
  applyChildWatchdogMessage,
@@ -120,7 +110,10 @@ import {
120
110
  isChildWatchdogStatusEvent,
121
111
  resolveChildWatchdogConfig,
122
112
  type ChildWatchdogStateSnapshot,
113
+ type ChildWatchdogStatusEvent,
123
114
  } from "../../watchdog/child-status.ts";
115
+ import { buildInProcessChildLaunch, createReportedChildSessionInput } from "../shared/child-launch.ts";
116
+ import { childSessionFactory, projectChildSessionEventForJson, type ChildSession, type ChildSessionEvent } from "../shared/child-session.ts";
124
117
 
125
118
  const artifactOutputByResult = new WeakMap<SingleResult, string>();
126
119
  const acceptanceOutputByResult = new WeakMap<SingleResult, string>();
@@ -288,8 +281,8 @@ function snapshotResult(result: SingleResult, progress: AgentProgress): SingleRe
288
281
  /**
289
282
  * Streaming variant of snapshotResult for `onUpdate` progress events: it drops the
290
283
  * unbounded `messages` transcript in favour of compact tool-call summaries so a
291
- * single `tool_execution_update` line stays well under the child-stdout protocol
292
- * cap (`MAX_CHILD_PENDING_LINE_BYTES`). Non-streaming consumers such as
284
+ * single `tool_execution_update` event stays small; the parent records every
285
+ * one in its transcript and `events.jsonl`. Non-streaming consumers such as
293
286
  * foreground detach receipts and terminal consumers use snapshotResult directly.
294
287
  */
295
288
  function snapshotStreamResult(result: SingleResult, progress: AgentProgress): SingleResult {
@@ -350,6 +343,10 @@ function structuredDelegationProgressChanged(
350
343
 
351
344
  const AFTER_COMPACTION_SETTLEMENT = Symbol("afterCompactionSettlement");
352
345
  type AbortRecoverySingleResult = SingleResult & { [AFTER_COMPACTION_SETTLEMENT]?: true };
346
+ const settledReadonlySource = new WeakMap<SingleResult, ChildSession>();
347
+
348
+ const STOPPED_BEFORE_COMPLETION_ERROR = "Subagent stopped before completion.";
349
+
353
350
 
354
351
  async function runSingleAttempt(
355
352
  runtimeCwd: string,
@@ -360,6 +357,7 @@ async function runSingleAttempt(
360
357
  shared: {
361
358
  sessionEnabled: boolean;
362
359
  systemPrompt: string;
360
+ acceptancePrompt: string;
363
361
  resolvedSkillNames?: string[];
364
362
  modelCandidates?: string[];
365
363
  skillsWarning?: string;
@@ -369,10 +367,12 @@ async function runSingleAttempt(
369
367
  attemptNotes: string[];
370
368
  outputSnapshot?: SingleOutputSnapshot;
371
369
  originalTask?: string;
372
- taskDelivery?: SubagentTaskDelivery;
373
370
  orcaProgressTab?: OrcaProgressTab;
374
371
  launchWarnings: { emitted: boolean };
375
372
  verifyModel: boolean;
373
+ readonlyExpected?: SettledReadonlyEvidence;
374
+ readonlyModel?: string;
375
+ readonlyHandoffAllowed?: () => boolean;
376
376
  },
377
377
  ): Promise<SingleResult> {
378
378
  const effectiveThinking = options.thinkingOverride ?? agent.thinking;
@@ -380,9 +380,9 @@ async function runSingleAttempt(
380
380
  assertThinkingWithinCeiling({ model: modelArg, configThinking: effectiveThinking, ceiling: options.thinkingCeiling, agent: agent.name, runId: options.runId });
381
381
  const expectedModelForVerification = shared.verifyModel ? modelArg : undefined;
382
382
  const resolvedThinking = resolveEffectiveThinking(modelArg, effectiveThinking);
383
- // Display name for the child session: applied inside the child via
384
- // SELESAI_SUBAGENT_SESSION_NAME and echoed back on the result payload so hosts
385
- // can label this run without reading the child's session file.
383
+ // Display name for the child session: applied inside the child through its
384
+ // runtime config and echoed back on the result payload so hosts can label
385
+ // this run without reading the child's session file.
386
386
  const childSessionName = deriveChildSessionName({ agent: agent.name, task: shared.originalTask ?? task });
387
387
  const watchdogConfig = resolveWatchdogConfig(options.cwd ?? runtimeCwd);
388
388
  const childWatchdog = watchdogConfig.ok
@@ -397,15 +397,12 @@ async function runSingleAttempt(
397
397
  const permissionAuditPath = permissionRules && options.artifactsDir
398
398
  ? path.join(options.artifactsDir, "permission-audit", `${options.runId}-${options.index ?? 0}.jsonl`)
399
399
  : undefined;
400
- const { args, env: sharedEnv, tempDir, toolDiagnosticPath, runtimeAcknowledgedExtensionsPath, capabilityAudit, warnings } = buildPiArgs({
401
- baseArgs: ["--mode", "json", "-p"],
402
- task,
403
- taskDelivery: shared.taskDelivery,
400
+ let onWatchdogStatus: ((event: ChildWatchdogStatusEvent) => void) | undefined;
401
+ const launch = buildInProcessChildLaunch({
404
402
  sessionEnabled: shared.sessionEnabled,
405
403
  sessionDir: options.sessionDir,
406
404
  sessionFile: options.sessionFile,
407
405
  model: modelArg,
408
- thinking: effectiveThinking,
409
406
  systemPromptMode: agent.systemPromptMode,
410
407
  inheritProjectContext: agent.inheritProjectContext,
411
408
  inheritGlobalContext: agent.inheritGlobalContext,
@@ -416,66 +413,45 @@ async function runSingleAttempt(
416
413
  allowNestedSubagents: agent.allowNestedSubagents,
417
414
  extensions: agent.extensions,
418
415
  subagentOnlyExtensions: agent.subagentOnlyExtensions,
419
- systemPrompt: shared.systemPrompt,
416
+ // Exact terminal protocol belongs to session resources, not compactable history.
417
+ systemPrompt: shared.acceptancePrompt ? `${shared.systemPrompt}\n${shared.acceptancePrompt}` : shared.systemPrompt,
420
418
  mcpDirectTools: agent.mcpDirectTools,
421
419
  cwd: options.cwd ?? runtimeCwd,
422
- promptFileStem: agent.name,
423
420
  intercomSessionName: options.intercomSessionName,
424
421
  sessionName: childSessionName,
425
422
  orchestratorIntercomTarget: options.orchestratorIntercomTarget,
426
423
  runId: options.runId,
427
424
  childAgentName: agent.name,
428
425
  childIndex: options.index ?? 0,
429
- parentEventSink: options.nestedRoute?.eventSink,
430
- parentControlInbox: options.nestedRoute?.controlInbox,
431
- parentRootRunId: options.nestedRoute?.rootRunId,
432
- parentCapabilityToken: options.nestedRoute?.capabilityToken,
426
+ nestedRoute: options.nestedRoute,
433
427
  runFanoutBudget: options.runFanoutBudget,
434
428
  parentSessionId: options.parentSessionId,
435
429
  forkCacheKey: options.context === "fork" ? deriveForkPromptCacheKey(options.parentSessionId) : undefined,
436
- steerInboxDir: options.steerInboxDir,
437
- steerCapabilityPath: options.steerCapabilityPath,
438
- steerAckDir: options.steerAckDir,
439
430
  structuredOutput: options.structuredOutput,
440
431
  fast: options.fast ?? agent.fast,
441
432
  modelCandidates: shared.modelCandidates,
442
433
  toolBudget: options.toolBudget,
443
- allowZeroToolBudget: options.allowZeroToolBudget,
444
434
  permissionRules,
445
435
  permissionAuditPath,
446
436
  childWatchdog,
437
+ // registerChildWatchdog returns before reading the sink when no watchdog exists.
438
+ watchdogStatus: childWatchdog ? (event) => onWatchdogStatus?.(event) : undefined,
447
439
  waitToolEnabled: options.waitToolEnabled,
448
440
  waitToolDefaultTimeoutMs: options.waitToolDefaultTimeoutMs,
449
441
  capabilityCeiling: options.capabilityCeiling,
450
442
  thinkingCeiling: options.thinkingCeiling,
451
- extensionBindings: options.extensionBindings,
443
+ maxSubagentDepth: options.maxSubagentDepth,
452
444
  runtimeSnapshotHost: options.runtimeSnapshotHost,
445
+ inherited: options.childRuntime,
446
+ host: "parent",
453
447
  });
448
+ const { toolPlan, capabilityAudit, warnings, launchResolvedExtensions, capture } = launch;
454
449
  if (!shared.launchWarnings.emitted && warnings.length > 0) {
455
450
  for (const warning of warnings) console.warn(`[pi-subagents] ${warning}`);
456
451
  shared.launchWarnings.emitted = true;
457
452
  }
458
453
 
459
454
  const effectiveSystemPrompt = shared.systemPrompt;
460
- const toolPlan = resolvePiLaunchToolPlan({
461
- tools: agent.tools,
462
- allowNestedSubagents: agent.allowNestedSubagents,
463
- extensions: agent.extensions,
464
- subagentOnlyExtensions: agent.subagentOnlyExtensions,
465
- mcpDirectTools: agent.mcpDirectTools,
466
- cwd: options.cwd ?? runtimeCwd,
467
- requireReadTool: Boolean(shared.resolvedSkillNames?.length),
468
- structuredOutput: Boolean(options.structuredOutput),
469
- excludeTools: agent.excludeTools,
470
- fast: options.fast ?? agent.fast,
471
- model: modelArg,
472
- modelCandidates: shared.modelCandidates,
473
- capabilityCeiling: options.capabilityCeiling,
474
- inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
475
- agentName: agent.name,
476
- permissionRules,
477
- runtimeSnapshotHost: options.runtimeSnapshotHost,
478
- });
479
455
  const contractTools = toolPlan.explicitToolAllowlist ? toolPlan.effectiveToolAllowlist : undefined;
480
456
  const contractError = validateImplementationToolContract({
481
457
  agent: agent.name,
@@ -488,7 +464,6 @@ async function runSingleAttempt(
488
464
  completionGuard: agent.completionGuard,
489
465
  });
490
466
  if (contractError) {
491
- cleanupTempDir(tempDir);
492
467
  return {
493
468
  index: options.index ?? 0,
494
469
  agent: agent.name,
@@ -507,7 +482,6 @@ async function runSingleAttempt(
507
482
  ...(toolPlan.capabilityAudit ? { capabilityAudit: toolPlan.capabilityAudit } : {}),
508
483
  };
509
484
  }
510
- const launchResolvedExtensions = projectLaunchResolvedChildExtensions(toolPlan);
511
485
  const launchContractDigest = launchBindingDigest({
512
486
  definitionDigest: agentDefinitionDigest(agent),
513
487
  task: shared.originalTask ?? task,
@@ -554,17 +528,6 @@ async function runSingleAttempt(
554
528
  ...(capabilityAudit ? { capabilityAudit } : {}),
555
529
  }, options.context);
556
530
  const startTime = Date.now();
557
- if (options.structuredOutput) {
558
- const cleanupError = clearStructuredOutputCaptures(options.structuredOutput);
559
- if (cleanupError) {
560
- cleanupTempDir(tempDir);
561
- result.exitCode = 1;
562
- result.error = cleanupError;
563
- result.finalOutput = cleanupError;
564
- result.progressSummary = { toolCount: 0, tokens: 0, durationMs: Date.now() - startTime };
565
- return result;
566
- }
567
- }
568
531
  const controlConfig = options.controlConfig ?? DEFAULT_CONTROL_CONFIG;
569
532
  let interruptedByControl = false;
570
533
  const allControlEvents: ControlEvent[] = [];
@@ -599,7 +562,6 @@ async function runSingleAttempt(
599
562
  result.progress = progress;
600
563
  const attemptTimeout = resolveAttemptTimeout(options);
601
564
  if (attemptTimeout?.remainingMs === 0) {
602
- cleanupTempDir(tempDir);
603
565
  result.exitCode = 1;
604
566
  result.timedOut = true;
605
567
  result.error = attemptTimeout.message;
@@ -613,7 +575,6 @@ async function runSingleAttempt(
613
575
  };
614
576
  return result;
615
577
  }
616
- const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };
617
578
  const mutationSnapshot = snapshotTrackedMutations(options.cwd ?? runtimeCwd);
618
579
  let observedMutationAttempt = false;
619
580
  let structuredOutputToolInvoked = false;
@@ -621,7 +582,6 @@ async function runSingleAttempt(
621
582
  let toolAvailabilityError: string | undefined;
622
583
  let abortedBySignal = options.signal?.aborted === true;
623
584
 
624
- const spawnSpec = getPiSpawnCommand(args);
625
585
  if (options.workflowChildPermitLaunch) {
626
586
  const permitError = consumeWorkflowChildPermit(options.workflowChildPermitLaunch.permit, {
627
587
  workflowRunId: options.workflowChildPermitLaunch.workflowRunId,
@@ -632,7 +592,6 @@ async function runSingleAttempt(
632
592
  runner: "pi",
633
593
  });
634
594
  if (permitError) {
635
- cleanupTempDir(tempDir);
636
595
  result.exitCode = 1;
637
596
  result.error = permitError;
638
597
  result.finalOutput = permitError;
@@ -641,16 +600,13 @@ async function runSingleAttempt(
641
600
  return result;
642
601
  }
643
602
  }
603
+ const childSessions = options.childSessionFactory ?? childSessionFactory();
644
604
  let afterCompactionSettlement = false;
645
605
  const exitCode = await new Promise<number>((resolve) => {
646
- const proc = spawn(spawnSpec.command, spawnSpec.args, {
647
- cwd: options.cwd ?? runtimeCwd,
648
- env: spawnEnv,
649
- stdio: ["ignore", "pipe", "pipe"],
650
- windowsHide: true,
651
- });
652
- const jsonlWriter = createJsonlWriter(shared.jsonlPath, proc.stdout);
653
- let processClosed = false;
606
+ const jsonlWriter = createJsonlWriter(shared.jsonlPath, { pause() {}, resume() {} });
607
+ let session: ChildSession | undefined;
608
+ let unsubscribe: (() => void) | undefined;
609
+ let sessionSettled = false;
654
610
  let lifecycleFinished = false;
655
611
  let detached = false;
656
612
  let intercomStarted = false;
@@ -659,26 +615,26 @@ async function runSingleAttempt(
659
615
  let removeInterruptListener: (() => void) | undefined;
660
616
  let activityTimer: NodeJS.Timeout | undefined;
661
617
  let timeoutTimer: NodeJS.Timeout | undefined;
662
- let timeoutTerminationTimer: NodeJS.Timeout | undefined;
663
- let timeoutHardKillTimer: NodeJS.Timeout | undefined;
664
- let protocolHardKillTimer: NodeJS.Timeout | undefined;
618
+ let timeoutHardFinishTimer: NodeJS.Timeout | undefined;
665
619
  const clearTimeoutTimers = () => {
666
620
  if (timeoutTimer) {
667
621
  clearTimeout(timeoutTimer);
668
622
  timeoutTimer = undefined;
669
623
  }
670
- if (timeoutTerminationTimer) {
671
- clearTimeout(timeoutTerminationTimer);
672
- timeoutTerminationTimer = undefined;
673
- }
674
- if (timeoutHardKillTimer) {
675
- clearTimeout(timeoutHardKillTimer);
676
- timeoutHardKillTimer = undefined;
624
+ if (timeoutHardFinishTimer) {
625
+ clearTimeout(timeoutHardFinishTimer);
626
+ timeoutHardFinishTimer = undefined;
677
627
  }
678
628
  };
629
+ const abortChild = (): void => {
630
+ if (!session || sessionSettled || lifecycleFinished) return;
631
+ void session.abort().catch(() => {
632
+ // The session settles through its prompt promise; abort failures are not separately actionable.
633
+ });
634
+ };
679
635
 
680
636
  const detachForeground = (reason: string): boolean => {
681
- if (detached || processClosed || lifecycleFinished || options.signal?.aborted) return false;
637
+ if (detached || sessionSettled || lifecycleFinished || options.signal?.aborted) return false;
682
638
  const receiptProgress = snapshotProgress(progress);
683
639
  receiptProgress.status = "detached";
684
640
  receiptProgress.durationMs = Date.now() - startTime;
@@ -713,20 +669,21 @@ async function runSingleAttempt(
713
669
  }
714
670
  if (!accepted) return false;
715
671
  detached = true;
672
+ if (session) session.detached = true;
716
673
  return true;
717
674
  };
718
675
 
719
- // If the child emits a terminal assistant stop but never exits,
720
- // give it a short grace period to flush naturally, then clean it up.
676
+ // If the child emits a terminal assistant stop but its run never settles
677
+ // (a hook is stuck), abort it after a short grace period and then finish
678
+ // without it.
721
679
  const FINAL_STOP_GRACE_MS = 1000;
722
- const HARD_KILL_MS = 3000;
723
- let childExited = false;
724
- let forcedTerminationSignal = false;
680
+ const HARD_FINISH_MS = 3000;
681
+ let forcedTermination = false;
725
682
  let cleanTerminalAssistantStopReceived = false;
726
683
  let agentSettledReceived = false;
727
684
  let compactionStartedReceived = false;
728
685
  let finalDrainTimer: NodeJS.Timeout | undefined;
729
- let finalHardKillTimer: NodeJS.Timeout | undefined;
686
+ let finalHardFinishTimer: NodeJS.Timeout | undefined;
730
687
  let watchdogTailTimer: NodeJS.Timeout | undefined;
731
688
  let childWatchdogState: ChildWatchdogStateSnapshot | undefined;
732
689
  const updateChildWatchdogState = (snapshot: ChildWatchdogStateSnapshot): void => {
@@ -745,9 +702,9 @@ async function runSingleAttempt(
745
702
  clearTimeout(finalDrainTimer);
746
703
  finalDrainTimer = undefined;
747
704
  }
748
- if (finalHardKillTimer) {
749
- clearTimeout(finalHardKillTimer);
750
- finalHardKillTimer = undefined;
705
+ if (finalHardFinishTimer) {
706
+ clearTimeout(finalHardFinishTimer);
707
+ finalHardFinishTimer = undefined;
751
708
  }
752
709
  };
753
710
  const startFinalDrain = () => {
@@ -755,25 +712,24 @@ async function runSingleAttempt(
755
712
  armWatchdogTail();
756
713
  return;
757
714
  }
758
- if (childExited || finalDrainTimer || lifecycleFinished || processClosed) return;
715
+ if (sessionSettled || finalDrainTimer || lifecycleFinished) return;
759
716
  finalDrainTimer = setTimeout(() => {
760
- if (lifecycleFinished || processClosed) return;
761
- const termSent = trySignalChild(proc, "SIGTERM");
762
- if (!termSent) return;
763
- forcedTerminationSignal = true;
717
+ if (lifecycleFinished || sessionSettled) return;
718
+ forcedTermination = true;
764
719
  if (!cleanTerminalAssistantStopReceived && !agentSettledReceived && !assistantError) {
765
- result.error = result.error ?? `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its terminal event. Forcing termination.`;
720
+ result.error = result.error ?? `Subagent session did not settle within ${FINAL_STOP_GRACE_MS}ms after its terminal event. Aborting it.`;
766
721
  }
767
- finalHardKillTimer = setTimeout(() => {
768
- if (lifecycleFinished || processClosed) return;
769
- forcedTerminationSignal = trySignalChild(proc, "SIGKILL") || forcedTerminationSignal;
770
- }, HARD_KILL_MS);
771
- finalHardKillTimer.unref?.();
722
+ abortChild();
723
+ finalHardFinishTimer = setTimeout(() => {
724
+ if (lifecycleFinished || sessionSettled) return;
725
+ settle(undefined, true);
726
+ }, HARD_FINISH_MS);
727
+ finalHardFinishTimer.unref?.();
772
728
  }, FINAL_STOP_GRACE_MS);
773
729
  finalDrainTimer.unref?.();
774
730
  };
775
731
  function armWatchdogTail(): void {
776
- if ((!cleanTerminalAssistantStopReceived && !agentSettledReceived) || watchdogTailTimer || lifecycleFinished || processClosed) return;
732
+ if ((!cleanTerminalAssistantStopReceived && !agentSettledReceived) || watchdogTailTimer || lifecycleFinished || sessionSettled) return;
777
733
  watchdogTailTimer = setTimeout(() => {
778
734
  watchdogTailTimer = undefined;
779
735
  updateChildWatchdogState({
@@ -799,7 +755,7 @@ async function runSingleAttempt(
799
755
  const childLifecycleState: ChildLifecycleState = { compactionRetryActive: false };
800
756
 
801
757
  const unsubscribeIntercomDetach = options.intercomEvents?.on?.(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {
802
- if (!options.allowIntercomDetach || processClosed) return;
758
+ if (!options.allowIntercomDetach || sessionSettled) return;
803
759
  if (!payload || typeof payload !== "object") return;
804
760
  const event = payload as { requestId?: unknown; runId?: unknown; agent?: unknown; childIndex?: unknown };
805
761
  const requestId = event.requestId;
@@ -819,13 +775,8 @@ async function runSingleAttempt(
819
775
  lifecycleFinished = true;
820
776
  clearFinalDrainTimers();
821
777
  clearWatchdogTailTimer();
822
- clearStdioGuard();
823
778
  clearTimeoutTimers();
824
779
  clearAllToolTimeouts();
825
- if (protocolHardKillTimer) {
826
- clearTimeout(protocolHardKillTimer);
827
- protocolHardKillTimer = undefined;
828
- }
829
780
  if (activityTimer) {
830
781
  clearInterval(activityTimer);
831
782
  activityTimer = undefined;
@@ -833,7 +784,16 @@ async function runSingleAttempt(
833
784
  unsubscribeIntercomDetach?.();
834
785
  removeAbortListener?.();
835
786
  removeInterruptListener?.();
836
- resolve(code);
787
+ unsubscribe?.();
788
+ onWatchdogStatus = undefined;
789
+ void jsonlWriter.close().catch(() => {
790
+ // JSONL artifact flush is best effort.
791
+ });
792
+ // Report the run only after the child's extensions have shut down.
793
+ void Promise.resolve().then(() => session?.dispose()).catch(() => undefined).then(() => {
794
+ if (session && getReadonlySessionEvidence(session)) settledReadonlySource.set(result, session);
795
+ resolve(code);
796
+ });
837
797
  };
838
798
 
839
799
  const drainPendingControlEvents = (): ControlEvent[] | undefined => {
@@ -996,7 +956,7 @@ async function runSingleAttempt(
996
956
 
997
957
  let lastStructuredDelegationProgressState: StructuredDelegationProgressState | undefined;
998
958
  const emitUpdateSnapshot = (text: string) => {
999
- if (!options.onUpdate || processClosed) return;
959
+ if (!options.onUpdate || sessionSettled) return;
1000
960
  const progressSnapshot = snapshotProgress(progress);
1001
961
  const resultSnapshot = snapshotStreamResult(result, progressSnapshot);
1002
962
  const controlEvents = drainPendingControlEvents();
@@ -1012,7 +972,7 @@ async function runSingleAttempt(
1012
972
  };
1013
973
 
1014
974
  const fireUpdate = () => {
1015
- if (!options.onUpdate || processClosed) return;
975
+ if (!options.onUpdate || sessionSettled) return;
1016
976
  progress.durationMs = Date.now() - startTime;
1017
977
  if (options.suppressUnchangedDelegationUpdates) {
1018
978
  if (lastStructuredDelegationProgressState && !structuredDelegationProgressChanged(lastStructuredDelegationProgressState, progress, result)) return;
@@ -1022,20 +982,9 @@ async function runSingleAttempt(
1022
982
  emitUpdateSnapshot(output || "(running...)");
1023
983
  };
1024
984
 
1025
- const rawStdoutTail = createBoundedByteTail();
1026
- const processLine = (line: string) => {
1027
- if (!line.trim()) return;
1028
- jsonlWriter.writeLine(line);
1029
- let evt: { type?: string; message?: Message; toolName?: string; toolCallId?: string; args?: unknown; willRetry?: unknown };
1030
- try {
1031
- evt = JSON.parse(line) as { type?: string; message?: Message; toolName?: string; toolCallId?: string; args?: unknown; willRetry?: unknown };
1032
- } catch {
1033
- rawStdoutTail.push(`${line}\n`);
1034
- shared.transcriptWriter?.writeStdoutLine(line);
1035
- shared.orcaProgressTab?.append(`${line}\n`);
1036
- // Non-JSON stdout lines are expected; only structured events are parsed.
1037
- return;
1038
- }
985
+ const processEvent = (evt: ChildSessionEvent & { message?: Message; toolName?: string; toolCallId?: string; args?: unknown; willRetry?: unknown }) => {
986
+ if (lifecycleFinished) return;
987
+ jsonlWriter.writeLine(JSON.stringify(projectChildSessionEventForJson(evt)));
1039
988
  shared.transcriptWriter?.writeChildEvent(evt);
1040
989
  shared.orcaProgressTab?.event(evt);
1041
990
  if (evt.type === "compaction_start") compactionStartedReceived = true;
@@ -1047,6 +996,20 @@ async function runSingleAttempt(
1047
996
  compactionStartedReceived = false;
1048
997
  afterCompactionSettlement = false;
1049
998
  }
999
+ if (evt.type === "agent_start") {
1000
+ const diagnostic = capture.toolDiagnostic();
1001
+ if (diagnostic) {
1002
+ const message = formatChildToolDiagnostic(diagnostic, { host: "parent" });
1003
+ toolAvailabilityError = message;
1004
+ result.error = message;
1005
+ result.finalOutput = message;
1006
+ progress.status = "failed";
1007
+ progress.error = message;
1008
+ fireUpdate();
1009
+ abortChild();
1010
+ return;
1011
+ }
1012
+ }
1050
1013
  const lifecycleAction = projectChildLifecycle(evt, false, childLifecycleState);
1051
1014
  if (evt.type === "agent_settled" && lifecycleAction === "start-drain") {
1052
1015
  agentSettledReceived = true;
@@ -1150,14 +1113,18 @@ async function runSingleAttempt(
1150
1113
  progress.model = evt.message.model;
1151
1114
  if (!result.model) result.model = evt.message.model;
1152
1115
  if (expectedModelForVerification && !hasToolCall) {
1153
- const modelVerificationError = formatSubagentModelVerificationError(expectedModelForVerification, evt.message.model, options.availableModels);
1116
+ const modelVerificationError = formatSubagentModelVerificationError(expectedModelForVerification, evt.message.model, options.availableModels, options.modelResponseAliases);
1154
1117
  if (modelVerificationError && !result.error) result.error = modelVerificationError;
1155
1118
  }
1156
1119
  }
1157
1120
  if (evt.message.errorMessage) assistantError = evt.message.errorMessage;
1121
+ else if (hasToolCall && evt.message.stopReason === "toolUse") {
1122
+ // A recovered request can finish via a terminating tool, without a text stop.
1123
+ assistantError = undefined;
1124
+ }
1158
1125
  const assistantText = extractTextFromContent(evt.message.content);
1159
1126
  appendRecentOutput(progress, assistantText.split("\n").slice(-10));
1160
- // Final assistant message: start the exit drain window.
1127
+ // Final assistant message: start the settle drain window.
1161
1128
  if (terminalAssistantStop) {
1162
1129
  if (!evt.message.errorMessage && assistantText.trim()) assistantError = undefined;
1163
1130
  cleanTerminalAssistantStopReceived ||= !evt.message.errorMessage;
@@ -1219,11 +1186,12 @@ async function runSingleAttempt(
1219
1186
  fireUpdate();
1220
1187
  }
1221
1188
  };
1189
+ onWatchdogStatus = (event) => processEvent(event as unknown as Parameters<typeof processEvent>[0]);
1222
1190
 
1223
1191
  fireUpdate();
1224
1192
  if (controlConfig.enabled || options.onUpdate) {
1225
1193
  activityTimer = setInterval(() => {
1226
- if (processClosed || lifecycleFinished) {
1194
+ if (sessionSettled || lifecycleFinished) {
1227
1195
  return;
1228
1196
  }
1229
1197
  updateActivityState(Date.now());
@@ -1234,7 +1202,7 @@ async function runSingleAttempt(
1234
1202
 
1235
1203
  if (attemptTimeout) {
1236
1204
  timeoutTimer = setTimeout(() => {
1237
- if (processClosed || lifecycleFinished || interruptedByControl) return;
1205
+ if (sessionSettled || lifecycleFinished || interruptedByControl) return;
1238
1206
  result.timedOut = true;
1239
1207
  clearAllToolTimeouts();
1240
1208
  result.error = attemptTimeout.message;
@@ -1243,17 +1211,12 @@ async function runSingleAttempt(
1243
1211
  progress.error = attemptTimeout.message;
1244
1212
  progress.durationMs = Date.now() - startTime;
1245
1213
  fireUpdate();
1246
- trySignalChild(proc, "SIGINT");
1247
- timeoutTerminationTimer = setTimeout(() => {
1248
- if (processClosed || lifecycleFinished) return;
1249
- trySignalChild(proc, "SIGTERM");
1250
- }, 1000);
1251
- timeoutTerminationTimer.unref?.();
1252
- timeoutHardKillTimer = setTimeout(() => {
1253
- if (processClosed || lifecycleFinished) return;
1254
- trySignalChild(proc, "SIGKILL");
1214
+ abortChild();
1215
+ timeoutHardFinishTimer = setTimeout(() => {
1216
+ if (sessionSettled || lifecycleFinished) return;
1217
+ settle(undefined, true);
1255
1218
  }, 4000);
1256
- timeoutHardKillTimer.unref?.();
1219
+ timeoutHardFinishTimer.unref?.();
1257
1220
  }, attemptTimeout.remainingMs);
1258
1221
  timeoutTimer.unref?.();
1259
1222
  }
@@ -1261,8 +1224,7 @@ async function runSingleAttempt(
1261
1224
  let toolTimeoutSequence = 0;
1262
1225
  const activeToolTimeouts = new Map<string, { toolName: string; timer: ReturnType<typeof setTimeout> }>();
1263
1226
  const activeToolTimeoutKeysByName = new Map<string, string[]>();
1264
- let toolTimeoutTerminationTimer: ReturnType<typeof setTimeout> | undefined;
1265
- let toolTimeoutHardKillTimer: ReturnType<typeof setTimeout> | undefined;
1227
+ let toolTimeoutHardFinishTimer: ReturnType<typeof setTimeout> | undefined;
1266
1228
  const removeToolTimeoutKey = (key: string): void => {
1267
1229
  const active = activeToolTimeouts.get(key);
1268
1230
  if (!active) return;
@@ -1284,17 +1246,13 @@ async function runSingleAttempt(
1284
1246
  };
1285
1247
  const clearAllToolTimeouts = (): void => {
1286
1248
  for (const key of [...activeToolTimeouts.keys()]) removeToolTimeoutKey(key);
1287
- if (toolTimeoutTerminationTimer) {
1288
- clearTimeout(toolTimeoutTerminationTimer);
1289
- toolTimeoutTerminationTimer = undefined;
1290
- }
1291
- if (toolTimeoutHardKillTimer) {
1292
- clearTimeout(toolTimeoutHardKillTimer);
1293
- toolTimeoutHardKillTimer = undefined;
1249
+ if (toolTimeoutHardFinishTimer) {
1250
+ clearTimeout(toolTimeoutHardFinishTimer);
1251
+ toolTimeoutHardFinishTimer = undefined;
1294
1252
  }
1295
1253
  };
1296
1254
  const terminateForToolTimeout = (message: string): void => {
1297
- if (processClosed || lifecycleFinished || interruptedByControl) return;
1255
+ if (sessionSettled || lifecycleFinished || interruptedByControl) return;
1298
1256
  result.timedOut = true;
1299
1257
  result.error = message;
1300
1258
  result.finalOutput = message;
@@ -1302,17 +1260,12 @@ async function runSingleAttempt(
1302
1260
  progress.error = message;
1303
1261
  progress.durationMs = Date.now() - startTime;
1304
1262
  fireUpdate();
1305
- trySignalChild(proc, "SIGINT");
1306
- toolTimeoutTerminationTimer = setTimeout(() => {
1307
- if (processClosed || lifecycleFinished) return;
1308
- trySignalChild(proc, "SIGTERM");
1309
- }, 1000);
1310
- toolTimeoutTerminationTimer.unref?.();
1311
- toolTimeoutHardKillTimer = setTimeout(() => {
1312
- if (processClosed || lifecycleFinished) return;
1313
- trySignalChild(proc, "SIGKILL");
1263
+ abortChild();
1264
+ toolTimeoutHardFinishTimer = setTimeout(() => {
1265
+ if (sessionSettled || lifecycleFinished) return;
1266
+ settle(undefined, true);
1314
1267
  }, 4000);
1315
- toolTimeoutHardKillTimer.unref?.();
1268
+ toolTimeoutHardFinishTimer.unref?.();
1316
1269
  };
1317
1270
  const armToolTimeout = (event: { toolCallId?: unknown; toolName: string }): void => {
1318
1271
  const timeoutForTool = effectiveToolTimeoutMs(event.toolName, options.toolTimeoutMs);
@@ -1333,111 +1286,42 @@ async function runSingleAttempt(
1333
1286
  activeToolTimeoutKeysByName.set(toolName, keys);
1334
1287
  };
1335
1288
 
1336
- const stderrTail = createBoundedByteTail();
1337
- const failProtocol = (limit: ProtocolOutputLimit): void => {
1338
- if (result.protocolError) return;
1339
- result.protocolError = limit;
1340
- result.error = formatProtocolOutputLimit(limit);
1341
- progress.status = "failed";
1342
- progress.error = result.error;
1343
- progress.durationMs = Date.now() - startTime;
1344
- fireUpdate();
1345
- if (!childExited) {
1346
- trySignalChild(proc, "SIGTERM");
1347
- protocolHardKillTimer = setTimeout(() => {
1348
- if (!childExited) trySignalChild(proc, "SIGKILL");
1349
- }, 3000);
1350
- protocolHardKillTimer.unref?.();
1351
- }
1352
- };
1353
- const stdoutReader = createBoundedLineReader({
1354
- oversizedLineProjector: PI_AGGREGATE_EVENT_PROJECTOR,
1355
- onLine: processLine,
1356
- onLimit: failProtocol,
1357
- });
1358
- const stderrReader = createBoundedLineReader({
1359
- stream: "stderr",
1360
- maxPendingLineBytes: MAX_CHILD_STDERR_BYTES,
1361
- onLine: (line) => shared.transcriptWriter?.writeStderrLine(line),
1362
- onLimit: (limit) => shared.transcriptWriter?.writeStderrLine(formatProtocolOutputLimit(limit)),
1363
- });
1364
-
1365
- const clearStdioGuard = attachPostExitStdioGuard(proc, { idleMs: 2000, hardMs: 8000 });
1366
- proc.stdout.on("data", (chunk: Buffer) => stdoutReader.push(chunk));
1367
- proc.stderr.on("data", (chunk: Buffer) => {
1368
- stderrTail.push(chunk);
1369
- stderrReader.push(chunk);
1370
- shared.orcaProgressTab?.append(chunk.toString("utf-8"));
1371
- });
1372
- proc.on("exit", () => {
1373
- childExited = true;
1289
+ /** The child run ended (or was forced to end); fold in the captures and finish. */
1290
+ const settle = (promptError: unknown, forced = false): void => {
1291
+ if (lifecycleFinished || sessionSettled) return;
1292
+ sessionSettled = true;
1374
1293
  clearFinalDrainTimers();
1375
- });
1376
- proc.on("close", (code, signal) => {
1377
- if (lifecycleFinished) return;
1378
- processClosed = true;
1379
- clearFinalDrainTimers();
1380
- clearStdioGuard();
1381
- void jsonlWriter.close().catch(() => {
1382
- // JSONL artifact flush is best effort.
1383
- });
1384
- const toolDiagnosticError = readChildToolDiagnosticError(toolDiagnosticPath);
1294
+ const diagnostic = capture.toolDiagnostic();
1295
+ const toolDiagnosticError = diagnostic ? formatChildToolDiagnostic(diagnostic, { host: "parent" }) : undefined;
1385
1296
  toolAvailabilityError = toolDiagnosticError;
1386
- result.runtimeAcknowledgedExtensions = readRuntimeAcknowledgedExtensions(runtimeAcknowledgedExtensionsPath);
1387
- cleanupTempDir(tempDir);
1388
- stdoutReader.end();
1389
- stderrReader.end();
1390
- const stderr = stderrTail.text();
1391
- const rawStdout = rawStdoutTail.text();
1297
+ result.runtimeAcknowledgedExtensions = capture.runtimeAcknowledgedExtensions();
1392
1298
  let closeError = result.error ?? toolDiagnosticError ?? assistantError;
1393
- const forcedDrainAfterFinalSuccess = Boolean(forcedTerminationSignal || signal) && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !closeError;
1394
- const forcedDrainAfterEmptyTerminal = forcedDrainAfterFinalSuccess && hasEmptyTerminalAssistantResponse(result.messages ?? []);
1395
- if (signal) result.processSignal = signal;
1396
- if (!closeError && forcedDrainAfterEmptyTerminal && stderr.trim()) {
1397
- closeError = stderr.trim();
1398
- }
1399
- if (!closeError && isUnexplainedProcessSignal({
1400
- processSignal: signal,
1401
- interrupted: result.interrupted,
1402
- timedOut: result.timedOut,
1403
- stopped: result.stopped,
1404
- forcedDrainAfterFinalSuccess: forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal,
1405
- })) {
1406
- closeError = formatProcessSignalError(signal!);
1299
+ if (!closeError && promptError !== undefined) {
1300
+ closeError = promptError instanceof Error ? promptError.message : String(promptError);
1407
1301
  }
1408
- if (code !== 0 && rawStdout.trim() && !closeError && !forcedDrainAfterFinalSuccess) {
1409
- closeError = rawStdout.trim();
1302
+ const forcedDrainAfterFinalSuccess = (forced || forcedTermination) && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !closeError;
1303
+ const forcedDrainAfterEmptyTerminal = forcedDrainAfterFinalSuccess && hasEmptyTerminalAssistantResponse(result.messages ?? []);
1304
+ if (!closeError && (abortedBySignal || session?.shutDown) && !result.interrupted && !result.timedOut) {
1305
+ closeError = session?.shutDown ? "Subagent stopped because the parent session shut down." : STOPPED_BEFORE_COMPLETION_ERROR;
1410
1306
  }
1411
- if (code !== 0 && stderr.trim() && !closeError && !forcedDrainAfterFinalSuccess) {
1412
- closeError = stderr.trim();
1307
+ if (!closeError && forced && !forcedDrainAfterFinalSuccess) {
1308
+ closeError = "Subagent session did not settle after it was aborted.";
1413
1309
  }
1414
- const finalCode = forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal ? 0 : forcedTerminationSignal || signal ? (code ?? 1) : (code ?? 0);
1310
+ const finalCode = forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal ? 0 : closeError || promptError !== undefined ? 1 : 0;
1415
1311
  if (!result.error && closeError) result.error = closeError;
1416
1312
  finish(finalCode);
1417
- });
1418
- proc.on("error", (error) => {
1419
- if (lifecycleFinished) return;
1420
- processClosed = true;
1421
- clearFinalDrainTimers();
1422
- clearStdioGuard();
1423
- void jsonlWriter.close().catch(() => {
1424
- // JSONL artifact flush is best effort.
1425
- });
1426
- cleanupTempDir(tempDir);
1427
- stdoutReader.end();
1428
- stderrReader.end();
1429
- if (!result.error) {
1430
- result.error = error instanceof Error ? error.message : String(error);
1431
- }
1432
- finish(1);
1433
- });
1313
+ };
1434
1314
 
1435
1315
  if (options.signal) {
1436
1316
  const kill = () => {
1437
- if (processClosed || lifecycleFinished) return;
1317
+ if (sessionSettled || lifecycleFinished) return;
1438
1318
  abortedBySignal = true;
1439
- proc.kill("SIGTERM");
1440
- setTimeout(() => !proc.killed && proc.kill("SIGKILL"), 3000);
1319
+ abortChild();
1320
+ const hardTimer = setTimeout(() => {
1321
+ if (sessionSettled || lifecycleFinished) return;
1322
+ settle(undefined, true);
1323
+ }, 3000);
1324
+ hardTimer.unref?.();
1441
1325
  };
1442
1326
  if (options.signal.aborted) kill();
1443
1327
  else {
@@ -1448,7 +1332,7 @@ async function runSingleAttempt(
1448
1332
 
1449
1333
  if (options.interruptSignal) {
1450
1334
  const interrupt = () => {
1451
- if (processClosed || lifecycleFinished) return;
1335
+ if (sessionSettled || lifecycleFinished) return;
1452
1336
  if (result.timedOut) return;
1453
1337
  interruptedByControl = true;
1454
1338
  clearTimeoutTimers();
@@ -1459,11 +1343,12 @@ async function runSingleAttempt(
1459
1343
  result.finalOutput = "Interrupted. Waiting for explicit next action.";
1460
1344
  progress.activityState = undefined;
1461
1345
  fireUpdate();
1462
- trySignalChild(proc, "SIGINT");
1463
- setTimeout(() => {
1464
- if (lifecycleFinished || processClosed) return;
1465
- trySignalChild(proc, "SIGTERM");
1466
- }, 1000).unref?.();
1346
+ abortChild();
1347
+ const hardTimer = setTimeout(() => {
1348
+ if (sessionSettled || lifecycleFinished) return;
1349
+ settle(undefined, true);
1350
+ }, 3000);
1351
+ hardTimer.unref?.();
1467
1352
  };
1468
1353
  if (options.interruptSignal.aborted) interrupt();
1469
1354
  else {
@@ -1478,9 +1363,37 @@ async function runSingleAttempt(
1478
1363
  options.onDetachReady?.((reason = "user request") => detachForeground(reason));
1479
1364
  } catch (error) {
1480
1365
  // A consumer callback is advisory. Keep the child attached and observable
1481
- // rather than rejecting this attempt and orphaning its live process.
1366
+ // rather than rejecting this attempt and orphaning its live session.
1482
1367
  appendRecentOutput(progress, [`Foreground detach callback failed: ${error instanceof Error ? error.message : String(error)}`]);
1483
1368
  }
1369
+
1370
+ void (async () => {
1371
+ try {
1372
+ const input = createReportedChildSessionInput(launch, shared.transcriptWriter);
1373
+ requestReadonlySessionEvidence(input, shared.readonlyExpected);
1374
+ if (shared.readonlyHandoffAllowed && !shared.readonlyHandoffAllowed()) throw new Error("Read-only continuation handoff vetoed.");
1375
+ const created = await childSessions.create(input);
1376
+ if (lifecycleFinished) {
1377
+ void created.dispose();
1378
+ return;
1379
+ }
1380
+ session = created;
1381
+ created.detached = detached;
1382
+ unsubscribe = created.subscribe((event) => processEvent(event as Parameters<typeof processEvent>[0]));
1383
+ if (abortedBySignal || interruptedByControl || result.timedOut) {
1384
+ abortChild();
1385
+ }
1386
+ options.onChildSession?.({ steer: (text) => created.steer(text), followUp: (text) => created.followUp(text) });
1387
+ const actualReadonlyModel = shared.readonlyExpected && getReadonlyChildModels(created)?.current;
1388
+ if (shared.readonlyExpected && (!actualReadonlyModel || actualReadonlyModel.fullId !== shared.readonlyModel
1389
+ || actualReadonlyModel.api !== shared.readonlyExpected.api || created.modelId !== shared.readonlyModel || abortedBySignal || interruptedByControl || result.timedOut
1390
+ || !shared.readonlyHandoffAllowed?.())) throw new Error("Read-only continuation handoff vetoed.");
1391
+ await created.prompt(`Task: ${task}`);
1392
+ settle(undefined);
1393
+ } catch (error) {
1394
+ settle(error ?? new Error("Child session failed."));
1395
+ }
1396
+ })();
1484
1397
  });
1485
1398
  result.exitCode = exitCode;
1486
1399
  if (afterCompactionSettlement) {
@@ -1506,20 +1419,10 @@ async function runSingleAttempt(
1506
1419
  && !abortedBySignal
1507
1420
  && !result.timedOut
1508
1421
  && !result.stopped
1509
- && !result.protocolError
1510
1422
  && !toolAvailabilityError) {
1511
- const processExitCode = result.exitCode;
1512
1423
  result.exitCode = 1;
1513
- result.error = formatMidToolExitError({
1514
- toolName: progress.currentTool,
1515
- exitCode: processExitCode,
1516
- processSignal: result.processSignal,
1517
- });
1424
+ result.error = formatMidToolExitError({ toolName: progress.currentTool });
1518
1425
  }
1519
- result.error = formatSubagentExtensionConflictError(result.error, {
1520
- agent: agent.name,
1521
- ambientExtensionsEnabled: !launchResolvedExtensions.disableAmbientExtensions,
1522
- });
1523
1426
  if (result.error && result.exitCode === 0) {
1524
1427
  result.exitCode = 1;
1525
1428
  }
@@ -1527,27 +1430,23 @@ async function runSingleAttempt(
1527
1430
  if (options.structuredOutput && result.exitCode === 0 && !result.error) {
1528
1431
  result.structuredOutputSchemaPath = options.structuredOutput.schemaPath;
1529
1432
  result.structuredOutputPath = options.structuredOutput.outputPath;
1530
- if (!structuredOutputToolInvoked) {
1433
+ const structured = capture.structuredOutput();
1434
+ if (!structuredOutputToolInvoked || !structured.called) {
1531
1435
  result.exitCode = 1;
1532
1436
  result.error = MISSING_STRUCTURED_OUTPUT_CALL_ERROR;
1533
1437
  result.structuredOutputFailed = true;
1534
1438
  } else {
1535
- const structured = await readStructuredOutput({
1536
- schema: options.structuredOutput.schema,
1537
- schemaPath: options.structuredOutput.schemaPath,
1538
- outputPath: options.structuredOutput.outputPath,
1539
- });
1540
- if (structured.error) {
1541
- result.exitCode = 1;
1542
- result.error = structured.error;
1543
- result.structuredOutputFailed = true;
1544
- } else {
1545
- result.structuredOutput = structured.value;
1546
- const acceptanceReport = readStructuredOutputAcceptanceReport(options.structuredOutput);
1547
- (result as SingleResult & { structuredAcceptanceReport?: unknown; structuredAcceptanceReportError?: string }).structuredAcceptanceReport = acceptanceReport.value;
1548
- (result as SingleResult & { structuredAcceptanceReport?: unknown; structuredAcceptanceReportError?: string }).structuredAcceptanceReportError = acceptanceReport.error;
1549
- validatedStructuredOutput = true;
1550
- }
1439
+ result.structuredOutput = structured.value;
1440
+ const acceptanceMode = options.structuredOutput.acceptanceReportPath
1441
+ ? options.structuredOutput.acceptanceReportRequired ? "required" : "optional"
1442
+ : undefined;
1443
+ const acceptanceReportError = acceptanceMode === "required" && !structured.acceptanceReportProvided
1444
+ ? MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR
1445
+ : undefined;
1446
+ (result as SingleResult & { structuredAcceptanceReport?: unknown; structuredAcceptanceReportError?: string }).structuredAcceptanceReport = acceptanceMode ? structured.acceptanceReport : undefined;
1447
+ (result as SingleResult & { structuredAcceptanceReport?: unknown; structuredAcceptanceReportError?: string }).structuredAcceptanceReportError = acceptanceReportError;
1448
+ writeStructuredOutputArtifacts(options.structuredOutput, structured.value, acceptanceMode ? structured.acceptanceReport : undefined);
1449
+ validatedStructuredOutput = true;
1551
1450
  }
1552
1451
  }
1553
1452
  if (result.exitCode === 0 && !result.error) {
@@ -1600,7 +1499,7 @@ async function runSingleAttempt(
1600
1499
  const outputChanged = hasSingleOutputChangedSinceSnapshot(options.outputPath, shared.outputSnapshot);
1601
1500
  requiredOutputMissing = outputChanged === undefined ? undefined : !outputChanged;
1602
1501
  } else if (options.structuredOutput) {
1603
- requiredOutputMissing = !existsSync(options.structuredOutput.outputPath);
1502
+ requiredOutputMissing = !capture.structuredOutput().called;
1604
1503
  }
1605
1504
  result.timeoutRecovery = buildTimeoutRecoverySummary({
1606
1505
  termination: "timed-out",
@@ -1617,7 +1516,7 @@ async function runSingleAttempt(
1617
1516
  ? `${timeoutMessage}\n\n${result.timeoutRecovery.message}\n\nPartial output before timeout:\n${fullOutput}`
1618
1517
  : `${timeoutMessage}\n\n${result.timeoutRecovery.message}`;
1619
1518
  }
1620
- const completionGuardEnabled = isAgentContractV1(options.agentContract) ? agent.completionGuard === true : agent.completionGuard !== false;
1519
+ const completionGuardEnabled = isAgentContract(options.agentContract) ? agent.completionGuard === true : agent.completionGuard !== false;
1621
1520
  const completionGuard = ((result.exitCode === 0 && !result.error) || toolAvailabilityError) && completionGuardEnabled
1622
1521
  ? evaluateCompletionMutationGuard({
1623
1522
  agent: agent.name,
@@ -1657,7 +1556,7 @@ async function runSingleAttempt(
1657
1556
  mutationAttemptObserved,
1658
1557
  mutationEvidence,
1659
1558
  arbiterRescued,
1660
- agentContractV1: isAgentContractV1(options.agentContract),
1559
+ agentContractEnabled: isAgentContract(options.agentContract),
1661
1560
  });
1662
1561
  if (completionEvidence.fileMutation) {
1663
1562
  result.effects = {
@@ -1719,6 +1618,20 @@ async function runSingleAttempt(
1719
1618
  return result;
1720
1619
  }
1721
1620
 
1621
+ /** Keep the structured output artifact files other consumers read by path. */
1622
+ function writeStructuredOutputArtifacts(runtime: NonNullable<RunSyncOptions["structuredOutput"]>, value: unknown, acceptanceReport: unknown): void {
1623
+ try {
1624
+ mkdirSync(path.dirname(runtime.outputPath), { recursive: true });
1625
+ writeFileSync(runtime.outputPath, JSON.stringify(value), { mode: 0o600 });
1626
+ if (runtime.acceptanceReportPath) {
1627
+ if (acceptanceReport !== undefined) writeFileSync(runtime.acceptanceReportPath, JSON.stringify(acceptanceReport), { mode: 0o600 });
1628
+ else rmSync(runtime.acceptanceReportPath, { force: true });
1629
+ }
1630
+ } catch {
1631
+ // The captured value is already on the result; the file is observability only.
1632
+ }
1633
+ }
1634
+
1722
1635
  /**
1723
1636
  * Run a subagent synchronously (blocking until complete)
1724
1637
  */
@@ -1744,7 +1657,7 @@ async function runSyncCompletionInner(
1744
1657
  }
1745
1658
  options = {
1746
1659
  ...options,
1747
- capabilityCeiling: intersectSubagentCapabilityCeilings(options.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(options.parentSessionId), decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV])),
1660
+ capabilityCeiling: intersectSubagentCapabilityCeilings(options.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(options.parentSessionId), options.childRuntime?.capabilityCeiling),
1748
1661
  };
1749
1662
  const childSessionName = deriveChildSessionName({ agent: agentName, task });
1750
1663
  const agent = agents.find((a) => a.name === agentName);
@@ -1766,7 +1679,7 @@ async function runSyncCompletionInner(
1766
1679
  thinkingCeiling: intersectThinkingCeilings(
1767
1680
  options.thinkingCeiling,
1768
1681
  agent.maxThinking,
1769
- decodeThinkingCeiling(process.env[SUBAGENT_THINKING_CEILING_ENV]),
1682
+ options.childRuntime?.thinkingCeiling,
1770
1683
  ),
1771
1684
  };
1772
1685
  try {
@@ -1839,7 +1752,7 @@ async function runSyncCompletionInner(
1839
1752
  dynamicGroup: options.acceptanceContext?.dynamicGroup,
1840
1753
  agentContract: options.agentContract,
1841
1754
  });
1842
- const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance, { reportOptional: isAgentContractV1(options.agentContract), structuredOutput: Boolean(options.structuredOutput?.acceptanceReportPath) });
1755
+ const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance, { reportOptional: isAgentContract(options.agentContract), structuredOutput: Boolean(options.structuredOutput?.acceptanceReportPath) });
1843
1756
  const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
1844
1757
  options.onEffectivePrompt?.(taskWithAcceptance);
1845
1758
  const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
@@ -1990,21 +1903,33 @@ async function runSyncCompletionInner(
1990
1903
  };
1991
1904
  let lastResult: SingleResult | undefined;
1992
1905
  const modelsToTry = candidates.length > 0 ? candidates : [undefined];
1993
- // Escalated to "file" after an unexplained zero-activity startup failure so
1994
- // retries keep the task text out of argv (endpoint pre-exec scans may deny it).
1995
- let taskDeliveryOverride: SubagentTaskDelivery | undefined;
1996
- let abortRecoveryAttempted = false;
1997
- let nextAttemptTask = taskWithAcceptance;
1906
+ let recoveryState: LogicalRecoveryState = "unused";
1907
+ let readonlyExpected: SettledReadonlyEvidence | undefined;
1908
+ let readonlyModel: string | undefined;
1909
+ let readonlySource: ChildSession | undefined;
1910
+ // Ordinary startup retries retain their per-attempt timeout. Only retained
1911
+ // continuation uses the original logical deadline, never a renewed allowance.
1912
+ const continuationDeadline = options.deadlineAt ?? (options.timeoutMs === undefined ? undefined : Date.now() + options.timeoutMs);
1913
+ const readonlyHandoffAllowed = () => !options.signal?.aborted && !options.interruptSignal?.aborted
1914
+ && !intercomDetached && !detachedReason && !options.workflowChildPermitLaunch
1915
+ && options.usageBudget === undefined && options.toolBudget === undefined
1916
+ && (continuationDeadline === undefined || Date.now() < continuationDeadline)
1917
+ && (!readonlySource || getReadonlySessionEvidence(readonlySource) === readonlyExpected)
1918
+ && !readonlySource?.detached && !readonlySource?.shutDown;
1919
+ let nextAttemptTask = task;
1998
1920
  modelAttemptsLoop: for (let modelIndex = 0; modelIndex < modelsToTry.length; modelIndex++) {
1999
1921
  const candidate = modelsToTry[modelIndex];
2000
- for (let startupAttemptIndex = 0; ; startupAttemptIndex++) {
2001
- const recoveringAbort = abortRecoveryAttempted;
1922
+ // The inner loop re-runs the same candidate at most once, for abort recovery.
1923
+ for (;;) {
1924
+ const recoveringAbort = recoveryState === "abort-recovery";
2002
1925
  const attemptTask = nextAttemptTask;
2003
1926
  const verifyModel = Boolean(candidate) && !(options.modelOverrideFromParent && modelIndex === 0);
2004
1927
  const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
1928
+ if (recoveryState === "readonly-continuation") attemptOptions.deadlineAt = continuationDeadline;
2005
1929
  const result = await runSingleAttempt(runtimeCwd, agent, attemptTask, candidate, attemptOptions, {
2006
1930
  sessionEnabled,
2007
1931
  systemPrompt,
1932
+ acceptancePrompt,
2008
1933
  resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
2009
1934
  skillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(", ")}` : undefined,
2010
1935
  jsonlPath,
@@ -2016,13 +1941,15 @@ async function runSyncCompletionInner(
2016
1941
  .filter((modelCandidate): modelCandidate is string => Boolean(modelCandidate)),
2017
1942
  outputSnapshot,
2018
1943
  originalTask: task,
2019
- taskDelivery: taskDeliveryOverride,
2020
1944
  orcaProgressTab,
2021
1945
  launchWarnings,
2022
1946
  verifyModel,
1947
+ readonlyExpected,
1948
+ readonlyModel,
1949
+ readonlyHandoffAllowed: readonlyExpected ? readonlyHandoffAllowed : undefined,
2023
1950
  });
2024
1951
  lastResult = result;
2025
- if (!recoveringAbort && startupAttemptIndex === 0) {
1952
+ if (!recoveringAbort) {
2026
1953
  if (result.model) attemptedModels.push(result.model);
2027
1954
  else if (candidate) attemptedModels.push(candidate);
2028
1955
  }
@@ -2038,6 +1965,46 @@ async function runSyncCompletionInner(
2038
1965
  usage: { ...result.usage },
2039
1966
  };
2040
1967
  modelAttempts.push(attempt);
1968
+ // A consumed retained continuation is terminal even on a startup error or abort.
1969
+ if (recoveryState === "readonly-continuation") break modelAttemptsLoop;
1970
+ const source = settledReadonlySource.get(result);
1971
+ const evidence = source && getReadonlySessionEvidence(source);
1972
+ const models = source && getReadonlyChildModels(source);
1973
+ // Deny non-text retained inputs, including unknown blocks. Count bytes once,
1974
+ // not restored usage (the latter belongs to earlier attempts/runs).
1975
+ const textOnly = evidence && (JSON.parse(evidence.contextJson) as Array<{ role: string; content: unknown }>).every((message) =>
1976
+ typeof message.content === "string" || Array.isArray(message.content) && message.content.every((block) =>
1977
+ block?.type === "text" || message.role === "assistant" && block?.type === "toolCall"));
1978
+ const retainedBytes = evidence && models ? Buffer.byteLength(evidence.contextJson) + models.requestBytes : Infinity;
1979
+ const resolvedCandidates = modelsToTry.map((reference, index) => index === modelIndex ? models?.current : reference ? models?.resolve(reference) : undefined);
1980
+ const continuation = planReadonlyModelContinuation({
1981
+ source, recoveryState, currentIndex: modelIndex,
1982
+ candidates: resolvedCandidates.map((resolved, index) => {
1983
+ // A conservative byte ceiling includes serialized history, actual system
1984
+ // prompt/tools, framing/continuation headroom and full output allowance.
1985
+ const required = resolved?.maxTokens ? retainedBytes + 4096 + resolved.maxTokens : Infinity;
1986
+ return {
1987
+ resolved: resolved?.api ? { provider: resolved.provider, model: resolved.id, api: resolved.api } : undefined,
1988
+ tried: index <= modelIndex,
1989
+ compatibility: textOnly && resolved?.input?.includes("text") && (resolved.contextWindow ?? 0) >= required ? "compatible" as const : "unknown" as const,
1990
+ };
1991
+ }),
1992
+ lifecycleAllowsContinuation: !attemptSucceeded && readonlyHandoffAllowed() && !result.stopped && !result.detached && !result.interrupted && !result.timedOut,
1993
+ effectsAllowContinuation: !result.structuredOutputFailed && !result.toolBudgetBlocked && !result.progress?.currentTool
1994
+ && !result.outputSaveError && (!result.effects?.fileMutation || result.effects.fileMutation.status === "not-applicable"),
1995
+ budget: options.toolBudget ? "tool-budget-configured" : options.usageBudget ? "unknown" : "unconfigured",
1996
+ knownContextOverflow: Boolean(result.contextOverflow || isContextOverflow(result.error)),
1997
+ });
1998
+ if (continuation.kind === "continue") {
1999
+ recoveryState = continuation.recoveryState; // consume BEFORE any sibling creation
2000
+ readonlyExpected = continuation.expected;
2001
+ readonlySource = source;
2002
+ readonlyModel = resolvedCandidates[continuation.candidateIndex]?.fullId;
2003
+ nextAttemptTask = continuation.prompt;
2004
+ attemptNotes.push(`[readonly-continuation] ${attempt.model} failed with HTTP 429 after read-only progress; continuing retained session once with ${readonlyModel}.`);
2005
+ modelIndex = continuation.candidateIndex - 1;
2006
+ continue modelAttemptsLoop;
2007
+ }
2041
2008
  if (!attemptSucceeded) {
2042
2009
  const afterCompactionSettlement = (result as AbortRecoverySingleResult)[AFTER_COMPACTION_SETTLEMENT];
2043
2010
  const abortRecovery = planAbortRecovery({
@@ -2045,7 +2012,7 @@ async function runSyncCompletionInner(
2045
2012
  error: result.error,
2046
2013
  processSignal: result.processSignal,
2047
2014
  sessionAvailable: Boolean(options.sessionFile && existsSync(options.sessionFile)),
2048
- alreadyResumed: abortRecoveryAttempted,
2015
+ alreadyResumed: recoveryState !== "unused",
2049
2016
  stopped: result.stopped || result.detached || options.signal?.aborted,
2050
2017
  interrupted: result.interrupted || intercomDetached || options.interruptSignal?.aborted,
2051
2018
  timedOut: result.timedOut,
@@ -2057,7 +2024,7 @@ async function runSyncCompletionInner(
2057
2024
  afterCompactionSettlement,
2058
2025
  });
2059
2026
  if (abortRecovery.action === "resume") {
2060
- abortRecoveryAttempted = true;
2027
+ recoveryState = "abort-recovery";
2061
2028
  nextAttemptTask = abortRecovery.prompt;
2062
2029
  attemptNotes.push("[abort-recovery] provider/transport abort after useful progress; resuming the retained child session once.");
2063
2030
  continue;
@@ -2072,75 +2039,10 @@ async function runSyncCompletionInner(
2072
2039
  if (options.workflowChildPermitLaunch && !attemptSucceeded) break modelAttemptsLoop;
2073
2040
  // Preserve the legacy intercom handoff contract: once this logical run has
2074
2041
  // been handed to a supervisor, terminating that attempt must not launch a
2075
- // startup retry or model fallback. Explicit user detach retains fallback.
2042
+ // model fallback. Explicit user detach retains fallback.
2076
2043
  if (intercomDetached || result.timedOut) break modelAttemptsLoop;
2077
2044
  if (attemptSucceeded) break modelAttemptsLoop;
2078
2045
 
2079
- const startupFailure = isRetryableSubagentStartupFailure({
2080
- exitCode: result.exitCode,
2081
- error: result.error,
2082
- finalOutput: result.finalOutput,
2083
- messageCount: result.messages?.length ?? 0,
2084
- toolCount: result.progressSummary?.toolCount ?? 0,
2085
- usage: result.usage,
2086
- durationMs: result.progressSummary?.durationMs ?? Number.POSITIVE_INFINITY,
2087
- protocolError: result.protocolError,
2088
- processSignal: result.processSignal,
2089
- detached: result.detached,
2090
- interrupted: result.interrupted,
2091
- timedOut: result.timedOut,
2092
- stopped: result.stopped,
2093
- });
2094
- const retryDelayMs = SUBAGENT_STARTUP_RETRY_DELAYS_MS[startupAttemptIndex];
2095
- if (startupFailure && retryDelayMs !== undefined) {
2096
- const retryNote = formatSubagentStartupRetryNote({
2097
- model: attempt.model,
2098
- attempt: startupAttemptIndex + 1,
2099
- maxAttempts: SUBAGENT_STARTUP_RETRY_DELAYS_MS.length + 1,
2100
- delayMs: retryDelayMs,
2101
- });
2102
- const shouldRetry = await waitForSubagentStartupRetry(retryDelayMs, [options.signal, options.interruptSignal]);
2103
- if (!shouldRetry) {
2104
- if (options.interruptSignal?.aborted) {
2105
- result.exitCode = 0;
2106
- result.interrupted = true;
2107
- result.error = undefined;
2108
- result.finalOutput = "Interrupted. Waiting for explicit next action.";
2109
- if (result.progress) {
2110
- result.progress.status = "running";
2111
- result.progress.error = undefined;
2112
- }
2113
- } else {
2114
- const cancellationError = "Subagent startup retry cancelled before relaunch.";
2115
- result.error = cancellationError;
2116
- result.finalOutput = cancellationError;
2117
- attempt.error = cancellationError;
2118
- if (result.progress) result.progress.error = cancellationError;
2119
- }
2120
- break modelAttemptsLoop;
2121
- }
2122
- if (!taskDeliveryOverride && result.processSignal === "SIGKILL") {
2123
- taskDeliveryOverride = "file";
2124
- attemptNotes.push("[startup-retry] retrying with file task delivery to keep the task text out of the child process argv.");
2125
- }
2126
- attempt.error = retryNote;
2127
- attemptNotes.push(retryNote);
2128
- continue;
2129
- }
2130
- if (startupFailure) {
2131
- const startupError = formatSubagentStartupRetryExhaustedError({
2132
- model: attempt.model,
2133
- attempts: startupAttemptIndex + 1,
2134
- });
2135
- result.error = startupError;
2136
- result.finalOutput = startupError;
2137
- if (result.progress) {
2138
- result.progress.error = startupError;
2139
- result.progress.status = "failed";
2140
- }
2141
- attempt.error = startupError;
2142
- break modelAttemptsLoop;
2143
- }
2144
2046
  const retryableModelFailure = isRetryableModelFailureAttempt({ error: result.error, messages: result.messages, toolCount: result.progressSummary?.toolCount });
2145
2047
  if (retryableModelFailure) recordRetryableModelFailure(result.model ?? candidate, result.error);
2146
2048
  if (isContextOverflow(result.error)) {
@@ -2233,7 +2135,7 @@ async function runSyncCompletionInner(
2233
2135
  ? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only", durable: result.savedOutputPath !== undefined }
2234
2136
  : undefined,
2235
2137
  cwd: options.cwd ?? runtimeCwd,
2236
- reportOptional: isAgentContractV1(options.agentContract),
2138
+ reportOptional: isAgentContract(options.agentContract),
2237
2139
  artifactsDir: options.artifactsDir,
2238
2140
  runId: options.runId,
2239
2141
  watchdog: result.watchdog,
@@ -2245,7 +2147,7 @@ async function runSyncCompletionInner(
2245
2147
  }
2246
2148
  const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
2247
2149
  stripAcceptanceReportsFromMessages(result.messages);
2248
- if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.interrupted && !result.timedOut && !isAgentContractV1(options.agentContract)) {
2150
+ if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.interrupted && !result.timedOut && !isAgentContract(options.agentContract)) {
2249
2151
  result.exitCode = 1;
2250
2152
  if (result.savedOutputPath) {
2251
2153
  result.finalOutput = finalizeSingleOutput({
@@ -2278,7 +2180,7 @@ async function runSyncCompletionInner(
2278
2180
  result.progress.error = result.error;
2279
2181
  }
2280
2182
  }
2281
- if (isAgentContractV1(options.agentContract)) attachContractProjections(result);
2183
+ if (isAgentContract(options.agentContract)) attachContractProjections(result);
2282
2184
  redactResultPrompt(result);
2283
2185
  try {
2284
2186
  persistResultMetadata(result);
@@ -2323,7 +2225,7 @@ export async function runSync(
2323
2225
  ): Promise<SingleResult> {
2324
2226
  // Capture the strict contract before consumer-owned objects can be mutated
2325
2227
  // after a detached receipt is published.
2326
- const strictContract = isAgentContractV1(options.agentContract);
2228
+ const strictContract = isAgentContract(options.agentContract);
2327
2229
  let detachedReason: string | undefined;
2328
2230
  let publishedReceipt: SingleResult | undefined;
2329
2231
  let activeDetachAttempt: ((reason?: string) => boolean) | undefined;
@@ -2421,13 +2323,13 @@ export async function runSync(
2421
2323
  });
2422
2324
 
2423
2325
  let terminalCallbackInvoked = false;
2424
- void authoritativeCompletion.then((terminalResult) => {
2326
+ void authoritativeCompletion.then(async (terminalResult) => {
2425
2327
  if (!detachedReason || terminalCallbackInvoked) return;
2426
2328
  terminalCallbackInvoked = true;
2427
2329
  terminalResult.detached = undefined;
2428
2330
  terminalResult.detachedReason = detachedReason;
2429
2331
  try {
2430
- options.onDetachedExit?.(terminalResult);
2332
+ await options.onDetachedExit?.(terminalResult);
2431
2333
  } catch {
2432
2334
  // The authoritative result has settled. Consumer callback failures are
2433
2335
  // contained here; each consumer owns cleanup through its own finally block.