@duckmind/dm-windows-x64 0.61.4 → 0.61.9

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 (279) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +211 -67
  3. package/extensions/dm-subagents/agents/claude-code-writer.md +15 -0
  4. package/extensions/dm-subagents/agents/claude-code.md +15 -0
  5. package/extensions/dm-subagents/agents/codex-exec-writer.md +15 -0
  6. package/extensions/dm-subagents/agents/codex-exec.md +15 -0
  7. package/extensions/dm-subagents/agents/cursor-agent-writer.md +14 -0
  8. package/extensions/dm-subagents/agents/cursor-agent.md +14 -0
  9. package/extensions/dm-subagents/agents/delegate.md +3 -2
  10. package/extensions/dm-subagents/agents/oracle.md +10 -5
  11. package/extensions/dm-subagents/agents/researcher.md +2 -2
  12. package/extensions/dm-subagents/agents/reviewer.md +17 -7
  13. package/extensions/dm-subagents/agents/scout.md +5 -5
  14. package/extensions/dm-subagents/agents/worker.md +6 -2
  15. package/extensions/dm-subagents/async-retention-discovery-worker.mjs +167 -0
  16. package/extensions/dm-subagents/index.js +4 -0
  17. package/extensions/dm-subagents/inspector-runner.mjs +10 -0
  18. package/extensions/dm-subagents/install.mjs +3 -2
  19. package/extensions/dm-subagents/package.json +2 -2
  20. package/extensions/dm-subagents/prompts/council.md +60 -0
  21. package/extensions/dm-subagents/prompts/parallel-review.md +5 -1
  22. package/extensions/dm-subagents/prompts/review-loop.md +13 -7
  23. package/extensions/dm-subagents/skills/council-mode/SKILL.md +59 -0
  24. package/extensions/dm-subagents/skills/council-mode/references/pass-contracts.md +150 -0
  25. package/extensions/dm-subagents/skills/dm-subagents/SKILL.md +96 -911
  26. package/extensions/dm-subagents/skills/dm-subagents/references/constraints-and-recipes.md +70 -0
  27. package/extensions/dm-subagents/skills/dm-subagents/references/execution-controls.md +539 -0
  28. package/extensions/dm-subagents/skills/dm-subagents/references/management-authoring-rpc.md +161 -0
  29. package/extensions/dm-subagents/skills/dm-subagents/references/multi-lane-orchestration.md +51 -0
  30. package/extensions/dm-subagents/skills/dm-subagents/references/prompting-and-roles.md +295 -0
  31. package/extensions/dm-subagents/skills/dm-subagents/references/review-and-validation.md +73 -0
  32. package/extensions/dm-subagents/src/agents/agent-management.js +716 -484
  33. package/extensions/dm-subagents/src/agents/agent-refinements.js +563 -0
  34. package/extensions/dm-subagents/src/agents/agent-serializer.js +70 -5
  35. package/extensions/dm-subagents/src/agents/agents.js +1447 -299
  36. package/extensions/dm-subagents/src/agents/builtin-names.js +15 -0
  37. package/extensions/dm-subagents/src/agents/chain-serializer.js +12 -7
  38. package/extensions/dm-subagents/src/agents/frontmatter.js +64 -14
  39. package/extensions/dm-subagents/src/agents/identity.js +1 -1
  40. package/extensions/dm-subagents/src/agents/proactive-skills.js +14 -11
  41. package/extensions/dm-subagents/src/agents/runtime-agent-events.js +49 -0
  42. package/extensions/dm-subagents/src/agents/runtime-agent-registry.js +412 -0
  43. package/extensions/dm-subagents/src/agents/skills.js +52 -35
  44. package/extensions/dm-subagents/src/api/agents.js +6 -0
  45. package/extensions/dm-subagents/src/api/background-work.js +151 -0
  46. package/extensions/dm-subagents/src/api/capability-ceiling.js +12 -0
  47. package/extensions/dm-subagents/src/api/control-channel.js +3 -0
  48. package/extensions/dm-subagents/src/api/delegation.js +5 -0
  49. package/extensions/dm-subagents/src/api/dm-args.js +3 -0
  50. package/extensions/dm-subagents/src/api/external-job-provider.js +137 -0
  51. package/extensions/dm-subagents/src/api/external-runs.js +233 -0
  52. package/extensions/dm-subagents/src/api/intercom-bridge.js +3 -0
  53. package/extensions/dm-subagents/src/api/preflight.js +322 -0
  54. package/extensions/dm-subagents/src/api/project-panes.js +11 -0
  55. package/extensions/dm-subagents/src/api/shared-types.js +4 -0
  56. package/extensions/dm-subagents/src/extension/config.js +175 -0
  57. package/extensions/dm-subagents/src/extension/control-notices.js +4 -43
  58. package/extensions/dm-subagents/src/extension/doctor.js +71 -17
  59. package/extensions/dm-subagents/src/extension/fanout-child.js +49 -32
  60. package/extensions/dm-subagents/src/extension/index.js +711 -265
  61. package/extensions/dm-subagents/src/extension/public-execution.js +114 -0
  62. package/extensions/dm-subagents/src/extension/rpc.js +427 -22
  63. package/extensions/dm-subagents/src/extension/schemas.js +158 -65
  64. package/extensions/dm-subagents/src/extension/steering-notices.js +23 -0
  65. package/extensions/dm-subagents/src/extension/subagent-guide.js +31 -0
  66. package/extensions/dm-subagents/src/extension/tool-description.js +92 -74
  67. package/extensions/dm-subagents/src/extension/tool-result.js +7 -0
  68. package/extensions/dm-subagents/src/inspectors/herdr/actions.js +218 -0
  69. package/extensions/dm-subagents/src/inspectors/herdr/client.js +123 -0
  70. package/extensions/dm-subagents/src/inspectors/herdr/focus.js +47 -0
  71. package/extensions/dm-subagents/src/inspectors/herdr/inspector-runner.js +160 -0
  72. package/extensions/dm-subagents/src/inspectors/herdr/project-panes.js +618 -0
  73. package/extensions/dm-subagents/src/inspectors/herdr/session-roots-codec.js +21 -0
  74. package/extensions/dm-subagents/src/inspectors/herdr/shell-command.js +15 -0
  75. package/extensions/dm-subagents/src/integrations/herdr-status.js +377 -0
  76. package/extensions/dm-subagents/src/intercom/intercom-bridge.js +17 -13
  77. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +371 -79
  78. package/extensions/dm-subagents/src/intercom/result-intercom.js +47 -7
  79. package/extensions/dm-subagents/src/missions/actions.js +394 -0
  80. package/extensions/dm-subagents/src/missions/goal-driver.js +149 -0
  81. package/extensions/dm-subagents/src/missions/lifecycle.js +331 -0
  82. package/extensions/dm-subagents/src/missions/store.js +548 -0
  83. package/extensions/dm-subagents/src/missions/types.js +9 -0
  84. package/extensions/dm-subagents/src/missions/workflow-state.js +245 -0
  85. package/extensions/dm-subagents/src/policy/authority.js +37 -0
  86. package/extensions/dm-subagents/src/profiles/profiles.js +36 -18
  87. package/extensions/dm-subagents/src/runs/background/active-async-capacity.js +427 -0
  88. package/extensions/dm-subagents/src/runs/background/active-run-index.js +122 -0
  89. package/extensions/dm-subagents/src/runs/background/async-execution.js +925 -137
  90. package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +515 -148
  91. package/extensions/dm-subagents/src/runs/background/async-resume.js +378 -51
  92. package/extensions/dm-subagents/src/runs/background/async-retention.js +828 -0
  93. package/extensions/dm-subagents/src/runs/background/async-status-snapshot.js +31 -0
  94. package/extensions/dm-subagents/src/runs/background/async-status.js +259 -22
  95. package/extensions/dm-subagents/src/runs/background/auto-drain.js +46 -0
  96. package/extensions/dm-subagents/src/runs/background/chain-append.js +50 -15
  97. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +67 -12
  98. package/extensions/dm-subagents/src/runs/background/completion-batcher.js +5 -1
  99. package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +3 -11
  100. package/extensions/dm-subagents/src/runs/background/completion-replay.js +245 -0
  101. package/extensions/dm-subagents/src/runs/background/control-channel.js +423 -36
  102. package/extensions/dm-subagents/src/runs/background/fleet-view.js +132 -59
  103. package/extensions/dm-subagents/src/runs/background/index-segment.js +38 -0
  104. package/extensions/dm-subagents/src/runs/background/inspect-rpc.js +373 -0
  105. package/extensions/dm-subagents/src/runs/background/notify.js +357 -57
  106. package/extensions/dm-subagents/src/runs/background/owned-process-tree.js +86 -0
  107. package/extensions/dm-subagents/src/runs/background/process-terminal.js +269 -0
  108. package/extensions/dm-subagents/src/runs/background/result-delivery-ownership.js +34 -0
  109. package/extensions/dm-subagents/src/runs/background/result-files.js +469 -0
  110. package/extensions/dm-subagents/src/runs/background/result-watcher.js +540 -75
  111. package/extensions/dm-subagents/src/runs/background/resume-guidance.js +44 -0
  112. package/extensions/dm-subagents/src/runs/background/retained-children.js +119 -0
  113. package/extensions/dm-subagents/src/runs/background/run-id-query.js +5 -0
  114. package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +93 -9
  115. package/extensions/dm-subagents/src/runs/background/run-status.js +303 -32
  116. package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +784 -376
  117. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +75 -34
  118. package/extensions/dm-subagents/src/runs/background/steering.js +221 -0
  119. package/extensions/dm-subagents/src/runs/background/subagent-runner.js +3106 -844
  120. package/extensions/dm-subagents/src/runs/background/subagent-wait.js +529 -0
  121. package/extensions/dm-subagents/src/runs/background/terminal-run-index.js +106 -0
  122. package/extensions/dm-subagents/src/runs/background/top-level-async.js +1 -1
  123. package/extensions/dm-subagents/src/runs/background/wait-completions.js +155 -0
  124. package/extensions/dm-subagents/src/runs/background/wait-config.js +46 -0
  125. package/extensions/dm-subagents/src/runs/background/wait-subscriptions.js +278 -0
  126. package/extensions/dm-subagents/src/runs/background/wait-tool.js +47 -0
  127. package/extensions/dm-subagents/src/runs/foreground/async-dismiss-action.js +81 -0
  128. package/extensions/dm-subagents/src/runs/foreground/async-steering-action.js +245 -0
  129. package/extensions/dm-subagents/src/runs/foreground/async-stop-action.js +74 -0
  130. package/extensions/dm-subagents/src/runs/foreground/execution.js +1401 -342
  131. package/extensions/dm-subagents/src/runs/foreground/foreground-control.js +133 -0
  132. package/extensions/dm-subagents/src/runs/foreground/foreground-history.js +148 -0
  133. package/extensions/dm-subagents/src/runs/foreground/prompt-audit.js +139 -0
  134. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +4278 -1441
  135. package/extensions/dm-subagents/src/runs/foreground/workflow-detach-reconcile.js +278 -0
  136. package/extensions/dm-subagents/src/runs/foreground/workflow-foreground-steering.js +155 -0
  137. package/extensions/dm-subagents/src/runs/shared/abort-recovery.js +97 -0
  138. package/extensions/dm-subagents/src/runs/shared/acceptance.js +597 -148
  139. package/extensions/dm-subagents/src/runs/shared/agent-contract.js +35 -0
  140. package/extensions/dm-subagents/src/runs/shared/async-status-projection.js +472 -0
  141. package/extensions/dm-subagents/src/runs/shared/background-process-options.js +6 -0
  142. package/extensions/dm-subagents/src/runs/shared/capability-ceiling.js +175 -0
  143. package/extensions/dm-subagents/src/runs/shared/child-identity.js +32 -0
  144. package/extensions/dm-subagents/src/runs/shared/child-launch-plan.js +65 -0
  145. package/extensions/dm-subagents/src/runs/shared/child-protocol.js +447 -0
  146. package/extensions/dm-subagents/src/runs/shared/claude-code-adapter.js +120 -0
  147. package/extensions/dm-subagents/src/runs/shared/codex-exec-adapter.js +129 -0
  148. package/extensions/dm-subagents/src/runs/shared/completion-evidence.js +40 -0
  149. package/extensions/dm-subagents/src/runs/shared/completion-guard.js +140 -83
  150. package/extensions/dm-subagents/src/runs/shared/context-mode.js +38 -0
  151. package/extensions/dm-subagents/src/runs/shared/cursor-agent-adapter.js +101 -0
  152. package/extensions/dm-subagents/src/runs/shared/dm-args.js +445 -72
  153. package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +27 -16
  154. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +19 -6
  155. package/extensions/dm-subagents/src/runs/shared/extension-bindings.js +81 -0
  156. package/extensions/dm-subagents/src/runs/shared/external-cli-contract.js +134 -0
  157. package/extensions/dm-subagents/src/runs/shared/external-cli-preflight.js +98 -0
  158. package/extensions/dm-subagents/src/runs/shared/external-cli-runner.js +419 -0
  159. package/extensions/dm-subagents/src/runs/shared/external-job-bridge.js +404 -0
  160. package/extensions/dm-subagents/src/runs/shared/external-job-runner.js +334 -0
  161. package/extensions/dm-subagents/src/runs/shared/fast-mode-extension.js +8 -0
  162. package/extensions/dm-subagents/src/runs/shared/host-step-status.js +228 -0
  163. package/extensions/dm-subagents/src/runs/shared/lane-metadata.js +104 -0
  164. package/extensions/dm-subagents/src/runs/shared/launch-cwd.js +17 -0
  165. package/extensions/dm-subagents/src/runs/shared/llm-intent-arbiter.js +190 -0
  166. package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +48 -3
  167. package/extensions/dm-subagents/src/runs/shared/mcp-config-sources.js +387 -0
  168. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +212 -137
  169. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-grant.js +131 -0
  170. package/extensions/dm-subagents/src/runs/shared/model-exclusions.js +207 -0
  171. package/extensions/dm-subagents/src/runs/shared/model-fallback.js +225 -55
  172. package/extensions/dm-subagents/src/runs/shared/model-scope.js +85 -28
  173. package/extensions/dm-subagents/src/runs/shared/mutation-evidence.js +182 -0
  174. package/extensions/dm-subagents/src/runs/shared/nested-events.js +264 -102
  175. package/extensions/dm-subagents/src/runs/shared/nested-render.js +15 -5
  176. package/extensions/dm-subagents/src/runs/shared/orca-progress-tabs.js +505 -0
  177. package/extensions/dm-subagents/src/runs/shared/parallel-handoff.js +653 -0
  178. package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +29 -12
  179. package/extensions/dm-subagents/src/runs/shared/permissions.js +108 -0
  180. package/extensions/dm-subagents/src/runs/shared/process-signal.js +13 -0
  181. package/extensions/dm-subagents/src/runs/shared/run-fanout-budget.js +257 -0
  182. package/extensions/dm-subagents/src/runs/shared/run-history.js +133 -13
  183. package/extensions/dm-subagents/src/runs/shared/runtime-acknowledged-extensions.js +62 -0
  184. package/extensions/dm-subagents/src/runs/shared/session-lease.js +225 -0
  185. package/extensions/dm-subagents/src/runs/shared/single-output.js +129 -27
  186. package/extensions/dm-subagents/src/runs/shared/spawn-budget.js +95 -0
  187. package/extensions/dm-subagents/src/runs/shared/structured-output.js +129 -10
  188. package/extensions/dm-subagents/src/runs/shared/subagent-control.js +66 -11
  189. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +548 -70
  190. package/extensions/dm-subagents/src/runs/shared/subagent-startup-retry.js +50 -0
  191. package/extensions/dm-subagents/src/runs/shared/task-intent.js +130 -0
  192. package/extensions/dm-subagents/src/runs/shared/tool-availability.js +59 -0
  193. package/extensions/dm-subagents/src/runs/shared/tool-budget.js +7 -5
  194. package/extensions/dm-subagents/src/runs/shared/tool-timeout.js +64 -0
  195. package/extensions/dm-subagents/src/runs/shared/usage-budget.js +74 -0
  196. package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +22 -0
  197. package/extensions/dm-subagents/src/runs/shared/worktree-cleanup-plan.js +721 -0
  198. package/extensions/dm-subagents/src/runs/shared/worktree.js +168 -19
  199. package/extensions/dm-subagents/src/shared/accessible-dir.js +35 -0
  200. package/extensions/dm-subagents/src/shared/agent-stream-options.js +3 -0
  201. package/extensions/dm-subagents/src/shared/artifacts.js +170 -11
  202. package/extensions/dm-subagents/src/shared/atomic-json.js +36 -38
  203. package/extensions/dm-subagents/src/shared/capacity-resilient-json.js +77 -0
  204. package/extensions/dm-subagents/src/shared/child-session-name.js +15 -0
  205. package/extensions/dm-subagents/src/shared/child-transcript.js +57 -2
  206. package/extensions/dm-subagents/src/shared/completion-owner.js +7 -0
  207. package/extensions/dm-subagents/src/shared/display-text.js +142 -0
  208. package/extensions/dm-subagents/src/shared/extension-context.js +17 -0
  209. package/extensions/dm-subagents/src/shared/file-coalescer.js +9 -0
  210. package/extensions/dm-subagents/src/shared/file-system-retry.js +56 -0
  211. package/extensions/dm-subagents/src/shared/fork-context.js +96 -33
  212. package/extensions/dm-subagents/src/shared/formatters.js +21 -7
  213. package/extensions/dm-subagents/src/shared/launch-contract.js +94 -0
  214. package/extensions/dm-subagents/src/shared/model-info.js +10 -5
  215. package/extensions/dm-subagents/src/shared/node-executable.js +19 -0
  216. package/extensions/dm-subagents/src/shared/prompt-resources.js +10 -0
  217. package/extensions/dm-subagents/src/shared/pruned-fork.js +427 -0
  218. package/extensions/dm-subagents/src/shared/session-file-trust.js +19 -0
  219. package/extensions/dm-subagents/src/shared/session-tokens.js +14 -3
  220. package/extensions/dm-subagents/src/shared/settings.js +39 -47
  221. package/extensions/dm-subagents/src/shared/shortcuts.js +16 -0
  222. package/extensions/dm-subagents/src/shared/status-format.js +9 -2
  223. package/extensions/dm-subagents/src/shared/thinking-ceiling.js +41 -0
  224. package/extensions/dm-subagents/src/shared/types.js +47 -7
  225. package/extensions/dm-subagents/src/shared/utf8.js +12 -0
  226. package/extensions/dm-subagents/src/shared/utils.js +151 -131
  227. package/extensions/dm-subagents/src/shared/watch-strategy.js +3 -0
  228. package/extensions/dm-subagents/src/shared/workflow-child-permit.js +84 -0
  229. package/extensions/dm-subagents/src/slash/delegation-adapters.js +274 -0
  230. package/extensions/dm-subagents/src/slash/delegation-json.js +113 -0
  231. package/extensions/dm-subagents/src/slash/delegation-request.js +152 -0
  232. package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +294 -243
  233. package/extensions/dm-subagents/src/slash/prompt-workflows.js +35 -73
  234. package/extensions/dm-subagents/src/slash/selector.js +101 -0
  235. package/extensions/dm-subagents/src/slash/slash-bridge.js +17 -1
  236. package/extensions/dm-subagents/src/slash/slash-commands.js +722 -732
  237. package/extensions/dm-subagents/src/slash/slash-live-state.js +37 -19
  238. package/extensions/dm-subagents/src/slash/subagents-admin.js +410 -0
  239. package/extensions/dm-subagents/src/tui/fleet-status.js +824 -0
  240. package/extensions/dm-subagents/src/tui/fleet-transcript.js +479 -0
  241. package/extensions/dm-subagents/src/tui/fleet.js +1326 -0
  242. package/extensions/dm-subagents/src/tui/render-helpers.js +22 -0
  243. package/extensions/dm-subagents/src/tui/render.js +1511 -261
  244. package/extensions/dm-subagents/src/watchdog/change-signature.js +220 -0
  245. package/extensions/dm-subagents/src/watchdog/child-status.js +151 -0
  246. package/extensions/dm-subagents/src/watchdog/emission-guard.js +90 -0
  247. package/extensions/dm-subagents/src/watchdog/lsp-diagnostics.js +484 -0
  248. package/extensions/dm-subagents/src/watchdog/model-selection.js +154 -0
  249. package/extensions/dm-subagents/src/watchdog/permission-arbiter.js +138 -0
  250. package/extensions/dm-subagents/src/watchdog/register-child.js +112 -0
  251. package/extensions/dm-subagents/src/watchdog/register-main.js +419 -0
  252. package/extensions/dm-subagents/src/watchdog/render.js +54 -0
  253. package/extensions/dm-subagents/src/watchdog/review.js +251 -0
  254. package/extensions/dm-subagents/src/watchdog/runtime.js +803 -0
  255. package/extensions/dm-subagents/src/watchdog/scope.js +56 -0
  256. package/extensions/dm-subagents/src/watchdog/settings.js +515 -0
  257. package/extensions/dm-subagents/src/watchdog/tool-actions.js +151 -0
  258. package/extensions/dm-subagents/src/watchdog/turn-delta.js +169 -0
  259. package/extensions/dm-subagents/src/watchdog/types.js +29 -0
  260. package/extensions/dm-subagents/src/watchdog/warning-format.js +58 -0
  261. package/extensions/dm-subagents/src/workflows/chat-progress.js +116 -0
  262. package/extensions/dm-subagents/src/workflows/host-command.js +227 -0
  263. package/extensions/dm-subagents/src/workflows/scripted-workflow.js +2011 -0
  264. package/extensions/dm-subagents/src/workflows/workflow-child-summary.js +116 -0
  265. package/extensions/dm-subagents/src/workflows/workflow-preflight.js +243 -0
  266. package/extensions/dm-subagents/src/workflows/workflow-receipt.js +387 -0
  267. package/extensions/dm-subagents/src/workflows/workflow-settlement.js +189 -0
  268. package/package.json +4 -3
  269. package/extensions/dm-fff/package.json +0 -21
  270. package/extensions/dm-fff/src/index.js +0 -691
  271. package/extensions/dm-fff/src/query.js +0 -60
  272. package/extensions/dm-subagents/agents/context-builder.md +0 -46
  273. package/extensions/dm-subagents/agents/planner.md +0 -55
  274. package/extensions/dm-subagents/prompts/parallel-context-build.md +0 -55
  275. package/extensions/dm-subagents/prompts/parallel-handoff-plan.md +0 -61
  276. package/extensions/dm-subagents/src/runs/background/wait.js +0 -206
  277. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +0 -1013
  278. package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +0 -981
  279. package/extensions/dm-subagents/src/runs/shared/turn-budget.js +0 -50
