@selesai/code 0.12.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (466) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/core/agent-session.d.ts +1 -1
  3. package/dist/core/agent-session.js +1 -1
  4. package/dist/core/sdk.d.ts +2 -2
  5. package/dist/core/sdk.js +1 -1
  6. package/dist/core/system-prompt.d.ts +1 -1
  7. package/dist/core/system-prompt.js +1 -1
  8. package/dist/core/usage-totals.d.ts +2 -0
  9. package/dist/core/usage-totals.js +10 -0
  10. package/dist/extensions/ascii.txt +9 -0
  11. package/dist/extensions/package.json +1 -2
  12. package/dist/extensions/pi-hermes-memory/CHANGELOG.md +371 -0
  13. package/dist/extensions/pi-hermes-memory/LICENSE +21 -0
  14. package/dist/extensions/pi-hermes-memory/README.md +643 -0
  15. package/dist/extensions/pi-hermes-memory/docs/0.1/TASKS.md +197 -0
  16. package/dist/extensions/pi-hermes-memory/docs/0.2/PLAN.md +290 -0
  17. package/dist/extensions/pi-hermes-memory/docs/0.2/TASKS.md +134 -0
  18. package/dist/extensions/pi-hermes-memory/docs/0.2/TEST-PLAN.md +216 -0
  19. package/dist/extensions/pi-hermes-memory/docs/0.3/PLAN.md +330 -0
  20. package/dist/extensions/pi-hermes-memory/docs/0.3/TASKS.md +125 -0
  21. package/dist/extensions/pi-hermes-memory/docs/0.4/PLAN.md +160 -0
  22. package/dist/extensions/pi-hermes-memory/docs/0.4/TASKS.md +113 -0
  23. package/dist/extensions/pi-hermes-memory/docs/0.6/CHANGELOG.md +57 -0
  24. package/dist/extensions/pi-hermes-memory/docs/0.6/PLAN.md +202 -0
  25. package/dist/extensions/pi-hermes-memory/docs/0.6/TASKS.md +144 -0
  26. package/dist/extensions/pi-hermes-memory/docs/0.7/PLAN.md +349 -0
  27. package/dist/extensions/pi-hermes-memory/docs/0.7/TASKS.md +110 -0
  28. package/dist/extensions/pi-hermes-memory/docs/PUBLISHING.md +149 -0
  29. package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +468 -0
  30. package/dist/extensions/pi-hermes-memory/docs/images/memory-architecture.svg +1 -0
  31. package/dist/extensions/pi-hermes-memory/docs/images/pi-logo.svg +22 -0
  32. package/dist/extensions/pi-hermes-memory/docs/images/pi_memory.png +0 -0
  33. package/dist/extensions/pi-hermes-memory/docs/images/pi_memory.svg +19 -0
  34. package/dist/extensions/pi-hermes-memory/docs/images/security-flow.svg +1 -0
  35. package/dist/extensions/pi-hermes-memory/docs/images/session-lifecycle.svg +1 -0
  36. package/dist/extensions/pi-hermes-memory/docs/images/source-architecture.svg +1 -0
  37. package/dist/extensions/pi-hermes-memory/docs/mermaid/memory-architecture.mmd +49 -0
  38. package/dist/extensions/pi-hermes-memory/docs/mermaid/security-flow.mmd +40 -0
  39. package/dist/extensions/pi-hermes-memory/docs/mermaid/session-lifecycle.mmd +56 -0
  40. package/dist/extensions/pi-hermes-memory/docs/mermaid/source-architecture.mmd +54 -0
  41. package/dist/extensions/pi-hermes-memory/package-lock.json +3799 -0
  42. package/dist/extensions/pi-hermes-memory/package.json +66 -0
  43. package/dist/extensions/pi-hermes-memory/scripts/check-min-sdk.mjs +99 -0
  44. package/dist/extensions/pi-hermes-memory/scripts/ensure-dev.mjs +57 -0
  45. package/dist/extensions/pi-hermes-memory/src/auto-consolidation-warning.ts +6 -0
  46. package/dist/extensions/pi-hermes-memory/src/config.ts +178 -0
  47. package/dist/extensions/pi-hermes-memory/src/constants.ts +424 -0
  48. package/dist/extensions/pi-hermes-memory/src/extension-root-migration.ts +639 -0
  49. package/dist/extensions/pi-hermes-memory/src/handlers/auto-consolidate.ts +401 -0
  50. package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +297 -0
  51. package/dist/extensions/pi-hermes-memory/src/handlers/child-process-watchdog.mjs +90 -0
  52. package/dist/extensions/pi-hermes-memory/src/handlers/correction-detector.ts +300 -0
  53. package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +86 -0
  54. package/dist/extensions/pi-hermes-memory/src/handlers/insights.ts +78 -0
  55. package/dist/extensions/pi-hermes-memory/src/handlers/interview.ts +37 -0
  56. package/dist/extensions/pi-hermes-memory/src/handlers/learn-memory.ts +186 -0
  57. package/dist/extensions/pi-hermes-memory/src/handlers/message-parts.ts +27 -0
  58. package/dist/extensions/pi-hermes-memory/src/handlers/pi-child-process.ts +485 -0
  59. package/dist/extensions/pi-hermes-memory/src/handlers/preview-context.ts +105 -0
  60. package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +506 -0
  61. package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +145 -0
  62. package/dist/extensions/pi-hermes-memory/src/handlers/session-flush.ts +151 -0
  63. package/dist/extensions/pi-hermes-memory/src/handlers/session-live-index.ts +94 -0
  64. package/dist/extensions/pi-hermes-memory/src/handlers/skills-command.ts +1334 -0
  65. package/dist/extensions/pi-hermes-memory/src/handlers/standing-pin.ts +111 -0
  66. package/dist/extensions/pi-hermes-memory/src/handlers/switch-project.ts +75 -0
  67. package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +266 -0
  68. package/dist/extensions/pi-hermes-memory/src/index.ts +378 -0
  69. package/dist/extensions/pi-hermes-memory/src/lifecycle-timing.ts +54 -0
  70. package/dist/extensions/pi-hermes-memory/src/paths.ts +61 -0
  71. package/dist/extensions/pi-hermes-memory/src/project-context.ts +13 -0
  72. package/dist/extensions/pi-hermes-memory/src/project-memory-migration.ts +93 -0
  73. package/dist/extensions/pi-hermes-memory/src/project.ts +157 -0
  74. package/dist/extensions/pi-hermes-memory/src/prompt-context.ts +55 -0
  75. package/dist/extensions/pi-hermes-memory/src/store/atomic-lock-coordinator.ts +375 -0
  76. package/dist/extensions/pi-hermes-memory/src/store/canonical-storage-path.ts +54 -0
  77. package/dist/extensions/pi-hermes-memory/src/store/content-scanner.ts +101 -0
  78. package/dist/extensions/pi-hermes-memory/src/store/db.ts +1180 -0
  79. package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +101 -0
  80. package/dist/extensions/pi-hermes-memory/src/store/markdown-mutation-lock.ts +38 -0
  81. package/dist/extensions/pi-hermes-memory/src/store/memory-lookup.ts +24 -0
  82. package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +1191 -0
  83. package/dist/extensions/pi-hermes-memory/src/store/recovery-maintenance.ts +66 -0
  84. package/dist/extensions/pi-hermes-memory/src/store/schema.ts +118 -0
  85. package/dist/extensions/pi-hermes-memory/src/store/session-anchor-search.ts +472 -0
  86. package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +511 -0
  87. package/dist/extensions/pi-hermes-memory/src/store/session-parser.ts +213 -0
  88. package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +234 -0
  89. package/dist/extensions/pi-hermes-memory/src/store/skill-store.ts +950 -0
  90. package/dist/extensions/pi-hermes-memory/src/store/skill-utils.ts +133 -0
  91. package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +1002 -0
  92. package/dist/extensions/pi-hermes-memory/src/store/sqlite-native.ts +241 -0
  93. package/dist/extensions/pi-hermes-memory/src/store/standing-instructions.ts +247 -0
  94. package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +95 -0
  95. package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +469 -0
  96. package/dist/extensions/pi-hermes-memory/src/tools/session-search-tool.ts +246 -0
  97. package/dist/extensions/pi-hermes-memory/src/tools/shared-output-view.ts +170 -0
  98. package/dist/extensions/pi-hermes-memory/src/tools/skill-tool.ts +333 -0
  99. package/dist/extensions/pi-hermes-memory/src/tools/tool-result-views.ts +105 -0
  100. package/dist/extensions/pi-hermes-memory/src/types.ts +214 -0
  101. package/dist/extensions/pi-hermes-memory/tests/config.test.ts +434 -0
  102. package/dist/extensions/pi-hermes-memory/tests/extension-root-migration.test.ts +368 -0
  103. package/dist/extensions/pi-hermes-memory/tests/handlers/auto-consolidate.test.ts +984 -0
  104. package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +950 -0
  105. package/dist/extensions/pi-hermes-memory/tests/handlers/correction-detector.test.ts +671 -0
  106. package/dist/extensions/pi-hermes-memory/tests/handlers/insights.test.ts +157 -0
  107. package/dist/extensions/pi-hermes-memory/tests/handlers/interview.test.ts +127 -0
  108. package/dist/extensions/pi-hermes-memory/tests/handlers/pi-child-process.test.ts +905 -0
  109. package/dist/extensions/pi-hermes-memory/tests/handlers/preview-context.test.ts +152 -0
  110. package/dist/extensions/pi-hermes-memory/tests/handlers/prompt-context.test.ts +166 -0
  111. package/dist/extensions/pi-hermes-memory/tests/handlers/resources-discover.test.ts +71 -0
  112. package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +614 -0
  113. package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +192 -0
  114. package/dist/extensions/pi-hermes-memory/tests/handlers/session-flush.test.ts +677 -0
  115. package/dist/extensions/pi-hermes-memory/tests/handlers/session-live-index.test.ts +225 -0
  116. package/dist/extensions/pi-hermes-memory/tests/handlers/skills-command.test.ts +827 -0
  117. package/dist/extensions/pi-hermes-memory/tests/handlers/standing-pin.test.ts +134 -0
  118. package/dist/extensions/pi-hermes-memory/tests/handlers/sync-markdown-memories.test.ts +491 -0
  119. package/dist/extensions/pi-hermes-memory/tests/handlers/system-prompt.test.ts +199 -0
  120. package/dist/extensions/pi-hermes-memory/tests/handlers/thinking-max.test.ts +64 -0
  121. package/dist/extensions/pi-hermes-memory/tests/index.test.ts +30 -0
  122. package/dist/extensions/pi-hermes-memory/tests/integration/flow.test.ts +126 -0
  123. package/dist/extensions/pi-hermes-memory/tests/lifecycle-timing.test.ts +76 -0
  124. package/dist/extensions/pi-hermes-memory/tests/paths.test.ts +23 -0
  125. package/dist/extensions/pi-hermes-memory/tests/project-memory-migration.test.ts +80 -0
  126. package/dist/extensions/pi-hermes-memory/tests/project-rebinding.test.ts +115 -0
  127. package/dist/extensions/pi-hermes-memory/tests/project.test.ts +120 -0
  128. package/dist/extensions/pi-hermes-memory/tests/run-all-timeout.test.ts +107 -0
  129. package/dist/extensions/pi-hermes-memory/tests/run-all.sh +45 -0
  130. package/dist/extensions/pi-hermes-memory/tests/store/atomic-lock-coordinator.test.ts +381 -0
  131. package/dist/extensions/pi-hermes-memory/tests/store/content-scanner.test.ts +388 -0
  132. package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +1046 -0
  133. package/dist/extensions/pi-hermes-memory/tests/store/markdown-mutation-lock.test.ts +34 -0
  134. package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +2150 -0
  135. package/dist/extensions/pi-hermes-memory/tests/store/recovery-maintenance.test.ts +170 -0
  136. package/dist/extensions/pi-hermes-memory/tests/store/session-anchor-search.test.ts +210 -0
  137. package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +643 -0
  138. package/dist/extensions/pi-hermes-memory/tests/store/session-parser.test.ts +282 -0
  139. package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +287 -0
  140. package/dist/extensions/pi-hermes-memory/tests/store/skill-store.test.ts +678 -0
  141. package/dist/extensions/pi-hermes-memory/tests/store/skill-utils.test.ts +21 -0
  142. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-lazy-load.test.ts +78 -0
  143. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +543 -0
  144. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +164 -0
  145. package/dist/extensions/pi-hermes-memory/tests/store/standing-instructions.test.ts +171 -0
  146. package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +84 -0
  147. package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +709 -0
  148. package/dist/extensions/pi-hermes-memory/tests/tools/session-search-tool.test.ts +345 -0
  149. package/dist/extensions/pi-hermes-memory/tests/tools/shared-output-view.test.ts +362 -0
  150. package/dist/extensions/pi-hermes-memory/tests/tools/skill-tool.test.ts +478 -0
  151. package/dist/extensions/pi-hermes-memory/tests/tools/tool-result-renderer-wiring.test.ts +40 -0
  152. package/dist/extensions/pi-hermes-memory/tsconfig.json +19 -0
  153. package/dist/extensions/pi-subagents/.oxlintrc.json +43 -0
  154. package/dist/extensions/pi-subagents/AGENTS.md +5 -0
  155. package/dist/extensions/pi-subagents/CHANGELOG.md +157 -0
  156. package/dist/extensions/pi-subagents/README.md +12 -6
  157. package/dist/extensions/pi-subagents/VISION.md +81 -0
  158. package/dist/extensions/pi-subagents/agents/claude-code-writer.md +15 -0
  159. package/dist/extensions/pi-subagents/agents/claude-code.md +15 -0
  160. package/dist/extensions/pi-subagents/agents/codex-exec-writer.md +15 -0
  161. package/dist/extensions/pi-subagents/agents/codex-exec.md +15 -0
  162. package/dist/extensions/pi-subagents/agents/cursor-agent-writer.md +14 -0
  163. package/dist/extensions/pi-subagents/agents/cursor-agent.md +14 -0
  164. package/dist/extensions/pi-subagents/agents/reviewer.md +1 -3
  165. package/dist/extensions/pi-subagents/docs/agents.md +488 -0
  166. package/dist/extensions/pi-subagents/docs/configuration.md +46 -9
  167. package/dist/extensions/pi-subagents/docs/extension-api.md +42 -3
  168. package/dist/extensions/pi-subagents/docs/models.md +27 -4
  169. package/dist/extensions/pi-subagents/docs/observability.md +10 -9
  170. package/dist/extensions/pi-subagents/docs/tool-reference.md +102 -6
  171. package/dist/extensions/pi-subagents/docs/workflows.md +135 -0
  172. package/dist/extensions/pi-subagents/package-lock.json +389 -2
  173. package/dist/extensions/pi-subagents/package.json +3 -1
  174. package/dist/extensions/pi-subagents/prompts/review-loop.md +2 -2
  175. package/dist/extensions/pi-subagents/skills/council-mode/SKILL.md +1 -1
  176. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +3 -1
  177. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +7 -3
  178. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  179. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +26 -5
  180. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +41 -22
  181. package/dist/extensions/pi-subagents/src/agents/agent-refinements.ts +4 -4
  182. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +9 -2
  183. package/dist/extensions/pi-subagents/src/agents/agents.ts +322 -75
  184. package/dist/extensions/pi-subagents/src/agents/builtin-names.ts +6 -0
  185. package/dist/extensions/pi-subagents/src/agents/runtime-agent-events.ts +70 -0
  186. package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +27 -20
  187. package/dist/extensions/pi-subagents/src/api/agents.ts +10 -5
  188. package/dist/extensions/pi-subagents/src/api/background-work.ts +5 -1
  189. package/dist/extensions/pi-subagents/src/api/delegation.ts +0 -7
  190. package/dist/extensions/pi-subagents/src/api/preflight.ts +34 -12
  191. package/dist/extensions/pi-subagents/src/extension/config.ts +70 -0
  192. package/dist/extensions/pi-subagents/src/extension/doctor.ts +3 -3
  193. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +5 -3
  194. package/dist/extensions/pi-subagents/src/extension/index.ts +83 -30
  195. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +44 -15
  196. package/dist/extensions/pi-subagents/src/extension/rpc.ts +55 -19
  197. package/dist/extensions/pi-subagents/src/extension/schemas.ts +41 -15
  198. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -31
  199. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +2 -1
  200. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +2 -10
  201. package/dist/extensions/pi-subagents/src/inspectors/herdr/session-roots-codec.ts +42 -0
  202. package/dist/extensions/pi-subagents/src/integrations/herdr-status.ts +51 -3
  203. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +2 -0
  204. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +5 -6
  205. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +77 -10
  206. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +176 -53
  207. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +19 -12
  208. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +29 -27
  209. package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +20 -3
  210. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +23 -261
  211. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +73 -7
  212. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +8 -3
  213. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +74 -8
  214. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +37 -21
  215. package/dist/extensions/pi-subagents/src/runs/background/inspect-rpc.ts +8 -8
  216. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +184 -9
  217. package/dist/extensions/pi-subagents/src/runs/background/result-delivery-ownership.ts +45 -0
  218. package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +28 -14
  219. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +38 -15
  220. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +1 -1
  221. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +1 -1
  222. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +50 -13
  223. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +93 -8
  224. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +10 -4
  225. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +4 -14
  226. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +549 -377
  227. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +58 -10
  228. package/dist/extensions/pi-subagents/src/runs/background/terminal-run-index.ts +1 -1
  229. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +26 -1
  230. package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +23 -9
  231. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +9 -2
  232. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +2 -2
  233. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +204 -129
  234. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +9 -0
  235. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +23 -1
  236. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +773 -311
  237. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +140 -115
  238. package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +119 -0
  239. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +463 -0
  240. package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +19 -4
  241. package/dist/extensions/pi-subagents/src/runs/shared/child-launch-plan.ts +151 -0
  242. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +21 -7
  243. package/dist/extensions/pi-subagents/src/runs/shared/claude-code-adapter.ts +129 -0
  244. package/dist/extensions/pi-subagents/src/runs/shared/codex-exec-adapter.ts +129 -0
  245. package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +89 -0
  246. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +5 -4
  247. package/dist/extensions/pi-subagents/src/runs/shared/cursor-agent-adapter.ts +114 -0
  248. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +3 -3
  249. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-contract.ts +167 -0
  250. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +122 -0
  251. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +348 -55
  252. package/dist/extensions/pi-subagents/src/runs/shared/fast-mode-extension.ts +5 -5
  253. package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +230 -0
  254. package/dist/extensions/pi-subagents/src/runs/shared/lane-metadata.ts +105 -0
  255. package/dist/extensions/pi-subagents/src/runs/shared/launch-cwd.ts +16 -0
  256. package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +2 -1
  257. package/dist/extensions/pi-subagents/src/runs/shared/mcp-config-sources.ts +422 -0
  258. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +221 -158
  259. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
  260. package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +69 -7
  261. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +47 -6
  262. package/dist/extensions/pi-subagents/src/runs/shared/mutation-evidence.ts +7 -2
  263. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +9 -3
  264. package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +9 -5
  265. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +419 -7
  266. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +15 -1
  267. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +81 -8
  268. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +13 -0
  269. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +21 -1
  270. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +44 -4
  271. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +98 -13
  272. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +1 -1
  273. package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +847 -0
  274. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +18 -0
  275. package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +46 -0
  276. package/dist/extensions/pi-subagents/src/shared/extension-context.ts +24 -0
  277. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +21 -0
  278. package/dist/extensions/pi-subagents/src/shared/formatters.ts +18 -3
  279. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +5 -1
  280. package/dist/extensions/pi-subagents/src/shared/pruned-fork.ts +450 -0
  281. package/dist/extensions/pi-subagents/src/shared/session-file-trust.ts +19 -0
  282. package/dist/extensions/pi-subagents/src/shared/session-tokens.ts +14 -3
  283. package/dist/extensions/pi-subagents/src/shared/settings.ts +19 -105
  284. package/dist/extensions/pi-subagents/src/shared/shortcuts.ts +17 -0
  285. package/dist/extensions/pi-subagents/src/shared/types.ts +359 -40
  286. package/dist/extensions/pi-subagents/src/shared/utils.ts +41 -84
  287. package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +116 -0
  288. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +1 -9
  289. package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +0 -4
  290. package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +1 -2
  291. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +378 -88
  292. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +22 -11
  293. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +3 -0
  294. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +147 -79
  295. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +11 -5
  296. package/dist/extensions/pi-subagents/src/tui/fleet.ts +39 -18
  297. package/dist/extensions/pi-subagents/src/tui/render.ts +369 -46
  298. package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +1 -1
  299. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +6 -3
  300. package/dist/extensions/pi-subagents/src/workflows/host-command.ts +230 -0
  301. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +740 -58
  302. package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +121 -0
  303. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +270 -0
  304. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +195 -6
  305. package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +246 -0
  306. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +2 -2
  307. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1114 -260
  308. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +34 -0
  309. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +150 -3
  310. package/dist/extensions/pi-subagents/test/integration/claude-code-smoke.test.ts +52 -0
  311. package/dist/extensions/pi-subagents/test/integration/claude-code-writer-smoke.test.ts +55 -0
  312. package/dist/extensions/pi-subagents/test/integration/codex-exec-smoke.test.ts +53 -0
  313. package/dist/extensions/pi-subagents/test/integration/codex-exec-writer-smoke.test.ts +57 -0
  314. package/dist/extensions/pi-subagents/test/integration/cursor-agent-smoke.test.ts +59 -0
  315. package/dist/extensions/pi-subagents/test/integration/cursor-agent-writer-smoke.test.ts +62 -0
  316. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +57 -1
  317. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
  318. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +20 -0
  319. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +4 -3
  320. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +2 -2
  321. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +54 -4
  322. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +482 -4
  323. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +157 -9
  324. package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +5 -5
  325. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1508 -291
  326. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +423 -1
  327. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +8 -0
  328. package/dist/extensions/pi-subagents/test/support/cursor-smoke-inputs.ts +51 -0
  329. package/dist/extensions/pi-subagents/test/support/helpers.ts +2 -0
  330. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +5 -5
  331. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +6 -6
  332. package/dist/extensions/pi-subagents/test/unit/abort-recovery.test.ts +152 -0
  333. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +48 -0
  334. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +184 -6
  335. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +66 -6
  336. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +77 -0
  337. package/dist/extensions/pi-subagents/test/unit/agent-refinements.test.ts +18 -0
  338. package/dist/extensions/pi-subagents/test/unit/anti-slop-oxlint.test.ts +86 -0
  339. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +1 -1
  340. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +101 -1
  341. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +121 -0
  342. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +28 -0
  343. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +14 -7
  344. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +53 -0
  345. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +190 -0
  346. package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +42 -0
  347. package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +8 -4
  348. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +23 -0
  349. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +78 -1
  350. package/dist/extensions/pi-subagents/test/unit/child-launch-plan.test.ts +94 -0
  351. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +21 -0
  352. package/dist/extensions/pi-subagents/test/unit/child-session-name.test.ts +50 -0
  353. package/dist/extensions/pi-subagents/test/unit/claude-code-adapter.test.ts +245 -0
  354. package/dist/extensions/pi-subagents/test/unit/codex-exec-adapter.test.ts +192 -0
  355. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +27 -0
  356. package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +150 -0
  357. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +21 -0
  358. package/dist/extensions/pi-subagents/test/unit/cursor-agent-adapter.test.ts +259 -0
  359. package/dist/extensions/pi-subagents/test/unit/cursor-smoke-inputs.test.ts +77 -0
  360. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +1 -4
  361. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +1 -1
  362. package/dist/extensions/pi-subagents/test/unit/extension-context.test.ts +27 -0
  363. package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +223 -0
  364. package/dist/extensions/pi-subagents/test/unit/fast-mode-extension.test.ts +27 -0
  365. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +208 -2
  366. package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +29 -1
  367. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +110 -1
  368. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +10 -0
  369. package/dist/extensions/pi-subagents/test/unit/formatters.test.ts +14 -0
  370. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +18 -9
  371. package/dist/extensions/pi-subagents/test/unit/herdr-session-roots-codec.test.ts +63 -0
  372. package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +2 -2
  373. package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +132 -0
  374. package/dist/extensions/pi-subagents/test/unit/host-command.test.ts +156 -0
  375. package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +98 -0
  376. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +178 -26
  377. package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +2 -1
  378. package/dist/extensions/pi-subagents/test/unit/inspect-rpc.test.ts +21 -0
  379. package/dist/extensions/pi-subagents/test/unit/mcp-direct-tool-grant.test.ts +138 -0
  380. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
  381. package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +91 -0
  382. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +72 -7
  383. package/dist/extensions/pi-subagents/test/unit/mutation-evidence.test.ts +21 -0
  384. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +1 -0
  385. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +20 -2
  386. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +129 -1
  387. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +2 -2
  388. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +4 -0
  389. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +358 -0
  390. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +574 -21
  391. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +109 -1
  392. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +71 -1
  393. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +7 -7
  394. package/dist/extensions/pi-subagents/test/unit/pruned-fork.test.ts +214 -0
  395. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +29 -8
  396. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +53 -0
  397. package/dist/extensions/pi-subagents/test/unit/result-delivery-ownership.test.ts +34 -0
  398. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +101 -3
  399. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +1 -0
  400. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +10 -8
  401. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +71 -1
  402. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +41 -7
  403. package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +136 -2
  404. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +137 -0
  405. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +33 -12
  406. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +504 -1
  407. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +8 -0
  408. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
  409. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +12 -1
  410. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +3 -5
  411. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +5 -6
  412. package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +0 -8
  413. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +5 -0
  414. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +157 -2
  415. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +1 -1
  416. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +63 -4
  417. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +27 -23
  418. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +44 -0
  419. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +1 -0
  420. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +7 -5
  421. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +106 -0
  422. package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +484 -9
  423. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +38 -4
  424. package/dist/extensions/pi-subagents/test/unit/workflow-preflight.test.ts +155 -0
  425. package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +318 -0
  426. package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +365 -0
  427. package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +349 -0
  428. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +21 -0
  429. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +2 -7
  430. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/effect/index.ts +13 -0
  431. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/effect/rules/no-service-constructor-imports.ts +52 -0
  432. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/index.ts +41 -0
  433. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-chained-type-assertions.ts +77 -0
  434. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-conditional-empty-object-spread.ts +49 -0
  435. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-known-value-widening.ts +247 -0
  436. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-module-mocking.ts +91 -0
  437. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-object-parameters.ts +126 -0
  438. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-reflect-apply.ts +28 -0
  439. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-reflect-get.ts +28 -0
  440. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-runtime-typeof.ts +67 -0
  441. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-shape-in-symbol-names.ts +39 -0
  442. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-parameters.ts +83 -0
  443. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-returns.ts +115 -0
  444. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-type-aliases.ts +72 -0
  445. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unsafe-dictionary-type.ts +134 -0
  446. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-widen-then-assert.ts +366 -0
  447. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/require-safety-comment-for-type-assertion.ts +62 -0
  448. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/dictionary-types.ts +502 -0
  449. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/lexical-type-parameters.ts +60 -0
  450. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/reflect-method.ts +35 -0
  451. package/dist/extensions/pi-tool-display/tests/capabilities.test.ts +1 -1
  452. package/dist/extensions/pi-tool-display/tests/settings-inspector.test.ts +2 -2
  453. package/dist/extensions/pi-tool-display/tests/tool-overrides-mcp.test.ts +1 -1
  454. package/dist/extensions/pi-zentui/extensions/zentui/format.ts +2 -1
  455. package/dist/extensions/pi-zentui/test/format.test.ts +15 -1
  456. package/dist/modes/interactive/components/footer.js +2 -2
  457. package/dist/modes/interactive/interactive-mode.js +11 -11
  458. package/dist/skills/workflow/SKILL.md +65 -50
  459. package/package.json +112 -110
  460. package/dist/extensions/enable-readonly-tools.test.ts +0 -63
  461. package/dist/extensions/enable-readonly-tools.ts +0 -24
  462. package/dist/extensions/model-prompt-injector/config.json +0 -16
  463. package/dist/extensions/model-prompt-injector/index.test.ts +0 -102
  464. package/dist/extensions/model-prompt-injector/index.ts +0 -148
  465. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +0 -98
  466. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +0 -250