@@ -1,7 +1,12 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { existsSync, unlinkSync } from "node:fs";
3
+ import * as path from "node:path";
4
+ import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext } from "../../agents/agents.js";
5
+ import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.js";
6
+ import { alignForkedSessionCwd } from "../../shared/fork-context.js";
3
7
  import {
4
8
  ensureArtifactsDir,
9
+ formatOutputArtifactContent,
5
10
  getArtifactPaths,
6
11
  writeArtifact,
7
12
  writeMetadata
@@ -19,29 +24,61 @@ import {
19
24
  buildControlEvent,
20
25
  claimControlNotification,
21
26
  deriveActivityState,
27
+ shouldEmitOpenToolAttention,
22
28
  shouldNotifyControlEvent
23
29
  } from "../shared/subagent-control.js";
24
30
  import {
25
31
  getFinalOutput,
26
32
  findLatestSessionFile,
27
33
  detectSubagentError,
34
+ hasEmptyTerminalAssistantResponse,
35
+ formatEmptyTerminalAssistantResponseError,
28
36
  extractToolArgsPreview,
29
- extractTextFromContent
37
+ extractTextFromContent,
38
+ boundStreamedRecentTools,
39
+ boundStreamedRecentOutput,
40
+ boundStreamedToolCalls
30
41
  } from "../../shared/utils.js";
31
42
  import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.js";
32
43
  import { buildAgentMemoryInjection } from "../../agents/agent-memory.js";
33
- import { evaluateCompletionMutationGuard } from "../shared/completion-guard.js";
44
+ import { effectiveToolTimeoutMs, formatToolTimeoutMessage, resolveToolTimeoutMs, toolTimeoutCallKey, toolTimeoutFromEnv } from "../shared/tool-timeout.js";
45
+ import { evaluateCompletionMutationGuard, expectsImplementationMutation, hasMutationToolCapability, validateImplementationToolContract } from "../shared/completion-guard.js";
46
+ import { planCompletionEvidence } from "../shared/completion-evidence.js";
47
+ import { planAbortRecovery } from "../shared/abort-recovery.js";
48
+ import { arbitrateCompletionGuardRescue } from "../shared/llm-intent-arbiter.js";
34
49
  import { getPiSpawnCommand } from "../shared/dm-spawn.js";
50
+ import { preflightLaunchCwd } from "../shared/launch-cwd.js";
35
51
  import { createJsonlWriter } from "../../shared/jsonl-writer.js";
52
+ import { createOrcaProgressTab } from "../shared/orca-progress-tabs.js";
36
53
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.js";
37
- import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "../shared/dm-args.js";
38
- import { readStructuredOutput } from "../shared/structured-output.js";
39
- import { captureSingleOutputSnapshot, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode } from "../shared/single-output.js";
54
+ import { resolvePermissionRules } from "../shared/permissions.js";
55
+ import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, deriveForkPromptCacheKey, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/dm-args.js";
56
+ import { deriveChildSessionName } from "../../shared/child-session-name.js";
57
+ import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.js";
58
+ import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.js";
59
+ import { resolveEffectiveThinking } from "../../shared/model-info.js";
60
+ import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV } from "../../shared/thinking-ceiling.js";
61
+ import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.js";
62
+ import { formatMidToolExitError, formatProcessSignalError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.js";
63
+ import { readChildToolDiagnosticError } from "../shared/tool-availability.js";
64
+ import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.js";
65
+ import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, hasSingleOutputChangedSinceSnapshot, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode } from "../shared/single-output.js";
40
66
  import {
41
67
  buildModelCandidates,
68
+ formatSubagentModelVerificationError,
42
69
  formatModelAttemptNote,
43
- isRetryableModelFailure
70
+ isContextOverflow,
71
+ isRetryableModelFailureAttempt,
72
+ recordRetryableModelFailure
44
73
  } from "../shared/model-fallback.js";