@@ -14,6 +14,7 @@ import { createChildTranscriptWriter, type ChildTranscriptWriter } from "../../s
14
14
  import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest, enqueueStepSteer, steerAcksDir, steerCapabilityPath, stepSteerInboxDir, watchAsyncControlInbox, type SteerAck, type SteerCapability, type SteerRequest, type StopRequest } from "./control-channel.ts";
15
15
  import { appendJsonl as appendRawJsonl, formatOutputArtifactContent, getArtifactPaths, writeArtifact, writeMetadata } from "../../shared/artifacts.ts";
16
16
  import { PI_CODING_AGENT_PACKAGE, getPiSpawnCommand, resolveInstalledPiPackageRoot } from "../shared/pi-spawn.ts";
17
+ import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
17
18
  import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, injectSingleOutputInstruction, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
18
19
  import {
19
20
  type ActivityState,
@@ -35,24 +36,21 @@ import {
35
36
  type NestedRouteInfo,
36
37
  type NestedRunSummary,
37
38
  type ResolvedControlConfig,
38
- type ResolvedTurnBudget,
39
39
  type ResolvedToolBudget,
40
40
  type RunFanoutBudgetDescriptor,
41
41
  type SubagentRunMode,
42
42
  type SubagentOutputState,
43
43
  type UsageBudgetConfig,
44
44
  type ToolBudgetState,
45
- type TurnBudgetState,
46
45
  type Usage,
47
46
  type WorkflowGraphSnapshot,
48
- type SteeringStatus,
49
47
  type SteeringTargetState,
50
48
  type SteeringTargetStatus,
51
49
  type SubagentChildStatusEvent,
50
+ type WorkflowLaneMetadata,
52
51
  DEFAULT_MAX_OUTPUT,
53
52
  type MaxOutputConfig,
54
53
  SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
55
- POLL_INTERVAL_MS,
56
54
  truncateOutput,
57
55
  getSubagentDepthEnv,
58
56
  } from "../../shared/types.ts";
@@ -78,16 +76,17 @@ import {
78
76
  Semaphore,
79
77
  } from "../shared/parallel-utils.ts";
80
78
  import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, type SubagentTaskDelivery } from "../shared/pi-args.ts";
79
+ import { deriveChildSessionName } from "../../shared/child-session-name.ts";
81
80
  import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
82
81
  import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