74
+ import {
75
+ SUBAGENT_STARTUP_RETRY_DELAYS_MS,
76
+ formatSubagentExtensionConflictError,
77
+ formatSubagentStartupRetryExhaustedError,
78
+ formatSubagentStartupRetryNote,
79
+ isRetryableSubagentStartupFailure,
80
+ waitForSubagentStartupRetry
81
+ } from "../shared/subagent-startup-retry.js";
45
82
  import {
46
83
  createMutatingFailureState,
47
84
  didMutatingToolFail,
@@ -53,14 +90,37 @@ import {
53
90
  shouldEscalateMutatingFailures,
54
91
  summarizeRecentMutatingFailures
55
92
  } from "../shared/long-running-guard.js";
56
- import { acceptanceFailureMessage, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.js";
57
- import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, shouldAbortForTurnBudget, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.js";
93
+ import { acceptanceFailureMessage, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport, validateAcceptanceInput } from "../shared/acceptance.js";
94
+ import { PROMPT_REDACTED } from "../../shared/utils.js";
95
+ import { attachContractProjections, isAgentContractV1 } from "../shared/agent-contract.js";
58
96
  import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.js";
97
+ import { resolveWatchdogConfig } from "../../watchdog/settings.js";
98
+ import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.js";
99
+ import { consumeWorkflowChildPermit } from "../../shared/workflow-child-permit.js";
100
+ import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle } from "../shared/child-protocol.js";
101
+ import {
102
+ acceptChildWatchdogEvent,
103
+ childWatchdogIsActive,
104
+ isChildWatchdogStatusEvent,
105
+ resolveChildWatchdogConfig
106
+ } from "../../watchdog/child-status.js";
59
107
  const artifactOutputByResult = new WeakMap;
60
108
  const acceptanceOutputByResult = new WeakMap;
61
109
  function emptyUsage() {
62
110
  return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
63
111
  }
112
+ function withRunContext(result, context) {
113
+ if (!context)
114
+ return result;
115
+ result.context = context;
116
+ return result;
117
+ }
118
+ function redactResultPrompt(result) {
119
+ result.task = PROMPT_REDACTED;
120
+ if (result.progress)
121
+ result.progress.task = PROMPT_REDACTED;
122
+ return result;
123
+ }
64
124
  function sumUsage(target, source) {
65
125
  target.input += source.input;
66
126
  target.output += source.output;
@@ -69,6 +129,40 @@ function sumUsage(target, source) {
69
129
  target.cost += source.cost;
70
130
  target.turns += source.turns;
71
131
  }
132
+ function persistSingleResultMetadata(input) {
133
+ if (!input.enabled || !input.metadataPath)
134
+ return;
135
+ const target = input.result;
136
+ writeMetadata(input.metadataPath, {
137
+ runId: input.runId,
138
+ agent: input.agent,
139
+ task: PROMPT_REDACTED,
140
+ exitCode: target.exitCode,
141
+ processSignal: target.processSignal,
142
+ usage: target.usage,
143
+ model: target.model,
144
+ attemptedModels: target.attemptedModels,
145
+ modelAttempts: target.modelAttempts,
146
+ durationMs: target.progressSummary?.durationMs,
147
+ toolCount: target.progressSummary?.toolCount,
148
+ error: target.error,
149
+ agentContract: target.agentContract,
150
+ launchContractDigest: target.launchContractDigest,
151
+ launchResolvedExtensions: target.launchResolvedExtensions,
152
+ runtimeAcknowledgedExtensions: target.runtimeAcknowledgedExtensions,
153
+ execution: target.execution,
154
+ acceptance: target.acceptance,
155
+ capabilityCeiling: target.capabilityCeiling,
156
+ capabilityAudit: target.capabilityAudit,
157
+ review: target.review,
158
+ effects: target.effects,
159
+ transcriptPath: target.transcriptPath,
160
+ transcriptError: target.transcriptError,
161
+ skills: target.skills,
162
+ skillsWarning: target.skillsWarning,
163
+ timestamp: Date.now()
164
+ });
165
+ }
72
166
  function formatTimeoutMessage(timeoutMs) {
73
167
  return `Subagent timed out after ${timeoutMs}ms.`;
74
168
  }
@@ -82,17 +176,32 @@ function resolveAttemptTimeout(options) {
82
176
  message: formatTimeoutMessage(options.timeoutMs)
83
177
  };
84
178
  }
85
- function buildSkippedAcceptanceLedger(acceptance, input) {
179
+ function buildPendingAcceptanceLedger(acceptance) {
86
180
  return {
87
- status: acceptance.level === "none" ? "not-required" : "rejected",
181
+ status: "pending",
182
+ evidenceStatus: "pending",
88
183
  explicit: acceptance.explicit,
89
184
  effectiveAcceptance: acceptance,
90
185
  inferredReason: acceptance.inferredReason,
91
186
  criteria: acceptance.criteria,
92
- runtimeChecks: acceptance.level === "none" ? [] : [{ id: input.id, status: "failed", message: input.message }],
187
+ runtimeChecks: [],
93
188
  verifyRuns: []
94
189
  };
95
190
  }
191
+ function buildInterruptedAcceptanceLedger(acceptance) {
192
+ const pending = buildPendingAcceptanceLedger(acceptance);
193
+ if (acceptance.level === "none") {
194
+ pending.status = "not-required";
195
+ pending.evidenceStatus = "not-required";
196
+ } else {
197
+ pending.runtimeChecks = [{
198
+ id: "interrupted",
199
+ status: "not-applicable",
200
+ message: "Acceptance was not evaluated because the subagent was interrupted."
201
+ }];
202
+ }
203
+ return pending;
204
+ }
96
205
  function appendRecentOutput(progress, lines) {
97
206
  if (lines.length === 0)
98
207
  return;
@@ -115,14 +224,16 @@ function stripAcceptanceReportsFromMessages(messages) {
115
224
  function snapshotProgress(progress) {
116
225
  return {
117
226
  ...progress,
227
+ task: PROMPT_REDACTED,
118
228
  skills: progress.skills ? [...progress.skills] : undefined,
119
- recentTools: progress.recentTools.map((tool) => ({ ...tool })),
120
- recentOutput: [...progress.recentOutput]
229
+ recentTools: boundStreamedRecentTools(progress.recentTools),
230
+ recentOutput: boundStreamedRecentOutput(progress.recentOutput)
121
231
  };
122
232
  }
123
233
  function snapshotResult(result, progress) {
124
234
  return {
125
235
  ...result,
236
+ task: PROMPT_REDACTED,
126
237
  messages: result.outputMode === "file-only" && result.savedOutputPath ? undefined : result.messages ? [...result.messages] : undefined,
127
238
  usage: { ...result.usage },
128
239
  skills: result.skills ? [...result.skills] : undefined,
@@ -139,12 +250,33 @@ function snapshotResult(result, progress) {
139
250
  outputReference: result.outputReference ? { ...result.outputReference } : undefined
140
251
  };
141
252
  }
253
+ function snapshotStreamResult(result, progress) {
254
+ const snapshot = snapshotResult(result, progress);
255
+ snapshot.messages = undefined;
256
+ snapshot.toolCalls = boundStreamedToolCalls(result);
257
+ return snapshot;
258
+ }
259
+ const AFTER_COMPACTION_SETTLEMENT = Symbol("afterCompactionSettlement");
142
260
  async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared) {
143
261
  const effectiveThinking = options.thinkingOverride ?? agent.thinking;
144
262
  const modelArg = applyThinkingSuffix(model, effectiveThinking, options.thinkingOverride !== undefined);
145
- const { args, env: sharedEnv, tempDir } = buildPiArgs({
263
+ assertThinkingWithinCeiling({ model: modelArg, configThinking: effectiveThinking, ceiling: options.thinkingCeiling, agent: agent.name, runId: options.runId });
264
+ const expectedModelForVerification = shared.verifyModel ? modelArg : undefined;
265
+ const resolvedThinking = resolveEffectiveThinking(modelArg, effectiveThinking);
266
+ const childSessionName = deriveChildSessionName({ agent: agent.name, task: shared.originalTask ?? task });
267
+ const watchdogConfig = resolveWatchdogConfig(options.cwd ?? runtimeCwd);
268
+ const childWatchdog = watchdogConfig.ok ? resolveChildWatchdogConfig({
269
+ config: watchdogConfig.config,
270
+ agent: agent.name,
271
+ runId: options.runId,
272
+ childIndex: options.index ?? 0
273
+ }) : undefined;
274
+ const permissionRules = resolvePermissionRules(options.permissions, agent.permissions);
275
+ const permissionAuditPath = permissionRules && options.artifactsDir ? path.join(options.artifactsDir, "permission-audit", `${options.runId}-${options.index ?? 0}.jsonl`) : undefined;
276
+ const { args, env: sharedEnv, tempDir, toolDiagnosticPath, runtimeAcknowledgedExtensionsPath, capabilityAudit, warnings } = buildPiArgs({
146
277
  baseArgs: ["--mode", "json", "-p"],
147
278
  task,
279
+ taskDelivery: shared.taskDelivery,
148
280
  sessionEnabled: shared.sessionEnabled,
149
281
  sessionDir: options.sessionDir,
150
282
  sessionFile: options.sessionFile,
@@ -152,16 +284,19 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
152
284
  thinking: effectiveThinking,
153
285
  systemPromptMode: agent.systemPromptMode,
154
286
  inheritProjectContext: agent.inheritProjectContext,
287
+ inheritGlobalContext: agent.inheritGlobalContext,
155
288
  inheritSkills: agent.inheritSkills,
156
289
  requireReadTool: Boolean(shared.resolvedSkillNames?.length),
157
290
  tools: agent.tools,
291
+ allowNestedSubagents: agent.allowNestedSubagents,
158
292
  extensions: agent.extensions,
159
293
  subagentOnlyExtensions: agent.subagentOnlyExtensions,
160
- systemPrompt: appendTurnBudgetSystemPrompt(shared.systemPrompt, options.turnBudget),
294
+ systemPrompt: shared.systemPrompt,
161
295
  mcpDirectTools: agent.mcpDirectTools,
162
296
  cwd: options.cwd ?? runtimeCwd,
163
297
  promptFileStem: agent.name,
164
298
  intercomSessionName: options.intercomSessionName,
299
+ sessionName: childSessionName,
165
300
  orchestratorIntercomTarget: options.orchestratorIntercomTarget,
166
301
  runId: options.runId,
167
302
  childAgentName: agent.name,
@@ -170,24 +305,127 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
170
305
  parentControlInbox: options.nestedRoute?.controlInbox,
171
306
  parentRootRunId: options.nestedRoute?.rootRunId,
172
307
  parentCapabilityToken: options.nestedRoute?.capabilityToken,
308
+ runFanoutBudget: options.runFanoutBudget,
173
309
  parentSessionId: options.parentSessionId,
310
+ forkCacheKey: options.context === "fork" ? deriveForkPromptCacheKey(options.parentSessionId) : undefined,
311
+ steerInboxDir: options.steerInboxDir,
312
+ steerCapabilityPath: options.steerCapabilityPath,
313
+ steerAckDir: options.steerAckDir,
174
314
  structuredOutput: options.structuredOutput,
175
- toolBudget: options.toolBudget
315
+ fast: options.fast ?? agent.fast,
316
+ modelCandidates: shared.modelCandidates,
317
+ toolBudget: options.toolBudget,
318
+ allowZeroToolBudget: options.allowZeroToolBudget,
319
+ permissionRules,
320
+ permissionAuditPath,
321
+ childWatchdog,
322
+ waitToolEnabled: options.waitToolEnabled,
323
+ waitToolDefaultTimeoutMs: options.waitToolDefaultTimeoutMs,
324
+ capabilityCeiling: options.capabilityCeiling,
325
+ thinkingCeiling: options.thinkingCeiling,
326
+ extensionBindings: options.extensionBindings,
327
+ runtimeSnapshotHost: options.runtimeSnapshotHost
328
+ });
329
+ if (!shared.launchWarnings.emitted && warnings.length > 0) {
330
+ for (const warning of warnings)
331
+ console.warn(`[dm-subagents] ${warning}`);
332
+ shared.launchWarnings.emitted = true;
333
+ }
334
+ const effectiveSystemPrompt = shared.systemPrompt;
335
+ const toolPlan = resolvePiLaunchToolPlan({
336
+ tools: agent.tools,
337
+ allowNestedSubagents: agent.allowNestedSubagents,
338
+ extensions: agent.extensions,
339
+ subagentOnlyExtensions: agent.subagentOnlyExtensions,
340
+ mcpDirectTools: agent.mcpDirectTools,
341
+ cwd: options.cwd ?? runtimeCwd,
342
+ requireReadTool: Boolean(shared.resolvedSkillNames?.length),
343
+ structuredOutput: Boolean(options.structuredOutput),
344
+ fast: options.fast ?? agent.fast,
345
+ model: modelArg,
346
+ modelCandidates: shared.modelCandidates,
347
+ capabilityCeiling: options.capabilityCeiling,
348
+ inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
349
+ agentName: agent.name,
350
+ permissionRules,
351
+ runtimeSnapshotHost: options.runtimeSnapshotHost
176
352
  });
177
- const result = {
353
+ const contractTools = toolPlan.explicitToolAllowlist ? toolPlan.effectiveToolAllowlist : undefined;
354
+ const contractError = validateImplementationToolContract({
178
355
  agent: agent.name,
179
356
  task: shared.originalTask ?? task,
357
+ tools: contractTools,
358
+ mcpDirectTools: toolPlan.effectiveMcpTools,
359
+ configuredExtensions: toolPlan.configuredExtensions,
360
+ requestedTools: toolPlan.requestedBuiltinTools,
361
+ acceptanceRole: agent.acceptanceRole,
362
+ completionGuard: agent.completionGuard
363
+ });
364
+ if (contractError) {
365
+ cleanupTempDir(tempDir);
366
+ return {
367
+ index: options.index ?? 0,
368
+ agent: agent.name,
369
+ task,
370
+ ...childSessionName ? { sessionName: childSessionName } : {},
371
+ messages: [],
372
+ finalOutput: "",
373
+ exitCode: 1,
374
+ error: contractError,
375
+ usage: emptyUsage(),
376
+ model: modelArg,
377
+ modelAttempts: [],
378
+ attemptedModels: [],
379
+ progressSummary: { status: "failed", toolCount: 0, tokens: 0, durationMs: 0 },
380
+ ...toolPlan.capabilityCeiling ? { capabilityCeiling: toolPlan.capabilityCeiling } : {},
381
+ ...toolPlan.capabilityAudit ? { capabilityAudit: toolPlan.capabilityAudit } : {}
382
+ };
383
+ }
384
+ const launchResolvedExtensions = projectLaunchResolvedChildExtensions(toolPlan);
385
+ const launchContractDigest = launchBindingDigest({
386
+ definitionDigest: agentDefinitionDigest(agent),
387
+ task: shared.originalTask ?? task,
388
+ ...modelArg ? { model: modelArg } : {},
389
+ modelCandidates: shared.modelCandidates,
390
+ ...(options.fast ?? agent.fast) !== undefined ? { fast: options.fast ?? agent.fast } : {},
391
+ ...resolvedThinking ? { thinking: resolvedThinking } : {},
392
+ ...options.thinkingCeiling ? { thinkingCeiling: options.thinkingCeiling } : {},
393
+ systemPrompt: effectiveSystemPrompt,
394
+ systemPromptMode: agent.systemPromptMode,
395
+ inheritProjectContext: agent.inheritProjectContext,
396
+ inheritGlobalContext: agent.inheritGlobalContext,
397
+ inheritSkills: agent.inheritSkills,
398
+ skills: shared.resolvedSkillNames ?? [],
399
+ tools: toolPlan.effectiveToolAllowlist,
400
+ extensions: toolPlan.extensionArgs,
401
+ mcpDirectTools: toolPlan.effectiveMcpTools,
402
+ ...options.outputPath ? { outputPath: options.outputPath } : {},
403
+ outputMode: options.outputMode ?? "inline",
404
+ ...options.structuredOutput ? { structuredOutputSchema: options.structuredOutput.schema } : {},
405
+ ...options.extensionBindings ? { extensionBindings: options.extensionBindings } : {}
406
+ });
407
+ const result = withRunContext({
408
+ index: options.index ?? 0,
409
+ agent: agent.name,
410
+ task: shared.originalTask ?? task,
411
+ ...childSessionName ? { sessionName: childSessionName } : {},
412
+ ...options.agentContract ? { agentContract: options.agentContract } : {},
413
+ launchContractDigest,
414
+ launchResolvedExtensions,
180
415
  exitCode: 0,
416
+ outputState: "absent",
181
417
  messages: [],
182
418
  usage: emptyUsage(),
183
419
  model: modelArg,
420
+ ...resolvedThinking ? { thinking: resolvedThinking } : {},
184
421
  artifactPaths: shared.artifactPaths,
185
422
  transcriptPath: shared.transcriptWriter ? shared.artifactPaths?.transcriptPath : undefined,
186
423
  skills: shared.resolvedSkillNames,
187
424
  skillsWarning: shared.skillsWarning,
188
- ...options.turnBudget ? { turnBudget: initialTurnBudgetState(options.turnBudget) } : {},
189
- ...options.toolBudget ? { toolBudget: initialToolBudgetState(options.toolBudget) } : {}
190
- };
425
+ ...options.toolBudget ? { toolBudget: initialToolBudgetState(options.toolBudget) } : {},
426
+ ...options.capabilityCeiling ? { capabilityCeiling: options.capabilityCeiling } : {},
427
+ ...capabilityAudit ? { capabilityAudit } : {}
428
+ }, options.context);
191
429
  const startTime = Date.now();
192
430
  if (options.structuredOutput) {
193
431
  try {
@@ -212,6 +450,7 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
212
450
  const progress = {
213
451
  index: options.index ?? 0,
214
452
  agent: agent.name,
453
+ ...childSessionName ? { sessionName: childSessionName } : {},
215
454
  status: "running",
216
455
  task,
217
456
  skills: shared.resolvedSkillNames,
@@ -219,12 +458,17 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
219
458
  recentOutput: [...shared.attemptNotes],
220
459
  toolCount: 0,
221
460
  tokens: 0,
461
+ ...modelArg ? { model: modelArg } : {},
462
+ ...resolvedThinking ? { thinking: resolvedThinking } : {},
463
+ inputTokens: 0,
464
+ outputTokens: 0,
222
465
  durationMs: 0,
223
466
  lastActivityAt: startTime
224
467
  };
225
468
  result.progress = progress;
226
469
  const attemptTimeout = resolveAttemptTimeout(options);
227
470
  if (attemptTimeout?.remainingMs === 0) {
471
+ cleanupTempDir(tempDir);
228
472
  result.exitCode = 1;
229
473
  result.timedOut = true;
230
474
  result.error = attemptTimeout.message;
@@ -239,9 +483,34 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
239
483
  return result;
240
484
  }
241
485
  const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };
486
+ const mutationSnapshot = snapshotTrackedMutations(options.cwd ?? runtimeCwd);
242
487
  let observedMutationAttempt = false;
488
+ let structuredOutputToolInvoked = false;
489
+ let structuredOutputMessageStartIndex;
490
+ let toolAvailabilityError;
491
+ let abortedBySignal = options.signal?.aborted === true;
492
+ const spawnSpec = getPiSpawnCommand(args);
493
+ if (options.workflowChildPermitLaunch) {
494
+ const permitError = consumeWorkflowChildPermit(options.workflowChildPermitLaunch.permit, {
495
+ workflowRunId: options.workflowChildPermitLaunch.workflowRunId,
496
+ childKey: options.workflowChildPermitLaunch.childKey,
497
+ agent: agent.name,
498
+ launchContractDigest,
499
+ context: options.context ?? "fresh",
500
+ runner: "dm"
501
+ });
502
+ if (permitError) {
503
+ cleanupTempDir(tempDir);
504
+ result.exitCode = 1;
505
+ result.error = permitError;
506
+ result.finalOutput = permitError;
507
+ progress.status = "failed";
508
+ progress.error = permitError;
509
+ return result;
510
+ }
511
+ }
512
+ let afterCompactionSettlement = false;
243
513
  const exitCode = await new Promise((resolve) => {
244
- const spawnSpec = getPiSpawnCommand(args);
245
514
  const proc = spawn(spawnSpec.command, spawnSpec.args, {
246
515
  cwd: options.cwd ?? runtimeCwd,
247
516
  env: spawnEnv,
@@ -249,9 +518,8 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
249
518
  windowsHide: true
250
519
  });
251
520
  const jsonlWriter = createJsonlWriter(shared.jsonlPath, proc.stdout);
252
- let buf = "";
253
521
  let processClosed = false;
254
- let settled = false;
522
+ let lifecycleFinished = false;
255
523
  let detached = false;
256
524
  let intercomStarted = false;
257
525
  let assistantError;
@@ -261,19 +529,7 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
261
529
  let timeoutTimer;
262
530
  let timeoutTerminationTimer;
263
531
  let timeoutHardKillTimer;
264
- let turnBudgetSoftReached = false;
265
- let turnBudgetTerminationTimer;
266
- let turnBudgetHardKillTimer;
267
- const clearTurnBudgetTimers = () => {
268
- if (turnBudgetTerminationTimer) {
269
- clearTimeout(turnBudgetTerminationTimer);
270
- turnBudgetTerminationTimer = undefined;
271
- }
272
- if (turnBudgetHardKillTimer) {
273
- clearTimeout(turnBudgetHardKillTimer);
274
- turnBudgetHardKillTimer = undefined;
275
- }
276
- };
532
+ let protocolHardKillTimer;
277
533
  const clearTimeoutTimers = () => {
278
534
  if (timeoutTimer) {
279
535
  clearTimeout(timeoutTimer);
@@ -288,27 +544,59 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
288
544
  timeoutHardKillTimer = undefined;
289
545
  }
290
546
  };
291
- const detachForIntercom = () => {
292
- detached = true;
293
- processClosed = true;
294
- result.detached = true;
295
- result.detachedReason = "intercom coordination";
296
- progress.status = "detached";
297
- progress.durationMs = Date.now() - startTime;
298
- result.progressSummary = {
299
- toolCount: progress.toolCount,
300
- tokens: progress.tokens,
301
- durationMs: progress.durationMs
547
+ const detachForeground = (reason) => {
548
+ if (detached || processClosed || lifecycleFinished || options.signal?.aborted)
549
+ return false;
550
+ const receiptProgress = snapshotProgress(progress);
551
+ receiptProgress.status = "detached";
552
+ receiptProgress.durationMs = Date.now() - startTime;
553
+ const receipt = snapshotResult(result, receiptProgress);
554
+ receipt.exitCode = -2;
555
+ receipt.detached = true;
556
+ receipt.detachedReason = reason;
557
+ receipt.finalOutput = reason === "intercom coordination" ? "Detached for intercom coordination before task completion." : reason === "user request" ? "Detached at user request before task completion." : `Detached for ${reason} before task completion.`;
558
+ receipt.outputMode = options.outputMode ?? "inline";
559
+ if (options.outputPath) {
560
+ receipt.outputSaveError = reason === "user request" ? "Output file was not finalized because the subagent detached at user request." : `Output file was not finalized because the subagent detached for ${reason}.`;
561
+ }
562
+ receipt.progressSummary = {
563
+ toolCount: receiptProgress.toolCount,
564
+ tokens: receiptProgress.tokens,
565
+ durationMs: receiptProgress.durationMs
302
566
  };
303
- finish(-2);
567
+ let accepted = false;
568
+ try {
569
+ accepted = options.onDetachReceipt?.(receipt) === true;
570
+ } catch {
571
+ return false;
572
+ }
573
+ if (!accepted)
574
+ return false;
575
+ detached = true;
576
+ return true;
304
577
  };
305
578
  const FINAL_STOP_GRACE_MS = 1000;
306
579
  const HARD_KILL_MS = 3000;
307
580
  let childExited = false;
308
581
  let forcedTerminationSignal = false;
309
582
  let cleanTerminalAssistantStopReceived = false;
583
+ let agentSettledReceived = false;
584
+ let compactionStartedReceived = false;
310
585
  let finalDrainTimer;
311
586
  let finalHardKillTimer;
587
+ let watchdogTailTimer;
588
+ let childWatchdogState;
589
+ const updateChildWatchdogState = (snapshot) => {
590
+ childWatchdogState = snapshot;
591
+ result.watchdog = snapshot;
592
+ progress.watchdog = snapshot;
593
+ };
594
+ const clearWatchdogTailTimer = () => {
595
+ if (watchdogTailTimer) {
596
+ clearTimeout(watchdogTailTimer);
597
+ watchdogTailTimer = undefined;
598
+ }
599
+ };
312
600
  const clearFinalDrainTimers = () => {
313
601
  if (finalDrainTimer) {
314
602
  clearTimeout(finalDrainTimer);
@@ -320,20 +608,24 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
320
608
  }
321
609
  };
322
610
  const startFinalDrain = () => {
323
- if (childExited || finalDrainTimer || settled || processClosed || detached)
611
+ if (childWatchdogIsActive(childWatchdogState)) {
612
+ armWatchdogTail();
613
+ return;
614
+ }
615
+ if (childExited || finalDrainTimer || lifecycleFinished || processClosed)
324
616
  return;
325
617
  finalDrainTimer = setTimeout(() => {
326
- if (settled || processClosed || detached)
618
+ if (lifecycleFinished || processClosed)
327
619
  return;
328
620
  const termSent = trySignalChild(proc, "SIGTERM");
329
621
  if (!termSent)
330
622
  return;
331
623
  forcedTerminationSignal = true;
332
- if (!cleanTerminalAssistantStopReceived && !assistantError) {
333
- result.error = result.error ?? `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its final message. Forcing termination.`;
624
+ if (!cleanTerminalAssistantStopReceived && !agentSettledReceived && !assistantError) {
625
+ result.error = result.error ?? `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its terminal event. Forcing termination.`;
334
626
  }
335
627
  finalHardKillTimer = setTimeout(() => {
336
- if (settled || processClosed || detached)
628
+ if (lifecycleFinished || processClosed)
337
629
  return;
338
630
  forcedTerminationSignal = trySignalChild(proc, "SIGKILL") || forcedTerminationSignal;
339
631
  }, HARD_KILL_MS);
@@ -341,25 +633,69 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
341
633
  }, FINAL_STOP_GRACE_MS);
342
634
  finalDrainTimer.unref?.();
343
635
  };
636
+ function armWatchdogTail() {
637
+ if (!cleanTerminalAssistantStopReceived && !agentSettledReceived || watchdogTailTimer || lifecycleFinished || processClosed)
638
+ return;
639
+ watchdogTailTimer = setTimeout(() => {
640
+ watchdogTailTimer = undefined;
641
+ updateChildWatchdogState({
642
+ phase: "stale",
643
+ seq: (childWatchdogState?.seq ?? 0) + 1,
644
+ lastUpdate: Date.now(),
645
+ followUpPending: false,
646
+ reason: "child watchdog tail timeout",
647
+ timedOut: true
648
+ });
649
+ startFinalDrain();
650
+ fireUpdate();
651
+ }, childWatchdog?.watchdogTailTimeoutMs ?? 120000);
652
+ watchdogTailTimer.unref?.();
653
+ }
654
+ const applyChildLifecycle = (action) => {
655
+ if (action === "cancel-drain") {
656
+ clearFinalDrainTimers();
657
+ clearWatchdogTailTimer();
658
+ return;
659
+ }
660
+ if (action === "start-drain")
661
+ startFinalDrain();
662
+ };
663
+ const childLifecycleState = { compactionRetryActive: false };
344
664
  const unsubscribeIntercomDetach = options.intercomEvents?.on?.(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {
345
- if (!options.allowIntercomDetach || detached || processClosed || !intercomStarted)
665
+ if (!options.allowIntercomDetach || processClosed)
346
666
  return;
347
667
  if (!payload || typeof payload !== "object")
348
668
  return;
349
- const requestId = payload.requestId;
669
+ const event = payload;
670
+ const requestId = event.requestId;
350
671
  if (typeof requestId !== "string" || requestId.length === 0)
351
672
  return;
352
- options.intercomEvents?.emit(INTERCOM_DETACH_RESPONSE_EVENT, { requestId, accepted: true });
353
- detachForIntercom();
673
+ const hasRoute = event.runId !== undefined || event.agent !== undefined || event.childIndex !== undefined;
674
+ if (hasRoute) {
675
+ if (typeof event.runId === "string" && event.runId !== options.runId)
676
+ return;
677
+ if (typeof event.agent === "string" && event.agent !== agent.name)
678
+ return;
679
+ if (typeof event.childIndex === "number" && event.childIndex !== (options.index ?? 0))
680
+ return;
681
+ } else if (!intercomStarted)
682
+ return;
683
+ const accepted = detachForeground("intercom coordination");
684
+ options.intercomEvents?.emit(INTERCOM_DETACH_RESPONSE_EVENT, { requestId, accepted, runId: options.runId, agent: agent.name, childIndex: options.index ?? 0 });
354
685
  });
355
686
  const finish = (code) => {
356
- if (settled)
687
+ if (lifecycleFinished)
357
688
  return;
358
- settled = true;
689
+ lifecycleFinished = true;
359
690
  clearFinalDrainTimers();
691
+ clearWatchdogTailTimer();
360
692
  clearStdioGuard();
361
693
  clearTimeoutTimers();
362
- clearTurnBudgetTimers();
694
+ clearAllToolTimeouts();
695
+ if (protocolHardKillTimer) {
696
+ clearTimeout(protocolHardKillTimer);
697
+ protocolHardKillTimer = undefined;
698
+ }
363
699
  if (activityTimer) {
364
700
  clearInterval(activityTimer);
365
701
  activityTimer = undefined;
@@ -378,6 +714,58 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
378
714
  };
379
715
  let activeLongRunningNotified = false;
380
716
  let pendingToolResult;
717
+ let activeToolSequence = 0;
718
+ const activeToolCalls = new Map;
719
+ const activeToolKeysByName = new Map;
720
+ const latestActiveToolCall = () => [...activeToolCalls.values()].sort((left, right) => right.startedAt - left.startedAt)[0];
721
+ const refreshCurrentTool = () => {
722
+ const active = latestActiveToolCall();
723
+ if (!active) {
724
+ progress.currentTool = undefined;
725
+ progress.currentToolArgs = undefined;
726
+ progress.currentToolStartedAt = undefined;
727
+ progress.currentPath = undefined;
728
+ return;
729
+ }
730
+ progress.currentTool = active.tool;
731
+ progress.currentToolArgs = active.args;
732
+ progress.currentToolStartedAt = active.startedAt;
733
+ progress.currentPath = active.path;
734
+ };
735
+ const recordActiveToolCall = (event, args, now) => {
736
+ const key = toolTimeoutCallKey(event, ++activeToolSequence);
737
+ const path = resolveCurrentPath(event.toolName, args);
738
+ const active = {
739
+ key,
740
+ tool: event.toolName,
741
+ args: extractToolArgsPreview(args),
742
+ startedAt: now,
743
+ ...path !== undefined ? { path } : {}
744
+ };
745
+ activeToolCalls.set(key, active);
746
+ const keys = activeToolKeysByName.get(active.tool) ?? [];
747
+ keys.push(key);
748
+ activeToolKeysByName.set(active.tool, keys);
749
+ refreshCurrentTool();
750
+ return active;
751
+ };
752
+ const removeActiveToolCallKey = (key) => {
753
+ const active = activeToolCalls.get(key);
754
+ if (!active)
755
+ return;
756
+ activeToolCalls.delete(key);
757
+ const keys = activeToolKeysByName.get(active.tool)?.filter((candidate) => candidate !== key) ?? [];
758
+ if (keys.length > 0)
759
+ activeToolKeysByName.set(active.tool, keys);
760
+ else
761
+ activeToolKeysByName.delete(active.tool);
762
+ return active;
763
+ };
764
+ const removeActiveToolCall = (event) => {
765
+ const key = typeof event.toolCallId === "string" && event.toolCallId.length > 0 ? `id:${event.toolCallId}` : typeof event.toolName === "string" ? activeToolKeysByName.get(event.toolName)?.[0] : activeToolCalls.size === 1 ? [...activeToolCalls.keys()][0] : undefined;
766
+ return key ? removeActiveToolCallKey(key) : undefined;
767
+ };
768
+ const openToolAttentionTarget = (now) => [...activeToolCalls.values()].filter((active) => shouldEmitOpenToolAttention({ config: controlConfig, currentTool: active.tool, currentToolStartedAt: active.startedAt, now })).sort((left, right) => left.startedAt - right.startedAt)[0];
381
769
  const mutatingFailures = createMutatingFailureState();
382
770
  const mutatingFailureWindowMs = 5 * 60000;
383
771
  const currentToolDurationMs = (now) => progress.currentToolStartedAt ? Math.max(0, now - progress.currentToolStartedAt) : undefined;
@@ -403,7 +791,8 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
403
791
  currentTool: input.currentTool ?? progress.currentTool,
404
792
  currentToolDurationMs: input.currentToolDurationMs ?? currentToolDurationMs(now),
405
793
  currentPath: input.currentPath ?? progress.currentPath,
406
- recentFailureSummary: input.recentFailureSummary
794
+ recentFailureSummary: input.recentFailureSummary,
795
+ taskPreview: task
407
796
  });
408
797
  emitControlEvent(event);
409
798
  return previous !== "needs_attention";
@@ -430,56 +819,11 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
430
819
  currentTool: progress.currentTool,
431
820
  currentToolDurationMs: currentToolDurationMs(now),
432
821
  currentPath: progress.currentPath,
433
- elapsedMs: now - startTime
822
+ elapsedMs: now - startTime,
823
+ taskPreview: task
434
824
  }));
435
825
  return true;
436
826
  };
437
- const requestTurnBudgetAbort = (turnCount) => {
438
- const budget = options.turnBudget;
439
- if (!budget || result.timedOut || result.turnBudgetExceeded || interruptedByControl || processClosed || settled || detached)
440
- return;
441
- const message = turnBudgetExceededMessage(budget, turnCount);
442
- result.turnBudgetExceeded = true;
443
- result.wrapUpRequested = true;
444
- result.turnBudget = turnBudgetState(budget, turnCount, true);
445
- result.error = message;
446
- result.finalOutput = message;
447
- progress.status = "failed";
448
- progress.error = message;
449
- progress.durationMs = Date.now() - startTime;
450
- fireUpdate();
451
- trySignalChild(proc, "SIGINT");
452
- turnBudgetTerminationTimer = setTimeout(() => {
453
- if (processClosed || settled || detached || result.timedOut)
454
- return;
455
- trySignalChild(proc, "SIGTERM");
456
- }, 1000);
457
- turnBudgetTerminationTimer.unref?.();
458
- turnBudgetHardKillTimer = setTimeout(() => {
459
- if (processClosed || settled || detached || result.timedOut)
460
- return;
461
- trySignalChild(proc, "SIGKILL");
462
- }, 4000);
463
- turnBudgetHardKillTimer.unref?.();
464
- };
465
- const updateTurnBudget = (turnCount, terminalAssistantStop) => {
466
- const budget = options.turnBudget;
467
- if (!budget || result.timedOut || result.turnBudgetExceeded)
468
- return;
469
- if (turnCount < budget.maxTurns) {
470
- result.turnBudget = { ...budget, outcome: "within-budget", turnCount };
471
- return;
472
- }
473
- if (!turnBudgetSoftReached) {
474
- turnBudgetSoftReached = true;
475
- result.wrapUpRequested = true;
476
- appendRecentOutput(progress, [turnBudgetSoftNote(budget, turnCount)]);
477
- }
478
- result.turnBudget = turnBudgetState(budget, turnCount, false);
479
- if (shouldAbortForTurnBudget(budget, turnCount, terminalAssistantStop)) {
480
- requestTurnBudgetAbort(turnCount);
481
- }
482
- };
483
827
  const updateActivityState = (now) => {
484
828
  if (!controlConfig.enabled)
485
829
  return false;
@@ -487,11 +831,24 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
487
831
  config: controlConfig,
488
832
  startedAt: startTime,
489
833
  lastActivityAt: progress.lastActivityAt,
834
+ currentTool: progress.currentTool,
835
+ thinking: resolvedThinking,
490
836
  now
491
837
  });
492
838
  if (idleState === "needs_attention") {
493
839
  return progress.activityState === "needs_attention" ? false : emitNeedsAttention(now);
494
840
  }
841
+ const toolAttentionTarget = progress.activityState !== "needs_attention" ? openToolAttentionTarget(now) : undefined;
842
+ if (toolAttentionTarget) {
843
+ const durationMs = Math.max(0, now - toolAttentionTarget.startedAt);
844
+ return emitNeedsAttention(now, {
845
+ message: `${agent.name} has had tool '${toolAttentionTarget.tool}' open for ${Math.floor(durationMs / 1000)}s`,
846
+ reason: "tool_open_threshold",
847
+ currentTool: toolAttentionTarget.tool,
848
+ currentPath: toolAttentionTarget.path,
849
+ currentToolDurationMs: durationMs
850
+ });
851
+ }
495
852
  const activeReason = nextLongRunningTrigger(controlConfig, {
496
853
  startedAt: startTime,
497
854
  now,
@@ -504,7 +861,7 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
504
861
  if (!options.onUpdate || processClosed)
505
862
  return;
506
863
  const progressSnapshot = snapshotProgress(progress);
507
- const resultSnapshot = snapshotResult(result, progressSnapshot);
864
+ const resultSnapshot = snapshotStreamResult(result, progressSnapshot);
508
865
  const controlEvents = drainPendingControlEvents();
509
866
  options.onUpdate({
510
867
  content: [{ type: "text", text }],
@@ -520,9 +877,10 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
520
877
  if (!options.onUpdate || processClosed)
521
878
  return;
522
879
  progress.durationMs = Date.now() - startTime;
523
- const output = (result.timedOut || result.turnBudgetExceeded) && result.finalOutput ? result.finalOutput : getFinalOutput(result.messages);
880
+ const output = result.timedOut && result.finalOutput ? result.finalOutput : getFinalOutput(result.messages ?? []);
524
881
  emitUpdateSnapshot(output || "(running...)");
525
882
  };
883
+ const rawStdoutTail = createBoundedByteTail();
526
884
  const processLine = (line) => {
527
885
  if (!line.trim())
528
886
  return;
@@ -531,49 +889,91 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
531
889
  try {
532
890
  evt = JSON.parse(line);
533
891
  } catch {
892
+ rawStdoutTail.push(`${line}
893
+ `);
534
894
  shared.transcriptWriter?.writeStdoutLine(line);
895
+ shared.orcaProgressTab?.append(`${line}
896
+ `);
535
897
  return;
536
898
  }
537
899
  shared.transcriptWriter?.writeChildEvent(evt);
900
+ shared.orcaProgressTab?.event(evt);
901
+ if (evt.type === "compaction_start")
902
+ compactionStartedReceived = true;
903
+ if (evt.type === "compaction_end" && evt.willRetry === true) {
904
+ compactionStartedReceived = false;
905
+ afterCompactionSettlement = false;
906
+ }
907
+ if (evt.type === "agent_start" || evt.type === "auto_retry_start") {
908
+ compactionStartedReceived = false;
909
+ afterCompactionSettlement = false;
910
+ }
911
+ const lifecycleAction = projectChildLifecycle(evt, false, childLifecycleState);
912
+ if (evt.type === "agent_settled" && lifecycleAction === "start-drain") {
913
+ agentSettledReceived = true;
914
+ afterCompactionSettlement = compactionStartedReceived;
915
+ }
916
+ applyChildLifecycle(lifecycleAction);
917
+ if (isChildWatchdogStatusEvent(evt)) {
918
+ if (!childWatchdog)
919
+ return;
920
+ const next = acceptChildWatchdogEvent({
921
+ current: childWatchdogState,
922
+ event: evt,
923
+ runId: options.runId,
924
+ agent: agent.name,
925
+ childIndex: options.index ?? 0
926
+ });
927
+ if (!next)
928
+ return;
929
+ updateChildWatchdogState(next);
930
+ if (childWatchdogIsActive(next)) {
931
+ clearFinalDrainTimers();
932
+ armWatchdogTail();
933
+ } else {
934
+ clearWatchdogTailTimer();
935
+ if (cleanTerminalAssistantStopReceived || agentSettledReceived)
936
+ startFinalDrain();
937
+ }
938
+ fireUpdate();
939
+ return;
940
+ }
538
941
  const now = Date.now();
539
942
  progress.durationMs = now - startTime;
540
943
  progress.lastActivityAt = now;
541
944
  updateActivityState(now);
542
945
  if (evt.type === "tool_execution_start") {
543
946
  const toolArgs = evt.args && typeof evt.args === "object" && !Array.isArray(evt.args) ? evt.args : {};
544
- let shouldDetachForBlockingIntercom = false;
947
+ const activeTool = evt.toolName !== undefined ? recordActiveToolCall(evt, toolArgs, now) : undefined;
948
+ if (evt.toolName !== undefined)
949
+ armToolTimeout(evt);
950
+ if (options.structuredOutput && evt.toolName === "structured_output") {
951
+ structuredOutputToolInvoked = true;
952
+ structuredOutputMessageStartIndex = result.messages?.length ?? 0;
953
+ }
545
954
  if (options.allowIntercomDetach && (evt.toolName === "intercom" || evt.toolName === "contact_supervisor")) {
546
955
  intercomStarted = true;
547
- shouldDetachForBlockingIntercom = evt.toolName === "intercom" && toolArgs.action === "ask" || evt.toolName === "contact_supervisor" && (toolArgs.reason === "need_decision" || toolArgs.reason === "interview_request");
548
956
  }
549
957
  progress.toolCount++;
550
958
  if (options.toolBudget) {
551
959
  result.toolBudget = toolBudgetState(options.toolBudget, progress.toolCount);
552
960
  }
553
- progress.currentTool = evt.toolName;
554
- progress.currentToolArgs = extractToolArgsPreview(toolArgs);
555
- progress.currentToolStartedAt = now;
556
- progress.currentPath = resolveCurrentPath(evt.toolName, toolArgs);
557
- const mutates = isMutatingTool(evt.toolName, toolArgs);
961
+ const mutates = isMutatingTool(evt.toolName, toolArgs, agent.mutationTools);
558
962
  observedMutationAttempt = observedMutationAttempt || mutates;
559
- pendingToolResult = { tool: evt.toolName ?? "tool", path: progress.currentPath, mutates, startedAt: now };
963
+ pendingToolResult = { tool: evt.toolName ?? "tool", path: activeTool?.path, mutates, startedAt: now };
560
964
  fireUpdate();
561
- if (shouldDetachForBlockingIntercom && !detached && !processClosed) {
562
- detachForIntercom();
563
- }
564
965
  }
565
966
  if (evt.type === "tool_execution_end") {
566
- if (progress.currentTool) {
967
+ clearActiveToolTimeout(evt);
968
+ const endedTool = removeActiveToolCall(evt);
969
+ if (endedTool) {
567
970
  progress.recentTools.push({
568
- tool: progress.currentTool,
569
- args: progress.currentToolArgs || "",
971
+ tool: endedTool.tool,
972
+ args: endedTool.args,
570
973
  endMs: now
571
974
  });
572
975
  }
573
- progress.currentTool = undefined;
574
- progress.currentToolArgs = undefined;
575
- progress.currentToolStartedAt = undefined;
576
- progress.currentPath = undefined;
976
+ refreshCurrentTool();
577
977
  fireUpdate();
578
978
  }
579
979
  if (evt.type === "message_end" && evt.message) {
@@ -581,21 +981,33 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
581
981
  if (evt.message.role === "assistant") {
582
982
  result.usage.turns++;
583
983
  progress.turnCount = result.usage.turns;
584
- const stopReason = evt.message.stopReason;
585
- const hasToolCall = Array.isArray(evt.message.content) && evt.message.content.some((part) => part.type === "toolCall");
586
- const terminalAssistantStop = stopReason === "stop" && !hasToolCall;
587
- updateTurnBudget(result.usage.turns, terminalAssistantStop);
984
+ const toolCalls = Array.isArray(evt.message.content) ? evt.message.content.filter((part) => part.type === "toolCall") : [];
985
+ const hasToolCall = toolCalls.length > 0;
986
+ const terminalAssistantStop = evt.message.stopReason === "stop" && !hasToolCall;
588
987
  const u = evt.message.usage;
589
988
  if (u) {
989
+ const window = (u.input || 0) + (u.cacheRead || 0);
590
990
  result.usage.input += u.input || 0;
591
991
  result.usage.output += u.output || 0;
592
992
  result.usage.cacheRead += u.cacheRead || 0;
593
993
  result.usage.cacheWrite += u.cacheWrite || 0;
594
994
  result.usage.cost += u.cost?.total || 0;
595
995
  progress.tokens = result.usage.input + result.usage.output;
996
+ progress.inputTokens = result.usage.input;
997
+ progress.outputTokens = result.usage.output;
998
+ progress.window = window;
999
+ progress.windowPeak = Math.max(progress.windowPeak ?? 0, window);
1000
+ }
1001
+ if (evt.message.model) {
1002
+ progress.model = evt.message.model;
1003
+ if (!result.model)
1004
+ result.model = evt.message.model;
1005
+ if (expectedModelForVerification && !hasToolCall) {
1006
+ const modelVerificationError = formatSubagentModelVerificationError(expectedModelForVerification, evt.message.model, options.availableModels);
1007
+ if (modelVerificationError && !result.error)
1008
+ result.error = modelVerificationError;
1009
+ }
596
1010
  }
597
- if (!result.model && evt.message.model)
598
- result.model = evt.message.model;
599
1011
  if (evt.message.errorMessage)
600
1012
  assistantError = evt.message.errorMessage;
601
1013
  const assistantText = extractTextFromContent(evt.message.content);
@@ -605,13 +1017,31 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
605
1017
  if (!evt.message.errorMessage && assistantText.trim())
606
1018
  assistantError = undefined;
607
1019
  cleanTerminalAssistantStopReceived ||= !evt.message.errorMessage;
608
- startFinalDrain();
1020
+ clearAllToolTimeouts();
1021
+ activeToolCalls.clear();
1022
+ activeToolKeysByName.clear();
1023
+ refreshCurrentTool();
1024
+ applyChildLifecycle(projectChildLifecycle(evt, true, childLifecycleState));
609
1025
  }
610
1026
  }
611
1027
  updateActivityState(now);
612
1028
  fireUpdate();
613
1029
  }
614
1030
  if (evt.type === "tool_result_end" && evt.message) {
1031
+ const toolResultCompletion = {
1032
+ toolCallId: evt.message.toolCallId ?? evt.toolCallId,
1033
+ toolName: evt.message.toolName ?? evt.toolName
1034
+ };
1035
+ clearActiveToolTimeout(toolResultCompletion);
1036
+ const endedTool = removeActiveToolCall(toolResultCompletion);
1037
+ if (endedTool) {
1038
+ progress.recentTools.push({
1039
+ tool: endedTool.tool,
1040
+ args: endedTool.args,
1041
+ endMs: now
1042
+ });
1043
+ refreshCurrentTool();
1044
+ }
615
1045
  result.messages.push(evt.message);
616
1046
  const resultText = extractTextFromContent(evt.message.content);
617
1047
  if (options.toolBudget && pendingToolResult && resultText.includes("Tool budget hard limit reached")) {
@@ -646,23 +1076,23 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
646
1076
  fireUpdate();
647
1077
  }
648
1078
  };
649
- if (controlConfig.enabled) {
1079
+ fireUpdate();
1080
+ if (controlConfig.enabled || options.onUpdate) {
650
1081
  activityTimer = setInterval(() => {
651
- if (processClosed || settled || detached)
1082
+ if (processClosed || lifecycleFinished) {
652
1083
  return;
653
- const now = Date.now();
654
- if (updateActivityState(now)) {
655
- progress.durationMs = now - startTime;
656
- fireUpdate();
657
1084
  }
1085
+ updateActivityState(Date.now());
1086
+ fireUpdate();
658
1087
  }, 1000);
659
1088
  activityTimer.unref?.();
660
1089
  }
661
1090
  if (attemptTimeout) {
662
1091
  timeoutTimer = setTimeout(() => {
663
- if (processClosed || settled || detached || interruptedByControl)
1092
+ if (processClosed || lifecycleFinished || interruptedByControl)
664
1093
  return;
665
1094
  result.timedOut = true;
1095
+ clearAllToolTimeouts();
666
1096
  result.error = attemptTimeout.message;
667
1097
  result.finalOutput = attemptTimeout.message;
668
1098
  progress.status = "failed";
@@ -671,13 +1101,13 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
671
1101
  fireUpdate();
672
1102
  trySignalChild(proc, "SIGINT");
673
1103
  timeoutTerminationTimer = setTimeout(() => {
674
- if (processClosed || settled || detached)
1104
+ if (processClosed || lifecycleFinished)
675
1105
  return;
676
1106
  trySignalChild(proc, "SIGTERM");
677
1107
  }, 1000);
678
1108
  timeoutTerminationTimer.unref?.();
679
1109
  timeoutHardKillTimer = setTimeout(() => {
680
- if (processClosed || settled || detached)
1110
+ if (processClosed || lifecycleFinished)
681
1111
  return;
682
1112
  trySignalChild(proc, "SIGKILL");
683
1113
  }, 4000);
@@ -685,70 +1115,178 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
685
1115
  }, attemptTimeout.remainingMs);
686
1116
  timeoutTimer.unref?.();
687
1117
  }
688
- let stderrBuf = "";
689
- const clearStdioGuard = attachPostExitStdioGuard(proc, { idleMs: 2000, hardMs: 8000 });
690
- proc.stdout.on("data", (d) => {
691
- buf += d.toString();
692
- const lines = buf.split(`
693
- `);
694
- buf = lines.pop() || "";
695
- lines.forEach(processLine);
1118
+ let toolTimeoutSequence = 0;
1119
+ const activeToolTimeouts = new Map;
1120
+ const activeToolTimeoutKeysByName = new Map;
1121
+ let toolTimeoutTerminationTimer;
1122
+ let toolTimeoutHardKillTimer;
1123
+ const removeToolTimeoutKey = (key) => {
1124
+ const active = activeToolTimeouts.get(key);
1125
+ if (!active)
1126
+ return;
1127
+ clearTimeout(active.timer);
1128
+ activeToolTimeouts.delete(key);
1129
+ const keys = activeToolTimeoutKeysByName.get(active.toolName)?.filter((candidate) => candidate !== key) ?? [];
1130
+ if (keys.length > 0)
1131
+ activeToolTimeoutKeysByName.set(active.toolName, keys);
1132
+ else
1133
+ activeToolTimeoutKeysByName.delete(active.toolName);
1134
+ };
1135
+ const clearActiveToolTimeout = (event) => {
1136
+ const key = typeof event.toolCallId === "string" && event.toolCallId.length > 0 ? `id:${event.toolCallId}` : typeof event.toolName === "string" ? activeToolTimeoutKeysByName.get(event.toolName)?.[0] : activeToolTimeouts.size === 1 ? [...activeToolTimeouts.keys()][0] : undefined;
1137
+ if (key)
1138
+ removeToolTimeoutKey(key);
1139
+ };
1140
+ const clearAllToolTimeouts = () => {
1141
+ for (const key of [...activeToolTimeouts.keys()])
1142
+ removeToolTimeoutKey(key);
1143
+ if (toolTimeoutTerminationTimer) {
1144
+ clearTimeout(toolTimeoutTerminationTimer);
1145
+ toolTimeoutTerminationTimer = undefined;
1146
+ }
1147
+ if (toolTimeoutHardKillTimer) {
1148
+ clearTimeout(toolTimeoutHardKillTimer);
1149
+ toolTimeoutHardKillTimer = undefined;
1150
+ }
1151
+ };
1152
+ const terminateForToolTimeout = (message) => {
1153
+ if (processClosed || lifecycleFinished || interruptedByControl)
1154
+ return;
1155
+ result.timedOut = true;
1156
+ result.error = message;
1157
+ result.finalOutput = message;
1158
+ progress.status = "failed";
1159
+ progress.error = message;
1160
+ progress.durationMs = Date.now() - startTime;
1161
+ fireUpdate();
1162
+ trySignalChild(proc, "SIGINT");
1163
+ toolTimeoutTerminationTimer = setTimeout(() => {
1164
+ if (processClosed || lifecycleFinished)
1165
+ return;
1166
+ trySignalChild(proc, "SIGTERM");
1167
+ }, 1000);
1168
+ toolTimeoutTerminationTimer.unref?.();
1169
+ toolTimeoutHardKillTimer = setTimeout(() => {
1170
+ if (processClosed || lifecycleFinished)
1171
+ return;
1172
+ trySignalChild(proc, "SIGKILL");
1173
+ }, 4000);
1174
+ toolTimeoutHardKillTimer.unref?.();
1175
+ };
1176
+ const armToolTimeout = (event) => {
1177
+ const timeoutForTool = effectiveToolTimeoutMs(event.toolName, options.toolTimeoutMs);
1178
+ if (timeoutForTool === undefined)
1179
+ return;
1180
+ const elapsed = Date.now() - startTime;
1181
+ const runRemaining = attemptTimeout ? Math.max(0, attemptTimeout.remainingMs - elapsed) : undefined;
1182
+ if (runRemaining !== undefined && timeoutForTool >= runRemaining)
1183
+ return;
1184
+ const key = toolTimeoutCallKey(event, ++toolTimeoutSequence);
1185
+ const toolName = event.toolName;
1186
+ const timer = setTimeout(() => {
1187
+ removeToolTimeoutKey(key);
1188
+ terminateForToolTimeout(formatToolTimeoutMessage(toolName, timeoutForTool));
1189
+ }, timeoutForTool);
1190
+ timer.unref?.();
1191
+ activeToolTimeouts.set(key, { toolName, timer });
1192
+ const keys = activeToolTimeoutKeysByName.get(toolName) ?? [];
1193
+ keys.push(key);
1194
+ activeToolTimeoutKeysByName.set(toolName, keys);
1195
+ };
1196
+ const stderrTail = createBoundedByteTail();
1197
+ const failProtocol = (limit) => {
1198
+ if (result.protocolError)
1199
+ return;
1200
+ result.protocolError = limit;
1201
+ result.error = formatProtocolOutputLimit(limit);
1202
+ progress.status = "failed";
1203
+ progress.error = result.error;
1204
+ progress.durationMs = Date.now() - startTime;
1205
+ fireUpdate();
1206
+ if (!childExited) {
1207
+ trySignalChild(proc, "SIGTERM");
1208
+ protocolHardKillTimer = setTimeout(() => {
1209
+ if (!childExited)
1210
+ trySignalChild(proc, "SIGKILL");
1211
+ }, 3000);
1212
+ protocolHardKillTimer.unref?.();
1213
+ }
1214
+ };
1215
+ const stdoutReader = createBoundedLineReader({
1216
+ oversizedLineProjector: PI_AGGREGATE_EVENT_PROJECTOR,
1217
+ onLine: processLine,
1218
+ onLimit: failProtocol
1219
+ });
1220
+ const stderrReader = createBoundedLineReader({
1221
+ stream: "stderr",
1222
+ maxPendingLineBytes: MAX_CHILD_STDERR_BYTES,
1223
+ onLine: (line) => shared.transcriptWriter?.writeStderrLine(line),
1224
+ onLimit: (limit) => shared.transcriptWriter?.writeStderrLine(formatProtocolOutputLimit(limit))
696
1225
  });
697
- proc.stderr.on("data", (d) => {
698
- stderrBuf += d.toString();
1226
+ const clearStdioGuard = attachPostExitStdioGuard(proc, { idleMs: 2000, hardMs: 8000 });
1227
+ proc.stdout.on("data", (chunk) => stdoutReader.push(chunk));
1228
+ proc.stderr.on("data", (chunk) => {
1229
+ stderrTail.push(chunk);
1230
+ stderrReader.push(chunk);
1231
+ shared.orcaProgressTab?.append(chunk.toString("utf-8"));
699
1232
  });
700
1233
  proc.on("exit", () => {
701
1234
  childExited = true;
702
1235
  clearFinalDrainTimers();
703
1236
  });
704
1237
  proc.on("close", (code, signal) => {
1238
+ if (lifecycleFinished)
1239
+ return;
1240
+ processClosed = true;
705
1241
  clearFinalDrainTimers();
706
1242
  clearStdioGuard();
707
1243
  jsonlWriter.close().catch(() => {});
1244
+ const toolDiagnosticError = readChildToolDiagnosticError(toolDiagnosticPath);
1245
+ toolAvailabilityError = toolDiagnosticError;
1246
+ result.runtimeAcknowledgedExtensions = readRuntimeAcknowledgedExtensions(runtimeAcknowledgedExtensionsPath);
708
1247
  cleanupTempDir(tempDir);
709
- if (buf.trim())
710
- processLine(buf);
711
- if (stderrBuf.trim())
712
- shared.transcriptWriter?.writeStderrText(stderrBuf);
713
- if (!result.error && assistantError)
714
- result.error = assistantError;
715
- const forcedDrainAfterFinalSuccess = forcedTerminationSignal && cleanTerminalAssistantStopReceived && !result.error;
716
- if (code !== 0 && stderrBuf.trim() && !result.error && !forcedDrainAfterFinalSuccess) {
717
- result.error = stderrBuf.trim();
1248
+ stdoutReader.end();
1249
+ stderrReader.end();
1250
+ const stderr = stderrTail.text();
1251
+ const rawStdout = rawStdoutTail.text();
1252
+ let closeError = result.error ?? toolDiagnosticError ?? assistantError;
1253
+ const forcedDrainAfterFinalSuccess = Boolean(forcedTerminationSignal || signal) && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !closeError;
1254
+ const forcedDrainAfterEmptyTerminal = forcedDrainAfterFinalSuccess && hasEmptyTerminalAssistantResponse(result.messages ?? []);
1255
+ if (signal)
1256
+ result.processSignal = signal;
1257
+ if (!closeError && forcedDrainAfterEmptyTerminal && stderr.trim()) {
1258
+ closeError = stderr.trim();
718
1259
  }
719
- const finalCode = forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? code ?? 1 : code ?? 0;
720
- if (detached) {
721
- result.exitCode = result.error && finalCode === 0 ? 1 : finalCode;
722
- progress.status = result.exitCode === 0 ? "completed" : "failed";
723
- progress.durationMs = Date.now() - startTime;
724
- if (result.error)
725
- progress.error = result.error;
726
- result.progressSummary = {
727
- toolCount: progress.toolCount,
728
- tokens: progress.tokens,
729
- durationMs: progress.durationMs
730
- };
731
- const finalOutput = getFinalOutput(result.messages);
732
- result.finalOutput = finalOutput.trim() || result.error || result.finalOutput || "Detached child exited without final output.";
733
- if (result.artifactPaths && options.artifactConfig?.enabled !== false && options.artifactConfig?.includeOutput !== false) {
734
- try {
735
- writeArtifact(result.artifactPaths.outputPath, result.finalOutput);
736
- } catch {}
737
- }
738
- options.onDetachedExit?.(snapshotResult(result, snapshotProgress(progress)));
739
- finish(-2);
740
- return;
1260
+ if (!closeError && isUnexplainedProcessSignal({
1261
+ processSignal: signal,
1262
+ interrupted: result.interrupted,
1263
+ timedOut: result.timedOut,
1264
+ stopped: result.stopped,
1265
+ forcedDrainAfterFinalSuccess: forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal
1266
+ })) {
1267
+ closeError = formatProcessSignalError(signal);
741
1268
  }
742
- processClosed = true;
1269
+ if (code !== 0 && rawStdout.trim() && !closeError && !forcedDrainAfterFinalSuccess) {
1270
+ closeError = rawStdout.trim();
1271
+ }
1272
+ if (code !== 0 && stderr.trim() && !closeError && !forcedDrainAfterFinalSuccess) {
1273
+ closeError = stderr.trim();
1274
+ }
1275
+ const finalCode = forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal ? 0 : forcedTerminationSignal || signal ? code ?? 1 : code ?? 0;
1276
+ if (!result.error && closeError)
1277
+ result.error = closeError;
743
1278
  finish(finalCode);
744
1279
  });
745
1280
  proc.on("error", (error) => {
1281
+ if (lifecycleFinished)
1282
+ return;
1283
+ processClosed = true;
746
1284
  clearFinalDrainTimers();
747
1285
  clearStdioGuard();
748
1286
  jsonlWriter.close().catch(() => {});
749
1287
  cleanupTempDir(tempDir);
750
- if (stderrBuf.trim())
751
- shared.transcriptWriter?.writeStderrText(stderrBuf);
1288
+ stdoutReader.end();
1289
+ stderrReader.end();
752
1290
  if (!result.error) {
753
1291
  result.error = error instanceof Error ? error.message : String(error);
754
1292
  }
@@ -756,12 +1294,9 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
756
1294
  });
757
1295
  if (options.signal) {
758
1296
  const kill = () => {
759
- if (processClosed || detached)
1297
+ if (processClosed || lifecycleFinished)
760
1298
  return;
761
- if (options.allowIntercomDetach && intercomStarted && !detached) {
762
- detachForIntercom();
763
- return;
764
- }
1299
+ abortedBySignal = true;
765
1300
  proc.kill("SIGTERM");
766
1301
  setTimeout(() => !proc.killed && proc.kill("SIGKILL"), 3000);
767
1302
  };
@@ -774,12 +1309,13 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
774
1309
  }
775
1310
  if (options.interruptSignal) {
776
1311
  const interrupt = () => {
777
- if (processClosed || detached || settled)
1312
+ if (processClosed || lifecycleFinished)
778
1313
  return;
779
1314
  if (result.timedOut)
780
1315
  return;
781
1316
  interruptedByControl = true;
782
1317
  clearTimeoutTimers();
1318
+ clearAllToolTimeouts();
783
1319
  progress.status = "running";
784
1320
  progress.durationMs = Date.now() - startTime;
785
1321
  result.interrupted = true;
@@ -788,7 +1324,7 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
788
1324
  fireUpdate();
789
1325
  trySignalChild(proc, "SIGINT");
790
1326
  setTimeout(() => {
791
- if (settled || processClosed || detached)
1327
+ if (lifecycleFinished || processClosed)
792
1328
  return;
793
1329
  trySignalChild(proc, "SIGTERM");
794
1330
  }, 1000).unref?.();
@@ -800,8 +1336,16 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
800
1336
  removeInterruptListener = () => options.interruptSignal?.removeEventListener("abort", interrupt);
801
1337
  }
802
1338
  }
1339
+ try {
1340
+ options.onDetachReady?.((reason = "user request") => detachForeground(reason));
1341
+ } catch (error) {
1342
+ appendRecentOutput(progress, [`Foreground detach callback failed: ${error instanceof Error ? error.message : String(error)}`]);
1343
+ }
803
1344
  });
804
1345
  result.exitCode = exitCode;
1346
+ if (afterCompactionSettlement) {
1347
+ result[AFTER_COMPACTION_SETTLEMENT] = true;
1348
+ }
805
1349
  if (interruptedByControl) {
806
1350
  result.exitCode = 0;
807
1351
  result.interrupted = true;
@@ -817,42 +1361,62 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
817
1361
  };
818
1362
  return result;
819
1363
  }
820
- if (result.detached) {
821
- result.exitCode = 0;
822
- result.finalOutput = "Detached for intercom coordination.";
823
- return result;
1364
+ if (progress.currentTool && isOrdinaryToolForMidToolExit(progress.currentTool) && !abortedBySignal && !result.timedOut && !result.stopped && !result.protocolError && !toolAvailabilityError) {
1365
+ const processExitCode = result.exitCode;
1366
+ result.exitCode = 1;
1367
+ result.error = formatMidToolExitError({
1368
+ toolName: progress.currentTool,
1369
+ exitCode: processExitCode,
1370
+ processSignal: result.processSignal
1371
+ });
824
1372
  }
1373
+ result.error = formatSubagentExtensionConflictError(result.error, {
1374
+ agent: agent.name,
1375
+ ambientExtensionsEnabled: !launchResolvedExtensions.disableAmbientExtensions
1376
+ });
825
1377
  if (result.error && result.exitCode === 0) {
826
1378
  result.exitCode = 1;
827
1379
  }
828
- if (result.exitCode === 0 && !result.error) {
829
- const errInfo = detectSubagentError(result.messages);
830
- if (errInfo.hasError) {
831
- result.exitCode = errInfo.exitCode ?? 1;
832
- result.error = errInfo.details ? `${errInfo.errorType} failed (exit ${errInfo.exitCode}): ${errInfo.details}` : `${errInfo.errorType} failed with exit code ${errInfo.exitCode}`;
833
- }
834
- }
835
- if (result.exitCode === 0 && !result.error) {
836
- const finalText = getFinalOutput(result.messages);
837
- const missingStructuredOutput = options.structuredOutput ? !existsSync(options.structuredOutput.outputPath) : false;
838
- if (!finalText?.trim() && (!options.structuredOutput || missingStructuredOutput)) {
839
- result.exitCode = 1;
840
- result.error = "Subagent produced no output (possible model cold-start or empty response).";
841
- }
842
- }
1380
+ let validatedStructuredOutput = false;
843
1381
  if (options.structuredOutput && result.exitCode === 0 && !result.error) {
844
- const structured = readStructuredOutput({
845
- schema: options.structuredOutput.schema,
846
- schemaPath: options.structuredOutput.schemaPath,
847
- outputPath: options.structuredOutput.outputPath
848
- });
849
1382
  result.structuredOutputSchemaPath = options.structuredOutput.schemaPath;
850
1383
  result.structuredOutputPath = options.structuredOutput.outputPath;
851
- if (structured.error) {
1384
+ if (!structuredOutputToolInvoked) {
852
1385
  result.exitCode = 1;
853
- result.error = structured.error;
1386
+ result.error = MISSING_STRUCTURED_OUTPUT_CALL_ERROR;
1387
+ result.structuredOutputFailed = true;
854
1388
  } else {
855
- result.structuredOutput = structured.value;
1389
+ const structured = await readStructuredOutput({
1390
+ schema: options.structuredOutput.schema,
1391
+ schemaPath: options.structuredOutput.schemaPath,
1392
+ outputPath: options.structuredOutput.outputPath
1393
+ });
1394
+ if (structured.error) {
1395
+ result.exitCode = 1;
1396
+ result.error = structured.error;
1397
+ result.structuredOutputFailed = true;
1398
+ } else {
1399
+ result.structuredOutput = structured.value;
1400
+ const acceptanceReport = readStructuredOutputAcceptanceReport(options.structuredOutput);
1401
+ result.structuredAcceptanceReport = acceptanceReport.value;
1402
+ result.structuredAcceptanceReportError = acceptanceReport.error;
1403
+ validatedStructuredOutput = true;
1404
+ }
1405
+ }
1406
+ }
1407
+ if (result.exitCode === 0 && !result.error) {
1408
+ const messages = result.messages ?? [];
1409
+ const finalText = getFinalOutput(messages);
1410
+ const errorMessages = validatedStructuredOutput ? messages.slice(structuredOutputMessageStartIndex ?? messages.length) : messages;
1411
+ const errInfo = detectSubagentError(errorMessages);
1412
+ const missingOutput = !finalText?.trim() && !validatedStructuredOutput;
1413
+ const terminalEmptyAfterUsefulWork = !validatedStructuredOutput && hasEmptyTerminalAssistantResponse(messages) && (progress.toolCount > 0 || Boolean(finalText?.trim()));
1414
+ if ((missingOutput || terminalEmptyAfterUsefulWork) && (!errInfo.hasError || hasEmptyTerminalAssistantResponse(messages))) {
1415
+ result.exitCode = 1;
1416
+ result.error = formatEmptyTerminalAssistantResponseError(messages);
1417
+ } else if (errInfo.hasError) {
1418
+ result.exitCode = errInfo.exitCode ?? 1;
1419
+ result.error = errInfo.details ? `${errInfo.errorType} failed (exit ${errInfo.exitCode}): ${errInfo.details}` : `${errInfo.errorType} failed with exit code ${errInfo.exitCode}`;
856
1420
  }
857
1421
  }
858
1422
  progress.status = result.exitCode === 0 ? "completed" : "failed";
@@ -864,37 +1428,89 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
864
1428
  }
865
1429
  }
866
1430
  result.progressSummary = {
1431
+ ...childSessionName ? { sessionName: childSessionName } : {},
867
1432
  toolCount: progress.toolCount,
868
1433
  tokens: progress.tokens,
869
1434
  durationMs: progress.durationMs
870
1435
  };
871
- const acceptanceOutput = getFinalOutput(result.messages);
1436
+ const mutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, options.cwd ?? runtimeCwd);
1437
+ const acceptanceOutput = getFinalOutput(result.messages ?? []);
872
1438
  let fullOutput = stripAcceptanceReport(acceptanceOutput);
1439
+ if (!fullOutput.trim() && result.structuredOutput !== undefined)
1440
+ fullOutput = JSON.stringify(result.structuredOutput, null, 2);
1441
+ result.outputState = fullOutput.trim() || result.structuredOutput !== undefined ? "present" : "absent";
873
1442
  if (result.timedOut) {
874
1443
  const timeoutMessage = formatTimeoutMessage(options.timeoutMs ?? 0);
1444
+ let requiredOutputMissing;
1445
+ if (options.outputMode === "file-only" && options.outputPath) {
1446
+ const outputChanged = hasSingleOutputChangedSinceSnapshot(options.outputPath, shared.outputSnapshot);
1447
+ requiredOutputMissing = outputChanged === undefined ? undefined : !outputChanged;
1448
+ } else if (options.structuredOutput) {
1449
+ requiredOutputMissing = !existsSync(options.structuredOutput.outputPath);
1450
+ }
1451
+ result.timeoutRecovery = buildTimeoutRecoverySummary({
1452
+ termination: "timed-out",
1453
+ evidence: mutationEvidence,
1454
+ requiredOutputMissing,
1455
+ currentTool: progress.currentTool,
1456
+ currentToolArgs: progress.currentToolArgs,
1457
+ currentPath: progress.currentPath,
1458
+ sessionFile: options.sessionFile,
1459
+ transcriptPath: shared.transcriptWriter ? shared.artifactPaths?.transcriptPath : undefined,
1460
+ artifactPaths: shared.artifactPaths
1461
+ });
875
1462
  fullOutput = fullOutput.trim() ? `${timeoutMessage}
876
1463
 
1464
+ ${result.timeoutRecovery.message}
1465
+
877
1466
  Partial output before timeout:
878
- ${fullOutput}` : timeoutMessage;
879
- } else if (result.turnBudgetExceeded && result.turnBudget) {
880
- fullOutput = formatTurnBudgetOutput(turnBudgetExceededMessage(result.turnBudget, result.turnBudget.turnCount), fullOutput);
881
- } else if (result.wrapUpRequested && result.turnBudget?.outcome === "wrap-up-requested") {
882
- const note = turnBudgetSoftNote(result.turnBudget, result.turnBudget.wrapUpRequestedAtTurn ?? result.turnBudget.turnCount);
883
- fullOutput = fullOutput.trim() ? `${note}
1467
+ ${fullOutput}` : `${timeoutMessage}
884
1468
 
885
- ${fullOutput}` : note;
1469
+ ${result.timeoutRecovery.message}`;
886
1470
  }
887
- const completionGuard = result.exitCode === 0 && !result.error && agent.completionGuard !== false ? evaluateCompletionMutationGuard({
1471
+ const completionGuardEnabled = isAgentContractV1(options.agentContract) ? agent.completionGuard === true : agent.completionGuard !== false;
1472
+ const completionGuard = (result.exitCode === 0 && !result.error || toolAvailabilityError) && completionGuardEnabled ? evaluateCompletionMutationGuard({
888
1473
  agent: agent.name,
889
1474
  task: shared.originalTask ?? task,
890
- messages: result.messages,
891
- tools: agent.tools,
892
- mcpDirectTools: agent.mcpDirectTools
1475
+ messages: result.messages ?? [],
1476
+ tools: contractTools,
1477
+ mcpDirectTools: toolPlan.effectiveMcpTools,
1478
+ mutationTools: agent.mutationTools,
1479
+ toolAvailabilityError,
1480
+ mutationEvidence
893
1481
  }) : undefined;
894
- if (completionGuard?.triggered && !observedMutationAttempt) {
1482
+ const mutationAttemptObserved = observedMutationAttempt || mutationEvidence.attemptedMutation;
1483
+ let completionGuardTriggered = completionGuard?.triggered === true && !mutationAttemptObserved;
1484
+ let arbiterRescued = false;
1485
+ if (completionGuardTriggered) {
1486
+ const arbitration = await arbitrateCompletionGuardRescue({
1487
+ guardTriggered: true,
1488
+ task: shared.originalTask ?? task,
1489
+ arbiter: options.llmIntentArbiter
1490
+ });
1491
+ completionGuardTriggered = arbitration.triggered;
1492
+ arbiterRescued = arbitration.rescued;
1493
+ }
1494
+ const completionEvidence = planCompletionEvidence({
1495
+ guard: completionGuard,
1496
+ guardTriggered: completionGuardTriggered,
1497
+ completionGuardEnabled,
1498
+ mutationCapable: hasMutationToolCapability(contractTools, toolPlan.effectiveMcpTools),
1499
+ implementationMutationExpected: expectsImplementationMutation(agent.name, shared.originalTask ?? task),
1500
+ mutationAttemptObserved,
1501
+ mutationEvidence,
1502
+ arbiterRescued,
1503
+ agentContractV1: isAgentContractV1(options.agentContract)
1504
+ });
1505
+ if (completionEvidence.fileMutation) {
1506
+ result.effects = {
1507
+ ...result.effects ?? {},
1508
+ fileMutation: completionEvidence.fileMutation
1509
+ };
1510
+ }
1511
+ if (completionEvidence.legacyFailureError) {
895
1512
  result.exitCode = 1;
896
- result.error = `Subagent completed without making edits for an implementation task.
897
- It appears to have returned planning or scratchpad output instead of applying changes.`;
1513
+ result.error = completionEvidence.legacyFailureError;
898
1514
  progress.status = "failed";
899
1515
  progress.error = result.error;
900
1516
  emitControlEvent(buildControlEvent({
@@ -909,12 +1525,19 @@ It appears to have returned planning or scratchpad output instead of applying ch
909
1525
  }));
910
1526
  }
911
1527
  if (options.outputPath && result.exitCode === 0) {
912
- const resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);
1528
+ const resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot, options.outputClaimPath);
913
1529
  fullOutput = stripAcceptanceReport(resolvedOutput.fullOutput);
914
1530
  result.savedOutputPath = resolvedOutput.savedPath;
915
1531
  result.outputSaveError = resolvedOutput.saveError;
1532
+ if (resolvedOutput.fatalError) {
1533
+ result.exitCode = 1;
1534
+ result.error = result.error ? `${result.error}
1535
+ ${resolvedOutput.saveError}` : resolvedOutput.saveError;
1536
+ }
916
1537
  if (resolvedOutput.savedPath) {
917
1538
  result.outputReference = formatSavedOutputReference(resolvedOutput.savedPath, fullOutput);
1539
+ if (result.outputState === "absent")
1540
+ result.outputState = "unknown";
918
1541
  }
919
1542
  }
920
1543
  artifactOutputByResult.set(result, fullOutput);
@@ -925,7 +1548,7 @@ It appears to have returned planning or scratchpad output instead of applying ch
925
1548
  if (options.onUpdate) {
926
1549
  const finalText = result.finalOutput || result.error || "(no output)";
927
1550
  const progressSnapshot = snapshotProgress(progress);
928
- const resultSnapshot = snapshotResult(result, progressSnapshot);
1551
+ const resultSnapshot = snapshotStreamResult(result, progressSnapshot);
929
1552
  options.onUpdate({
930
1553
  content: [{ type: "text", text: finalText }],
931
1554
  details: {
@@ -938,21 +1561,90 @@ It appears to have returned planning or scratchpad output instead of applying ch
938
1561
  }
939
1562
  return result;
940
1563
  }
941
- export async function runSync(runtimeCwd, agents, agentName, task, options) {
1564
+ async function runSyncCompletionInner(runtimeCwd, agents, agentName, task, options) {
1565
+ const effectiveCwd = options.cwd ?? runtimeCwd;
1566
+ const cwdError = preflightLaunchCwd(options.requestedCwd ?? effectiveCwd, effectiveCwd);
1567
+ if (cwdError) {
1568
+ return redactResultPrompt(withRunContext({
1569
+ index: options.index ?? 0,
1570
+ agent: agentName,
1571
+ task,
1572
+ exitCode: 1,
1573
+ messages: [],
1574
+ usage: emptyUsage(),
1575
+ error: cwdError
1576
+ }, options.context));
1577
+ }
1578
+ options = {
1579
+ ...options,
1580
+ capabilityCeiling: intersectSubagentCapabilityCeilings(options.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(options.parentSessionId), decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]))
1581
+ };
1582
+ const childSessionName = deriveChildSessionName({ agent: agentName, task });
942
1583
  const agent = agents.find((a) => a.name === agentName);
943
1584
  if (!agent) {
944
- return {
1585
+ const diagnosticContext = options.unknownAgentDiagnosticContext ?? unknownAgentDiagnosticContext(discoverAgents(path.resolve(options.cwd ?? runtimeCwd), "both"));
1586
+ return redactResultPrompt(withRunContext({
1587
+ index: options.index ?? 0,
945
1588
  agent: agentName,
946
1589
  task,
947
1590
  exitCode: 1,
948
1591
  messages: [],
949
1592
  usage: emptyUsage(),
950
- error: `Unknown agent: ${agentName}`
951
- };
1593
+ error: formatUnknownAgentError(agentName, diagnosticContext)
1594
+ }, options.context));
952
1595
  }
1596
+ options = {
1597
+ ...options,
1598
+ thinkingCeiling: intersectThinkingCeilings(options.thinkingCeiling, agent.maxThinking, decodeThinkingCeiling(process.env[SUBAGENT_THINKING_CEILING_ENV]))
1599
+ };
1600
+ try {
1601
+ assertAgentAllowedByCapabilityCeiling(agent.name, options.capabilityCeiling);
1602
+ } catch (error) {
1603
+ return redactResultPrompt(withRunContext({
1604
+ index: options.index ?? 0,
1605
+ agent: agent.name,
1606
+ task,
1607
+ exitCode: 1,
1608
+ messages: [],
1609
+ usage: emptyUsage(),
1610
+ error: error instanceof Error ? error.message : String(error),
1611
+ ...options.capabilityCeiling ? { capabilityCeiling: options.capabilityCeiling } : {}
1612
+ }, options.context));
1613
+ }
1614
+ const acceptanceErrors = validateAcceptanceInput(options.acceptance);
1615
+ if (acceptanceErrors.length > 0) {
1616
+ return redactResultPrompt(withRunContext({
1617
+ index: options.index ?? 0,
1618
+ agent: agentName,
1619
+ task,
1620
+ exitCode: 1,
1621
+ messages: [],
1622
+ usage: emptyUsage(),
1623
+ error: acceptanceErrors.join(" ")
1624
+ }, options.context));
1625
+ }
1626
+ const toolTimeout = resolveToolTimeoutMs({
1627
+ callValue: options.toolTimeoutMs,
1628
+ agentValue: agent.defaultToolTimeoutMs,
1629
+ configValue: options.configToolTimeoutMs,
1630
+ envValue: toolTimeoutFromEnv()
1631
+ });
1632
+ if (toolTimeout.error) {
1633
+ return redactResultPrompt(withRunContext({
1634
+ index: options.index ?? 0,
1635
+ agent: agentName,
1636
+ task,
1637
+ exitCode: 1,
1638
+ messages: [],
1639
+ usage: emptyUsage(),
1640
+ error: toolTimeout.error
1641
+ }, options.context));
1642
+ }
1643
+ options = { ...options, toolTimeoutMs: toolTimeout.toolTimeoutMs };
953
1644
  const outputModeValidationError = validateFileOnlyOutputMode(options.outputMode, options.outputPath, `Single run (${agentName})`);
954
1645
  if (outputModeValidationError) {
955
- return {
1646
+ return redactResultPrompt(withRunContext({
1647
+ index: options.index ?? 0,
956
1648
  agent: agentName,
957
1649
  task,
958
1650
  exitCode: 1,
@@ -960,34 +1652,41 @@ export async function runSync(runtimeCwd, agents, agentName, task, options) {
960
1652
  usage: emptyUsage(),
961
1653
  outputMode: options.outputMode,
962
1654
  error: outputModeValidationError
963
- };
1655
+ }, options.context));
964
1656
  }
965
1657
  const shareEnabled = options.share === true;
966
1658
  const effectiveAcceptance = resolveEffectiveAcceptance({
967
1659
  explicit: options.acceptance,
968
1660
  agentName,
1661
+ acceptanceRole: agent.acceptanceRole,
969
1662
  task,
970
1663
  mode: options.acceptanceContext?.mode ?? "single",
971
1664
  async: options.acceptanceContext?.async,
972
1665
  dynamic: options.acceptanceContext?.dynamic,
973
- dynamicGroup: options.acceptanceContext?.dynamicGroup
1666
+ dynamicGroup: options.acceptanceContext?.dynamicGroup,
1667
+ agentContract: options.agentContract
974
1668
  });
975
- const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance);
1669
+ const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance, { reportOptional: isAgentContractV1(options.agentContract), structuredOutput: Boolean(options.structuredOutput?.acceptanceReportPath) });
976
1670
  const taskWithAcceptance = acceptancePrompt ? `${task}
977
1671
  ${acceptancePrompt}` : task;
1672
+ options.onEffectivePrompt?.(taskWithAcceptance);
978
1673
  const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
1674
+ if (options.context === "fork" && options.sessionFile && existsSync(options.sessionFile)) {
1675
+ alignForkedSessionCwd(options.sessionFile, options.cwd ?? runtimeCwd);
1676
+ }
979
1677
  const skillNames = options.skills ?? agent.skills ?? [];
980
1678
  const skillCwd = options.cwd ?? runtimeCwd;
981
- const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(skillNames, skillCwd, runtimeCwd);
1679
+ const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(skillNames, skillCwd, runtimeCwd, agent.skillPath, agent.filePath ? path.dirname(agent.filePath) : skillCwd);
982
1680
  if (skillNames.some((skill) => skill.trim() === "dm-subagents") && missingSkills.includes("dm-subagents")) {
983
- return {
1681
+ return redactResultPrompt(withRunContext({
1682
+ index: options.index ?? 0,
984
1683
  agent: agentName,
985
1684
  task,
986
1685
  exitCode: 1,
987
1686
  messages: [],
988
1687
  usage: emptyUsage(),
989
1688
  error: "Skills not found: dm-subagents"
990
- };
1689
+ }, options.context));
991
1690
  }
992
1691
  let systemPrompt = agent.systemPrompt?.trim() || "";
993
1692
  if (resolvedSkills.length > 0) {
@@ -1002,12 +1701,42 @@ ${skillInjection}` : skillInjection;
1002
1701
 
1003
1702
  ${memoryInjection}` : memoryInjection;
1004
1703
  }
1005
- systemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath);
1006
- const candidates = buildModelCandidates(options.modelOverride ?? agent.model, agent.fallbackModels, options.availableModels, options.preferredModelProvider, { scope: options.modelScope });
1704
+ systemPrompt = appendAgentRefinementOverlay(systemPrompt, { cwd: skillCwd, agentName });
1705
+ systemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath, agent);
1706
+ const candidates = buildModelCandidates(options.modelOverride ?? agent.model, agent.fallbackModels, options.availableModels, agent.modelProvider ?? options.preferredModelProvider, { scope: options.modelScope, primaryModelFromParent: options.modelOverrideFromParent });
1707
+ if (options.workflowChildPermitLaunch && candidates.length > 1) {
1708
+ const error = "Workflow child permit does not support model fallback.";
1709
+ return redactResultPrompt(withRunContext({
1710
+ index: options.index ?? 0,
1711
+ agent: agent.name,
1712
+ task,
1713
+ exitCode: 1,
1714
+ messages: [],
1715
+ usage: emptyUsage(),
1716
+ error
1717
+ }, options.context));
1718
+ }
1719
+ try {
1720
+ for (const candidate of candidates) {
1721
+ const model = applyThinkingSuffix(candidate, options.thinkingOverride ?? agent.thinking, options.thinkingOverride !== undefined);
1722
+ assertThinkingWithinCeiling({ model, configThinking: options.thinkingOverride ?? agent.thinking, ceiling: options.thinkingCeiling, agent: agent.name, runId: options.runId });
1723
+ }
1724
+ } catch (error) {
1725
+ return redactResultPrompt(withRunContext({
1726
+ index: options.index ?? 0,
1727
+ agent: agent.name,
1728
+ task,
1729
+ exitCode: 1,
1730
+ messages: [],
1731
+ usage: emptyUsage(),
1732
+ error: error instanceof Error ? error.message : String(error)
1733
+ }, options.context));
1734
+ }
1007
1735
  const attemptedModels = [];
1008
1736
  const modelAttempts = [];
1009
1737
  const aggregateUsage = emptyUsage();
1010
1738
  const attemptNotes = [];
1739
+ const launchWarnings = { emitted: false };
1011
1740
  let totalToolCount = 0;
1012
1741
  let totalDurationMs = 0;
1013
1742
  let artifactPathsResult;
@@ -1019,7 +1748,8 @@ ${memoryInjection}` : memoryInjection;
1019
1748
  if (options.artifactConfig?.includeInput !== false) {
1020
1749
  writeArtifact(artifactPathsResult.inputPath, `# Task for ${agentName}
1021
1750
 
1022
- ${taskWithAcceptance}`);
1751
+ ${PROMPT_REDACTED}; live Prompt Audit only.
1752
+ `);
1023
1753
  }
1024
1754
  if (options.artifactConfig?.includeJsonl !== false) {
1025
1755
  jsonlPath = artifactPathsResult.jsonlPath;
@@ -1033,116 +1763,265 @@ ${taskWithAcceptance}`);
1033
1763
  childIndex: options.index,
1034
1764
  cwd: options.cwd ?? runtimeCwd
1035
1765
  });
1036
- transcriptWriter.writeInitialUserMessage(taskWithAcceptance);
1766
+ transcriptWriter.writeInitialUserMessage(`${PROMPT_REDACTED}; live Prompt Audit only.`);
1037
1767
  }
1038
1768
  }
1039
- let lastResult;
1040
- const modelsToTry = candidates.length > 0 ? candidates : [undefined];
1041
- for (let i = 0;i < modelsToTry.length; i++) {
1042
- const candidate = modelsToTry[i];
1043
- const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
1044
- const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate, options, {
1045
- sessionEnabled,
1046
- systemPrompt,
1047
- resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
1048
- skillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(", ")}` : undefined,
1049
- jsonlPath,
1050
- artifactPaths: artifactPathsResult,
1051
- transcriptWriter,
1052
- attemptNotes,
1053
- outputSnapshot,
1054
- originalTask: task
1769
+ const orcaProgressTab = createOrcaProgressTab({
1770
+ cwd: options.cwd ?? runtimeCwd,
1771
+ runId: options.runId,
1772
+ agent: agentName,
1773
+ index: options.index ?? 0
1774
+ });
1775
+ if (orcaProgressTab)
1776
+ options.onOrcaProgressTabCreated?.(orcaProgressTab);
1777
+ const persistResultMetadata = (target) => {
1778
+ persistSingleResultMetadata({
1779
+ metadataPath: artifactPathsResult?.metadataPath,
1780
+ enabled: options.artifactConfig?.enabled !== false && options.artifactConfig?.includeMetadata !== false,
1781
+ runId: options.runId,
1782
+ agent: agentName,
1783
+ task,
1784
+ result: target
1055
1785
  });
1056
- lastResult = result;
1057
- if (result.model)
1058
- attemptedModels.push(result.model);
1059
- else if (candidate)
1060
- attemptedModels.push(candidate);
1061
- sumUsage(aggregateUsage, result.usage);
1062
- totalToolCount += result.progressSummary?.toolCount ?? 0;
1063
- totalDurationMs += result.progressSummary?.durationMs ?? 0;
1064
- const attemptSucceeded = result.exitCode === 0 && !result.error;
1065
- const attempt = {
1066
- model: result.model ?? candidate ?? agent.model ?? "default",
1067
- success: attemptSucceeded,
1068
- exitCode: result.exitCode,
1069
- error: result.error,
1070
- usage: { ...result.usage }
1071
- };
1072
- modelAttempts.push(attempt);
1073
- if (result.timedOut || result.turnBudgetExceeded) {
1074
- break;
1075
- }
1076
- if (attemptSucceeded) {
1077
- break;
1786
+ };
1787
+ let intercomDetached = false;
1788
+ let detachedReason;
1789
+ const attemptOptions = {
1790
+ ...options,
1791
+ onDetachReceipt: (receipt) => {
1792
+ receipt.acceptance = buildPendingAcceptanceLedger(effectiveAcceptance);
1793
+ try {
1794
+ persistResultMetadata(receipt);
1795
+ } catch (error) {
1796
+ receipt.metadataSaveError = error instanceof Error ? error.message : String(error);
1797
+ }
1798
+ const accepted = options.onDetachReceipt?.(receipt) === true;
1799
+ if (accepted) {
1800
+ detachedReason = receipt.detachedReason;
1801
+ if (receipt.detachedReason === "intercom coordination")
1802
+ intercomDetached = true;
1803
+ }
1804
+ return accepted;
1078
1805
  }
1079
- if (!isRetryableModelFailure(result.error) || i === modelsToTry.length - 1) {
1080
- break;
1806
+ };
1807
+ let lastResult;
1808
+ const modelsToTry = candidates.length > 0 ? candidates : [undefined];
1809
+ let taskDeliveryOverride;
1810
+ let abortRecoveryAttempted = false;
1811
+ let nextAttemptTask = taskWithAcceptance;
1812
+ modelAttemptsLoop:
1813
+ for (let modelIndex = 0;modelIndex < modelsToTry.length; modelIndex++) {
1814
+ const candidate = modelsToTry[modelIndex];
1815
+ for (let startupAttemptIndex = 0;; startupAttemptIndex++) {
1816
+ const recoveringAbort = abortRecoveryAttempted;
1817
+ const attemptTask = nextAttemptTask;
1818
+ const verifyModel = Boolean(candidate) && !(options.modelOverrideFromParent && modelIndex === 0);
1819
+ const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
1820
+ const result = await runSingleAttempt(runtimeCwd, agent, attemptTask, candidate, attemptOptions, {
1821
+ sessionEnabled,
1822
+ systemPrompt,
1823
+ resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
1824
+ skillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(", ")}` : undefined,
1825
+ jsonlPath,
1826
+ artifactPaths: artifactPathsResult,
1827
+ transcriptWriter,
1828
+ attemptNotes,
1829
+ modelCandidates: candidates.map((modelCandidate) => applyThinkingSuffix(modelCandidate, options.thinkingOverride ?? agent.thinking, options.thinkingOverride !== undefined)).filter((modelCandidate) => Boolean(modelCandidate)),
1830
+ outputSnapshot,
1831
+ originalTask: task,
1832
+ taskDelivery: taskDeliveryOverride,
1833
+ orcaProgressTab,
1834
+ launchWarnings,
1835
+ verifyModel
1836
+ });
1837
+ lastResult = result;
1838
+ if (!recoveringAbort && startupAttemptIndex === 0) {
1839
+ if (result.model)
1840
+ attemptedModels.push(result.model);
1841
+ else if (candidate)
1842
+ attemptedModels.push(candidate);
1843
+ }
1844
+ sumUsage(aggregateUsage, result.usage);
1845
+ totalToolCount += result.progressSummary?.toolCount ?? 0;
1846
+ totalDurationMs += result.progressSummary?.durationMs ?? 0;
1847
+ const attemptSucceeded = result.exitCode === 0 && !result.error;
1848
+ const attempt = {
1849
+ model: result.model ?? candidate ?? agent.model ?? "default",
1850
+ success: attemptSucceeded,
1851
+ exitCode: result.exitCode,
1852
+ error: result.error,
1853
+ usage: { ...result.usage }
1854
+ };
1855
+ modelAttempts.push(attempt);
1856
+ if (!attemptSucceeded) {
1857
+ const afterCompactionSettlement = result[AFTER_COMPACTION_SETTLEMENT];
1858
+ const abortRecovery = planAbortRecovery({
1859
+ messages: result.messages ?? [],
1860
+ error: result.error,
1861
+ processSignal: result.processSignal,
1862
+ sessionAvailable: Boolean(options.sessionFile && existsSync(options.sessionFile)),
1863
+ alreadyResumed: abortRecoveryAttempted,
1864
+ stopped: result.stopped || result.detached || options.signal?.aborted,
1865
+ interrupted: result.interrupted || intercomDetached || options.interruptSignal?.aborted,
1866
+ timedOut: result.timedOut,
1867
+ toolBudgetExhausted: result.toolBudgetBlocked,
1868
+ usageBudgetExhausted: false,
1869
+ structuredOutputFailed: result.structuredOutputFailed,
1870
+ acceptanceFailed: false,
1871
+ currentTool: result.progress?.currentTool,
1872
+ afterCompactionSettlement
1873
+ });
1874
+ if (abortRecovery.action === "resume") {
1875
+ abortRecoveryAttempted = true;
1876
+ nextAttemptTask = abortRecovery.prompt;
1877
+ attemptNotes.push("[abort-recovery] provider/transport abort after useful progress; resuming the retained child session once.");
1878
+ continue;
1879
+ }
1880
+ if (abortRecovery.diagnostic) {
1881
+ result.error = result.error ? `${result.error}
1882
+ ${abortRecovery.diagnostic}` : abortRecovery.diagnostic;
1883
+ attempt.error = result.error;
1884
+ break modelAttemptsLoop;
1885
+ }
1886
+ }
1887
+ if (recoveringAbort && !attemptSucceeded)
1888
+ break modelAttemptsLoop;
1889
+ if (options.workflowChildPermitLaunch && !attemptSucceeded)
1890
+ break modelAttemptsLoop;
1891
+ if (intercomDetached || result.timedOut)
1892
+ break modelAttemptsLoop;
1893
+ if (attemptSucceeded)
1894
+ break modelAttemptsLoop;
1895
+ const startupFailure = isRetryableSubagentStartupFailure({
1896
+ exitCode: result.exitCode,
1897
+ error: result.error,
1898
+ finalOutput: result.finalOutput,
1899
+ messageCount: result.messages?.length ?? 0,
1900
+ toolCount: result.progressSummary?.toolCount ?? 0,
1901
+ usage: result.usage,
1902
+ durationMs: result.progressSummary?.durationMs ?? Number.POSITIVE_INFINITY,
1903
+ protocolError: result.protocolError,
1904
+ processSignal: result.processSignal,
1905
+ detached: result.detached,
1906
+ interrupted: result.interrupted,
1907
+ timedOut: result.timedOut,
1908
+ stopped: result.stopped
1909
+ });
1910
+ const retryDelayMs = SUBAGENT_STARTUP_RETRY_DELAYS_MS[startupAttemptIndex];
1911
+ if (startupFailure && retryDelayMs !== undefined) {
1912
+ const retryNote = formatSubagentStartupRetryNote({
1913
+ model: attempt.model,
1914
+ attempt: startupAttemptIndex + 1,
1915
+ maxAttempts: SUBAGENT_STARTUP_RETRY_DELAYS_MS.length + 1,
1916
+ delayMs: retryDelayMs
1917
+ });
1918
+ const shouldRetry = await waitForSubagentStartupRetry(retryDelayMs, [options.signal, options.interruptSignal]);
1919
+ if (!shouldRetry) {
1920
+ if (options.interruptSignal?.aborted) {
1921
+ result.exitCode = 0;
1922
+ result.interrupted = true;
1923
+ result.error = undefined;
1924
+ result.finalOutput = "Interrupted. Waiting for explicit next action.";
1925
+ if (result.progress) {
1926
+ result.progress.status = "running";
1927
+ result.progress.error = undefined;
1928
+ }
1929
+ } else {
1930
+ const cancellationError = "Subagent startup retry cancelled before relaunch.";
1931
+ result.error = cancellationError;
1932
+ result.finalOutput = cancellationError;
1933
+ attempt.error = cancellationError;
1934
+ if (result.progress)
1935
+ result.progress.error = cancellationError;
1936
+ }
1937
+ break modelAttemptsLoop;
1938
+ }
1939
+ if (!taskDeliveryOverride && result.processSignal === "SIGKILL") {
1940
+ taskDeliveryOverride = "file";
1941
+ attemptNotes.push("[startup-retry] retrying with file task delivery to keep the task text out of the child process argv.");
1942
+ }
1943
+ attempt.error = retryNote;
1944
+ attemptNotes.push(retryNote);
1945
+ continue;
1946
+ }
1947
+ if (startupFailure) {
1948
+ const startupError = formatSubagentStartupRetryExhaustedError({
1949
+ model: attempt.model,
1950
+ attempts: startupAttemptIndex + 1
1951
+ });
1952
+ result.error = startupError;
1953
+ result.finalOutput = startupError;
1954
+ if (result.progress) {
1955
+ result.progress.error = startupError;
1956
+ result.progress.status = "failed";
1957
+ }
1958
+ attempt.error = startupError;
1959
+ break modelAttemptsLoop;
1960
+ }
1961
+ const retryableModelFailure = isRetryableModelFailureAttempt({ error: result.error, messages: result.messages, toolCount: result.progressSummary?.toolCount });
1962
+ if (retryableModelFailure)
1963
+ recordRetryableModelFailure(result.model ?? candidate, result.error);
1964
+ if (isContextOverflow(result.error)) {
1965
+ result.contextOverflow = true;
1966
+ attemptNotes.push(`[fallback] ${attempt.model} failed: context overflow — the input exceeds this model's context window. Reduce the task input or use a model with a larger context window.`);
1967
+ break modelAttemptsLoop;
1968
+ }
1969
+ if (!retryableModelFailure || modelIndex === modelsToTry.length - 1)
1970
+ break modelAttemptsLoop;
1971
+ attemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[modelIndex + 1]));
1972
+ break;
1973
+ }
1081
1974
  }
1082
- attemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[i + 1]));
1083
- }
1084
- const result = lastResult ?? {
1975
+ const result = withRunContext(lastResult ?? {
1976
+ index: options.index ?? 0,
1085
1977
  agent: agentName,
1086
1978
  task,
1087
1979
  exitCode: 1,
1088
1980
  messages: [],
1089
1981
  usage: emptyUsage(),
1090
1982
  error: "Subagent did not produce a result."
1091
- };
1983
+ }, options.context);
1984
+ result.task = task;
1092
1985
  result.usage = aggregateUsage;
1093
1986
  result.attemptedModels = attemptedModels.length > 0 ? attemptedModels : undefined;
1094
1987
  result.modelAttempts = modelAttempts.length > 0 ? modelAttempts : undefined;
1095
1988
  result.progressSummary = {
1989
+ ...childSessionName ? { sessionName: childSessionName } : {},
1096
1990
  toolCount: totalToolCount,
1097
1991
  tokens: aggregateUsage.input + aggregateUsage.output,
1098
1992
  durationMs: totalDurationMs
1099
1993
  };
1100
- if (attemptNotes.length > 0 && result.progress) {
1101
- result.progress.recentOutput = [...attemptNotes, ...result.progress.recentOutput];
1102
- if (result.progress.recentOutput.length > 50) {
1103
- result.progress.recentOutput.splice(50);
1104
- }
1105
- }
1106
1994
  if (transcriptWriter)
1107
1995
  result.transcriptPath = artifactPathsResult?.transcriptPath;
1108
1996
  if (transcriptWriter?.getError())
1109
1997
  result.transcriptError = transcriptWriter.getError();
1110
- if (artifactPathsResult && options.artifactConfig?.enabled !== false) {
1111
- result.artifactPaths = artifactPathsResult;
1112
- if (options.artifactConfig?.includeOutput !== false) {
1113
- writeArtifact(artifactPathsResult.outputPath, artifactOutputByResult.get(result) ?? result.finalOutput ?? "");
1114
- }
1115
- if (options.artifactConfig?.includeMetadata !== false) {
1116
- writeMetadata(artifactPathsResult.metadataPath, {
1117
- runId: options.runId,
1118
- agent: agentName,
1119
- task,
1120
- exitCode: result.exitCode,
1121
- usage: result.usage,
1122
- model: result.model,
1123
- attemptedModels: result.attemptedModels,
1124
- modelAttempts: result.modelAttempts,
1125
- durationMs: result.progressSummary?.durationMs,
1126
- toolCount: result.progressSummary?.toolCount,
1127
- error: result.error,
1128
- ...transcriptWriter ? { transcriptPath: artifactPathsResult.transcriptPath } : {},
1129
- transcriptError: result.transcriptError,
1130
- skills: result.skills,
1131
- skillsWarning: result.skillsWarning,
1132
- timestamp: Date.now()
1133
- });
1134
- }
1135
- if (options.maxOutput) {
1998
+ try {
1999
+ if (artifactPathsResult && options.artifactConfig?.enabled !== false) {
2000
+ result.artifactPaths = artifactPathsResult;
2001
+ if (options.artifactConfig?.includeOutput !== false) {
2002
+ writeArtifact(artifactPathsResult.outputPath, formatOutputArtifactContent({
2003
+ output: artifactOutputByResult.get(result) ?? result.finalOutput ?? "",
2004
+ error: result.error,
2005
+ transcriptPath: result.transcriptPath,
2006
+ metadataPath: options.artifactConfig?.includeMetadata === false ? undefined : artifactPathsResult.metadataPath
2007
+ }));
2008
+ }
2009
+ if (options.maxOutput) {
2010
+ const config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };
2011
+ const truncationResult = truncateOutput(result.finalOutput ?? "", config, artifactPathsResult.outputPath);
2012
+ if (truncationResult.truncated)
2013
+ result.truncation = truncationResult;
2014
+ }
2015
+ } else if (options.maxOutput) {
1136
2016
  const config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };
1137
- const truncationResult = truncateOutput(result.finalOutput ?? "", config, artifactPathsResult.outputPath);
2017
+ const truncationResult = truncateOutput(result.finalOutput ?? "", config);
1138
2018
  if (truncationResult.truncated)
1139
2019
  result.truncation = truncationResult;
1140
2020
  }
1141
- } else if (options.maxOutput) {
1142
- const config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };
1143
- const truncationResult = truncateOutput(result.finalOutput ?? "", config);
1144
- if (truncationResult.truncated)
1145
- result.truncation = truncationResult;
2021
+ } catch (error) {
2022
+ const message = `Artifact output post-processing failed: ${error instanceof Error ? error.message : String(error)}`;
2023
+ result.outputSaveError = result.outputSaveError ? `${result.outputSaveError}
2024
+ ${message}` : message;
1146
2025
  }
1147
2026
  if (options.sessionFile && (existsSync(options.sessionFile) || result.messages?.length)) {
1148
2027
  result.sessionFile = options.sessionFile;
@@ -1151,21 +2030,201 @@ ${taskWithAcceptance}`);
1151
2030
  if (sessionFile)
1152
2031
  result.sessionFile = sessionFile;
1153
2032
  }
1154
- result.acceptance = result.timedOut ? buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "timeout", message: "Acceptance was not evaluated because the subagent timed out." }) : result.turnBudgetExceeded ? buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "turn-budget", message: "Acceptance was not evaluated because the subagent exceeded its turn budget." }) : await evaluateAcceptance({
1155
- acceptance: effectiveAcceptance,
1156
- output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
1157
- cwd: options.cwd ?? runtimeCwd
1158
- });
2033
+ const childWrittenOutput = options.outputPath ? extractChildWrittenOutput(result.messages, options.outputPath, options.cwd ?? runtimeCwd) : undefined;
2034
+ try {
2035
+ if (result.interrupted && detachedReason === "user request") {
2036
+ result.acceptance = buildInterruptedAcceptanceLedger(effectiveAcceptance);
2037
+ } else if (result.stopped) {
2038
+ result.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "stopped", message: "Acceptance was not evaluated because the subagent was stopped." });
2039
+ } else if (result.timedOut) {
2040
+ result.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "timeout", message: "Acceptance was not evaluated because the subagent timed out." });
2041
+ } else {
2042
+ result.acceptance = await evaluateAcceptance({
2043
+ acceptance: effectiveAcceptance,
2044
+ output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
2045
+ report: result.structuredAcceptanceReport,
2046
+ reportError: result.structuredAcceptanceReportError,
2047
+ fileOutput: childWrittenOutput !== undefined && options.outputPath ? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only", durable: result.savedOutputPath !== undefined } : undefined,
2048
+ cwd: options.cwd ?? runtimeCwd,
2049
+ reportOptional: isAgentContractV1(options.agentContract),
2050
+ artifactsDir: options.artifactsDir,
2051
+ runId: options.runId
2052
+ });
2053
+ }
2054
+ } catch (error) {
2055
+ const message = `Acceptance evaluation failed: ${error instanceof Error ? error.message : String(error)}`;
2056
+ result.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "acceptance-evaluation", message });
2057
+ }
1159
2058
  const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
1160
2059
  stripAcceptanceReportsFromMessages(result.messages);
1161
- if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted && !result.timedOut) {
2060
+ if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.interrupted && !result.timedOut && !isAgentContractV1(options.agentContract)) {
1162
2061
  result.exitCode = 1;
2062
+ if (result.savedOutputPath) {
2063
+ result.finalOutput = finalizeSingleOutput({
2064
+ fullOutput: result.finalOutput ?? "",
2065
+ outputPath: options.outputPath,
2066
+ outputMode: result.outputMode,
2067
+ exitCode: result.exitCode,
2068
+ preserveSavedOutput: true,
2069
+ savedPath: result.savedOutputPath,
2070
+ outputReference: result.outputReference
2071
+ }).displayOutput;
2072
+ artifactOutputByResult.set(result, result.finalOutput);
2073
+ }
1163
2074
  result.error = result.error ? `${result.error}
1164
2075
  ${acceptanceFailure}` : acceptanceFailure;
2076
+ if (artifactPathsResult && options.artifactConfig?.enabled !== false && options.artifactConfig?.includeOutput !== false) {
2077
+ try {
2078
+ writeArtifact(artifactPathsResult.outputPath, formatOutputArtifactContent({
2079
+ output: artifactOutputByResult.get(result) ?? result.finalOutput ?? "",
2080
+ error: result.error,
2081
+ transcriptPath: result.transcriptPath,
2082
+ metadataPath: options.artifactConfig?.includeMetadata === false ? undefined : artifactPathsResult.metadataPath
2083
+ }));
2084
+ } catch (error) {
2085
+ const message = `Artifact output post-processing failed: ${error instanceof Error ? error.message : String(error)}`;
2086
+ result.outputSaveError = result.outputSaveError ? `${result.outputSaveError}
2087
+ ${message}` : message;
2088
+ }
2089
+ }
1165
2090
  if (result.progress) {
1166
2091
  result.progress.status = "failed";
1167
2092
  result.progress.error = result.error;
1168
2093
  }
1169
2094
  }
2095
+ if (isAgentContractV1(options.agentContract))
2096
+ attachContractProjections(result);
2097
+ redactResultPrompt(result);
2098
+ try {
2099
+ persistResultMetadata(result);
2100
+ } catch (error) {
2101
+ result.metadataSaveError = error instanceof Error ? error.message : String(error);
2102
+ }
1170
2103
  return result;
1171
2104
  }
2105
+ async function runSyncCompletion(runtimeCwd, agents, agentName, task, options) {
2106
+ let orcaProgressTab;
2107
+ try {
2108
+ const result = await runSyncCompletionInner(runtimeCwd, agents, agentName, task, {
2109
+ ...options,
2110
+ onOrcaProgressTabCreated: (tab) => {
2111
+ orcaProgressTab = tab;
2112
+ }
2113
+ });
2114
+ orcaProgressTab?.finish(result.stopped ? "stopped" : result.exitCode === 0 && !result.error ? "completed" : "failed", result.sessionFile);
2115
+ return result;
2116
+ } catch (error) {
2117
+ orcaProgressTab?.finish("failed");
2118
+ throw error;
2119
+ }
2120
+ }
2121
+ export async function runSync(runtimeCwd, agents, agentName, task, options) {
2122
+ const strictContract = isAgentContractV1(options.agentContract);
2123
+ let detachedReason;
2124
+ let publishedReceipt;
2125
+ let activeDetachAttempt;
2126
+ let detachReadyPublished = false;
2127
+ let resolveReceipt;
2128
+ const receipt = new Promise((resolve) => {
2129
+ resolveReceipt = resolve;
2130
+ });
2131
+ const originSignal = options.signal;
2132
+ const originController = new AbortController;
2133
+ const forwardOriginAbort = () => {
2134
+ if (!detachedReason)
2135
+ originController.abort(originSignal?.reason);
2136
+ };
2137
+ if (originSignal?.aborted)
2138
+ forwardOriginAbort();
2139
+ else
2140
+ originSignal?.addEventListener("abort", forwardOriginAbort, { once: true });
2141
+ const completion = runSyncCompletion(runtimeCwd, agents, agentName, task, {
2142
+ ...options,
2143
+ signal: originController.signal,
2144
+ onDetachedExit: undefined,
2145
+ onDetachReceipt: (detachedReceipt) => {
2146
+ if (detachedReason || publishedReceipt)
2147
+ return false;
2148
+ publishedReceipt = structuredClone(detachedReceipt);
2149
+ const callerReceipt = structuredClone(detachedReceipt);
2150
+ publishedReceipt.agentContract = strictContract ? { version: 1 } : undefined;
2151
+ callerReceipt.agentContract = strictContract ? { version: 1 } : undefined;
2152
+ detachedReason = detachedReceipt.detachedReason ?? "user request";
2153
+ resolveReceipt(callerReceipt);
2154
+ return true;
2155
+ },
2156
+ onDetachReady: (detachAttempt) => {
2157
+ activeDetachAttempt = detachAttempt;
2158
+ if (detachReadyPublished)
2159
+ return;
2160
+ detachReadyPublished = true;
2161
+ options.onDetachReady?.((reason = "user request") => {
2162
+ if (detachedReason || originController.signal.aborted)
2163
+ return false;
2164
+ return activeDetachAttempt?.(reason) ?? false;
2165
+ });
2166
+ }
2167
+ });
2168
+ const authoritativeCompletion = completion.catch((error) => {
2169
+ if (!publishedReceipt || !detachedReason)
2170
+ throw error;
2171
+ const message = error instanceof Error ? error.message : String(error);
2172
+ const failureMessage = `Detached completion pipeline failed after receipt: ${message}`;
2173
+ const failedProgress = publishedReceipt.progress ? { ...publishedReceipt.progress, status: "failed", error: failureMessage } : {
2174
+ index: options.index ?? 0,
2175
+ agent: publishedReceipt.agent,
2176
+ status: "failed",
2177
+ task: publishedReceipt.task,
2178
+ recentTools: [],
2179
+ recentOutput: [],
2180
+ toolCount: publishedReceipt.progressSummary?.toolCount ?? 0,
2181
+ tokens: publishedReceipt.progressSummary?.tokens ?? 0,
2182
+ durationMs: publishedReceipt.progressSummary?.durationMs ?? 0,
2183
+ error: failureMessage
2184
+ };
2185
+ const failedResult = redactResultPrompt({
2186
+ ...publishedReceipt,
2187
+ detached: undefined,
2188
+ detachedReason,
2189
+ exitCode: 1,
2190
+ error: failureMessage,
2191
+ finalOutput: failureMessage,
2192
+ progress: failedProgress,
2193
+ progressSummary: {
2194
+ toolCount: failedProgress.toolCount,
2195
+ tokens: failedProgress.tokens,
2196
+ durationMs: failedProgress.durationMs
2197
+ },
2198
+ acceptance: publishedReceipt.acceptance ? buildSkippedAcceptanceLedger(publishedReceipt.acceptance.effectiveAcceptance, { id: "completion-pipeline", message: failureMessage }) : undefined
2199
+ });
2200
+ if (strictContract)
2201
+ attachContractProjections(failedResult);
2202
+ try {
2203
+ persistSingleResultMetadata({
2204
+ metadataPath: failedResult.artifactPaths?.metadataPath,
2205
+ enabled: options.artifactConfig?.enabled !== false && options.artifactConfig?.includeMetadata !== false,
2206
+ runId: options.runId,
2207
+ agent: failedResult.agent,
2208
+ task: failedResult.task,
2209
+ result: failedResult
2210
+ });
2211
+ } catch (metadataError) {
2212
+ failedResult.metadataSaveError = metadataError instanceof Error ? metadataError.message : String(metadataError);
2213
+ }
2214
+ return failedResult;
2215
+ });
2216
+ let terminalCallbackInvoked = false;
2217
+ authoritativeCompletion.then((terminalResult) => {
2218
+ if (!detachedReason || terminalCallbackInvoked)
2219
+ return;
2220
+ terminalCallbackInvoked = true;
2221
+ terminalResult.detached = undefined;
2222
+ terminalResult.detachedReason = detachedReason;
2223
+ try {
2224
+ options.onDetachedExit?.(terminalResult);
2225
+ } catch {}
2226
+ }).catch(() => {}).finally(() => {
2227
+ originSignal?.removeEventListener("abort", forwardOriginAbort);
2228
+ });
2229
+ return Promise.race([authoritativeCompletion, receipt]);
2230
+ }