83
82
  import { createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
84
- import { formatProcessSignalError, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
83
+ import { formatMidToolExitError, formatProcessSignalError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
85
84
  import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
86
85
  import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
87
86
  import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
88
87
  import { claimRunFanoutBatch, getRunFanoutBudgetSnapshot } from "../shared/run-fanout-budget.ts";
89
88
  import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent } from "../shared/nested-events.ts";
90
- import { formatModelAttemptNote, formatSubagentModelVerificationError, isContextOverflow, isRetryableModelFailure, recordRetryableModelFailure } from "../shared/model-fallback.ts";
89
+ import { formatModelAttemptNote, formatSubagentModelVerificationError, isContextOverflow, isRetryableModelFailureAttempt, recordRetryableModelFailure } from "../shared/model-fallback.ts";
91
90
  import {
92
91
  SUBAGENT_STARTUP_RETRY_DELAYS_MS,
93
92
  formatSubagentExtensionConflictError,
@@ -100,8 +99,10 @@ import { markProcessTerminalCandidateLeaseRelease, writeProcessTerminalCandidate
100
99
  import { createOwnedProcessTreeController, type OwnedProcessTreeController } from "./owned-process-tree.ts";
101
100
  import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, updateSteeringTarget } from "./steering.ts";
102
101
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
103
- import { PROMPT_REDACTED, detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
104
- import { evaluateCompletionMutationGuard, validateImplementationToolContract } from "../shared/completion-guard.ts";
102
+ import { PROMPT_REDACTED, detectSubagentError, extractTextFromContent, extractToolArgsPreview, formatEmptyTerminalAssistantResponseError, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
103
+ import { evaluateCompletionMutationGuard, expectsImplementationMutation, hasMutationToolCapability, validateImplementationToolContract } from "../shared/completion-guard.ts";
104
+ import { planCompletionEvidence, projectSettlementDiagnostic } from "../shared/completion-evidence.ts";
105
+ import { planAbortRecovery } from "../shared/abort-recovery.ts";
105
106
  import {
106
107
  createMutatingFailureState,
107
108
  didMutatingToolFail,
@@ -124,7 +125,7 @@ import {
124
125
  formatWorktreeTaskCwdConflict,
125
126
  type WorktreeSetup,
126
127
  } from "../shared/worktree.ts";
127
- import { resolveEffectiveThinking } from "../../shared/model-info.ts";
128
+ import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.ts";
128
129
  import { assertThinkingWithinCeiling, decodeThinkingCeiling, SUBAGENT_THINKING_CEILING_ENV } from "../../shared/thinking-ceiling.ts";
129
130
  import { launchBindingDigest } from "../../shared/launch-contract.ts";
130
131
  import { writeInitialProgressFile } from "../../shared/settings.ts";
@@ -135,15 +136,18 @@ import { waitForImportedAsyncRoot } from "./chain-root-attachment.ts";
135
136
  import { normalizeExtensionBindings } from "../shared/extension-bindings.ts";
136
137
  import { appendRunnerStepsToStatus, consumeChainAppendRequests, countPendingChainAppendRequests, statusStepDescription } from "./chain-append.ts";
137
138
  import { asyncStatusChildIdentity } from "../shared/child-identity.ts";
138
- import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, turnBudgetDecision, turnBudgetDeferredNote, turnBudgetDeferredState, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
139
139
  import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
140
140
  import { effectiveToolTimeoutMs, formatToolTimeoutMessage, toolTimeoutCallKey } from "../shared/tool-timeout.ts";
141
141
  import { usageBudgetExceededMessage, usageBudgetState } from "../shared/usage-budget.ts";
142
142
  import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
143
143
  import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
144
- import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
144
+ import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ChildLifecycleState, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
145
145
  import { acquireSessionLease, type SessionLeaseRequest } from "../shared/session-lease.ts";
146
146
  import { buildExternalCliPrompt, runExternalCli } from "../shared/external-cli-runner.ts";
147
+ import { resolveClaudeCodeLaunch } from "../shared/claude-code-adapter.ts";
148
+ import { resolveCodexExecLaunch } from "../shared/codex-exec-adapter.ts";
149
+ import { resolveCursorAgentLaunch } from "../shared/cursor-agent-adapter.ts";
150
+ import { resolveExternalCliRunnerStatus } from "../shared/external-cli-contract.ts";
147
151
  import { runExternalJob } from "../shared/external-job-runner.ts";
148
152
  import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
149
153
  import { decodeSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
@@ -193,7 +197,6 @@ interface SubagentRunConfig {
193
197
  deadlineAt?: number;
194
198
  /** Resolved configured hard per-tool-call timeout (ms); fast tools still have a default when undefined. */
195
199
  toolTimeoutMs?: number;
196
- turnBudget?: ResolvedTurnBudget;
197
200
  toolBudget?: ResolvedToolBudget;
198
201
  usageBudget?: UsageBudgetConfig;
199
202
  revivalLease?: SessionLeaseRequest;
@@ -206,12 +209,16 @@ interface SubagentRunConfig {
206
209
  launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
207
210
  runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
208
211
  runnerProcessInstanceId?: string;
212
+ launchBarrierToken?: string;
209
213
  parentWorkflowRunId?: string;
210
214
  workflowKey?: string;
215
+ lane?: WorkflowLaneMetadata;
211
216
  }
212
217
 
213
218
  interface StepResult {
214
219
  agent: string;
220
+ /** Human-readable display name for the child session, when derived at launch. */
221
+ sessionName?: string;
215
222
  context?: "fresh" | "fork";
216
223
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
217
224
  capabilityAudit?: import("../shared/capability-ceiling.ts").SubagentCapabilityAudit;
@@ -232,9 +239,6 @@ interface StepResult {
232
239
  stopped?: boolean;
233
240
  processSignal?: string | null;
234
241
  timeoutRecovery?: import("../../shared/types.ts").TimeoutRecoverySummary;
235
- turnBudget?: TurnBudgetState;
236
- turnBudgetExceeded?: boolean;
237
- wrapUpRequested?: boolean;
238
242
  toolBudget?: ToolBudgetState;
239
243
  toolBudgetBlocked?: boolean;
240
244
  sessionFile?: string;
@@ -422,6 +426,23 @@ function costSummaryFromAttempts(attempts: ModelAttempt[] | undefined): CostSumm
422
426
  : undefined;
423
427
  }
424
428
 
429
+ function usageFromAttempts(attempts: ModelAttempt[] | undefined): Usage | undefined {
430
+ if (!attempts || attempts.length === 0) return undefined;
431
+ const usage = emptyUsage();
432
+ for (const attempt of attempts) {
433
+ if (!attempt.usage) continue;
434
+ usage.input += attempt.usage.input;
435
+ usage.output += attempt.usage.output;
436
+ usage.cacheRead += attempt.usage.cacheRead;
437
+ usage.cacheWrite += attempt.usage.cacheWrite;
438
+ usage.cost += attempt.usage.cost;
439
+ usage.turns += attempt.usage.turns;
440
+ }
441
+ return usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0
442
+ ? usage
443
+ : undefined;
444
+ }
445
+
425
446
  function appendRecentStepOutput(step: RunnerStatusStep, lines: string[]): void {
426
447
  const nonEmpty = lines.filter((line) => line.trim());
427
448
  if (nonEmpty.length === 0) return;
@@ -496,6 +517,7 @@ interface ChildUsage {
496
517
  output?: number;
497
518
  outputTokens?: number;
498
519
  cacheRead?: number;
520
+ cacheReadTokens?: number;
499
521
  cacheWrite?: number;
500
522
  cost?: { total?: number };
501
523
  }
@@ -529,14 +551,12 @@ interface RunPiStreamingResult {
529
551
  interrupted?: boolean;
530
552
  timedOut?: boolean;
531
553
  stopped?: boolean;
532
- turnBudget?: TurnBudgetState;
533
- turnBudgetExceeded?: boolean;
534
- wrapUpRequested?: boolean;
535
554
  toolBudget?: ToolBudgetState;
536
555
  toolBudgetBlocked?: boolean;
537
556
  observedMutationAttempt?: boolean;
538
557
  structuredOutputToolInvoked?: boolean;
539
558
  structuredOutputMessageStartIndex?: number;
559
+ structuredOutput?: unknown;
540
560
  watchdog?: ChildWatchdogStateSnapshot;
541
561
  runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
542
562
  processInstanceId: string;
@@ -546,6 +566,43 @@ interface RunPiStreamingResult {
546
566
  currentTool?: string;
547
567
  currentToolArgs?: string;
548
568
  currentPath?: string;
569
+ afterCompactionSettlement?: boolean;
570
+ abortRecoveryDiagnostic?: string;
571
+ effects?: import("../../shared/types.ts").EffectsProjection;
572
+ }
573
+
574
+ const MAX_CHILD_FAILURE_DIAGNOSTIC_CHARS = 8_192;
575
+
576
+ function formatRequiredOutputError(requiredOutput: {
577
+ kind: "file-only" | "structured";
578
+ path: string;
579
+ missing: boolean;
580
+ } | undefined): string | undefined {
581
+ if (!requiredOutput?.missing) return undefined;
582
+ return `Required ${requiredOutput.kind} output was not produced: ${requiredOutput.path.slice(0, 2_048)}`;
583
+ }
584
+
585
+ function formatChildFailureDiagnostic(input: {
586
+ error: string | undefined;
587
+ afterCompactionSettlement?: boolean;
588
+ abortRecoveryDiagnostic?: string;
589
+ requiredOutput?: {
590
+ kind: "file-only" | "structured";
591
+ path: string;
592
+ missing: boolean;
593
+ };
594
+ }): string | undefined {
595
+ const missingOutput = formatRequiredOutputError(input.requiredOutput);
596
+ const notes = [
597
+ input.abortRecoveryDiagnostic,
598
+ input.afterCompactionSettlement ? "Child failure followed session compaction and agent settlement." : undefined,
599
+ missingOutput && input.error !== missingOutput ? missingOutput : undefined,
600
+ ].filter((note): note is string => Boolean(note));
601
+ if (notes.length === 0) return input.error;
602
+ const context = notes.join("\n");
603
+ const errorLimit = MAX_CHILD_FAILURE_DIAGNOSTIC_CHARS - (context ? context.length + 1 : 0);
604
+ const baseError = input.error || "Subagent failed.";
605
+ return `${baseError.slice(0, Math.max(0, errorLimit))}${context ? `\n${context}` : ""}`;
549
606
  }
550
607
 
551
608
  function runPiStreaming(
@@ -564,13 +621,13 @@ function runPiStreaming(
564
621
  timeoutMessage?: string,
565
622
  registerStop?: (stop: (() => void) | undefined) => void,
566
623
  stopMessage?: string,
567
- registerTurnBudgetAbort?: (abort: ((message: string, state?: TurnBudgetState) => void) | undefined) => void,
568
624
  onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void,
569
625
  toolTimeoutMs?: number,
570
626
  runDeadlineAt?: number,
571
627
  orcaProgressTab?: OrcaProgressTab,
572
628
  expectedModelForVerification?: string,
573
629
  modelVerificationRegistry?: Array<{ provider: string; id: string; fullId: string }>,
630
+ mutationTools?: readonly string[],
574
631
  ): Promise<RunPiStreamingResult> {
575
632
  return new Promise((resolve) => {
576
633
  const startedAt = Date.now();
@@ -610,9 +667,6 @@ function runPiStreaming(
610
667
  let interrupted = false;
611
668
  let timedOut = false;
612
669
  let stopped = false;
613
- let turnBudgetExceeded = false;
614
- let turnBudgetMessage: string | undefined;
615
- let turnBudget: TurnBudgetState | undefined;
616
670
  let observedMutationAttempt = false;
617
671
  let structuredOutputToolInvoked = false;
618
672
  let structuredOutputMessageStartIndex: number | undefined;
@@ -620,8 +674,50 @@ function runPiStreaming(
620
674
  let currentToolArgs: string | undefined;
621
675
  let currentPath: string | undefined;
622
676
  let toolCount = 0;
677
+ type ActiveToolCall = { key: string; tool: string; args?: string; path?: string };
678
+ let activeToolSequence = 0;
679
+ const activeToolCalls = new Map<string, ActiveToolCall>();
680
+ const activeToolKeysByName = new Map<string, string[]>();
681
+ const refreshCurrentTool = (): void => {
682
+ const active = [...activeToolCalls.values()].at(-1);
683
+ currentTool = active?.tool;
684
+ currentToolArgs = active?.args;
685
+ currentPath = active?.path;
686
+ };
687
+ const recordActiveToolCall = (event: { toolCallId?: unknown; toolName: string; args?: Record<string, unknown> }): void => {
688
+ const key = toolTimeoutCallKey(event, ++activeToolSequence);
689
+ const active = omitUndefinedProperties({
690
+ key,
691
+ tool: event.toolName,
692
+ args: extractToolArgsPreview(event.args ?? {}),
693
+ path: resolveCurrentPath(event.toolName, event.args),
694
+ });
695
+ activeToolCalls.set(key, active);
696
+ const keys = activeToolKeysByName.get(active.tool) ?? [];
697
+ keys.push(key);
698
+ activeToolKeysByName.set(active.tool, keys);
699
+ refreshCurrentTool();
700
+ };
701
+ const removeActiveToolCall = (event: { toolCallId?: unknown; toolName?: unknown }): void => {
702
+ const key = typeof event.toolCallId === "string" && event.toolCallId.length > 0
703
+ ? `id:${event.toolCallId}`
704
+ : typeof event.toolName === "string"
705
+ ? activeToolKeysByName.get(event.toolName)?.[0]
706
+ : activeToolCalls.size === 1
707
+ ? [...activeToolCalls.keys()][0]
708
+ : undefined;
709
+ if (!key) return;
710
+ const active = activeToolCalls.get(key);
711
+ if (!active) return;
712
+ activeToolCalls.delete(key);
713
+ const keys = activeToolKeysByName.get(active.tool)?.filter((candidate) => candidate !== key) ?? [];
714
+ if (keys.length > 0) activeToolKeysByName.set(active.tool, keys);
715
+ else activeToolKeysByName.delete(active.tool);
716
+ refreshCurrentTool();
717
+ };
623
718
  const childWatchdogConfig = decodeChildWatchdogConfig(env?.[CHILD_WATCHDOG_CONFIG_ENV]);
624
719
  let childWatchdogState: ChildWatchdogStateSnapshot | undefined;
720
+ const childLifecycleState: ChildLifecycleState = { compactionRetryActive: false };
625
721
  let applyChildLifecycle = (_action: ChildLifecycleAction): void => {};
626
722
  const updateChildWatchdogState = (snapshot: ChildWatchdogStateSnapshot): void => {
627
723
  childWatchdogState = snapshot;
@@ -672,8 +768,21 @@ function runPiStreaming(
672
768
 
673
769
  appendChildEvent(event as unknown as Record<string, unknown>);
674
770
  transcriptWriter?.writeChildEvent(event);
675
- if (event.type === "agent_settled") agentSettledReceived = true;
676
- applyChildLifecycle(projectChildLifecycle(event));
771
+ if (event.type === "compaction_start") compactionStartedReceived = true;
772
+ if (event.type === "compaction_end" && event.willRetry === true) {
773
+ compactionStartedReceived = false;
774
+ afterCompactionSettlement = false;
775
+ }
776
+ if (event.type === "agent_start" || event.type === "auto_retry_start") {
777
+ compactionStartedReceived = false;
778
+ afterCompactionSettlement = false;
779
+ }
780
+ const lifecycleAction = projectChildLifecycle(event, false, childLifecycleState);
781
+ if (event.type === "agent_settled" && lifecycleAction === "start-drain") {
782
+ agentSettledReceived = true;
783
+ afterCompactionSettlement = compactionStartedReceived;
784
+ }
785
+ applyChildLifecycle(lifecycleAction);
677
786
 
678
787
  if (isChildWatchdogStatusEvent(event)) {
679
788
  if (!childWatchdogConfig) return;
@@ -710,29 +819,32 @@ function runPiStreaming(
710
819
 
711
820
  if (event.type === "tool_execution_end") {
712
821
  clearActiveToolTimeout(event);
713
- currentTool = undefined;
714
- currentToolArgs = undefined;
715
- currentPath = undefined;
822
+ removeActiveToolCall(event);
716
823
  return;
717
824
  }
718
825
 
719
826
  if (event.type === "tool_execution_start" && event.toolName) {
720
827
  toolCount += 1;
721
828
  armToolTimeout({ toolCallId: (event as { toolCallId?: unknown }).toolCallId, toolName: event.toolName });
722
- currentTool = event.toolName;
723
- currentToolArgs = extractToolArgsPreview(event.args ?? {});
724
- currentPath = resolveCurrentPath(event.toolName, event.args);
829
+ recordActiveToolCall({ toolCallId: (event as { toolCallId?: unknown }).toolCallId, toolName: event.toolName, args: event.args });
725
830
  if (event.toolName === "structured_output") {
726
831
  structuredOutputToolInvoked = true;
727
832
  structuredOutputMessageStartIndex = messages.length;
728
833
  }
729
- observedMutationAttempt = observedMutationAttempt || isMutatingTool(event.toolName, event.args);
834
+ observedMutationAttempt = observedMutationAttempt || isMutatingTool(event.toolName, event.args, mutationTools);
730
835
  const toolArgs = extractToolArgsPreview(event.args ?? {});
731
836
  writeOutputLine(toolArgs ? `${event.toolName}: ${toolArgs}` : event.toolName);
732
837
  return;
733
838
  }
734
839
 
735
840
  if ((event.type === "message_end" || event.type === "tool_result_end") && event.message) {
841
+ if (event.type === "tool_result_end") {
842
+ clearActiveToolTimeout(event);
843
+ removeActiveToolCall({
844
+ toolCallId: (event.message as { toolCallId?: unknown }).toolCallId ?? (event as { toolCallId?: unknown }).toolCallId,
845
+ toolName: (event.message as { toolName?: unknown }).toolName ?? event.toolName,
846
+ });
847
+ }
736
848
  messages.push(event.message);
737
849
  const text = extractTextFromContent(event.message.content);
738
850
  if (text) writeOutputText(text);
@@ -759,7 +871,11 @@ function runPiStreaming(
759
871
  if (isTerminalAssistantStop(event.message)) {
760
872
  if (!event.message.errorMessage && extractTextFromContent(event.message.content).trim()) assistantError = undefined;
761
873
  cleanTerminalAssistantStopReceived ||= !event.message.errorMessage;
762
- applyChildLifecycle(projectChildLifecycle(event, true));
874
+ clearAllToolTimeouts();
875
+ activeToolCalls.clear();
876
+ activeToolKeysByName.clear();
877
+ refreshCurrentTool();
878
+ applyChildLifecycle(projectChildLifecycle(event, true, childLifecycleState));
763
879
  }
764
880
  }
765
881
  };
@@ -772,11 +888,11 @@ function runPiStreaming(
772
888
  let forcedTerminationSignal = false;
773
889
  let cleanTerminalAssistantStopReceived = false;
774
890
  let agentSettledReceived = false;
891
+ let compactionStartedReceived = false;
892
+ let afterCompactionSettlement = false;
775
893
  let finalDrainTimer: NodeJS.Timeout | undefined;
776
894
  let finalHardKillTimer: NodeJS.Timeout | undefined;
777
895
  let watchdogTailTimer: NodeJS.Timeout | undefined;
778
- let turnBudgetTerminationTimer: NodeJS.Timeout | undefined;
779
- let turnBudgetHardKillTimer: NodeJS.Timeout | undefined;
780
896
  let protocolHardKillTimer: NodeJS.Timeout | undefined;
781
897
  let protocolError: ProtocolOutputLimit | undefined;
782
898
  let settled = false;
@@ -897,23 +1013,6 @@ function runPiStreaming(
897
1013
  if (processTreeController) void processTreeController.terminate();
898
1014
  else trySignalChild(child, "SIGTERM");
899
1015
  });
900
- registerTurnBudgetAbort?.((message, state) => {
901
- if (settled || timedOut || stopped || turnBudgetExceeded) return;
902
- turnBudgetExceeded = true;
903
- turnBudgetMessage = message;
904
- turnBudget = state;
905
- interrupted = false;
906
- error = message;
907
- trySignalChild(child, "SIGINT");
908
- turnBudgetTerminationTimer = setTimeout(() => {
909
- if (!settled && !timedOut && !stopped) trySignalChild(child, "SIGTERM");
910
- }, 1000);
911
- turnBudgetTerminationTimer.unref?.();
912
- turnBudgetHardKillTimer = setTimeout(() => {
913
- if (!settled && !timedOut && !stopped) trySignalChild(child, "SIGKILL");
914
- }, 4000);
915
- turnBudgetHardKillTimer.unref?.();
916
- });
917
1016
  const clearDrainTimers = () => {
918
1017
  clearAllToolTimeouts();
919
1018
  if (finalDrainTimer) {
@@ -925,14 +1024,6 @@ function runPiStreaming(
925
1024
  finalHardKillTimer = undefined;
926
1025
  }
927
1026
  clearWatchdogTailTimer();
928
- if (turnBudgetTerminationTimer) {
929
- clearTimeout(turnBudgetTerminationTimer);
930
- turnBudgetTerminationTimer = undefined;
931
- }
932
- if (turnBudgetHardKillTimer) {
933
- clearTimeout(turnBudgetHardKillTimer);
934
- turnBudgetHardKillTimer = undefined;
935
- }
936
1027
  if (protocolHardKillTimer) {
937
1028
  clearTimeout(protocolHardKillTimer);
938
1029
  protocolHardKillTimer = undefined;
@@ -1000,7 +1091,6 @@ function runPiStreaming(
1000
1091
  registerInterrupt?.(undefined);
1001
1092
  registerTimeout?.(undefined);
1002
1093
  registerStop?.(undefined);
1003
- registerTurnBudgetAbort?.(undefined);
1004
1094
  clearDrainTimers();
1005
1095
  clearStdioGuard();
1006
1096
  stdoutReader.end();
@@ -1010,32 +1100,32 @@ function runPiStreaming(
1010
1100
  const finalOutput = getFinalOutput(messages) || rawStdoutTail.text().trim();
1011
1101
  const finalError = error ?? assistantError;
1012
1102
  const forcedDrainAfterFinalSuccess = Boolean(forcedTerminationSignal || signal) && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !finalError;
1103
+ const forcedDrainAfterEmptyTerminal = forcedDrainAfterFinalSuccess && hasEmptyTerminalAssistantResponse(messages);
1104
+ const forcedDrainError = forcedDrainAfterEmptyTerminal && stderr.trim()
1105
+ ? stderr.trim()
1106
+ : undefined;
1013
1107
  const signalError = isUnexplainedProcessSignal({
1014
1108
  processSignal: signal,
1015
1109
  interrupted,
1016
1110
  timedOut,
1017
1111
  stopped,
1018
- turnBudgetExceeded,
1019
- forcedDrainAfterFinalSuccess,
1112
+ forcedDrainAfterFinalSuccess: forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal,
1020
1113
  }) ? formatProcessSignalError(signal!) : undefined;
1021
1114
  resolve(omitUndefinedProperties({
1022
1115
  stderr,
1023
- exitCode: timedOut || stopped ? 1 : turnBudgetExceeded ? 1 : interrupted || forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
1116
+ exitCode: timedOut || stopped ? 1 : interrupted || (forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal) ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
1024
1117
  messages,
1025
1118
  usage,
1026
1119
  toolCount,
1027
1120
  durationMs: Date.now() - startedAt,
1028
1121
  model,
1029
- error: stopped ? (stopMessage ?? "Subagent stopped by user.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? turnBudgetMessage : interrupted || forcedDrainAfterFinalSuccess ? undefined : finalError ?? signalError,
1122
+ error: stopped ? (stopMessage ?? "Subagent stopped by user.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : interrupted || (forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal) ? undefined : finalError ?? forcedDrainError ?? signalError,
1030
1123
  protocolError,
1031
1124
  finalOutput: (timedOut || stopped) && !finalOutput.trim() ? (stopped ? stopMessage ?? "Subagent stopped by user." : timeoutMessage ?? "Subagent timed out.") : finalOutput,
1032
1125
  outputState: finalOutput.trim() ? "present" : "absent",
1033
1126
  interrupted,
1034
1127
  timedOut,
1035
1128
  stopped,
1036
- turnBudget,
1037
- turnBudgetExceeded,
1038
- wrapUpRequested: turnBudget?.outcome === "wrap-up-requested" || turnBudget?.outcome === "termination-deferred" || turnBudgetExceeded || undefined,
1039
1129
  observedMutationAttempt,
1040
1130
  structuredOutputToolInvoked,
1041
1131
  structuredOutputMessageStartIndex,
@@ -1047,6 +1137,7 @@ function runPiStreaming(
1047
1137
  currentTool,
1048
1138
  currentToolArgs,
1049
1139
  currentPath,
1140
+ afterCompactionSettlement: afterCompactionSettlement || undefined,
1050
1141
  }));
1051
1142
  });
1052
1143
 
@@ -1060,7 +1151,6 @@ function runPiStreaming(
1060
1151
  registerInterrupt?.(undefined);
1061
1152
  registerTimeout?.(undefined);
1062
1153
  registerStop?.(undefined);
1063
- registerTurnBudgetAbort?.(undefined);
1064
1154
  clearDrainTimers();
1065
1155
  clearStdioGuard();
1066
1156
  stdoutReader.end();
@@ -1069,7 +1159,7 @@ function runPiStreaming(
1069
1159
  const stderr = stderrTail.text();
1070
1160
  const finalOutput = getFinalOutput(messages) || rawStdoutTail.text().trim();
1071
1161
  const spawnErrorMessage = spawnError instanceof Error ? spawnError.message : String(spawnError);
1072
- resolve(omitUndefinedProperties({ stderr, exitCode: 1, messages, usage, toolCount, durationMs: Date.now() - startedAt, model, error: stopped ? (stopMessage ?? "Subagent stopped by user.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? turnBudgetMessage : error ?? assistantError ?? spawnErrorMessage, protocolError, finalOutput: (timedOut || stopped) && !finalOutput.trim() ? (stopped ? stopMessage ?? "Subagent stopped by user." : timeoutMessage ?? "Subagent timed out.") : finalOutput, outputState: finalOutput.trim() ? "present" : "absent", timedOut, stopped, turnBudget, turnBudgetExceeded, wrapUpRequested: turnBudget?.outcome === "wrap-up-requested" || turnBudget?.outcome === "termination-deferred" || turnBudgetExceeded || undefined, observedMutationAttempt, structuredOutputToolInvoked, structuredOutputMessageStartIndex, watchdog: childWatchdogState, processInstanceId, processTree: { state: "unknown", reason: "verification-failed", diagnostic: spawnErrorMessage } }));
1162
+ resolve(omitUndefinedProperties({ stderr, exitCode: 1, messages, usage, toolCount, durationMs: Date.now() - startedAt, model, error: stopped ? (stopMessage ?? "Subagent stopped by user.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : error ?? assistantError ?? spawnErrorMessage, protocolError, finalOutput: (timedOut || stopped) && !finalOutput.trim() ? (stopped ? stopMessage ?? "Subagent stopped by user." : timeoutMessage ?? "Subagent timed out.") : finalOutput, outputState: finalOutput.trim() ? "present" : "absent", timedOut, stopped, observedMutationAttempt, structuredOutputToolInvoked, structuredOutputMessageStartIndex, watchdog: childWatchdogState, processInstanceId, processTree: { state: "unknown", reason: "verification-failed", diagnostic: spawnErrorMessage } }));
1073
1163
  });
1074
1164
  });
1075
1165
  }
@@ -1204,7 +1294,6 @@ interface SingleStepContext {
1204
1294
  registerInterrupt?: (interrupt: (() => void) | undefined) => void;
1205
1295
  registerTimeout?: (interrupt: (() => void) | undefined) => void;
1206
1296
  registerStop?: (stop: (() => void) | undefined) => void;
1207
- registerTurnBudgetAbort?: (abort: ((message: string, state?: TurnBudgetState) => void) | undefined) => void;
1208
1297
  timeoutSignal?: AbortSignal;
1209
1298
  stopSignal?: AbortSignal;
1210
1299
  timeoutMessage?: string;
@@ -1213,18 +1302,18 @@ interface SingleStepContext {
1213
1302
  toolTimeoutMs?: number;
1214
1303
  /** Effective step deadline (Date.now() + effective timeout) when a run budget exists. */
1215
1304
  deadlineAt?: number;
1216
- turnBudget?: ResolvedTurnBudget;
1217
1305
  childIntercomTarget?: string;
1218
1306
  orchestratorIntercomTarget?: string;
1219
1307
  nestedRoute?: NestedRouteInfo;
1220
1308
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
1221
1309
  runFanoutBudget?: RunFanoutBudgetDescriptor;
1222
- onAttemptStart?: (attempt: { model?: string; thinking?: string }) => void;
1310
+ onAttemptStart?: (attempt: { model?: string; thinking?: string; contextLimit?: number }) => void;
1223
1311
  onChildEvent?: (event: ChildEvent) => void;
1224
1312
  onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void;
1225
1313
  onExternalProcess?: (process: ExternalProcessStatus) => void;
1226
1314
  onExternalJob?: (status: ExternalJobStatus) => void;
1227
1315
  skipAcceptance?: () => boolean;
1316
+ usageBudgetExhausted?: () => boolean;
1228
1317
  /** False when sibling work in the same Git worktree could have caused the tracked diff. */
1229
1318
  trackedMutationEvidenceForCompletionGuard?: boolean;
1230
1319
  orcaProgressTab?: OrcaProgressTab;
@@ -1293,10 +1382,13 @@ async function runSingleStepInner(
1293
1382
  modelAttempts: imported.modelAttempts,
1294
1383
  contextOverflow: imported.contextOverflow,
1295
1384
  totalCost: imported.totalCost,
1385
+ usage: imported.usage,
1296
1386
  structuredOutput: timedOut || stopped ? undefined : imported.structuredOutput,
1297
1387
  structuredOutputPath: timedOut || stopped ? undefined : imported.structuredOutputPath,
1298
1388
  structuredOutputSchemaPath: timedOut || stopped ? undefined : imported.structuredOutputSchemaPath,
1299
1389
  acceptance: timedOut || stopped ? undefined : imported.acceptance,
1390
+ execution: timedOut || stopped ? undefined : imported.execution,
1391
+ effects: timedOut || stopped ? undefined : imported.effects,
1300
1392
  });
1301
1393
  } finally {
1302
1394
  ctx.registerTimeout?.(undefined);
@@ -1315,12 +1407,15 @@ async function runSingleStepInner(
1315
1407
  if (!step.runner) {
1316
1408
  resolvedTaskToolPlan = resolvePiLaunchToolPlan(omitUndefinedProperties({
1317
1409
  tools: step.tools,
1410
+ allowNestedSubagents: step.allowNestedSubagents,
1318
1411
  extensions: step.extensions,
1319
1412
  subagentOnlyExtensions: step.subagentOnlyExtensions,
1320
1413
  fast: step.fast,
1321
1414
  model: step.model,
1322
1415
  modelCandidates: step.modelCandidates,
1323
1416
  mcpDirectTools: step.mcpDirectTools,
1417
+ mcpConfig: step.mcpConfig,
1418
+ runtimeServerNames: step.runtimeServerNames,
1324
1419
  cwd: step.cwd ?? ctx.cwd,
1325
1420
  requireReadTool: Boolean(step.skills?.length),
1326
1421
  structuredOutput: Boolean(effectiveStructuredOutput),
@@ -1351,6 +1446,9 @@ async function runSingleStepInner(
1351
1446
  });
1352
1447
  }
1353
1448
  }
1449
+ // Derive from the pre-acceptance task so internal acceptance/recovery
1450
+ // instructions never leak into the display name.
1451
+ const childSessionName = step.sessionName ?? deriveChildSessionName({ agent: step.agent, task, label: step.label });
1354
1452
  if (step.effectiveAcceptance) {
1355
1453
  const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance, { reportOptional: isAgentContractV1(step.agentContract), structuredOutput: Boolean(step.structuredOutput?.acceptanceReportPath) });
1356
1454
  if (acceptancePrompt) task = `${task}\n${acceptancePrompt}`;
@@ -1381,21 +1479,29 @@ async function runSingleStepInner(
1381
1479
  transcriptWriter?.writeInitialUserMessage(`${PROMPT_REDACTED}; live Prompt Audit only.`);
1382
1480
 
1383
1481
  if (step.runner?.type === "external-cli") {
1384
- const runner: ExternalCliRunnerStatus = {
1385
- type: "external-cli",
1386
- command: step.runner.command,
1387
- args: step.runner.args ?? [],
1388
- promptDelivery: step.runner.promptDelivery ?? "stdin",
1389
- capabilities: { stop: true, steer: false, resume: false, structuredOutput: false, toolEvents: false },
1390
- };
1482
+ const externalCwd = step.cwd ?? ctx.cwd;
1483
+ const adapterLaunch = step.runner.adapter === "codex-exec" || step.runner.adapter === "codex-exec-writer"
1484
+ ? resolveCodexExecLaunch({ adapter: step.runner.adapter, command: step.runner.command, asyncDir: path.dirname(ctx.outputFile), stepIndex: ctx.flatIndex })
1485
+ : step.runner.adapter === "claude-code" || step.runner.adapter === "claude-code-writer"
1486
+ ? resolveClaudeCodeLaunch({ adapter: step.runner.adapter, command: step.runner.command })
1487
+ : step.runner.adapter === "cursor-agent" || step.runner.adapter === "cursor-agent-writer"
1488
+ ? resolveCursorAgentLaunch({ adapter: step.runner.adapter, command: step.runner.command, cwd: externalCwd, asyncDir: path.dirname(ctx.outputFile), stepIndex: ctx.flatIndex })
1489
+ : undefined;
1490
+ const runner = resolveExternalCliRunnerStatus({ ...step.runner, ...(adapterLaunch ? { args: adapterLaunch.args } : {}) });
1391
1491
  const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
1392
1492
  const external = await runExternalCli(omitUndefinedProperties({
1393
- command: runner.command,
1394
- args: runner.args,
1395
- cwd: step.cwd ?? ctx.cwd,
1493
+ command: adapterLaunch?.command ?? runner.command,
1494
+ args: adapterLaunch?.args ?? runner.args,
1495
+ cwd: externalCwd,
1396
1496
  prompt: buildExternalCliPrompt(step.systemPrompt ?? "", task),
1397
1497
  asyncDir: path.dirname(ctx.outputFile),
1398
1498
  stepIndex: ctx.flatIndex,
1499
+ environment: adapterLaunch?.environment,
1500
+ preflight: adapterLaunch?.preflight,
1501
+ parser: adapterLaunch?.parser,
1502
+ finalOutputPath: adapterLaunch?.finalOutputPath,
1503
+ promptFilePath: adapterLaunch?.promptFilePath,
1504
+ temporaryDirectories: adapterLaunch?.temporaryDirectories,
1399
1505
  registerTimeout: ctx.registerTimeout,
1400
1506
  registerStop: ctx.registerStop,
1401
1507
  timeoutMessage: ctx.timeoutMessage,
@@ -1406,14 +1512,18 @@ async function runSingleStepInner(
1406
1512
  }));
1407
1513
  try { fs.writeFileSync(ctx.outputFile, external.output, "utf-8"); } catch { /* Observability output is best-effort. */ }
1408
1514
  const resolvedOutput = step.outputPath && external.exitCode === 0
1409
- ? resolveSingleOutput(step.outputPath, external.output, outputSnapshot)
1515
+ ? resolveSingleOutput(step.outputPath, external.output, outputSnapshot, step.outputClaimPath)
1410
1516
  : { fullOutput: external.output };
1411
1517
  const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, resolvedOutput.fullOutput) : undefined;
1518
+ const exitCode = resolvedOutput.fatalError ? 1 : external.exitCode;
1519
+ const error = resolvedOutput.fatalError && resolvedOutput.saveError
1520
+ ? external.error ? `${external.error}\n${resolvedOutput.saveError}` : resolvedOutput.saveError
1521
+ : external.error;
1412
1522
  const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
1413
1523
  fullOutput: resolvedOutput.fullOutput,
1414
1524
  outputPath: step.outputPath,
1415
1525
  outputMode: step.outputMode,
1416
- exitCode: external.exitCode ?? 1,
1526
+ exitCode: exitCode ?? 1,
1417
1527
  savedPath: resolvedOutput.savedPath,
1418
1528
  outputReference,
1419
1529
  saveError: resolvedOutput.saveError,
@@ -1428,16 +1538,17 @@ async function runSingleStepInner(
1428
1538
  : {};
1429
1539
  return omitUndefinedProperties({
1430
1540
  agent: step.agent,
1541
+ ...(childSessionName ? { sessionName: childSessionName } : {}),
1431
1542
  context: step.context,
1432
1543
  output: finalizedOutput.displayOutput,
1433
1544
  outputState: external.output.trim() ? "present" : "absent",
1434
- exitCode: external.exitCode,
1435
- error: external.error,
1545
+ exitCode,
1546
+ error,
1436
1547
  timedOut: external.timedOut,
1437
1548
  stopped: external.stopped,
1438
1549
  processSignal: external.processSignal,
1439
1550
  artifactPaths,
1440
- outputSaveError: artifactErrors.outputSaveError,
1551
+ outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
1441
1552
  metadataSaveError: artifactErrors.metadataSaveError,
1442
1553
  runner,
1443
1554
  externalProcess: external.externalProcess,
@@ -1471,14 +1582,18 @@ async function runSingleStepInner(
1471
1582
  }));
1472
1583
  try { fs.writeFileSync(ctx.outputFile, external.output, "utf-8"); } catch { /* Observability output is best-effort. */ }
1473
1584
  const resolvedOutput = step.outputPath && external.exitCode === 0
1474
- ? resolveSingleOutput(step.outputPath, external.output, outputSnapshot)
1585
+ ? resolveSingleOutput(step.outputPath, external.output, outputSnapshot, step.outputClaimPath)
1475
1586
  : { fullOutput: external.output };
1476
1587
  const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, resolvedOutput.fullOutput) : undefined;
1588
+ const exitCode = resolvedOutput.fatalError ? 1 : external.exitCode;
1589
+ const error = resolvedOutput.fatalError && resolvedOutput.saveError
1590
+ ? external.error ? `${external.error}\n${resolvedOutput.saveError}` : resolvedOutput.saveError
1591
+ : external.error;
1477
1592
  const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
1478
1593
  fullOutput: resolvedOutput.fullOutput,
1479
1594
  outputPath: step.outputPath,
1480
1595
  outputMode: step.outputMode,
1481
- exitCode: external.exitCode,
1596
+ exitCode: exitCode ?? 1,
1482
1597
  savedPath: resolvedOutput.savedPath,
1483
1598
  outputReference,
1484
1599
  saveError: resolvedOutput.saveError,
@@ -1493,21 +1608,26 @@ async function runSingleStepInner(
1493
1608
  : {};
1494
1609
  return omitUndefinedProperties({
1495
1610
  agent: step.agent,
1611
+ ...(childSessionName ? { sessionName: childSessionName } : {}),
1496
1612
  context: step.context,
1497
1613
  output: finalizedOutput.displayOutput,
1498
1614
  outputState: external.output.trim() ? "present" : "absent",
1499
- exitCode: external.exitCode,
1500
- error: external.error,
1615
+ exitCode,
1616
+ error,
1501
1617
  timedOut: external.timedOut,
1502
1618
  stopped: external.stopped,
1503
1619
  artifactPaths,
1504
- outputSaveError: artifactErrors.outputSaveError,
1620
+ outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
1505
1621
  metadataSaveError: artifactErrors.metadataSaveError,
1506
1622
  runner,
1507
1623
  externalJob: external.externalJob,
1508
1624
  });
1509
1625
  }
1510
1626
 
1627
+ const effectiveCwd = step.cwd ?? ctx.cwd;
1628
+ const cwdError = preflightLaunchCwd(step.requestedCwd ?? effectiveCwd, effectiveCwd);
1629
+ if (cwdError) return { agent: step.agent, output: cwdError, error: cwdError, exitCode: 1, context: step.context };
1630
+
1511
1631
  const candidates = step.modelCandidates !== undefined
1512
1632
  ? step.modelCandidates.length > 0 ? step.modelCandidates : [undefined]
1513
1633
  : step.model
@@ -1526,7 +1646,6 @@ async function runSingleStepInner(
1526
1646
  let structuredAcceptanceReport: unknown;
1527
1647
  let structuredAcceptanceReportError: string | undefined;
1528
1648
  let completionGuardTriggeredFinal = false;
1529
- let turnBudget = ctx.turnBudget ? initialTurnBudgetState(ctx.turnBudget) : undefined;
1530
1649
  let toolBudget = step.toolBudget ? initialToolBudgetState(step.toolBudget) : undefined;
1531
1650
  let toolBudgetBlocked = false;
1532
1651
  let actualLaunchContractDigest = step.launchContractDigest;
@@ -1540,8 +1659,12 @@ async function runSingleStepInner(
1540
1659
  let taskDeliveryOverride: SubagentTaskDelivery | undefined;
1541
1660
  let contextOverflow = false;
1542
1661
  let launchWarningsEmitted = false;
1662
+ let abortRecoveryAttempted = false;
1663
+ let nextAttemptTask = task;
1543
1664
  modelAttemptsLoop: while (modelIndex < candidates.length) {
1544
1665
  if (ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break;
1666
+ const recoveringAbort = abortRecoveryAttempted;
1667
+ const attemptTask = nextAttemptTask;
1545
1668
  const candidate = candidates[modelIndex];
1546
1669
  const expectedModelForVerification = candidate && !(step.skipPrimaryModelVerification && modelIndex === 0) ? candidate : undefined;
1547
1670
  try {
@@ -1550,7 +1673,11 @@ async function runSingleStepInner(
1550
1673
  const message = error instanceof Error ? error.message : String(error);
1551
1674
  return omitUndefinedProperties({ agent: step.agent, output: message, error: message, exitCode: 1, context: step.context, thinkingCeiling: step.thinkingCeiling });
1552
1675
  }
1553
- ctx.onAttemptStart?.(omitUndefinedProperties({ model: candidate, thinking: resolveEffectiveThinking(candidate, step.thinking) }));
1676
+ ctx.onAttemptStart?.(omitUndefinedProperties({
1677
+ model: candidate,
1678
+ thinking: resolveEffectiveThinking(candidate, step.thinking),
1679
+ contextLimit: findModelInfo(candidate, step.modelVerificationRegistry)?.contextWindow,
1680
+ }));
1554
1681
  const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
1555
1682
  if (effectiveStructuredOutput) {
1556
1683
  try {
@@ -1572,27 +1699,32 @@ async function runSingleStepInner(
1572
1699
  const { args, env, tempDir, toolDiagnosticPath, runtimeAcknowledgedExtensionsPath, capabilityAudit: attemptCapabilityAudit, warnings } = buildPiArgs(omitUndefinedProperties({
1573
1700
  parentSessionId: step.parentSessionId,
1574
1701
  baseArgs: ["--mode", "json", "-p"],
1575
- task,
1702
+ task: attemptTask,
1576
1703
  taskDelivery: taskDeliveryOverride,
1577
1704
  sessionEnabled,
1578
1705
  sessionDir,
1579
1706
  sessionFile: step.sessionFile,
1580
1707
  model: candidate,
1581
1708
  inheritProjectContext: step.inheritProjectContext,
1709
+ inheritGlobalContext: step.inheritGlobalContext,
1582
1710
  inheritSkills: step.inheritSkills,
1583
1711
  requireReadTool: Boolean(step.skills?.length),
1584
1712
  tools: step.tools,
1713
+ allowNestedSubagents: step.allowNestedSubagents,
1585
1714
  extensions: step.extensions,
1586
1715
  subagentOnlyExtensions: step.subagentOnlyExtensions,
1587
1716
  fast: step.fast,
1588
1717
  modelCandidates: step.modelCandidates,
1589
- systemPrompt: appendTurnBudgetSystemPrompt(step.systemPrompt ?? "", ctx.turnBudget),
1718
+ systemPrompt: step.systemPrompt ?? "",
1590
1719
  systemPromptMode: step.systemPromptMode,
1591
1720
  mcpDirectTools: step.mcpDirectTools,
1721
+ mcpConfig: step.mcpConfig,
1722
+ runtimeServerNames: step.runtimeServerNames,
1592
1723
  capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
1593
1724
  cwd: step.cwd ?? ctx.cwd,
1594
1725
  promptFileStem: step.agent,
1595
1726
  intercomSessionName: ctx.childIntercomTarget,
1727
+ sessionName: childSessionName,
1596
1728
  orchestratorIntercomTarget: ctx.orchestratorIntercomTarget,
1597
1729
  runId: ctx.id,
1598
1730
  childAgentName: step.agent,
@@ -1616,6 +1748,7 @@ async function runSingleStepInner(
1616
1748
  : undefined,
1617
1749
  childWatchdog,
1618
1750
  waitToolEnabled: step.waitToolEnabled,
1751
+ waitToolDefaultTimeoutMs: step.waitToolDefaultTimeoutMs,
1619
1752
  thinkingCeiling: step.thinkingCeiling,
1620
1753
  extensionBindings,
1621
1754
  }));
@@ -1626,12 +1759,15 @@ async function runSingleStepInner(
1626
1759
  if (step.definitionDigest) {
1627
1760
  const toolPlan = resolvedTaskToolPlan ?? resolvePiLaunchToolPlan(omitUndefinedProperties({
1628
1761
  tools: step.tools,
1762
+ allowNestedSubagents: step.allowNestedSubagents,
1629
1763
  extensions: step.extensions,
1630
1764
  subagentOnlyExtensions: step.subagentOnlyExtensions,
1631
1765
  fast: step.fast,
1632
1766
  model: step.model,
1633
1767
  modelCandidates: step.modelCandidates,
1634
1768
  mcpDirectTools: step.mcpDirectTools,
1769
+ mcpConfig: step.mcpConfig,
1770
+ runtimeServerNames: step.runtimeServerNames,
1635
1771
  cwd: step.cwd ?? ctx.cwd,
1636
1772
  requireReadTool: Boolean(step.skills?.length),
1637
1773
  structuredOutput: Boolean(effectiveStructuredOutput),
@@ -1648,9 +1784,10 @@ async function runSingleStepInner(
1648
1784
  ...(step.fast !== undefined ? { fast: step.fast } : {}),
1649
1785
  ...(resolveEffectiveThinking(candidate, step.thinking) ? { thinking: resolveEffectiveThinking(candidate, step.thinking) } : {}),
1650
1786
  ...(step.thinkingCeiling ? { thinkingCeiling: step.thinkingCeiling } : {}),
1651
- systemPrompt: appendTurnBudgetSystemPrompt(step.systemPrompt ?? "", ctx.turnBudget),
1787
+ systemPrompt: step.systemPrompt ?? "",
1652
1788
  systemPromptMode: step.systemPromptMode,
1653
1789
  inheritProjectContext: step.inheritProjectContext,
1790
+ inheritGlobalContext: step.inheritGlobalContext,
1654
1791
  inheritSkills: step.inheritSkills,
1655
1792
  skills: step.skills,
1656
1793
  tools: toolPlan.effectiveToolAllowlist,
@@ -1680,13 +1817,13 @@ async function runSingleStepInner(
1680
1817
  ctx.timeoutMessage,
1681
1818
  ctx.registerStop,
1682
1819
  ctx.stopMessage,
1683
- ctx.registerTurnBudgetAbort,
1684
1820
  ctx.onWriterProcess,
1685
1821
  ctx.toolTimeoutMs,
1686
1822
  ctx.deadlineAt,
1687
1823
  ctx.orcaProgressTab,
1688
1824
  expectedModelForVerification,
1689
1825
  step.modelVerificationRegistry,
1826
+ step.mutationTools,
1690
1827
  );
1691
1828
  if (run.processCloseObservedAt !== undefined) {
1692
1829
  writerProcesses.push({
@@ -1699,35 +1836,29 @@ async function runSingleStepInner(
1699
1836
  processTree: run.processTree,
1700
1837
  });
1701
1838
  }
1702
- if (run.turnBudget) turnBudget = run.turnBudget;
1703
- else if (ctx.turnBudget) {
1704
- const assistantMessages = run.messages.filter((message) => message.role === "assistant");
1705
- const turnCount = assistantMessages.length;
1706
- const lastAssistantMessage = assistantMessages.at(-1);
1707
- if (turnCount > 0 && turnCount < ctx.turnBudget.maxTurns) {
1708
- turnBudget = { ...ctx.turnBudget, outcome: "within-budget", turnCount };
1709
- } else if (turnCount >= ctx.turnBudget.maxTurns) {
1710
- const decision = turnBudgetDecision(
1711
- ctx.turnBudget,
1712
- turnCount,
1713
- lastAssistantMessage ? isTerminalAssistantStop(lastAssistantMessage) : false,
1714
- lastAssistantMessage ? assistantStartsToolCall(lastAssistantMessage) : false,
1715
- );
1716
- turnBudget = decision === "defer"
1717
- ? turnBudgetDeferredState(ctx.turnBudget, turnCount)
1718
- : turnBudgetState(ctx.turnBudget, turnCount, decision === "abort");
1719
- }
1720
- }
1721
1839
  const toolAvailabilityError = run.exitCode === 0 && !run.error
1722
1840
  ? readChildToolDiagnosticError(toolDiagnosticPath)
1723
1841
  : undefined;
1724
1842
  const runtimeAcknowledgedExtensions = readRuntimeAcknowledgedExtensions(runtimeAcknowledgedExtensionsPath);
1725
1843
  cleanupTempDir(tempDir);
1844
+ const midToolExitError = run.currentTool
1845
+ && isOrdinaryToolForMidToolExit(run.currentTool)
1846
+ && !run.interrupted
1847
+ && !run.timedOut
1848
+ && !run.stopped
1849
+ && !run.protocolError
1850
+ && !toolAvailabilityError
1851
+ ? formatMidToolExitError({
1852
+ toolName: run.currentTool,
1853
+ exitCode: run.exitCode,
1854
+ processSignal: run.processSignal,
1855
+ })
1856
+ : undefined;
1726
1857
 
1727
1858
  let structuredOutput: unknown;
1728
1859
  let structuredError: string | undefined;
1729
1860
  let validatedStructuredOutput = false;
1730
- if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !toolAvailabilityError) {
1861
+ if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !toolAvailabilityError && !midToolExitError) {
1731
1862
  if (!run.structuredOutputToolInvoked) {
1732
1863
  structuredError = MISSING_STRUCTURED_OUTPUT_CALL_ERROR;
1733
1864
  } else {
@@ -1749,51 +1880,58 @@ async function runSingleStepInner(
1749
1880
  const errorMessages = validatedStructuredOutput
1750
1881
  ? run.messages.slice(run.structuredOutputMessageStartIndex ?? run.messages.length)
1751
1882
  : run.messages;
1752
- const hiddenError = run.exitCode === 0 && !run.error && !toolAvailabilityError && !structuredError
1883
+ const hiddenError = run.exitCode === 0 && !run.error && !toolAvailabilityError && !structuredError && !midToolExitError
1753
1884
  ? detectSubagentError(errorMessages)
1754
1885
  : null;
1886
+ const terminalEmptyAfterUsefulWork = !validatedStructuredOutput
1887
+ && hasEmptyTerminalAssistantResponse(run.messages)
1888
+ && (run.toolCount > 0 || Boolean(run.finalOutput.trim()));
1755
1889
  const emptyOutputError = run.exitCode === 0
1756
1890
  && !run.error
1757
1891
  && !toolAvailabilityError
1758
1892
  && !structuredError
1759
- && !run.finalOutput.trim()
1760
1893
  && !validatedStructuredOutput
1894
+ && (!run.finalOutput.trim() || terminalEmptyAfterUsefulWork)
1761
1895
  && (!hiddenError?.hasError || hasEmptyTerminalAssistantResponse(run.messages))
1762
- ? "Subagent produced no output (possible model cold-start or empty response)."
1896
+ ? formatEmptyTerminalAssistantResponseError(run.messages)
1763
1897
  : undefined;
1764
1898
  const completionGuardEnabled = isAgentContractV1(step.agentContract) ? step.completionGuard === true : step.completionGuard !== false;
1765
1899
  const completionToolPlan = resolvedTaskToolPlan;
1900
+ const completionTools = completionToolPlan ? (completionToolPlan.explicitToolAllowlist ? completionToolPlan.effectiveToolAllowlist : undefined) : step.tools;
1766
1901
  const mutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
1767
1902
  finalMutationEvidence = mutationEvidence;
1768
1903
  const completionMutationEvidence = ctx.trackedMutationEvidenceForCompletionGuard === false ? undefined : mutationEvidence;
1769
- const completionGuard = run.exitCode === 0 && !run.error && !structuredError && !hiddenError?.hasError && !emptyOutputError && completionGuardEnabled
1904
+ const completionGuard = run.exitCode === 0 && !run.error && !structuredError && !hiddenError?.hasError && !midToolExitError && !emptyOutputError && completionGuardEnabled
1770
1905
  ? evaluateCompletionMutationGuard(omitUndefinedProperties({
1771
1906
  agent: step.agent,
1772
1907
  task: taskForCompletionGuard,
1773
1908
  messages: run.messages,
1774
- tools: completionToolPlan ? (completionToolPlan.explicitToolAllowlist ? completionToolPlan.effectiveToolAllowlist : undefined) : step.tools,
1909
+ tools: completionTools,
1775
1910
  mcpDirectTools: completionToolPlan?.effectiveMcpTools ?? step.mcpDirectTools,
1911
+ mutationTools: step.mutationTools,
1776
1912
  toolAvailabilityError,
1777
1913
  mutationEvidence: completionMutationEvidence,
1778
1914
  }))
1779
1915
  : undefined;
1780
1916
  const mutationAttemptObserved = run.observedMutationAttempt === true || completionMutationEvidence?.attemptedMutation === true;
1781
- const completionGuardTriggered = completionGuard?.triggered === true && !mutationAttemptObserved;
1782
- const completionGuardBlocked = completionGuard?.blocked === true;
1783
- const fileMutationEffect = completionGuard
1784
- ? {
1785
- status: completionGuardBlocked ? "blocked" as const : completionGuard.expectedMutation ? completionGuardTriggered ? "missing" as const : "observed" as const : "not-applicable" as const,
1786
- expected: completionGuard.expectedMutation,
1787
- attempted: completionGuardBlocked ? false : completionGuard.attemptedMutation || mutationAttemptObserved,
1788
- ...(completionMutationEvidence ? { evidence: completionMutationEvidence } : {}),
1789
- ...(completionGuardBlocked && completionGuard.message ? { message: completionGuard.message } : {}),
1790
- ...(completionGuardTriggered ? { message: "Subagent completed without making edits for an implementation task." } : {}),
1791
- }
1792
- : undefined;
1793
- const completionGuardError = completionGuardTriggered && !isAgentContractV1(step.agentContract)
1794
- ? "Subagent completed without making edits for an implementation task.\nIt appears to have returned planning or scratchpad output instead of applying changes."
1917
+ const completionEvidence = planCompletionEvidence({
1918
+ guard: completionGuard,
1919
+ completionGuardEnabled,
1920
+ mutationCapable: hasMutationToolCapability(completionTools, completionToolPlan?.effectiveMcpTools ?? step.mcpDirectTools),
1921
+ implementationMutationExpected: expectsImplementationMutation(step.agent, taskForCompletionGuard),
1922
+ mutationAttemptObserved,
1923
+ mutationEvidence: completionMutationEvidence,
1924
+ agentContractV1: isAgentContractV1(step.agentContract),
1925
+ });
1926
+ const finalOutputHasPersistableFileContent = run.exitCode === 0 && !run.error && !emptyOutputError && Boolean(stripAcceptanceReport(run.finalOutput).trim());
1927
+ const requiredOutput = step.outputMode === "file-only" && step.outputPath
1928
+ ? { kind: "file-only" as const, path: step.outputPath, missing: !fs.existsSync(step.outputPath) && !finalOutputHasPersistableFileContent }
1929
+ : effectiveStructuredOutput
1930
+ ? { kind: "structured" as const, path: effectiveStructuredOutput.outputPath, missing: !fs.existsSync(effectiveStructuredOutput.outputPath) }
1795
1931
  : undefined;
1796
- const effectiveExitCode = toolAvailabilityError || (completionGuardTriggered && !isAgentContractV1(step.agentContract)) || structuredError || emptyOutputError
1932
+ const missingRequiredOutputError = formatRequiredOutputError(requiredOutput);
1933
+ const missingRequiredOutputAfterMutation = Boolean(missingRequiredOutputError) && (mutationAttemptObserved || Boolean(mutationEvidence.changedFiles.length));
1934
+ const effectiveExitCode = toolAvailabilityError || completionEvidence.legacyFailureError || midToolExitError || structuredError || emptyOutputError || missingRequiredOutputError
1797
1935
  ? 1
1798
1936
  : hiddenError?.hasError
1799
1937
  ? (hiddenError.exitCode ?? 1)
@@ -1805,18 +1943,23 @@ async function runSingleStepInner(
1805
1943
  interrupted: run.interrupted,
1806
1944
  timedOut: run.timedOut,
1807
1945
  stopped: run.stopped,
1808
- turnBudgetExceeded: run.turnBudgetExceeded,
1809
1946
  })) ? formatProcessSignalError(run.processSignal!) : undefined;
1947
+ const underlyingError = toolAvailabilityError
1948
+ ?? midToolExitError
1949
+ ?? structuredError
1950
+ ?? run.error
1951
+ ?? signalError
1952
+ ?? (run.exitCode !== 0 && run.stderr.trim() ? run.stderr.trim() : undefined)
1953
+ ?? ((emptyOutputError || missingRequiredOutputError) && run.stderr.trim() ? run.stderr.trim() : undefined)
1954
+ ?? emptyOutputError
1955
+ ?? (missingRequiredOutputAfterMutation ? missingRequiredOutputError : undefined)
1956
+ ?? (hiddenError?.hasError
1957
+ ? hiddenError.details
1958
+ ? `${hiddenError.errorType} failed (exit ${effectiveExitCode}): ${hiddenError.details}`
1959
+ : `${hiddenError.errorType} failed with exit code ${effectiveExitCode}`
1960
+ : undefined);
1810
1961
  const error = formatSubagentExtensionConflictError(
1811
- toolAvailabilityError
1812
- ?? completionGuardError
1813
- ?? structuredError
1814
- ?? emptyOutputError
1815
- ?? (hiddenError?.hasError
1816
- ? hiddenError.details
1817
- ? `${hiddenError.errorType} failed (exit ${effectiveExitCode}): ${hiddenError.details}`
1818
- : `${hiddenError.errorType} failed with exit code ${effectiveExitCode}`
1819
- : run.error || signalError || (run.exitCode !== 0 && run.stderr.trim() ? run.stderr.trim() : undefined)),
1962
+ underlyingError ?? missingRequiredOutputError ?? completionEvidence.legacyFailureError,
1820
1963
  {
1821
1964
  agent: step.agent,
1822
1965
  ambientExtensionsEnabled: launchResolvedExtensions?.disableAmbientExtensions === false,
@@ -1830,8 +1973,8 @@ async function runSingleStepInner(
1830
1973
  usage: run.usage,
1831
1974
  });
1832
1975
  modelAttempts.push(attempt);
1833
- if (candidate && startupAttemptIndex === 0) attemptedModels.push(candidate);
1834
- completionGuardTriggeredFinal = completionGuardTriggered;
1976
+ if (!recoveringAbort && candidate && startupAttemptIndex === 0) attemptedModels.push(candidate);
1977
+ completionGuardTriggeredFinal = completionEvidence.guardTriggered && !underlyingError && !missingRequiredOutputError;
1835
1978
  finalOutputSnapshot = outputSnapshot;
1836
1979
  if (step.toolBudget) {
1837
1980
  const toolMessages = run.messages.filter((message) => message.role === "toolResult");
@@ -1839,10 +1982,48 @@ async function runSingleStepInner(
1839
1982
  toolBudgetBlocked = Boolean(blockedMessage);
1840
1983
  toolBudget = toolBudgetState(step.toolBudget, toolMessages.length, blockedMessage ? (blockedMessage as { toolName?: string }).toolName : undefined);
1841
1984
  }
1842
- finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput, runtimeAcknowledgedExtensions, ...(step.agentContract ? { agentContract: step.agentContract } : {}), ...(fileMutationEffect ? { effects: { fileMutation: fileMutationEffect } } : {}) } as RunPiStreamingResult & { structuredOutput?: unknown; agentContract?: import("../../shared/types.ts").AgentContract; effects?: import("../../shared/types.ts").EffectsProjection };
1843
- if (run.turnBudgetExceeded) break modelAttemptsLoop;
1985
+ const settlementDiagnostic = projectSettlementDiagnostic(completionEvidence, {
1986
+ terminalFailed: effectiveExitCode !== 0,
1987
+ finalTextPresent: Boolean(stripAcceptanceReport(run.finalOutput).trim()),
1988
+ mutationObserved: mutationEvidence.attemptedMutation,
1989
+ requiredOutput,
1990
+ afterCompactionSettlement: run.afterCompactionSettlement === true,
1991
+ });
1992
+ const fileMutationEffect = completionEvidence.fileMutation ?? (missingRequiredOutputAfterMutation ? { status: "observed" as const, expected: completionEvidence.mutationExpected, attempted: true, evidence: mutationEvidence } : undefined);
1993
+ finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput, runtimeAcknowledgedExtensions, ...(step.agentContract ? { agentContract: step.agentContract } : {}), ...(fileMutationEffect || settlementDiagnostic ? { effects: { ...(fileMutationEffect ? { fileMutation: fileMutationEffect } : {}), ...(settlementDiagnostic ? { settlementDiagnostic } : {}) } } : {}) } as RunPiStreamingResult;
1994
+ const abortRecovery = !attempt.success ? planAbortRecovery({
1995
+ messages: run.messages,
1996
+ error,
1997
+ processSignal: run.processSignal,
1998
+ sessionAvailable: Boolean(step.sessionFile && fs.existsSync(step.sessionFile)),
1999
+ alreadyResumed: abortRecoveryAttempted,
2000
+ stopped: run.stopped || ctx.stopSignal?.aborted || ctx.skipAcceptance?.(),
2001
+ interrupted: run.interrupted,
2002
+ timedOut: run.timedOut || ctx.timeoutSignal?.aborted,
2003
+ toolBudgetExhausted: run.toolBudgetBlocked || toolBudgetBlocked,
2004
+ usageBudgetExhausted: ctx.usageBudgetExhausted?.(),
2005
+ structuredOutputFailed: Boolean(structuredError),
2006
+ acceptanceFailed: false,
2007
+ currentTool: run.currentTool,
2008
+ afterCompactionSettlement: run.afterCompactionSettlement,
2009
+ }) : undefined;
2010
+ if (abortRecovery?.action === "settle" && abortRecovery.diagnostic) {
2011
+ attempt.error = attempt.error
2012
+ ? `${abortRecovery.diagnostic}\n${attempt.error.slice(0, 8_000)}`
2013
+ : abortRecovery.diagnostic;
2014
+ if (finalResult) finalResult.abortRecoveryDiagnostic = abortRecovery.diagnostic;
2015
+ }
1844
2016
  if (run.stopped || run.timedOut || ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break modelAttemptsLoop;
1845
- if (attempt.success || completionGuardTriggered) break modelAttemptsLoop;
2017
+ if (abortRecovery?.action === "settle" && abortRecovery.diagnostic) break modelAttemptsLoop;
2018
+ if (attempt.success) break modelAttemptsLoop;
2019
+ if (recoveringAbort) break modelAttemptsLoop;
2020
+ if (abortRecovery?.action === "resume") {
2021
+ abortRecoveryAttempted = true;
2022
+ nextAttemptTask = abortRecovery.prompt;
2023
+ attemptNotes.push("[abort-recovery] provider/transport abort after useful progress; resuming the retained child session once.");
2024
+ continue;
2025
+ }
2026
+ if (completionEvidence.guardTriggered) break modelAttemptsLoop;
1846
2027
 
1847
2028
  const startupFailure = isRetryableSubagentStartupFailure(omitUndefinedProperties({
1848
2029
  exitCode: effectiveExitCode,
@@ -1858,7 +2039,6 @@ async function runSingleStepInner(
1858
2039
  interrupted: run.interrupted,
1859
2040
  timedOut: run.timedOut,
1860
2041
  stopped: run.stopped,
1861
- turnBudgetExceeded: run.turnBudgetExceeded,
1862
2042
  }));
1863
2043
  const retryDelayMs = SUBAGENT_STARTUP_RETRY_DELAYS_MS[startupAttemptIndex];
1864
2044
  if (startupFailure && retryDelayMs !== undefined) {
@@ -1889,7 +2069,7 @@ async function runSingleStepInner(
1889
2069
  finalResult.finalOutput = startupError;
1890
2070
  break modelAttemptsLoop;
1891
2071
  }
1892
- const retryableModelFailure = isRetryableModelFailure(error);
2072
+ const retryableModelFailure = isRetryableModelFailureAttempt({ error, messages: run.messages, toolCount: run.toolCount });
1893
2073
  if (retryableModelFailure) recordRetryableModelFailure(candidate ?? run.model ?? step.model, error);
1894
2074
  if (isContextOverflow(error)) {
1895
2075
  contextOverflow = true;
@@ -1905,8 +2085,14 @@ async function runSingleStepInner(
1905
2085
  const rawOutput = finalResult?.finalOutput ?? "";
1906
2086
  const outputForPersistence = stripAcceptanceReport(rawOutput);
1907
2087
  const resolvedOutput = step.outputPath && finalResult?.exitCode === 0
1908
- ? resolveSingleOutput(step.outputPath, outputForPersistence, finalOutputSnapshot)
2088
+ ? resolveSingleOutput(step.outputPath, outputForPersistence, finalOutputSnapshot, step.outputClaimPath)
1909
2089
  : { fullOutput: outputForPersistence };
2090
+ if (resolvedOutput.fatalError) {
2091
+ if (finalResult) {
2092
+ finalResult.exitCode = 1;
2093
+ finalResult.error = finalResult.error ? `${finalResult.error}\n${resolvedOutput.saveError}` : resolvedOutput.saveError;
2094
+ }
2095
+ }
1910
2096
  const output = stripAcceptanceReport(resolvedOutput.fullOutput);
1911
2097
  const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, output) : undefined;
1912
2098
  let outputForSummary = output;
@@ -1915,14 +2101,6 @@ async function runSingleStepInner(
1915
2101
  }
1916
2102
  if (finalResult?.stopped && !outputForSummary.trim()) {
1917
2103
  outputForSummary = ctx.stopMessage ?? "Subagent stopped by user.";
1918
- } else if (!finalResult?.timedOut && !finalResult?.stopped && finalResult?.turnBudgetExceeded && turnBudget) {
1919
- outputForSummary = formatTurnBudgetOutput(turnBudgetExceededMessage(turnBudget, turnBudget.turnCount), outputForSummary);
1920
- } else if (!finalResult?.timedOut && !finalResult?.stopped && turnBudget?.outcome === "termination-deferred") {
1921
- const note = turnBudgetDeferredNote(turnBudget, turnBudget.terminationDeferredAtTurn ?? turnBudget.turnCount);
1922
- outputForSummary = outputForSummary.trim() ? `${note}\n\n${outputForSummary}` : note;
1923
- } else if (!finalResult?.timedOut && !finalResult?.stopped && turnBudget?.outcome === "wrap-up-requested") {
1924
- const note = turnBudgetSoftNote(turnBudget, turnBudget.wrapUpRequestedAtTurn ?? turnBudget.turnCount);
1925
- outputForSummary = outputForSummary.trim() ? `${note}\n\n${outputForSummary}` : note;
1926
2104
  }
1927
2105
  const outputForAcceptance = rawOutput;
1928
2106
  const childWrittenOutput = step.outputPath
@@ -1960,7 +2138,7 @@ async function runSingleStepInner(
1960
2138
  saveError: resolvedOutput.saveError,
1961
2139
  }));
1962
2140
  outputForSummary = finalizedOutput.displayOutput;
1963
- const acceptance = step.effectiveAcceptance && !finalResult?.stopped && !finalResult?.turnBudgetExceeded && !ctx.timeoutSignal?.aborted && !ctx.stopSignal?.aborted && !ctx.skipAcceptance?.()
2141
+ const acceptance = step.effectiveAcceptance && !finalResult?.stopped && !ctx.timeoutSignal?.aborted && !ctx.stopSignal?.aborted && !ctx.skipAcceptance?.()
1964
2142
  ? await evaluateAcceptance(omitUndefinedProperties({
1965
2143
  acceptance: step.effectiveAcceptance,
1966
2144
  output: outputForAcceptance,
@@ -1979,29 +2157,31 @@ async function runSingleStepInner(
1979
2157
  : undefined;
1980
2158
  const stoppedAfterAcceptance = finalResult?.stopped === true || ctx.stopSignal?.aborted === true;
1981
2159
  const timedOutAfterAcceptance = !stoppedAfterAcceptance && (finalResult?.timedOut === true || ctx.timeoutSignal?.aborted === true);
1982
- const turnBudgetExceeded = finalResult?.turnBudgetExceeded === true;
1983
2160
  const effectiveAcceptance = step.effectiveAcceptance
1984
2161
  ? stoppedAfterAcceptance
1985
2162
  ? buildSkippedAcceptanceLedger(step.effectiveAcceptance, { id: "stopped", message: "Acceptance was not evaluated because the subagent was stopped." })
1986
2163
  : timedOutAfterAcceptance
1987
2164
  ? buildSkippedAcceptanceLedger(step.effectiveAcceptance, { id: "timeout", message: "Acceptance was not evaluated because the subagent timed out." })
1988
- : turnBudgetExceeded
1989
- ? buildSkippedAcceptanceLedger(step.effectiveAcceptance, { id: "turn-budget", message: "Acceptance was not evaluated because the subagent exceeded its turn budget." })
1990
- : acceptance
2165
+ : acceptance
1991
2166
  : undefined;
1992
2167
  const acceptanceFailure = effectiveAcceptance ? acceptanceFailureMessage(effectiveAcceptance) : undefined;
1993
- const acceptanceCanFailRun = acceptanceFailure && effectiveAcceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted && !timedOutAfterAcceptance && !stoppedAfterAcceptance && !turnBudgetExceeded && !isAgentContractV1(step.agentContract);
1994
- const effectiveFinalExitCode = timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? 1 : acceptanceCanFailRun ? 1 : finalResult?.exitCode ?? 1;
2168
+ const acceptanceCanFailRun = acceptanceFailure && effectiveAcceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted && !timedOutAfterAcceptance && !stoppedAfterAcceptance && !isAgentContractV1(step.agentContract);
2169
+ const effectiveFinalExitCode = timedOutAfterAcceptance || stoppedAfterAcceptance ? 1 : acceptanceCanFailRun ? 1 : finalResult?.exitCode ?? 1;
1995
2170
  const intercomDetachReceipt = finalResult?.finalOutput === INTERCOM_DETACH_RECEIPT;
1996
- const effectiveFinalError = stoppedAfterAcceptance
2171
+ const baseFinalError = stoppedAfterAcceptance
1997
2172
  ? ctx.stopMessage ?? "Subagent stopped by user."
1998
2173
  : timedOutAfterAcceptance
1999
2174
  ? finalResult?.error ?? ctx.timeoutMessage ?? "Subagent timed out."
2000
- : turnBudgetExceeded
2001
- ? finalResult?.error ?? (turnBudget ? turnBudgetExceededMessage(turnBudget, turnBudget.turnCount) : "Subagent exceeded turn budget.")
2002
- : acceptanceCanFailRun
2175
+ : acceptanceCanFailRun
2003
2176
  ? (finalResult?.error ? `${finalResult.error}\n${acceptanceFailure}` : acceptanceFailure)
2004
2177
  : finalResult?.error ?? (intercomDetachReceipt ? INTERCOM_DETACH_RECEIPT : undefined);
2178
+ const effectiveFinalError = formatChildFailureDiagnostic({
2179
+ error: baseFinalError,
2180
+ afterCompactionSettlement: effectiveFinalExitCode !== 0 ? finalResult?.afterCompactionSettlement : undefined,
2181
+ abortRecoveryDiagnostic: effectiveFinalExitCode !== 0 ? finalResult?.abortRecoveryDiagnostic : undefined,
2182
+ requiredOutput: effectiveFinalExitCode !== 0 ? finalResult?.effects?.settlementDiagnostic?.requiredOutput : undefined,
2183
+ });
2184
+ const usage = usageFromAttempts(modelAttempts);
2005
2185
 
2006
2186
  const artifactErrors = artifactPaths && ctx.artifactConfig?.enabled !== false
2007
2187
  ? persistStepArtifacts({
@@ -2021,6 +2201,7 @@ async function runSingleStepInner(
2021
2201
  model: finalResult?.model,
2022
2202
  attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
2023
2203
  modelAttempts,
2204
+ usage,
2024
2205
  error: effectiveFinalError,
2025
2206
  acceptance: effectiveAcceptance,
2026
2207
  ...(capabilityAudit ? { capabilityCeiling: capabilityAudit.ceiling, capabilityAudit } : {}),
@@ -2037,6 +2218,7 @@ async function runSingleStepInner(
2037
2218
 
2038
2219
  const result: StepResult & { completionGuardTriggered?: boolean } = omitUndefinedProperties({
2039
2220
  agent: step.agent,
2221
+ ...(childSessionName ? { sessionName: childSessionName } : {}),
2040
2222
  context: step.context,
2041
2223
  ...(step.agentContract ? { agentContract: step.agentContract } : {}),
2042
2224
  launchContractDigest: actualLaunchContractDigest,
@@ -2052,26 +2234,24 @@ async function runSingleStepInner(
2052
2234
  modelAttempts,
2053
2235
  contextOverflow: contextOverflow || undefined,
2054
2236
  totalCost: costSummaryFromAttempts(modelAttempts),
2237
+ usage,
2055
2238
  artifactPaths,
2056
- outputSaveError: artifactErrors.outputSaveError,
2239
+ outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
2057
2240
  metadataSaveError: artifactErrors.metadataSaveError,
2058
2241
  transcriptPath: transcriptWriter ? artifactPaths?.transcriptPath : undefined,
2059
2242
  transcriptError: transcriptWriter?.getError(),
2060
- interrupted: timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? false : finalResult?.interrupted,
2243
+ interrupted: timedOutAfterAcceptance || stoppedAfterAcceptance ? false : finalResult?.interrupted,
2061
2244
  timedOut: timedOutAfterAcceptance ? true : finalResult?.timedOut,
2062
2245
  stopped: stoppedAfterAcceptance ? true : finalResult?.stopped,
2063
2246
  processSignal: finalResult?.processSignal,
2064
2247
  timeoutRecovery,
2065
- turnBudget,
2066
- turnBudgetExceeded: turnBudgetExceeded || undefined,
2067
- wrapUpRequested: finalResult?.wrapUpRequested || turnBudget?.outcome === "wrap-up-requested" || turnBudget?.outcome === "termination-deferred" || turnBudgetExceeded || undefined,
2068
2248
  toolBudget,
2069
2249
  toolBudgetBlocked: toolBudgetBlocked || undefined,
2070
2250
  completionGuardTriggered: completionGuardTriggeredFinal,
2071
2251
  ...((finalResult as (RunPiStreamingResult & { effects?: import("../../shared/types.ts").EffectsProjection }) | undefined)?.effects ? { effects: (finalResult as RunPiStreamingResult & { effects?: import("../../shared/types.ts").EffectsProjection }).effects } : {}),
2072
- structuredOutput: timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? undefined : (finalResult as (RunPiStreamingResult & { structuredOutput?: unknown }) | undefined)?.structuredOutput,
2073
- structuredOutputPath: timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? undefined : effectiveStructuredOutput?.outputPath,
2074
- structuredOutputSchemaPath: timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? undefined : effectiveStructuredOutput?.schemaPath,
2252
+ structuredOutput: timedOutAfterAcceptance || stoppedAfterAcceptance ? undefined : (finalResult as (RunPiStreamingResult & { structuredOutput?: unknown }) | undefined)?.structuredOutput,
2253
+ structuredOutputPath: timedOutAfterAcceptance || stoppedAfterAcceptance ? undefined : effectiveStructuredOutput?.outputPath,
2254
+ structuredOutputSchemaPath: timedOutAfterAcceptance || stoppedAfterAcceptance ? undefined : effectiveStructuredOutput?.schemaPath,
2075
2255
  acceptance: effectiveAcceptance,
2076
2256
  watchdog: finalResult?.watchdog,
2077
2257
  ...(capabilityAudit ? { capabilityCeiling: capabilityAudit.ceiling, capabilityAudit } : {}),
@@ -2098,13 +2278,7 @@ type RunnerStatusStep = NonNullable<AsyncStatus["steps"]>[number] & {
2098
2278
 
2099
2279
  function externalRunnerStatus(runner: SubagentStep["runner"]): ExternalCliRunnerStatus | ExternalJobRunnerStatus | undefined {
2100
2280
  if (runner?.type === "external-cli") {
2101
- return {
2102
- type: "external-cli",
2103
- command: runner.command,
2104
- args: runner.args ?? [],
2105
- promptDelivery: runner.promptDelivery ?? "stdin",
2106
- capabilities: { stop: true, steer: false, resume: false, structuredOutput: false, toolEvents: false },
2107
- };
2281
+ return resolveExternalCliRunnerStatus(runner);
2108
2282
  }
2109
2283
  if (runner?.type === "external-job") {
2110
2284
  return {
@@ -2151,6 +2325,11 @@ function requiredStatusStep(statusPayload: RunnerStatusPayload, index: number):
2151
2325
  return step;
2152
2326
  }
2153
2327
 
2328
+ function setStatusWorktreeReference(statusStep: RunnerStatusStep, worktree: WorktreeSetup["worktrees"][number]): void {
2329
+ statusStep.worktreePath = worktree.path;
2330
+ statusStep.branch = worktree.branch;
2331
+ }
2332
+
2154
2333
  function markParallelGroupSetupFailure(input: {
2155
2334
  statusPayload: RunnerStatusPayload;
2156
2335
  results: StepResult[];
@@ -2279,6 +2458,25 @@ function resolveAsyncStepTranscriptPath(input: {
2279
2458
 
2280
2459
  type SingleStepResult = Awaited<ReturnType<typeof runSingleStep>>;
2281
2460
 
2461
+ function missingRequiredOutputAfterUsefulMutation(result: SingleStepResult): boolean {
2462
+ const effects = result.effects;
2463
+ return effects?.settlementDiagnostic?.requiredOutput?.missing === true
2464
+ && (effects.settlementDiagnostic.mutation.attempted || effects.fileMutation?.attempted === true || Boolean(effects.fileMutation?.evidence?.changedFiles.length));
2465
+ }
2466
+
2467
+ function partialExecutionWithUsefulMutation(result: SingleStepResult): boolean {
2468
+ const fileMutation = result.effects?.fileMutation;
2469
+ return result.execution?.status === "partial" && (fileMutation?.attempted === true || Boolean(fileMutation?.evidence?.changedFiles.length));
2470
+ }
2471
+
2472
+ function partialEvidenceResult(result: SingleStepResult): boolean {
2473
+ return missingRequiredOutputAfterUsefulMutation(result) || partialExecutionWithUsefulMutation(result);
2474
+ }
2475
+
2476
+ function concreteFailureResult(result: SingleStepResult): boolean {
2477
+ return result.success === false && !partialEvidenceResult(result);
2478
+ }
2479
+
2282
2480
  function combinedAbortSignal(signals: Array<AbortSignal | undefined>): AbortSignal | undefined {
2283
2481
  const activeSignals = signals.filter((signal): signal is AbortSignal => Boolean(signal));
2284
2482
  if (activeSignals.length === 0) return undefined;
@@ -2350,6 +2548,8 @@ async function runSubagent(
2350
2548
  const overallStartTime = Date.now();
2351
2549
  const shareEnabled = config.share === true;
2352
2550
  const asyncDir = config.asyncDir;
2551
+ const handoffWorkflowKey = config.workflowKey;
2552
+ const handoffChildRunId = handoffWorkflowKey ? id : undefined;
2353
2553
  const statusPath = path.join(asyncDir, "status.json");
2354
2554
  const eventsPath = path.join(asyncDir, "events.jsonl");
2355
2555
  const logPath = path.join(asyncDir, `subagent-log-${id}.md`);
@@ -2358,7 +2558,6 @@ async function runSubagent(
2358
2558
  const activeChildTimeouts = new Map<number, () => void>();
2359
2559
  const activeChildStops = new Map<number, () => void>();
2360
2560
  const childStopRequests = new Map<number, { childId: string; requestedAt: number }>();
2361
- const activeChildTurnBudgetAborts = new Map<number, (message: string, state?: TurnBudgetState) => void>();
2362
2561
  const pendingStepSteers: SteerRequest[] = [];
2363
2562
  const steeringCapabilities = new Map<number, SteerCapability>();
2364
2563
  let interrupted = false;
@@ -2367,7 +2566,6 @@ async function runSubagent(
2367
2566
  let timeoutTimer: NodeJS.Timeout | undefined;
2368
2567
  let timedOut = false;
2369
2568
  let stopped = false;
2370
- let turnBudgetExceeded = false;
2371
2569
  let usageBudgetExceeded = false;
2372
2570
  const timeoutMessage = config.timeoutMs !== undefined ? `Subagent timed out after ${config.timeoutMs}ms.` : undefined;
2373
2571
  const stopMessage = "Subagent stopped by user.";
@@ -2388,8 +2586,11 @@ async function runSubagent(
2388
2586
  for (const task of step.parallel) {
2389
2587
  const taskFlatIndex = flatStepCount;
2390
2588
  const transcriptPath = resolveAsyncStepTranscriptPath(omitUndefinedProperties({ artifactsDir, artifactConfig, runId: id, agent: task.agent, flatIndex: taskFlatIndex, flatStepCount: initialFlatStepCount }));
2589
+ const taskSessionName = task.sessionName ?? deriveChildSessionName({ agent: task.agent, task: task.task, label: task.label });
2391
2590
  initialStatusSteps.push(omitUndefinedProperties({
2392
2591
  agent: task.agent,
2592
+ ...(task.lane ? { lane: task.lane } : config.lane ? { lane: config.lane } : {}),
2593
+ ...(taskSessionName ? { sessionName: taskSessionName } : {}),
2393
2594
  ...(externalRunnerStatus(task.runner) ? { runner: externalRunnerStatus(task.runner) } : {}),
2394
2595
  ...(statusStepDescription(task.task) ? { description: statusStepDescription(task.task) } : {}),
2395
2596
  ...(task.context ? { context: task.context } : {}),
@@ -2408,6 +2609,7 @@ async function runSubagent(
2408
2609
  ...(transcriptPath ? { transcriptPath } : {}),
2409
2610
  skills: task.skills,
2410
2611
  model: task.model,
2612
+ ...(task.contextLimit !== undefined ? { contextLimit: task.contextLimit } : {}),
2411
2613
  thinking: task.thinking,
2412
2614
  attemptedModels: task.modelCandidates && task.modelCandidates.length > 0 ? task.modelCandidates : task.model ? [task.model] : undefined,
2413
2615
  recentTools: [],
@@ -2425,6 +2627,7 @@ async function runSubagent(
2425
2627
  label: step.label ?? step.parallel.label ?? `Dynamic fanout (${step.collect.as})`,
2426
2628
  outputName: step.collect.as,
2427
2629
  structured: Boolean(step.collect.outputSchema),
2630
+ ...(step.parallel.contextLimit !== undefined ? { contextLimit: step.parallel.contextLimit } : {}),
2428
2631
  ...(step.agentContract ? { agentContract: step.agentContract } : {}),
2429
2632
  ...(step.capabilityCeiling ? { capabilityCeiling: step.capabilityCeiling } : {}),
2430
2633
  ...(step.thinkingCeiling ? { thinkingCeiling: step.thinkingCeiling } : {}),
@@ -2437,8 +2640,11 @@ async function runSubagent(
2437
2640
  } else {
2438
2641
  const stepFlatIndex = flatStepCount;
2439
2642
  const transcriptPath = resolveAsyncStepTranscriptPath(omitUndefinedProperties({ artifactsDir, artifactConfig, runId: id, agent: step.agent, flatIndex: stepFlatIndex, flatStepCount: initialFlatStepCount }));
2643
+ const stepSessionName = step.sessionName ?? deriveChildSessionName({ agent: step.agent, task: step.task, label: step.label });
2440
2644
  initialStatusSteps.push(omitUndefinedProperties({
2441
2645
  agent: step.agent,
2646
+ ...(step.lane ? { lane: step.lane } : config.lane ? { lane: config.lane } : {}),
2647
+ ...(stepSessionName ? { sessionName: stepSessionName } : {}),
2442
2648
  ...(externalRunnerStatus(step.runner) ? { runner: externalRunnerStatus(step.runner) } : {}),
2443
2649
  ...(statusStepDescription(step.task) ? { description: statusStepDescription(step.task) } : {}),
2444
2650
  ...(step.context ? { context: step.context } : {}),
@@ -2457,6 +2663,7 @@ async function runSubagent(
2457
2663
  ...(transcriptPath ? { transcriptPath } : {}),
2458
2664
  skills: step.skills,
2459
2665
  model: step.model,
2666
+ ...(step.contextLimit !== undefined ? { contextLimit: step.contextLimit } : {}),
2460
2667
  thinking: step.thinking,
2461
2668
  attemptedModels: step.modelCandidates && step.modelCandidates.length > 0 ? step.modelCandidates : step.model ? [step.model] : undefined,
2462
2669
  recentTools: [],
@@ -2500,7 +2707,6 @@ async function runSubagent(
2500
2707
  lastUpdate: overallStartTime,
2501
2708
  ...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),
2502
2709
  ...(config.deadlineAt !== undefined ? { deadlineAt: config.deadlineAt } : {}),
2503
- ...(config.turnBudget ? { turnBudget: initialTurnBudgetState(config.turnBudget) } : {}),
2504
2710
  ...(config.toolBudget ? { toolBudget: initialToolBudgetState(config.toolBudget) } : {}),
2505
2711
  ...(config.usageBudget ? { usageBudget: usageBudgetState(config.usageBudget, undefined) } : {}),
2506
2712
  pid: process.pid,
@@ -2515,6 +2721,7 @@ async function runSubagent(
2515
2721
  ...(config.runFanoutBudget ? { runFanoutBudget: getRunFanoutBudgetSnapshot(config.runFanoutBudget) } : {}),
2516
2722
  ...(config.parentWorkflowRunId ? { parentWorkflowRunId: config.parentWorkflowRunId } : {}),
2517
2723
  ...(config.workflowKey ? { workflowKey: config.workflowKey } : {}),
2724
+ ...(config.lane ? { lane: config.lane } : {}),
2518
2725
  ...(config.runnerProcessInstanceId ? { processTerminal: { version: 1 as const, state: "pending" as const, runId: id, runnerProcessInstanceId: config.runnerProcessInstanceId } } : {}),
2519
2726
  steps: initialStatusSteps,
2520
2727
  artifactsDir,
@@ -2632,6 +2839,7 @@ async function runSubagent(
2632
2839
  const statusResultSummary = (state: AsyncStatus["state"]): string => {
2633
2840
  if (statusPayload.error) return statusPayload.error;
2634
2841
  if (state === "paused") return "Paused after interrupt. Waiting for explicit next action.";
2842
+ if (state === "partial") return "Subagent needs attention after partial work.";
2635
2843
  if (state === "stopped") return stopMessage;
2636
2844
  if (state === "rejected") return "Subagent rejected.";
2637
2845
  return state === "complete" ? "Subagent completed." : "Subagent failed.";
@@ -2640,12 +2848,13 @@ async function runSubagent(
2640
2848
  if (step.status === "complete" || step.status === "completed") return true;
2641
2849
  if (step.status === "failed" || step.status === "stopped" || step.status === "rejected") return false;
2642
2850
  if (state === "complete") return true;
2643
- if (state === "failed" || state === "stopped" || state === "rejected") return false;
2851
+ if (state === "failed" || state === "partial" || state === "stopped" || state === "rejected") return false;
2644
2852
  return undefined;
2645
2853
  };
2646
2854
  const writeRecoverableStatusResult = (): void => {
2647
2855
  const state = statusResultState();
2648
2856
  if (!state || finalResultCommitted || !config.sessionId) return;
2857
+ if ((state as string) === "complete") return;
2649
2858
  const now = statusPayload.endedAt ?? statusPayload.lastUpdate ?? Date.now();
2650
2859
  const summary = statusResultSummary(state);
2651
2860
  runPersistence.write(resultPath, omitUndefinedProperties({
@@ -2657,10 +2866,11 @@ async function runSubagent(
2657
2866
  success: state === "complete",
2658
2867
  state,
2659
2868
  summary,
2660
- error: state === "failed" || state === "stopped" || state === "rejected" ? summary : undefined,
2869
+ error: state === "failed" || state === "partial" || state === "stopped" || state === "rejected" ? summary : undefined,
2661
2870
  stopped: state === "stopped" ? true : undefined,
2662
2871
  results: statusPayload.steps.map((step) => omitUndefinedProperties({
2663
2872
  agent: step.agent,
2873
+ ...(step.sessionName ? { sessionName: step.sessionName } : {}),
2664
2874
  output: step.status === "complete" || step.status === "completed" ? "" : step.error ?? summary,
2665
2875
  error: step.error,
2666
2876
  success: statusResultSuccess(state, step),
@@ -2668,6 +2878,7 @@ async function runSubagent(
2668
2878
  model: step.model,
2669
2879
  attemptedModels: step.attemptedModels,
2670
2880
  modelAttempts: step.modelAttempts,
2881
+ usage: usageFromAttempts(step.modelAttempts),
2671
2882
  contextOverflow: step.contextOverflow,
2672
2883
  })),
2673
2884
  exitCode: state === "complete" || state === "paused" ? 0 : 1,
@@ -2762,7 +2973,7 @@ async function runSubagent(
2762
2973
  };
2763
2974
  const childStopResult = (index: number, agent: string, context?: "fresh" | "fork"): SingleStepResult => {
2764
2975
  markChildStopped(index);
2765
- return stoppedStepResult(agent, context);
2976
+ return stoppedStepResult(agent, context, requiredStatusStep(statusPayload, index).sessionName);
2766
2977
  };
2767
2978
  const stopChildStep = (request: StopRequest): void => {
2768
2979
  if (request.targetIndex === undefined) {
@@ -2805,13 +3016,6 @@ async function runSubagent(
2805
3016
  activeChildStops.set(flatIndex, stop);
2806
3017
  if (stopped || childStopRequests.has(flatIndex)) stop();
2807
3018
  };
2808
- const registerStepTurnBudgetAbort = (flatIndex: number, abort: ((message: string, state?: TurnBudgetState) => void) | undefined): void => {
2809
- if (!abort) {
2810
- activeChildTurnBudgetAborts.delete(flatIndex);
2811
- return;
2812
- }
2813
- activeChildTurnBudgetAborts.set(flatIndex, abort);
2814
- };
2815
3019
  const interruptActiveChildren = (): void => {
2816
3020
  for (const interrupt of [...activeChildInterrupts.values()]) interrupt();
2817
3021
  };
@@ -2921,23 +3125,26 @@ async function runSubagent(
2921
3125
  }
2922
3126
  }
2923
3127
  };
2924
- const pausedStepResult = (agent: string, context?: "fresh" | "fork"): SingleStepResult => omitUndefinedProperties({
3128
+ const pausedStepResult = (agent: string, context?: "fresh" | "fork", sessionName?: string): SingleStepResult => omitUndefinedProperties({
2925
3129
  agent,
3130
+ sessionName,
2926
3131
  context,
2927
3132
  output: "Paused after interrupt. Waiting for explicit next action.",
2928
3133
  exitCode: 0,
2929
3134
  interrupted: true,
2930
3135
  });
2931
- const timedOutStepResult = (agent: string, context?: "fresh" | "fork"): SingleStepResult => omitUndefinedProperties({
3136
+ const timedOutStepResult = (agent: string, context?: "fresh" | "fork", sessionName?: string): SingleStepResult => omitUndefinedProperties({
2932
3137
  agent,
3138
+ sessionName,
2933
3139
  context,
2934
3140
  output: timeoutMessage ?? "Subagent timed out.",
2935
3141
  error: timeoutMessage ?? "Subagent timed out.",
2936
3142
  exitCode: 1,
2937
3143
  timedOut: true,
2938
3144
  });
2939
- const stoppedStepResult = (agent: string, context?: "fresh" | "fork"): SingleStepResult => omitUndefinedProperties({
3145
+ const stoppedStepResult = (agent: string, context?: "fresh" | "fork", sessionName?: string): SingleStepResult => omitUndefinedProperties({
2940
3146
  agent,
3147
+ sessionName,
2941
3148
  context,
2942
3149
  output: stopMessage,
2943
3150
  error: stopMessage,
@@ -3318,74 +3525,15 @@ async function runSubagent(
3318
3525
  }
3319
3526
  pendingStepSteers.push(...remaining);
3320
3527
  };
3321
- const updateStepModel = (flatIndex: number, model: string | undefined, thinking: string | undefined, now = Date.now()): void => {
3528
+ const updateStepModel = (flatIndex: number, model: string | undefined, thinking: string | undefined, contextLimit?: number, now = Date.now()): void => {
3322
3529
  const step = statusPayload.steps[flatIndex];
3323
3530
  if (!step) return;
3324
3531
  setOptionalProperty(step, "model", model);
3325
3532
  setOptionalProperty(step, "thinking", thinking);
3533
+ setOptionalProperty(step, "contextLimit", contextLimit);
3326
3534
  statusPayload.lastUpdate = now;
3327
3535
  writeStatusPayload();
3328
3536
  };
3329
- const updateStepTurnBudget = (
3330
- flatIndex: number,
3331
- turnCount: number,
3332
- now: number,
3333
- terminalAssistantStop: boolean,
3334
- toolWorkActiveOrStarting: boolean,
3335
- ): void => {
3336
- const budget = config.turnBudget;
3337
- const step = statusPayload.steps[flatIndex];
3338
- if (!budget || !step || timedOut || stopped || turnBudgetExceeded || step.turnBudgetExceeded) return;
3339
- if (turnCount < budget.maxTurns) {
3340
- const state: TurnBudgetState = { ...budget, outcome: "within-budget", turnCount };
3341
- step.turnBudget = state;
3342
- statusPayload.turnBudget = state;
3343
- return;
3344
- }
3345
- if (!step.wrapUpRequested) {
3346
- step.wrapUpRequested = true;
3347
- statusPayload.wrapUpRequested = true;
3348
- appendRecentStepOutput(step, [turnBudgetSoftNote(budget, turnCount)]);
3349
- }
3350
- const decision = turnBudgetDecision(budget, turnCount, terminalAssistantStop, toolWorkActiveOrStarting);
3351
- if (decision === "defer") {
3352
- const firstDeferredTurn = step.turnBudget?.terminationDeferredAtTurn;
3353
- const deferredState = turnBudgetDeferredState(budget, turnCount, firstDeferredTurn);
3354
- step.turnBudget = deferredState;
3355
- statusPayload.turnBudget = deferredState;
3356
- if (firstDeferredTurn === undefined) {
3357
- appendJsonl(eventsPath, JSON.stringify({
3358
- type: "subagent.step.turn_budget_deferred",
3359
- ts: now,
3360
- runId: id,
3361
- stepIndex: flatIndex,
3362
- agent: step.agent,
3363
- turnCount,
3364
- maxTurns: budget.maxTurns,
3365
- graceTurns: budget.graceTurns,
3366
- }));
3367
- }
3368
- return;
3369
- }
3370
- const state = turnBudgetState(budget, turnCount, false);
3371
- step.turnBudget = state;
3372
- statusPayload.turnBudget = state;
3373
- if (decision !== "abort") return;
3374
- const exceededState = turnBudgetState(budget, turnCount, true);
3375
- const message = turnBudgetExceededMessage(budget, turnCount);
3376
- step.turnBudget = exceededState;
3377
- step.turnBudgetExceeded = true;
3378
- step.wrapUpRequested = true;
3379
- step.error = message;
3380
- turnBudgetExceeded = true;
3381
- statusPayload.turnBudget = exceededState;
3382
- statusPayload.turnBudgetExceeded = true;
3383
- statusPayload.wrapUpRequested = true;
3384
- statusPayload.error = message;
3385
- statusPayload.lastUpdate = now;
3386
- appendJsonl(eventsPath, JSON.stringify({ type: "subagent.step.turn_budget_exceeded", ts: now, runId: id, stepIndex: flatIndex, agent: step.agent, turnCount, maxTurns: budget.maxTurns, graceTurns: budget.graceTurns, message }));
3387
- activeChildTurnBudgetAborts.get(flatIndex)?.(message, exceededState);
3388
- };
3389
3537
  const updateStepFromChildEvent = (flatIndex: number, event: ChildEvent): void => {
3390
3538
  const step = statusPayload.steps[flatIndex];
3391
3539
  if (!step) return;
@@ -3408,7 +3556,7 @@ async function runSubagent(
3408
3556
  return;
3409
3557
  }
3410
3558
  if (event.type === "tool_execution_start" && event.toolName) {
3411
- const mutates = isMutatingTool(event.toolName, event.args);
3559
+ const mutates = isMutatingTool(event.toolName, event.args, flatSteps[flatIndex]?.mutationTools);
3412
3560
  const currentPath = resolveCurrentPath(event.toolName, event.args);
3413
3561
  const argsPreview = extractToolArgsPreview(event.args ?? {});
3414
3562
  const blocksSupervisor = isBlockingSupervisorTool(event.toolName, event.args);
@@ -3516,22 +3664,16 @@ async function runSubagent(
3516
3664
  if (usage) {
3517
3665
  const input = usage.input ?? usage.inputTokens ?? 0;
3518
3666
  const output = usage.output ?? usage.outputTokens ?? 0;
3667
+ const window = input + (usage.cacheRead ?? usage.cacheReadTokens ?? 0);
3519
3668
  const previousInput = step.tokens?.input ?? 0;
3520
3669
  const previousOutput = step.tokens?.output ?? 0;
3521
- step.tokens = { input: previousInput + input, output: previousOutput + output, total: previousInput + previousOutput + input + output };
3670
+ step.tokens = { input: previousInput + input, output: previousOutput + output, total: previousInput + previousOutput + input + output, window, windowPeak: Math.max(step.tokens?.windowPeak ?? 0, window) };
3522
3671
  const totalInput = statusPayload.totalTokens?.input ?? 0;
3523
3672
  const totalOutput = statusPayload.totalTokens?.output ?? 0;
3524
- statusPayload.totalTokens = { input: totalInput + input, output: totalOutput + output, total: totalInput + totalOutput + input + output };
3673
+ statusPayload.totalTokens = { input: totalInput + input, output: totalOutput + output, total: totalInput + totalOutput + input + output, window, windowPeak: Math.max(statusPayload.totalTokens?.windowPeak ?? 0, window) };
3525
3674
  refreshUsageBudget();
3526
3675
  }
3527
3676
  statusPayload.turnCount = Math.max(statusPayload.turnCount ?? 0, step.turnCount);
3528
- updateStepTurnBudget(
3529
- flatIndex,
3530
- step.turnCount,
3531
- now,
3532
- isTerminalAssistantStop(event.message),
3533
- assistantStartsToolCall(event.message) || Boolean(step.currentTool),
3534
- );
3535
3677
  }
3536
3678
  syncTopLevelCurrentTool();
3537
3679
  step.lastActivityAt = now;
@@ -3760,7 +3902,7 @@ async function runSubagent(
3760
3902
  let flatIndex = 0;
3761
3903
  let stepCursor = 0;
3762
3904
  while (true) {
3763
- if (interrupted || timedOut || stopped || turnBudgetExceeded) break;
3905
+ if (interrupted || timedOut || stopped) break;
3764
3906
  consumePendingAppendRequests();
3765
3907
  if (stepCursor >= steps.length) break;
3766
3908
  refreshUsageBudget();
@@ -3905,10 +4047,12 @@ async function runSubagent(
3905
4047
  : step.parallel.outputPath;
3906
4048
  const taskText = task.task ?? step.parallel.task;
3907
4049
  const materializedTask = step.parallel.namespaceOutputPath ? injectSingleOutputInstruction(taskText, outputPath, step.parallel) : taskText;
4050
+ const sessionName = deriveChildSessionName({ agent: step.parallel.agent, task: taskText, label: task.label ?? step.parallel.label });
3908
4051
  return omitUndefinedProperties({
3909
4052
  ...step.parallel,
3910
4053
  runFanoutPath: `chain[${stepIndex}].expand[${itemIndex}]`,
3911
4054
  task: materializedTask,
4055
+ ...(sessionName ? { sessionName } : {}),
3912
4056
  effectiveAcceptance: resolveEffectiveAcceptance(omitUndefinedProperties({
3913
4057
  explicit: step.parallel.acceptanceInput,
3914
4058
  agentName: step.parallel.agent,
@@ -3939,6 +4083,7 @@ async function runSubagent(
3939
4083
  const transcriptPath = resolveAsyncStepTranscriptPath(omitUndefinedProperties({ artifactsDir, artifactConfig, runId: id, agent: task.agent, flatIndex: groupStartFlatIndex + itemIndex, flatStepCount: dynamicFlatStepCount }));
3940
4084
  return omitUndefinedProperties({
3941
4085
  agent: task.agent,
4086
+ ...(task.sessionName ? { sessionName: task.sessionName } : {}),
3942
4087
  ...(statusStepDescription(task.task) ? { description: statusStepDescription(task.task) } : {}),
3943
4088
  ...(task.context ? { context: task.context } : {}),
3944
4089
  ...(task.phase ?? step.phase ? { phase: task.phase ?? step.phase } : {}),
@@ -3952,6 +4097,7 @@ async function runSubagent(
3952
4097
  ...(transcriptPath ? { transcriptPath } : {}),
3953
4098
  ...(task.skills ? { skills: task.skills } : {}),
3954
4099
  ...(task.model ? { model: task.model } : {}),
4100
+ ...(task.contextLimit !== undefined ? { contextLimit: task.contextLimit } : {}),
3955
4101
  ...(task.thinking ? { thinking: task.thinking } : {}),
3956
4102
  ...(task.thinkingCeiling ? { thinkingCeiling: task.thinkingCeiling } : {}),
3957
4103
  ...(task.modelCandidates && task.modelCandidates.length > 0 ? { attemptedModels: task.modelCandidates } : task.model ? { attemptedModels: [task.model] } : {}),
@@ -4021,12 +4167,12 @@ async function runSubagent(
4021
4167
  usageBudgetExceeded = true;
4022
4168
  writeStatusPayload();
4023
4169
  appendJsonl(eventsPath, JSON.stringify({ type: "subagent.step.failed", ts: skippedAt, runId: id, stepIndex: fi, agent: task.agent, exitCode: 1, durationMs: 0 }));
4024
- return omitUndefinedProperties({ agent: task.agent, context: task.context, output: message, error: message, exitCode: 1 as number | null, skipped: true });
4170
+ return omitUndefinedProperties({ agent: task.agent, ...(task.sessionName ? { sessionName: task.sessionName } : {}), context: task.context, output: message, error: message, exitCode: 1 as number | null, skipped: true });
4025
4171
  }
4026
- if (timedOut) return timedOutStepResult(task.agent, task.context);
4027
- if (stopped) return stoppedStepResult(task.agent, task.context);
4172
+ if (timedOut) return timedOutStepResult(task.agent, task.context, task.sessionName);
4173
+ if (stopped) return stoppedStepResult(task.agent, task.context, task.sessionName);
4028
4174
  if (childStopRequests.has(fi)) return childStopResult(fi, task.agent, task.context);
4029
- if (interrupted) return pausedStepResult(task.agent, task.context);
4175
+ if (interrupted) return pausedStepResult(task.agent, task.context, task.sessionName);
4030
4176
  if (aborted && failFast) {
4031
4177
  const skippedAt = Date.now();
4032
4178
  requiredStatusStep(statusPayload, fi).status = "failed";
@@ -4037,7 +4183,7 @@ async function runSubagent(
4037
4183
  requiredStatusStep(statusPayload, fi).exitCode = -1;
4038
4184
  statusPayload.lastUpdate = skippedAt;
4039
4185
  writeStatusPayload();
4040
- return omitUndefinedProperties({ agent: task.agent, context: task.context, output: "(skipped — fail-fast)", exitCode: -1 as number | null, skipped: true });
4186
+ return omitUndefinedProperties({ agent: task.agent, ...(task.sessionName ? { sessionName: task.sessionName } : {}), context: task.context, output: "(skipped — fail-fast)", exitCode: -1 as number | null, skipped: true });
4041
4187
  }
4042
4188
  const taskStartTime = Date.now();
4043
4189
  statusPayload.currentStep = fi;
@@ -4073,20 +4219,19 @@ async function runSubagent(
4073
4219
  registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
4074
4220
  registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
4075
4221
  registerStop: (stop) => registerStepStop(fi, stop),
4076
- registerTurnBudgetAbort: (abort) => registerStepTurnBudgetAbort(fi, abort),
4077
4222
  timeoutSignal: timeoutAbortController.signal,
4078
4223
  stopSignal: stopAbortController.signal,
4079
4224
  trackedMutationEvidenceForCompletionGuard: false,
4080
4225
  timeoutMessage,
4081
4226
  stopMessage,
4082
- turnBudget: config.turnBudget,
4083
4227
  toolTimeoutMs: task.toolTimeoutMs ?? config.toolTimeoutMs,
4084
- onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
4228
+ onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking, attempt.contextLimit),
4085
4229
  onChildEvent: (event) => updateStepFromChildEvent(fi, event),
4086
4230
  onWriterProcess,
4087
4231
  onExternalProcess: (process) => updateExternalProcess(fi, process),
4088
4232
  onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
4089
4233
  skipAcceptance: () => timedOut || stopped || childStopRequests.has(fi),
4234
+ usageBudgetExhausted: () => refreshUsageBudget()?.exhausted === true,
4090
4235
  orcaProgressTab,
4091
4236
  }), config.deadlineAt);
4092
4237
  const taskEndTime = Date.now();
@@ -4098,16 +4243,11 @@ async function runSubagent(
4098
4243
  requiredStatusStep(statusPayload, fi).exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
4099
4244
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "timedOut", timedOut || singleResult.timedOut ? true : undefined);
4100
4245
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "stopped", stopped || childStopped ? true : undefined);
4101
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "turnBudget", singleResult.turnBudget);
4102
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "turnBudgetExceeded", singleResult.turnBudgetExceeded);
4103
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "wrapUpRequested", singleResult.wrapUpRequested);
4104
4246
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "toolBudget", singleResult.toolBudget);
4105
4247
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "toolBudgetBlocked", singleResult.toolBudgetBlocked);
4106
4248
  if (singleResult.toolBudget) statusPayload.toolBudget = singleResult.toolBudget;
4107
4249
  if (singleResult.toolBudgetBlocked) statusPayload.toolBudgetBlocked = true;
4108
- if (singleResult.turnBudget) statusPayload.turnBudget = singleResult.turnBudget;
4109
- if (singleResult.turnBudgetExceeded) statusPayload.turnBudgetExceeded = true;
4110
- if (singleResult.wrapUpRequested) statusPayload.wrapUpRequested = true;
4250
+ setOptionalProperty(requiredStatusStep(statusPayload, fi), "sessionName", singleResult.sessionName);
4111
4251
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "model", singleResult.model);
4112
4252
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "thinking", resolveEffectiveThinking(singleResult.model, requiredStatusStep(statusPayload, fi).thinking));
4113
4253
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "attemptedModels", singleResult.attemptedModels);
@@ -4159,6 +4299,7 @@ async function runSubagent(
4159
4299
  for (const pr of parallelResults) {
4160
4300
  results.push(omitUndefinedProperties({
4161
4301
  agent: pr.agent,
4302
+ ...(pr.sessionName ? { sessionName: pr.sessionName } : {}),
4162
4303
  context: pr.context,
4163
4304
  agentContract: pr.agentContract,
4164
4305
  launchContractDigest: pr.launchContractDigest,
@@ -4174,9 +4315,6 @@ async function runSubagent(
4174
4315
  interrupted: pr.interrupted,
4175
4316
  timedOut: pr.timedOut,
4176
4317
  stopped: pr.stopped,
4177
- turnBudget: pr.turnBudget,
4178
- turnBudgetExceeded: pr.turnBudgetExceeded,
4179
- wrapUpRequested: pr.wrapUpRequested,
4180
4318
  toolBudget: pr.toolBudget,
4181
4319
  toolBudgetBlocked: pr.toolBudgetBlocked,
4182
4320
  sessionFile: pr.sessionFile,
@@ -4186,6 +4324,7 @@ async function runSubagent(
4186
4324
  modelAttempts: pr.modelAttempts,
4187
4325
  contextOverflow: pr.contextOverflow,
4188
4326
  totalCost: pr.totalCost,
4327
+ usage: pr.usage,
4189
4328
  artifactPaths: pr.artifactPaths,
4190
4329
  transcriptPath: pr.transcriptPath,
4191
4330
  transcriptError: pr.transcriptError,
@@ -4329,6 +4468,23 @@ async function runSubagent(
4329
4468
  ? omitUndefinedProperties({ hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs })
4330
4469
  : undefined,
4331
4470
  baseDir: config.worktreeBaseDir,
4471
+ beforeCreate: (plannedSetup) => {
4472
+ for (const worktree of plannedSetup.worktrees) setStatusWorktreeReference(requiredStatusStep(statusPayload, groupStartFlatIndex + worktree.index), worktree);
4473
+ const pendingHandoff = writePendingParallelHandoff({
4474
+ manifestPath: parallelHandoffPath(asyncDir),
4475
+ runId: id,
4476
+ mode: (config.resultMode ?? statusPayload.mode) === "parallel" ? "parallel" : "chain",
4477
+ source: "async",
4478
+ cwd,
4479
+ stepIndex,
4480
+ flatStartIndex: groupStartFlatIndex,
4481
+ setup: plannedSetup,
4482
+ laneBindings: handoffWorkflowKey || config.lane ? [{ index: groupStartFlatIndex, taskIndex: 0, ...(handoffWorkflowKey ? { workflowKey: handoffWorkflowKey } : {}), ...(handoffChildRunId ? { runId: handoffChildRunId } : {}), ...(config.lane ? { lane: config.lane } : {}) }] : undefined,
4483
+ });
4484
+ statusPayload.parallelHandoff = pendingHandoff;
4485
+ statusPayload.lastUpdate = Date.now();
4486
+ writeStatusPayload();
4487
+ },
4332
4488
  }));
4333
4489
  } catch (error) {
4334
4490
  const setupError = error instanceof Error ? error.message : String(error);
@@ -4353,18 +4509,6 @@ async function runSubagent(
4353
4509
  }
4354
4510
 
4355
4511
  try {
4356
- if (worktreeSetup) {
4357
- writePendingParallelHandoff({
4358
- manifestPath: parallelHandoffPath(asyncDir),
4359
- runId: id,
4360
- mode: (config.resultMode ?? statusPayload.mode) === "parallel" ? "parallel" : "chain",
4361
- source: "async",
4362
- cwd,
4363
- stepIndex,
4364
- flatStartIndex: groupStartFlatIndex,
4365
- setup: worktreeSetup,
4366
- });
4367
- }
4368
4512
  if (group.worktree) ensureParallelProgressFile(cwd, group);
4369
4513
  const groupStartTime = Date.now();
4370
4514
  markParallelGroupRunning({
@@ -4401,12 +4545,12 @@ async function runSubagent(
4401
4545
  appendJsonl(eventsPath, JSON.stringify({
4402
4546
  type: "subagent.step.failed", ts: skippedAt, runId: id, stepIndex: fi, agent: task.agent, exitCode: 1, durationMs: 0,
4403
4547
  }));
4404
- return omitUndefinedProperties({ agent: task.agent, context: task.context, output: message, error: message, exitCode: 1 as number | null, skipped: true });
4548
+ return omitUndefinedProperties({ agent: task.agent, ...(task.sessionName ? { sessionName: task.sessionName } : {}), context: task.context, output: message, error: message, exitCode: 1 as number | null, skipped: true });
4405
4549
  }
4406
- if (timedOut) return timedOutStepResult(task.agent, task.context);
4407
- if (stopped) return stoppedStepResult(task.agent, task.context);
4550
+ if (timedOut) return timedOutStepResult(task.agent, task.context, task.sessionName);
4551
+ if (stopped) return stoppedStepResult(task.agent, task.context, task.sessionName);
4408
4552
  if (childStopRequests.has(fi)) return childStopResult(fi, task.agent, task.context);
4409
- if (interrupted) return pausedStepResult(task.agent, task.context);
4553
+ if (interrupted) return pausedStepResult(task.agent, task.context, task.sessionName);
4410
4554
  if (aborted && failFast) {
4411
4555
  const skippedAt = Date.now();
4412
4556
  requiredStatusStep(statusPayload, fi).status = "failed";
@@ -4421,7 +4565,7 @@ async function runSubagent(
4421
4565
  appendJsonl(eventsPath, JSON.stringify({
4422
4566
  type: "subagent.step.failed", ts: skippedAt, runId: id, stepIndex: fi, agent: task.agent, exitCode: -1, durationMs: 0,
4423
4567
  }));
4424
- return omitUndefinedProperties({ agent: task.agent, context: task.context, output: "(skipped — fail-fast)", exitCode: -1 as number | null, skipped: true });
4568
+ return omitUndefinedProperties({ agent: task.agent, ...(task.sessionName ? { sessionName: task.sessionName } : {}), context: task.context, output: "(skipped — fail-fast)", exitCode: -1 as number | null, skipped: true });
4425
4569
  }
4426
4570
 
4427
4571
  const taskStartTime = Date.now();
@@ -4469,20 +4613,19 @@ async function runSubagent(
4469
4613
  registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
4470
4614
  registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
4471
4615
  registerStop: (stop) => registerStepStop(fi, stop),
4472
- registerTurnBudgetAbort: (abort) => registerStepTurnBudgetAbort(fi, abort),
4473
4616
  timeoutSignal: timeoutAbortController.signal,
4474
4617
  stopSignal: stopAbortController.signal,
4475
4618
  trackedMutationEvidenceForCompletionGuard: Boolean(worktreeSetup),
4476
4619
  timeoutMessage,
4477
4620
  stopMessage,
4478
- turnBudget: config.turnBudget,
4479
4621
  toolTimeoutMs: taskForRun.toolTimeoutMs ?? config.toolTimeoutMs,
4480
- onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
4622
+ onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking, attempt.contextLimit),
4481
4623
  onChildEvent: (event) => updateStepFromChildEvent(fi, event),
4482
4624
  onWriterProcess,
4483
4625
  onExternalProcess: (process) => updateExternalProcess(fi, process),
4484
4626
  onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
4485
4627
  skipAcceptance: () => timedOut || stopped || childStopRequests.has(fi),
4628
+ usageBudgetExhausted: () => refreshUsageBudget()?.exhausted === true,
4486
4629
  orcaProgressTab,
4487
4630
  }), config.deadlineAt);
4488
4631
  if (task.sessionFile) {
@@ -4500,16 +4643,11 @@ async function runSubagent(
4500
4643
  requiredStatusStep(statusPayload, fi).exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
4501
4644
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "timedOut", timedOut || singleResult.timedOut ? true : undefined);
4502
4645
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "stopped", stopped || childStopped ? true : undefined);
4503
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "turnBudget", singleResult.turnBudget);
4504
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "turnBudgetExceeded", singleResult.turnBudgetExceeded);
4505
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "wrapUpRequested", singleResult.wrapUpRequested);
4506
4646
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "toolBudget", singleResult.toolBudget);
4507
4647
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "toolBudgetBlocked", singleResult.toolBudgetBlocked);
4508
4648
  if (singleResult.toolBudget) statusPayload.toolBudget = singleResult.toolBudget;
4509
4649
  if (singleResult.toolBudgetBlocked) statusPayload.toolBudgetBlocked = true;
4510
- if (singleResult.turnBudget) statusPayload.turnBudget = singleResult.turnBudget;
4511
- if (singleResult.turnBudgetExceeded) statusPayload.turnBudgetExceeded = true;
4512
- if (singleResult.wrapUpRequested) statusPayload.wrapUpRequested = true;
4650
+ setOptionalProperty(requiredStatusStep(statusPayload, fi), "sessionName", singleResult.sessionName);
4513
4651
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "model", singleResult.model);
4514
4652
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "thinking", resolveEffectiveThinking(singleResult.model, requiredStatusStep(statusPayload, fi).thinking));
4515
4653
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "attemptedModels", singleResult.attemptedModels);
@@ -4580,13 +4718,21 @@ async function runSubagent(
4580
4718
  const sessionTokens = config.sessionDir
4581
4719
  ? parseSessionTokens(path.join(config.sessionDir, `parallel-${t}`))
4582
4720
  : null;
4583
- const taskTokens = sessionTokens ?? tokenUsageFromAttempts(parallelResults[t]?.modelAttempts);
4721
+ const fallbackTokens = tokenUsageFromAttempts(parallelResults[t]?.modelAttempts);
4722
+ const observedTokens = requiredStatusStep(statusPayload, fi).tokens;
4723
+ const taskTokens = sessionTokens ?? (fallbackTokens
4724
+ ? { ...fallbackTokens, ...(observedTokens?.window !== undefined ? { window: observedTokens.window } : {}), ...(observedTokens?.windowPeak !== undefined ? { windowPeak: observedTokens.windowPeak } : {}) }
4725
+ : null);
4584
4726
  if (!taskTokens) continue;
4585
4727
  requiredStatusStep(statusPayload, fi).tokens = taskTokens;
4586
4728
  previousCumulativeTokens = {
4587
4729
  input: previousCumulativeTokens.input + taskTokens.input,
4588
4730
  output: previousCumulativeTokens.output + taskTokens.output,
4589
4731
  total: previousCumulativeTokens.total + taskTokens.total,
4732
+ ...(taskTokens.window !== undefined ? { window: taskTokens.window } : {}),
4733
+ ...(previousCumulativeTokens.windowPeak !== undefined || taskTokens.windowPeak !== undefined
4734
+ ? { windowPeak: Math.max(previousCumulativeTokens.windowPeak ?? 0, taskTokens.windowPeak ?? 0) }
4735
+ : {}),
4590
4736
  };
4591
4737
  }
4592
4738
  statusPayload.totalTokens = { ...previousCumulativeTokens };
@@ -4610,9 +4756,6 @@ async function runSubagent(
4610
4756
  interrupted: pr.interrupted,
4611
4757
  timedOut: pr.timedOut,
4612
4758
  stopped: pr.stopped,
4613
- turnBudget: pr.turnBudget,
4614
- turnBudgetExceeded: pr.turnBudgetExceeded,
4615
- wrapUpRequested: pr.wrapUpRequested,
4616
4759
  toolBudget: pr.toolBudget,
4617
4760
  toolBudgetBlocked: pr.toolBudgetBlocked,
4618
4761
  sessionFile: pr.sessionFile,
@@ -4622,6 +4765,7 @@ async function runSubagent(
4622
4765
  modelAttempts: pr.modelAttempts,
4623
4766
  contextOverflow: pr.contextOverflow,
4624
4767
  totalCost: pr.totalCost,
4768
+ usage: pr.usage,
4625
4769
  artifactPaths: pr.artifactPaths,
4626
4770
  transcriptPath: pr.transcriptPath,
4627
4771
  transcriptError: pr.transcriptError,
@@ -4675,12 +4819,14 @@ async function runSubagent(
4675
4819
  diffs: captured.diffs,
4676
4820
  results: parallelResults.map((result) => ({
4677
4821
  agent: result.agent,
4822
+ ...(handoffWorkflowKey ? { workflowKey: handoffWorkflowKey } : {}),
4823
+ ...(handoffChildRunId ? { runId: handoffChildRunId } : {}),
4824
+ ...(config.lane ? { lane: config.lane } : {}),
4678
4825
  status: result.stopped || (result.exitCode !== 0 && isUnexplainedProcessSignal(omitUndefinedProperties({
4679
4826
  processSignal: result.processSignal,
4680
4827
  interrupted: result.interrupted,
4681
4828
  timedOut: result.timedOut,
4682
4829
  stopped: result.stopped,
4683
- turnBudgetExceeded: result.turnBudgetExceeded,
4684
4830
  }))) ? "stopped" as const : result.interrupted ? "paused" as const : result.exitCode === 0 ? "completed" as const : "failed" as const,
4685
4831
  summary: result.output || result.error || "(no output)",
4686
4832
  ...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
@@ -4726,12 +4872,12 @@ async function runSubagent(
4726
4872
  } else {
4727
4873
  const seqStep = step as SubagentStep;
4728
4874
  if (timedOut) {
4729
- results.push(timedOutStepResult(seqStep.agent, seqStep.context));
4875
+ results.push(timedOutStepResult(seqStep.agent, seqStep.context, seqStep.sessionName));
4730
4876
  flatIndex++;
4731
4877
  continue;
4732
4878
  }
4733
4879
  if (stopped) {
4734
- results.push(stoppedStepResult(seqStep.agent, seqStep.context));
4880
+ results.push(stoppedStepResult(seqStep.agent, seqStep.context, seqStep.sessionName));
4735
4881
  flatIndex++;
4736
4882
  continue;
4737
4883
  }
@@ -4741,7 +4887,7 @@ async function runSubagent(
4741
4887
  continue;
4742
4888
  }
4743
4889
  if (interrupted) {
4744
- results.push(pausedStepResult(seqStep.agent, seqStep.context));
4890
+ results.push(pausedStepResult(seqStep.agent, seqStep.context, seqStep.sessionName));
4745
4891
  flatIndex++;
4746
4892
  continue;
4747
4893
  }
@@ -4754,17 +4900,27 @@ async function runSubagent(
4754
4900
  ? omitUndefinedProperties({ hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs })
4755
4901
  : undefined,
4756
4902
  baseDir: config.worktreeBaseDir,
4903
+ beforeCreate: (plannedSetup) => {
4904
+ const worktree = plannedSetup.worktrees[0];
4905
+ if (worktree) {
4906
+ setStatusWorktreeReference(requiredStatusStep(statusPayload, flatIndex), worktree);
4907
+ }
4908
+ const pendingHandoff = writePendingParallelHandoff({
4909
+ manifestPath: parallelHandoffPath(asyncDir),
4910
+ runId: id,
4911
+ mode: "single",
4912
+ source: "async",
4913
+ cwd,
4914
+ stepIndex,
4915
+ flatStartIndex: flatIndex,
4916
+ setup: plannedSetup,
4917
+ laneBindings: handoffWorkflowKey || config.lane ? [{ index: flatIndex, taskIndex: 0, ...(handoffWorkflowKey ? { workflowKey: handoffWorkflowKey } : {}), ...(handoffChildRunId ? { runId: handoffChildRunId } : {}), ...(config.lane ? { lane: config.lane } : {}) }] : undefined,
4918
+ });
4919
+ statusPayload.parallelHandoff = pendingHandoff;
4920
+ statusPayload.lastUpdate = Date.now();
4921
+ writeStatusPayload();
4922
+ },
4757
4923
  }));
4758
- writePendingParallelHandoff({
4759
- manifestPath: parallelHandoffPath(asyncDir),
4760
- runId: id,
4761
- mode: "single",
4762
- source: "async",
4763
- cwd,
4764
- stepIndex,
4765
- flatStartIndex: flatIndex,
4766
- setup: singleWorktreeSetup,
4767
- });
4768
4924
  } catch (error) {
4769
4925
  if (singleWorktreeSetup) cleanupWorktrees(singleWorktreeSetup);
4770
4926
  throw error;
@@ -4817,19 +4973,18 @@ async function runSubagent(
4817
4973
  registerInterrupt: (interrupt) => registerStepInterrupt(flatIndex, interrupt),
4818
4974
  registerTimeout: (interrupt) => registerStepTimeout(flatIndex, interrupt),
4819
4975
  registerStop: (stop) => registerStepStop(flatIndex, stop),
4820
- registerTurnBudgetAbort: (abort) => registerStepTurnBudgetAbort(flatIndex, abort),
4821
4976
  timeoutSignal: timeoutAbortController.signal,
4822
4977
  stopSignal: stopAbortController.signal,
4823
4978
  timeoutMessage,
4824
4979
  stopMessage,
4825
- turnBudget: config.turnBudget,
4826
4980
  toolTimeoutMs: seqStep.toolTimeoutMs ?? config.toolTimeoutMs,
4827
- onAttemptStart: (attempt) => updateStepModel(flatIndex, attempt.model, attempt.thinking),
4981
+ onAttemptStart: (attempt) => updateStepModel(flatIndex, attempt.model, attempt.thinking, attempt.contextLimit),
4828
4982
  onChildEvent: (event) => updateStepFromChildEvent(flatIndex, event),
4829
4983
  onWriterProcess,
4830
4984
  onExternalProcess: (process) => updateExternalProcess(flatIndex, process),
4831
4985
  onExternalJob: (externalJob) => updateExternalJob(flatIndex, externalJob),
4832
4986
  skipAcceptance: () => timedOut || stopped || childStopRequests.has(flatIndex),
4987
+ usageBudgetExhausted: () => refreshUsageBudget()?.exhausted === true,
4833
4988
  orcaProgressTab,
4834
4989
  }), config.deadlineAt);
4835
4990
  } catch (error) {
@@ -4844,6 +4999,7 @@ async function runSubagent(
4844
4999
  const childStopped = singleResult.stopped === true;
4845
5000
  results.push(omitUndefinedProperties({
4846
5001
  agent: singleResult.agent,
5002
+ ...(singleResult.sessionName ? { sessionName: singleResult.sessionName } : {}),
4847
5003
  context: singleResult.context,
4848
5004
  agentContract: singleResult.agentContract,
4849
5005
  launchContractDigest: singleResult.launchContractDigest,
@@ -4862,6 +5018,7 @@ async function runSubagent(
4862
5018
  modelAttempts: singleResult.modelAttempts,
4863
5019
  contextOverflow: singleResult.contextOverflow,
4864
5020
  totalCost: singleResult.totalCost,
5021
+ usage: singleResult.usage,
4865
5022
  artifactPaths: singleResult.artifactPaths,
4866
5023
  transcriptPath: singleResult.transcriptPath,
4867
5024
  transcriptError: singleResult.transcriptError,
@@ -4879,9 +5036,6 @@ async function runSubagent(
4879
5036
  interrupted: singleResult.interrupted,
4880
5037
  timedOut: timedOut || singleResult.timedOut ? true : undefined,
4881
5038
  stopped: stopped || childStopped ? true : undefined,
4882
- turnBudget: singleResult.turnBudget,
4883
- turnBudgetExceeded: singleResult.turnBudgetExceeded,
4884
- wrapUpRequested: singleResult.wrapUpRequested,
4885
5039
  toolBudget: singleResult.toolBudget,
4886
5040
  toolBudgetBlocked: singleResult.toolBudgetBlocked,
4887
5041
  runner: singleResult.runner,
@@ -4903,17 +5057,27 @@ async function runSubagent(
4903
5057
  input: cumulativeTokens.input - previousCumulativeTokens.input,
4904
5058
  output: cumulativeTokens.output - previousCumulativeTokens.output,
4905
5059
  total: cumulativeTokens.total - previousCumulativeTokens.total,
5060
+ ...(cumulativeTokens.window !== undefined ? { window: cumulativeTokens.window } : {}),
5061
+ ...(cumulativeTokens.windowPeak !== undefined ? { windowPeak: cumulativeTokens.windowPeak } : {}),
4906
5062
  }
4907
5063
  : null;
4908
5064
  if (cumulativeTokens) {
4909
5065
  previousCumulativeTokens = cumulativeTokens;
4910
5066
  } else {
4911
- stepTokens = tokenUsageFromAttempts(singleResult.modelAttempts);
5067
+ const fallbackTokens = tokenUsageFromAttempts(singleResult.modelAttempts);
5068
+ const observedTokens = requiredStatusStep(statusPayload, flatIndex).tokens;
5069
+ stepTokens = fallbackTokens
5070
+ ? { ...fallbackTokens, ...(observedTokens?.window !== undefined ? { window: observedTokens.window } : {}), ...(observedTokens?.windowPeak !== undefined ? { windowPeak: observedTokens.windowPeak } : {}) }
5071
+ : null;
4912
5072
  if (stepTokens) {
4913
5073
  previousCumulativeTokens = {
4914
5074
  input: previousCumulativeTokens.input + stepTokens.input,
4915
5075
  output: previousCumulativeTokens.output + stepTokens.output,
4916
5076
  total: previousCumulativeTokens.total + stepTokens.total,
5077
+ ...(stepTokens.window !== undefined ? { window: stepTokens.window } : {}),
5078
+ ...(previousCumulativeTokens.windowPeak !== undefined || stepTokens.windowPeak !== undefined
5079
+ ? { windowPeak: Math.max(previousCumulativeTokens.windowPeak ?? 0, stepTokens.windowPeak ?? 0) }
5080
+ : {}),
4917
5081
  };
4918
5082
  }
4919
5083
  }
@@ -4926,16 +5090,11 @@ async function runSubagent(
4926
5090
  requiredStatusStep(statusPayload, flatIndex).exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
4927
5091
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "timedOut", timedOut || singleResult.timedOut ? true : undefined);
4928
5092
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "stopped", stopped || childStopped ? true : undefined);
4929
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "turnBudget", singleResult.turnBudget);
4930
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "turnBudgetExceeded", singleResult.turnBudgetExceeded);
4931
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "wrapUpRequested", singleResult.wrapUpRequested);
4932
5093
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "toolBudget", singleResult.toolBudget);
4933
5094
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "toolBudgetBlocked", singleResult.toolBudgetBlocked);
4934
5095
  if (singleResult.toolBudget) statusPayload.toolBudget = singleResult.toolBudget;
4935
5096
  if (singleResult.toolBudgetBlocked) statusPayload.toolBudgetBlocked = true;
4936
- if (singleResult.turnBudget) statusPayload.turnBudget = singleResult.turnBudget;
4937
- if (singleResult.turnBudgetExceeded) statusPayload.turnBudgetExceeded = true;
4938
- if (singleResult.wrapUpRequested) statusPayload.wrapUpRequested = true;
5097
+ setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "sessionName", singleResult.sessionName);
4939
5098
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "model", singleResult.model);
4940
5099
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "thinking", resolveEffectiveThinking(singleResult.model, requiredStatusStep(statusPayload, flatIndex).thinking));
4941
5100
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "attemptedModels", singleResult.attemptedModels);
@@ -4996,6 +5155,9 @@ async function runSubagent(
4996
5155
  diffs,
4997
5156
  results: [{
4998
5157
  agent: singleResult.agent,
5158
+ ...(handoffWorkflowKey ? { workflowKey: handoffWorkflowKey } : {}),
5159
+ ...(handoffChildRunId ? { runId: handoffChildRunId } : {}),
5160
+ ...(config.lane ? { lane: config.lane } : {}),
4999
5161
  status: singleResult.stopped ? "stopped" as const : singleResult.interrupted ? "paused" as const : singleResult.exitCode === 0 ? "completed" as const : "failed" as const,
5000
5162
  summary: singleResult.output || singleResult.error || "(no output)",
5001
5163
  ...(singleResult.artifactPaths?.outputPath ? { outputPath: singleResult.artifactPaths.outputPath } : {}),
@@ -5048,7 +5210,7 @@ async function runSubagent(
5048
5210
  }
5049
5211
  }
5050
5212
 
5051
- let summary = results.map((r) => `${r.agent}:\n${r.output}`).join("\n\n");
5213
+ let summary = results.map((r) => `${r.agent}:\n${r.output || (r.exitCode !== 0 ? r.error : undefined) || "(no output)"}`).join("\n\n");
5052
5214
  let truncated = false;
5053
5215
 
5054
5216
  if (maxOutput) {
@@ -5116,14 +5278,14 @@ async function runSubagent(
5116
5278
  if (!timedOut && !stopped && !interrupted && config.timeoutMs !== undefined && results.some((result) => result.timedOut === true && result.error === timeoutMessage)) {
5117
5279
  timedOut = true;
5118
5280
  }
5119
- const signalTerminated = !stopped && !timedOut && !turnBudgetExceeded && !interrupted && results.some((result) => result.exitCode !== 0 && isUnexplainedProcessSignal(omitUndefinedProperties({
5281
+ const signalTerminated = !stopped && !timedOut && !interrupted && results.some((result) => result.exitCode !== 0 && isUnexplainedProcessSignal(omitUndefinedProperties({
5120
5282
  processSignal: result.processSignal,
5121
5283
  interrupted: result.interrupted,
5122
5284
  timedOut: result.timedOut,
5123
5285
  stopped: result.stopped,
5124
- turnBudgetExceeded: result.turnBudgetExceeded,
5125
5286
  })));
5126
- statusPayload.state = stopped || signalTerminated ? "stopped" : timedOut || turnBudgetExceeded || usageBudgetExceeded || statusPayload.error ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed";
5287
+ const partialWithEvidence = !stopped && !signalTerminated && !timedOut && !usageBudgetExceeded && !interrupted && results.some(partialEvidenceResult) && !results.some(concreteFailureResult);
5288
+ statusPayload.state = stopped || signalTerminated ? "stopped" : timedOut || usageBudgetExceeded ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : partialWithEvidence ? "partial" : "failed";
5127
5289
  closeSteerInbox(asyncDir, statusPayload.state, (filePath, payload) => runPersistence.write(filePath, payload));
5128
5290
  disposeControlInbox();
5129
5291
  for (const request of consumeSteerRequests(asyncDir)) deliverSteerRequest(request);
@@ -5153,13 +5315,17 @@ async function runSubagent(
5153
5315
  statusPayload.timedOut = true;
5154
5316
  statusPayload.error = timeoutMessage ?? "Subagent timed out.";
5155
5317
  }
5156
- if (turnBudgetExceeded && !statusPayload.error) {
5157
- const budget = statusPayload.turnBudget;
5158
- statusPayload.error = budget ? turnBudgetExceededMessage(budget, budget.turnCount) : "Subagent exceeded turn budget.";
5159
- }
5160
5318
  if (usageBudgetExceeded && statusPayload.usageBudget && !statusPayload.error) {
5161
5319
  statusPayload.error = usageBudgetExceededMessage(statusPayload.usageBudget);
5162
5320
  }
5321
+ if (partialWithEvidence) {
5322
+ const partialResult = results.find(partialEvidenceResult);
5323
+ statusPayload.activityState = "needs_attention";
5324
+ statusPayload.error = partialResult?.error ?? statusPayload.error;
5325
+ for (const step of statusPayload.steps) {
5326
+ if (step.status === "failed") step.activityState = "needs_attention";
5327
+ }
5328
+ }
5163
5329
  statusPayload.endedAt = runEndedAt;
5164
5330
  statusPayload.lastUpdate = runEndedAt;
5165
5331
  setOptionalProperty(statusPayload, "sessionFile", effectiveSessionFile);
@@ -5169,8 +5335,10 @@ async function runSubagent(
5169
5335
  setOptionalProperty(statusPayload, "shareUrl", shareUrl);
5170
5336
  setOptionalProperty(statusPayload, "gistUrl", gistUrl);
5171
5337
  setOptionalProperty(statusPayload, "shareError", shareError);
5172
- if (statusPayload.state === "failed" && !statusPayload.error) {
5173
- const failedStep = statusPayload.steps.find((s) => s.status === "failed");
5338
+ if ((statusPayload.state === "failed" || statusPayload.state === "partial") && !statusPayload.error) {
5339
+ const concreteFailure = results.find(concreteFailureResult);
5340
+ const failedStep = concreteFailure ? undefined : statusPayload.steps.find((s) => s.status === "failed");
5341
+ if (concreteFailure?.error) statusPayload.error = concreteFailure.error;
5174
5342
  if (failedStep?.agent) {
5175
5343
  statusPayload.error = `Step failed: ${failedStep.agent}`;
5176
5344
  }
@@ -5182,20 +5350,18 @@ async function runSubagent(
5182
5350
  id,
5183
5351
  agent: agentName,
5184
5352
  mode: resultMode,
5185
- success: !stopped && !timedOut && !turnBudgetExceeded && !usageBudgetExceeded && !interrupted && !signalTerminated && results.every((r) => r.success),
5186
- state: stopped || signalTerminated ? "stopped" : timedOut || turnBudgetExceeded || usageBudgetExceeded ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed",
5187
- summary: stopped ? stopMessage : signalTerminated ? (statusPayload.error ?? "Subagent process terminated by signal.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? (statusPayload.error ?? "Subagent exceeded turn budget.") : usageBudgetExceeded ? (statusPayload.error ?? "Usage budget exhausted.") : interrupted ? "Paused after interrupt. Waiting for explicit next action." : summary,
5353
+ success: statusPayload.state === "complete",
5354
+ state: statusPayload.state,
5355
+ summary: stopped ? stopMessage : signalTerminated ? (statusPayload.error ?? "Subagent process terminated by signal.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : usageBudgetExceeded ? (statusPayload.error ?? "Usage budget exhausted.") : interrupted ? "Paused after interrupt. Waiting for explicit next action." : statusPayload.state === "partial" ? (statusPayload.error ?? summary) : summary,
5188
5356
  ...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),
5189
5357
  ...(config.deadlineAt !== undefined ? { deadlineAt: config.deadlineAt } : {}),
5190
- ...(statusPayload.turnBudget ? { turnBudget: statusPayload.turnBudget } : {}),
5191
- ...(statusPayload.turnBudgetExceeded ? { turnBudgetExceeded: true } : {}),
5192
- ...(statusPayload.wrapUpRequested ? { wrapUpRequested: true } : {}),
5193
5358
  ...(statusPayload.toolBudget ? { toolBudget: statusPayload.toolBudget } : {}),
5194
5359
  ...(statusPayload.toolBudgetBlocked ? { toolBudgetBlocked: true } : {}),
5195
5360
  ...(statusPayload.usageBudget ? { usageBudget: statusPayload.usageBudget } : {}),
5196
- ...(stopped ? { stopped: true, error: stopMessage } : timedOut ? { timedOut: true, error: timeoutMessage ?? "Subagent timed out." } : turnBudgetExceeded ? { error: statusPayload.error ?? "Subagent exceeded turn budget." } : usageBudgetExceeded ? { error: statusPayload.error ?? "Usage budget exhausted." } : {}),
5361
+ ...(stopped ? { stopped: true, error: stopMessage } : timedOut ? { timedOut: true, error: timeoutMessage ?? "Subagent timed out." } : usageBudgetExceeded ? { error: statusPayload.error ?? "Usage budget exhausted." } : {}),
5197
5362
  results: results.map((r) => omitUndefinedProperties({
5198
5363
  agent: r.agent,
5364
+ ...(r.sessionName ? { sessionName: r.sessionName } : {}),
5199
5365
  context: r.context,
5200
5366
  output: r.output,
5201
5367
  outputState: r.outputState,
@@ -5207,9 +5373,6 @@ async function runSubagent(
5207
5373
  timedOut: r.timedOut || undefined,
5208
5374
  stopped: r.stopped || undefined,
5209
5375
  processSignal: r.processSignal || undefined,
5210
- turnBudget: r.turnBudget,
5211
- turnBudgetExceeded: r.turnBudgetExceeded || undefined,
5212
- wrapUpRequested: r.wrapUpRequested || undefined,
5213
5376
  toolBudget: r.toolBudget,
5214
5377
  toolBudgetBlocked: r.toolBudgetBlocked || undefined,
5215
5378
  sessionFile: r.sessionFile,
@@ -5219,6 +5382,7 @@ async function runSubagent(
5219
5382
  modelAttempts: r.modelAttempts,
5220
5383
  contextOverflow: r.contextOverflow,
5221
5384
  totalCost: r.totalCost,
5385
+ usage: r.usage,
5222
5386
  artifactPaths: r.artifactPaths,
5223
5387
  outputSaveError: r.outputSaveError,
5224
5388
  metadataSaveError: r.metadataSaveError,
@@ -5251,7 +5415,7 @@ async function runSubagent(
5251
5415
  capabilityAudit: statusPayload.capabilityAudit,
5252
5416
  ...(config.parentWorkflowRunId ? { parentWorkflowRunId: config.parentWorkflowRunId } : {}),
5253
5417
  ...(config.workflowKey ? { workflowKey: config.workflowKey } : {}),
5254
- exitCode: stopped || timedOut || turnBudgetExceeded || usageBudgetExceeded ? 1 : interrupted || results.every((r) => r.success) ? 0 : 1,
5418
+ exitCode: statusPayload.state === "complete" || statusPayload.state === "paused" ? 0 : 1,
5255
5419
  timestamp: runEndedAt,
5256
5420
  durationMs: runEndedAt - overallStartTime,
5257
5421
  totalTokens: statusPayload.totalTokens,
@@ -5359,7 +5523,7 @@ async function waitForStartupControl(
5359
5523
  } catch (error) {
5360
5524
  throw new Error(`Failed to read runner startup control '${controlPath}': ${error instanceof Error ? error.message : String(error)}`);
5361
5525
  }
5362
- if (payload.token !== token) throw new Error("Runner startup control token does not match the acquired session lease.");
5526
+ if (payload.token !== token) throw new Error("Runner startup control token does not match.");
5363
5527
  if (payload.action === action) return;
5364
5528
  if (payload.action !== "ack" && payload.action !== "proceed") throw new Error("Runner startup control action is invalid.");
5365
5529
  }
@@ -5370,7 +5534,7 @@ async function waitForStartupControl(
5370
5534
 
5371
5535
  async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
5372
5536
  let lease: ReturnType<typeof acquireSessionLease> | undefined;
5373
- let startupCommitted = config.revivalLease === undefined;
5537
+ let startupCommitted = config.revivalLease === undefined && config.launchBarrierToken === undefined;
5374
5538
  const startupPath = path.join(config.asyncDir, "runner-startup.json");
5375
5539
  const startupAckPath = path.join(config.asyncDir, "runner-startup-ack.json");
5376
5540
  const startupProceedPath = path.join(config.asyncDir, "runner-startup-proceed.json");
@@ -5383,7 +5547,15 @@ async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
5383
5547
  };
5384
5548
  process.once("exit", releaseOnExit);
5385
5549
  try {
5386
- if (config.revivalLease) {
5550
+ if (config.launchBarrierToken) {
5551
+ await waitForStartupControl(startupProceedPath, config.launchBarrierToken, "proceed");
5552
+ startupCommitted = true;
5553
+ try {
5554
+ fs.rmSync(startupProceedPath, { force: true });
5555
+ } catch {
5556
+ // Startup control cleanup is best effort after the parent commits the run.
5557
+ }
5558
+ } else if (config.revivalLease) {
5387
5559
  lease = acquireSessionLease(config.revivalLease);
5388
5560
  config.revivalLeaseToken = lease.owner.token;
5389
5561
  writeAtomicJson(startupPath, { state: "ready", token: lease.owner.token, pid: process.pid, owner: lease.owner });
@@ -5401,7 +5573,7 @@ async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
5401
5573
  }
5402
5574
  await runSubagent(config, lease ? (writer) => lease!.updateWriter(writer) : undefined);
5403
5575
  } catch (error) {
5404
- if (config.revivalLease && !startupCommitted) {
5576
+ if (!startupCommitted) {
5405
5577
  try {
5406
5578
  writeAtomicJson(startupPath, { state: "error", pid: process.pid, error: error instanceof Error ? error.message : String(error) });
5407
5579
  } catch {