pi-subagents 0.77.0 → 0.79.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/agents/evidence-auditor.md +1 -0
  3. package/agents/oracle.md +1 -0
  4. package/agents/reviewer.md +1 -0
  5. package/docs/agents.md +4 -2
  6. package/docs/configuration.md +22 -6
  7. package/docs/extension-api.md +9 -1
  8. package/docs/observability.md +11 -3
  9. package/docs/tool-reference.md +14 -3
  10. package/docs/workflows.md +6 -4
  11. package/package.json +1 -1
  12. package/skills/pi-subagents/SKILL.md +2 -1
  13. package/skills/pi-subagents/references/execution-controls.md +2 -0
  14. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  15. package/src/api/external-runs.d.ts.map +1 -1
  16. package/src/api/external-runs.js +5 -4
  17. package/src/api/external-runs.js.map +1 -1
  18. package/src/api/preflight.js +1 -1
  19. package/src/api/preflight.js.map +1 -1
  20. package/src/extension/active-runs-prompt.d.ts +8 -0
  21. package/src/extension/active-runs-prompt.d.ts.map +1 -0
  22. package/src/extension/active-runs-prompt.js +30 -0
  23. package/src/extension/active-runs-prompt.js.map +1 -0
  24. package/src/extension/config.d.ts.map +1 -1
  25. package/src/extension/config.js +6 -0
  26. package/src/extension/config.js.map +1 -1
  27. package/src/extension/declaration-pinning.d.ts +5 -0
  28. package/src/extension/declaration-pinning.d.ts.map +1 -1
  29. package/src/extension/declaration-pinning.js +24 -3
  30. package/src/extension/declaration-pinning.js.map +1 -1
  31. package/src/extension/index.d.ts +0 -1
  32. package/src/extension/index.d.ts.map +1 -1
  33. package/src/extension/index.js +73 -18
  34. package/src/extension/index.js.map +1 -1
  35. package/src/extension/public-execution.d.ts +1 -0
  36. package/src/extension/public-execution.d.ts.map +1 -1
  37. package/src/extension/public-execution.js +3 -0
  38. package/src/extension/public-execution.js.map +1 -1
  39. package/src/extension/rpc.d.ts.map +1 -1
  40. package/src/extension/rpc.js +5 -3
  41. package/src/extension/rpc.js.map +1 -1
  42. package/src/extension/schemas.d.ts +2 -0
  43. package/src/extension/schemas.d.ts.map +1 -1
  44. package/src/extension/schemas.js +1 -0
  45. package/src/extension/schemas.js.map +1 -1
  46. package/src/extension/tool-activation.d.ts.map +1 -1
  47. package/src/extension/tool-activation.js +25 -6
  48. package/src/extension/tool-activation.js.map +1 -1
  49. package/src/intercom/native-supervisor-channel.d.ts +18 -2
  50. package/src/intercom/native-supervisor-channel.d.ts.map +1 -1
  51. package/src/intercom/native-supervisor-channel.js +130 -15
  52. package/src/intercom/native-supervisor-channel.js.map +1 -1
  53. package/src/runs/background/active-async-capacity.d.ts +2 -1
  54. package/src/runs/background/active-async-capacity.d.ts.map +1 -1
  55. package/src/runs/background/active-async-capacity.js +10 -6
  56. package/src/runs/background/active-async-capacity.js.map +1 -1
  57. package/src/runs/background/async-execution.js +3 -3
  58. package/src/runs/background/async-execution.js.map +1 -1
  59. package/src/runs/background/async-resume.d.ts.map +1 -1
  60. package/src/runs/background/async-resume.js +5 -2
  61. package/src/runs/background/async-resume.js.map +1 -1
  62. package/src/runs/background/async-status-snapshot.d.ts.map +1 -1
  63. package/src/runs/background/async-status-snapshot.js +4 -2
  64. package/src/runs/background/async-status-snapshot.js.map +1 -1
  65. package/src/runs/background/async-status.d.ts.map +1 -1
  66. package/src/runs/background/async-status.js +10 -6
  67. package/src/runs/background/async-status.js.map +1 -1
  68. package/src/runs/background/chain-root-attachment.js +4 -4
  69. package/src/runs/background/chain-root-attachment.js.map +1 -1
  70. package/src/runs/background/control-channel.d.ts +1 -0
  71. package/src/runs/background/control-channel.d.ts.map +1 -1
  72. package/src/runs/background/control-channel.js +27 -3
  73. package/src/runs/background/control-channel.js.map +1 -1
  74. package/src/runs/background/inspect-rpc.d.ts +5 -0
  75. package/src/runs/background/inspect-rpc.d.ts.map +1 -1
  76. package/src/runs/background/inspect-rpc.js +20 -3
  77. package/src/runs/background/inspect-rpc.js.map +1 -1
  78. package/src/runs/background/notify.d.ts +1 -1
  79. package/src/runs/background/notify.d.ts.map +1 -1
  80. package/src/runs/background/notify.js +8 -4
  81. package/src/runs/background/notify.js.map +1 -1
  82. package/src/runs/background/result-delivery-ownership.d.ts +0 -2
  83. package/src/runs/background/result-delivery-ownership.d.ts.map +1 -1
  84. package/src/runs/background/result-delivery-ownership.js +5 -21
  85. package/src/runs/background/result-delivery-ownership.js.map +1 -1
  86. package/src/runs/background/result-files.d.ts +5 -3
  87. package/src/runs/background/result-files.d.ts.map +1 -1
  88. package/src/runs/background/result-files.js +10 -6
  89. package/src/runs/background/result-files.js.map +1 -1
  90. package/src/runs/background/result-watcher.d.ts.map +1 -1
  91. package/src/runs/background/result-watcher.js +15 -8
  92. package/src/runs/background/result-watcher.js.map +1 -1
  93. package/src/runs/background/resume-guidance.d.ts.map +1 -1
  94. package/src/runs/background/resume-guidance.js +3 -2
  95. package/src/runs/background/resume-guidance.js.map +1 -1
  96. package/src/runs/background/run-child-session.d.ts.map +1 -1
  97. package/src/runs/background/run-child-session.js +13 -17
  98. package/src/runs/background/run-child-session.js.map +1 -1
  99. package/src/runs/background/run-status.d.ts.map +1 -1
  100. package/src/runs/background/run-status.js +11 -4
  101. package/src/runs/background/run-status.js.map +1 -1
  102. package/src/runs/background/runner-aliases.d.ts.map +1 -1
  103. package/src/runs/background/runner-aliases.js +11 -8
  104. package/src/runs/background/runner-aliases.js.map +1 -1
  105. package/src/runs/background/subagent-runner.d.ts.map +1 -1
  106. package/src/runs/background/subagent-runner.js +212 -283
  107. package/src/runs/background/subagent-runner.js.map +1 -1
  108. package/src/runs/background/workflow-terminal-proof.d.ts +1 -1
  109. package/src/runs/background/workflow-terminal-proof.d.ts.map +1 -1
  110. package/src/runs/background/workflow-terminal-proof.js +12 -3
  111. package/src/runs/background/workflow-terminal-proof.js.map +1 -1
  112. package/src/runs/foreground/async-dismiss-action.d.ts.map +1 -1
  113. package/src/runs/foreground/async-dismiss-action.js +2 -1
  114. package/src/runs/foreground/async-dismiss-action.js.map +1 -1
  115. package/src/runs/foreground/async-steering-action.d.ts.map +1 -1
  116. package/src/runs/foreground/async-steering-action.js +3 -2
  117. package/src/runs/foreground/async-steering-action.js.map +1 -1
  118. package/src/runs/foreground/async-stop-action.d.ts +1 -0
  119. package/src/runs/foreground/async-stop-action.d.ts.map +1 -1
  120. package/src/runs/foreground/async-stop-action.js +9 -8
  121. package/src/runs/foreground/async-stop-action.js.map +1 -1
  122. package/src/runs/foreground/command-action.d.ts.map +1 -1
  123. package/src/runs/foreground/command-action.js +2 -1
  124. package/src/runs/foreground/command-action.js.map +1 -1
  125. package/src/runs/foreground/execution.d.ts.map +1 -1
  126. package/src/runs/foreground/execution.js +67 -60
  127. package/src/runs/foreground/execution.js.map +1 -1
  128. package/src/runs/foreground/subagent-executor.d.ts +2 -0
  129. package/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  130. package/src/runs/foreground/subagent-executor.js +124 -67
  131. package/src/runs/foreground/subagent-executor.js.map +1 -1
  132. package/src/runs/shared/async-status-projection.d.ts +4 -0
  133. package/src/runs/shared/async-status-projection.d.ts.map +1 -1
  134. package/src/runs/shared/async-status-projection.js +23 -1
  135. package/src/runs/shared/async-status-projection.js.map +1 -1
  136. package/src/runs/shared/child-commands.d.ts +2 -0
  137. package/src/runs/shared/child-commands.d.ts.map +1 -1
  138. package/src/runs/shared/child-commands.js +4 -0
  139. package/src/runs/shared/child-commands.js.map +1 -1
  140. package/src/runs/shared/child-runtime-config.d.ts +6 -0
  141. package/src/runs/shared/child-runtime-config.d.ts.map +1 -1
  142. package/src/runs/shared/child-runtime-config.js +6 -0
  143. package/src/runs/shared/child-runtime-config.js.map +1 -1
  144. package/src/runs/shared/child-session.d.ts +2 -2
  145. package/src/runs/shared/child-session.d.ts.map +1 -1
  146. package/src/runs/shared/child-session.js +104 -42
  147. package/src/runs/shared/child-session.js.map +1 -1
  148. package/src/runs/shared/effective-system-prompt.d.ts +4 -4
  149. package/src/runs/shared/effective-system-prompt.d.ts.map +1 -1
  150. package/src/runs/shared/effective-system-prompt.js +5 -4
  151. package/src/runs/shared/effective-system-prompt.js.map +1 -1
  152. package/src/runs/shared/external-cli-runner.d.ts.map +1 -1
  153. package/src/runs/shared/external-cli-runner.js +3 -2
  154. package/src/runs/shared/external-cli-runner.js.map +1 -1
  155. package/src/runs/shared/long-running-guard.d.ts +3 -1
  156. package/src/runs/shared/long-running-guard.d.ts.map +1 -1
  157. package/src/runs/shared/long-running-guard.js +4 -14
  158. package/src/runs/shared/long-running-guard.js.map +1 -1
  159. package/src/runs/shared/model-resolution-diagnostic.d.ts +7 -5
  160. package/src/runs/shared/model-resolution-diagnostic.d.ts.map +1 -1
  161. package/src/runs/shared/model-resolution-diagnostic.js +13 -10
  162. package/src/runs/shared/model-resolution-diagnostic.js.map +1 -1
  163. package/src/runs/shared/model-resolution.d.ts +5 -1
  164. package/src/runs/shared/model-resolution.d.ts.map +1 -1
  165. package/src/runs/shared/model-resolution.js +8 -1
  166. package/src/runs/shared/model-resolution.js.map +1 -1
  167. package/src/runs/shared/nested-events.d.ts.map +1 -1
  168. package/src/runs/shared/nested-events.js +2 -0
  169. package/src/runs/shared/nested-events.js.map +1 -1
  170. package/src/runs/shared/orca-progress-tabs.js +1 -1
  171. package/src/runs/shared/orca-progress-tabs.js.map +1 -1
  172. package/src/runs/shared/partial-output.d.ts +7 -0
  173. package/src/runs/shared/partial-output.d.ts.map +1 -1
  174. package/src/runs/shared/partial-output.js +7 -0
  175. package/src/runs/shared/partial-output.js.map +1 -1
  176. package/src/runs/shared/run-ownership.d.ts +4 -0
  177. package/src/runs/shared/run-ownership.d.ts.map +1 -0
  178. package/src/runs/shared/run-ownership.js +9 -0
  179. package/src/runs/shared/run-ownership.js.map +1 -0
  180. package/src/runs/shared/single-output.d.ts +11 -1
  181. package/src/runs/shared/single-output.d.ts.map +1 -1
  182. package/src/runs/shared/single-output.js +48 -26
  183. package/src/runs/shared/single-output.js.map +1 -1
  184. package/src/runs/shared/structured-output.d.ts +2 -0
  185. package/src/runs/shared/structured-output.d.ts.map +1 -1
  186. package/src/runs/shared/structured-output.js +9 -0
  187. package/src/runs/shared/structured-output.js.map +1 -1
  188. package/src/runs/shared/subagent-control.d.ts +1 -0
  189. package/src/runs/shared/subagent-control.d.ts.map +1 -1
  190. package/src/runs/shared/subagent-control.js +2 -1
  191. package/src/runs/shared/subagent-control.js.map +1 -1
  192. package/src/runs/shared/subagent-prompt-runtime.d.ts +56 -0
  193. package/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
  194. package/src/runs/shared/subagent-prompt-runtime.js +192 -6
  195. package/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
  196. package/src/runs/shared/tool-budget.js +1 -1
  197. package/src/runs/shared/tool-budget.js.map +1 -1
  198. package/src/shared/child-transcript.js +1 -1
  199. package/src/shared/child-transcript.js.map +1 -1
  200. package/src/shared/extension-context.d.ts +5 -0
  201. package/src/shared/extension-context.d.ts.map +1 -1
  202. package/src/shared/extension-context.js +7 -0
  203. package/src/shared/extension-context.js.map +1 -1
  204. package/src/shared/file-lease.d.ts +7 -1
  205. package/src/shared/file-lease.d.ts.map +1 -1
  206. package/src/shared/file-lease.js +8 -2
  207. package/src/shared/file-lease.js.map +1 -1
  208. package/src/shared/fork-context.d.ts +3 -1
  209. package/src/shared/fork-context.d.ts.map +1 -1
  210. package/src/shared/fork-context.js +23 -13
  211. package/src/shared/fork-context.js.map +1 -1
  212. package/src/shared/parent-wake.d.ts +6 -0
  213. package/src/shared/parent-wake.d.ts.map +1 -1
  214. package/src/shared/parent-wake.js +11 -0
  215. package/src/shared/parent-wake.js.map +1 -1
  216. package/src/shared/session-succession.d.ts +5 -0
  217. package/src/shared/session-succession.d.ts.map +1 -0
  218. package/src/shared/session-succession.js +25 -0
  219. package/src/shared/session-succession.js.map +1 -0
  220. package/src/shared/types.d.ts +22 -9
  221. package/src/shared/types.d.ts.map +1 -1
  222. package/src/shared/types.js +7 -4
  223. package/src/shared/types.js.map +1 -1
  224. package/src/shared/utils.d.ts +13 -0
  225. package/src/shared/utils.d.ts.map +1 -1
  226. package/src/shared/utils.js +19 -0
  227. package/src/shared/utils.js.map +1 -1
  228. package/src/slash/slash-commands.d.ts.map +1 -1
  229. package/src/slash/slash-commands.js +22 -11
  230. package/src/slash/slash-commands.js.map +1 -1
  231. package/src/slash/subagents-admin.d.ts.map +1 -1
  232. package/src/slash/subagents-admin.js +3 -2
  233. package/src/slash/subagents-admin.js.map +1 -1
  234. package/src/tui/fleet-status.d.ts +5 -0
  235. package/src/tui/fleet-status.d.ts.map +1 -1
  236. package/src/tui/fleet-status.js +24 -3
  237. package/src/tui/fleet-status.js.map +1 -1
  238. package/src/tui/fleet.js +2 -2
  239. package/src/tui/fleet.js.map +1 -1
  240. package/src/watchdog/change-signature.d.ts.map +1 -1
  241. package/src/watchdog/change-signature.js +1 -3
  242. package/src/watchdog/change-signature.js.map +1 -1
  243. package/src/watchdog/register-main.js +2 -2
  244. package/src/watchdog/register-main.js.map +1 -1
  245. package/src/watchdog/runtime.d.ts.map +1 -1
  246. package/src/watchdog/runtime.js +3 -5
  247. package/src/watchdog/runtime.js.map +1 -1
  248. package/src/workflows/scripted-workflow.d.ts +1 -1
  249. package/src/workflows/scripted-workflow.d.ts.map +1 -1
  250. package/src/workflows/scripted-workflow.js +13 -4
  251. package/src/workflows/scripted-workflow.js.map +1 -1
  252. package/src/workflows/workflow-child-summary.d.ts +5 -0
  253. package/src/workflows/workflow-child-summary.d.ts.map +1 -1
  254. package/src/workflows/workflow-child-summary.js +10 -0
  255. package/src/workflows/workflow-child-summary.js.map +1 -1
  256. package/src/workflows/workflow-reuse.d.ts +6 -1
  257. package/src/workflows/workflow-reuse.d.ts.map +1 -1
  258. package/src/workflows/workflow-reuse.js +22 -1
  259. package/src/workflows/workflow-reuse.js.map +1 -1
  260. package/src/runs/shared/run-history.d.ts +0 -67
  261. package/src/runs/shared/run-history.d.ts.map +0 -1
  262. package/src/runs/shared/run-history.js +0 -270
  263. package/src/runs/shared/run-history.js.map +0 -1
@@ -23,17 +23,19 @@ import { closeSteerInbox, closeStopInbox, consumeInterruptRequest, consumeSteerR
23
23
  import { appendJsonl as appendRawJsonl, formatOutputArtifactContent, getArtifactPaths, writeArtifact, writeMetadata } from "../../shared/artifacts.js";
24
24
  import { PI_CODING_AGENT_PACKAGE, resolveInstalledPiPackageRoot } from "../shared/pi-spawn.js";
25
25
  import { preflightLaunchCwd } from "../shared/launch-cwd.js";
26
- import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, injectSingleOutputInstruction, resolveSingleOutput } from "../shared/single-output.js";
26
+ import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectSingleOutputInstruction, isSettlementFatalOutputError, resolveSingleOutput } from "../shared/single-output.js";
27
+ import { timedOutRunSummary } from "../shared/partial-output.js";
27
28
  import { runSetupCommand } from "../shared/worktree-setup-command.js";
28
29
  import { DEFAULT_MAX_OUTPUT, SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, truncateOutput, } from "../../shared/types.js";
29
30
  import { DEFAULT_CONTROL_CONFIG, buildControlEvent, deriveActivityState, claimControlNotification, formatControlIntercomMessage, formatControlNoticeMessage, shouldEmitOpenToolAttention, } from "../shared/subagent-control.js";
30
31
  import { isDynamicRunnerGroup, isParallelGroup, flattenSteps, mapConcurrent, aggregateParallelOutputs, MAX_PARALLEL_CONCURRENCY, DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore, } from "../shared/parallel-utils.js";
31
- import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/child-tool-plan.js";
32
+ import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, supervisorChannelDir } from "../shared/child-tool-plan.js";
33
+ import { retainsChildCommand } from "../shared/child-commands.js";
32
34
  import { buildRunnerChildLaunch } from "./runner-child-launch.js";
33
35
  import { normalizeExtensionBindings } from "../shared/extension-bindings.js";
34
36
  import { runChildSession } from "./run-child-session.js";
35
37
  import { loadRunnerChildSessionFactory } from "./runner-child-sessions.js";
36
- import { SUBAGENT_CHILD_ENV } from "../shared/child-runtime-config.js";
38
+ import { SUBAGENT_CHILD_ENV, SUBAGENT_RUNNER_PID_ENV } from "../shared/child-runtime-config.js";
37
39
  import { deriveChildSessionName } from "../../shared/child-session-name.js";
38
40
  import { alignForkedSessionCwd } from "../../shared/fork-session-cwd.js";
39
41
  import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.js";
@@ -49,13 +51,12 @@ import { processTerminalPath, writeProcessTerminalCandidate } from "./process-te
49
51
  import { persistRunnerStartupFailure } from "./runner-startup-failure.js";
50
52
  import { currentPidNamespaceScope } from "./pid-namespace.js";
51
53
  import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, unconsumedSteerReason, updateSteeringTarget } from "./steering.js";
52
- import { PROMPT_REDACTED, detectSubagentError, extractTextFromContent, extractToolArgsPreview, formatEmptyTerminalAssistantResponseError, getAgentDir, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.js";
53
- import { planBackgroundRunHistory, recordRun } from "../shared/run-history.js";
54
+ import { PROMPT_REDACTED, SUBAGENT_STOPPED_MESSAGE, SUBAGENT_TIMED_OUT_MESSAGE, detectSubagentError, extractTextFromContent, extractToolArgsPreview, formatEmptyTerminalAssistantResponseError, getAgentDir, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.js";
54
55
  import { planAbortRecovery } from "../shared/abort-recovery.js";
55
56
  import { createMutatingFailureState, didMutatingToolFail, isMutatingTool, nextLongRunningTrigger, recordMutatingFailure, resetMutatingFailureState, resolveCurrentPath, shouldEscalateMutatingFailures, summarizeRecentMutatingFailures, } from "../shared/long-running-guard.js";
56
57
  import { parseSessionTokens } from "../../shared/session-tokens.js";
57
58
  import { cleanupWorktrees, createWorktrees, withWorktreeTransaction, WorktreeSetupError, diffWorktrees, findWorktreeTaskCwdConflict, formatWorktreeDiffSummary, formatWorktreeTaskCwdConflict, WORKTREE_AGENT_CWD_PLACEHOLDER, } from "../shared/worktree.js";
58
- import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.js";
59
+ import { findModelInfo, qualifyModelWithProvider, resolveEffectiveThinking } from "../../shared/model-info.js";
59
60
  import { assertThinkingWithinCeiling } from "../../shared/thinking-ceiling.js";
60
61
  import { resolveLaunchBinding } from "../../shared/launch-contract.js";
61
62
  import { writeInitialProgressFile } from "../../shared/settings.js";
@@ -65,7 +66,7 @@ import { attachContractProjections, isAgentContract } from "../shared/agent-cont
65
66
  import { waitForImportedAsyncRoot } from "./chain-root-attachment.js";
66
67
  import { appendRunnerStepsToStatus, consumeChainAppendRequests, countPendingChainAppendRequests, statusStepDescription } from "./chain-append.js";
67
68
  import { asyncStatusChildIdentity } from "../shared/child-identity.js";
68
- import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.js";
69
+ import { initialToolBudgetState, isToolBudgetBlockedMessage, toolBudgetState } from "../shared/tool-budget.js";
69
70
  import { toolTimeoutCallKey } from "../shared/tool-timeout.js";
70
71
  import { usageBudgetExceededMessage, usageBudgetState } from "../shared/usage-budget.js";
71
72
  import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writeWorktreeSetupHandoff } from "../shared/parallel-handoff.js";
@@ -85,6 +86,7 @@ const INTERCOM_DETACH_RECEIPT = "Detached for intercom coordination before task
85
86
  // into one of them must register nothing; the variable marks the process as a
86
87
  // child host.
87
88
  process.env[SUBAGENT_CHILD_ENV] = "1";
89
+ process.env[SUBAGENT_RUNNER_PID_ENV] = String(process.pid);
88
90
  function persistStepArtifacts(input) {
89
91
  const errors = {};
90
92
  if (input.artifactConfig?.includeOutput !== false) {
@@ -467,7 +469,7 @@ export async function runSingleStepInner(step, ctx) {
467
469
  }
468
470
  const stopped = importStopped || imported.stopped === true || ctx.stopSignal?.aborted === true;
469
471
  const timedOut = !stopped && (importTimedOut || imported.timedOut === true || ctx.timeoutSignal?.aborted === true || ctx.skipAcceptance?.() === true);
470
- const message = stopped ? ctx.stopMessage ?? "Subagent stopped by user." : ctx.timeoutMessage ?? "Subagent timed out.";
472
+ const message = stopped ? ctx.stopMessage ?? SUBAGENT_STOPPED_MESSAGE : ctx.timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE;
471
473
  return omitUndefinedProperties({
472
474
  agent: imported.agent,
473
475
  output: timedOut || stopped ? message : imported.output,
@@ -554,6 +556,47 @@ export async function runSingleStepInner(step, ctx) {
554
556
  }
555
557
  }
556
558
  transcriptWriter?.writeInitialUserMessage(`${PROMPT_REDACTED}; live Prompt Audit only.`);
559
+ // External CLI and external job runs settle their output the same way; only their runner metadata differs.
560
+ const settleExternalOutput = (external, outputSnapshot, runnerMetadata) => {
561
+ try {
562
+ fs.writeFileSync(ctx.outputFile, external.output, "utf-8");
563
+ }
564
+ catch { /* Observability output is best-effort. */ }
565
+ const resolvedOutput = step.outputPath && external.exitCode === 0
566
+ ? resolveSingleOutput(step.outputPath, external.output, outputSnapshot, step.outputClaimPath)
567
+ : { fullOutput: external.output };
568
+ const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, resolvedOutput.fullOutput) : undefined;
569
+ const settlementFatalOutputError = isSettlementFatalOutputError(resolvedOutput, step.outputMode);
570
+ const exitCode = settlementFatalOutputError ? 1 : external.exitCode;
571
+ const error = settlementFatalOutputError && resolvedOutput.saveError
572
+ ? external.error ? `${external.error}\n${resolvedOutput.saveError}` : resolvedOutput.saveError
573
+ : external.error;
574
+ const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
575
+ fullOutput: resolvedOutput.fullOutput,
576
+ outputPath: step.outputPath,
577
+ outputMode: step.outputMode,
578
+ exitCode: exitCode ?? 1,
579
+ savedPath: resolvedOutput.savedPath,
580
+ outputReference,
581
+ saveError: resolvedOutput.saveError,
582
+ }));
583
+ const artifactErrors = artifactPaths && ctx.artifactConfig?.enabled !== false
584
+ ? persistStepArtifacts({
585
+ artifactPaths,
586
+ artifactConfig: ctx.artifactConfig,
587
+ output: formatOutputArtifactContent(omitUndefinedProperties({ output: resolvedOutput.fullOutput, error: error, metadataPath: ctx.artifactConfig?.includeMetadata === false ? undefined : artifactPaths.metadataPath })),
588
+ metadata: { runId: ctx.id, parentSessionId: step.parentSessionId, agent: step.agent, task: PROMPT_REDACTED, ...runnerMetadata, exitCode: exitCode, error: error, timestamp: Date.now() },
589
+ })
590
+ : {};
591
+ return {
592
+ output: finalizedOutput.displayOutput,
593
+ exitCode,
594
+ error,
595
+ outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
596
+ artifactOutputSaveFailed: artifactErrors.outputSaveError ? true : undefined,
597
+ metadataSaveError: artifactErrors.metadataSaveError,
598
+ };
599
+ };
557
600
  if (step.runner?.type === "external-cli") {
558
601
  const externalCwd = step.machine?.cwd ?? step.cwd ?? ctx.cwd;
559
602
  const externalAbortSignal = combinedAbortSignal([ctx.timeoutSignal, ctx.stopSignal]);
@@ -561,7 +604,7 @@ export async function runSingleStepInner(step, ctx) {
561
604
  await ctx.prepareExternalActivity?.(externalCwd, externalAbortSignal);
562
605
  if (externalAbortSignal?.aborted) {
563
606
  const stopped = ctx.stopSignal?.aborted === true;
564
- const message = stopped ? ctx.stopMessage ?? "Subagent stopped by user." : ctx.timeoutMessage ?? "Subagent timed out.";
607
+ const message = stopped ? ctx.stopMessage ?? SUBAGENT_STOPPED_MESSAGE : ctx.timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE;
565
608
  return omitUndefinedProperties({ agent: step.agent, context: step.context, output: message, error: message, exitCode: 1, stopped: stopped || undefined, timedOut: stopped ? undefined : true });
566
609
  }
567
610
  if (step.machine) {
@@ -584,7 +627,7 @@ export async function runSingleStepInner(step, ctx) {
584
627
  else if (raced.kind === "timeout")
585
628
  settled = createHerdrExternalAdapter(step.runner.adapter).normalize(evidenceInput, "Placed external run timed out; truthful pane retained for inspection.");
586
629
  else
587
- return { agent: step.agent, context: step.context, output: "Placed external run stopped by user.", outputState: "present", exitCode: 1, stopped: true, runner, execution: { status: "stopped", success: false, exitCode: 1, stopped: true } };
630
+ return { agent: step.agent, context: step.context, output: "Placed external run stopped.", outputState: "present", exitCode: 1, stopped: true, runner, execution: { status: "stopped", success: false, exitCode: 1, stopped: true } };
588
631
  }
589
632
  catch (error) {
590
633
  retain = true;
@@ -599,7 +642,7 @@ export async function runSingleStepInner(step, ctx) {
599
642
  const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, resolvedOutput.fullOutput) : undefined;
600
643
  const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({ fullOutput: resolvedOutput.fullOutput, outputPath: step.outputPath, outputMode: step.outputMode, exitCode: 1, preserveSavedOutput: true, savedPath: resolvedOutput.savedPath, outputReference, saveError: resolvedOutput.saveError }));
601
644
  const artifactErrors = artifactPaths && ctx.artifactConfig?.enabled !== false ? persistStepArtifacts({ artifactPaths, artifactConfig: ctx.artifactConfig, output: formatOutputArtifactContent(omitUndefinedProperties({ output: resolvedOutput.fullOutput, metadataPath: ctx.artifactConfig?.includeMetadata === false ? undefined : artifactPaths.metadataPath })), metadata: { runId: ctx.id, parentSessionId: step.parentSessionId, agent: step.agent, task: PROMPT_REDACTED, runner, placement: session.owner.identity, settlement: settled.settlement, outcome: settled.outcome, timestamp: Date.now() } }) : {};
602
- return omitUndefinedProperties({ agent: step.agent, ...(childSessionName ? { sessionName: childSessionName } : {}), context: step.context, output: finalizedOutput.displayOutput, outputState: output.trim() ? "present" : "absent", exitCode: 1, error: resolvedOutput.fatalError ? resolvedOutput.saveError : undefined, timedOut, stopped, artifactPaths, outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined, metadataSaveError: artifactErrors.metadataSaveError, runner, execution: { status: "partial", success: false, exitCode: 1 } });
645
+ return omitUndefinedProperties({ agent: step.agent, ...(childSessionName ? { sessionName: childSessionName } : {}), context: step.context, output: finalizedOutput.displayOutput, outputState: output.trim() ? "present" : "absent", exitCode: 1, error: isSettlementFatalOutputError(resolvedOutput, step.outputMode) ? resolvedOutput.saveError : undefined, timedOut, stopped, artifactPaths, outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined, metadataSaveError: artifactErrors.metadataSaveError, runner, execution: { status: "partial", success: false, exitCode: 1 } });
603
646
  }
604
647
  catch (error) {
605
648
  // Any post-allocation uncertainty retains the pane; only explicit stop
@@ -655,50 +698,22 @@ export async function runSingleStepInner(step, ctx) {
655
698
  const ran = await runExternalCli(preparedExternal.input);
656
699
  const externalProcess = preparedExternal.decorateProcess(ran.externalProcess);
657
700
  const external = step.machine ? decorateHerdrMachineResult(ran, step.machine, step.runner.adapter) : ran;
658
- try {
659
- fs.writeFileSync(ctx.outputFile, external.output, "utf-8");
660
- }
661
- catch { /* Observability output is best-effort. */ }
662
- const resolvedOutput = step.outputPath && external.exitCode === 0
663
- ? resolveSingleOutput(step.outputPath, external.output, outputSnapshot, step.outputClaimPath)
664
- : { fullOutput: external.output };
665
- const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, resolvedOutput.fullOutput) : undefined;
666
- const exitCode = resolvedOutput.fatalError ? 1 : external.exitCode;
667
- const error = resolvedOutput.fatalError && resolvedOutput.saveError
668
- ? external.error ? `${external.error}\n${resolvedOutput.saveError}` : resolvedOutput.saveError
669
- : external.error;
670
- const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
671
- fullOutput: resolvedOutput.fullOutput,
672
- outputPath: step.outputPath,
673
- outputMode: step.outputMode,
674
- exitCode: exitCode ?? 1,
675
- savedPath: resolvedOutput.savedPath,
676
- outputReference,
677
- saveError: resolvedOutput.saveError,
678
- }));
679
- const artifactErrors = artifactPaths && ctx.artifactConfig?.enabled !== false
680
- ? persistStepArtifacts({
681
- artifactPaths,
682
- artifactConfig: ctx.artifactConfig,
683
- output: formatOutputArtifactContent(omitUndefinedProperties({ output: resolvedOutput.fullOutput, error: external.error, metadataPath: ctx.artifactConfig?.includeMetadata === false ? undefined : artifactPaths.metadataPath })),
684
- metadata: { runId: ctx.id, parentSessionId: step.parentSessionId, agent: step.agent, task: PROMPT_REDACTED, runner, externalProcess: externalProcess, exitCode: external.exitCode, error: external.error, timestamp: Date.now() },
685
- })
686
- : {};
701
+ const settled = settleExternalOutput(external, outputSnapshot, { runner, externalProcess });
687
702
  return omitUndefinedProperties({
688
703
  agent: step.agent,
689
704
  ...(childSessionName ? { sessionName: childSessionName } : {}),
690
705
  context: step.context,
691
- output: finalizedOutput.displayOutput,
706
+ output: settled.output,
692
707
  outputState: external.output.trim() ? "present" : "absent",
693
- exitCode,
694
- error,
708
+ exitCode: settled.exitCode,
709
+ error: settled.error,
695
710
  timedOut: external.timedOut,
696
711
  stopped: external.stopped,
697
712
  processSignal: external.processSignal,
698
713
  artifactPaths,
699
- outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
700
- artifactOutputSaveFailed: artifactErrors.outputSaveError ? true : undefined,
701
- metadataSaveError: artifactErrors.metadataSaveError,
714
+ outputSaveError: settled.outputSaveError,
715
+ artifactOutputSaveFailed: settled.artifactOutputSaveFailed,
716
+ metadataSaveError: settled.metadataSaveError,
702
717
  runner,
703
718
  externalProcess: externalProcess,
704
719
  });
@@ -728,49 +743,21 @@ export async function runSingleStepInner(step, ctx) {
728
743
  onExternalJob: ctx.onExternalJob,
729
744
  followUp: step.externalJobFollowUp,
730
745
  }));
731
- try {
732
- fs.writeFileSync(ctx.outputFile, external.output, "utf-8");
733
- }
734
- catch { /* Observability output is best-effort. */ }
735
- const resolvedOutput = step.outputPath && external.exitCode === 0
736
- ? resolveSingleOutput(step.outputPath, external.output, outputSnapshot, step.outputClaimPath)
737
- : { fullOutput: external.output };
738
- const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, resolvedOutput.fullOutput) : undefined;
739
- const exitCode = resolvedOutput.fatalError ? 1 : external.exitCode;
740
- const error = resolvedOutput.fatalError && resolvedOutput.saveError
741
- ? external.error ? `${external.error}\n${resolvedOutput.saveError}` : resolvedOutput.saveError
742
- : external.error;
743
- const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
744
- fullOutput: resolvedOutput.fullOutput,
745
- outputPath: step.outputPath,
746
- outputMode: step.outputMode,
747
- exitCode: exitCode ?? 1,
748
- savedPath: resolvedOutput.savedPath,
749
- outputReference,
750
- saveError: resolvedOutput.saveError,
751
- }));
752
- const artifactErrors = artifactPaths && ctx.artifactConfig?.enabled !== false
753
- ? persistStepArtifacts({
754
- artifactPaths,
755
- artifactConfig: ctx.artifactConfig,
756
- output: formatOutputArtifactContent(omitUndefinedProperties({ output: resolvedOutput.fullOutput, error: external.error, metadataPath: ctx.artifactConfig?.includeMetadata === false ? undefined : artifactPaths.metadataPath })),
757
- metadata: { runId: ctx.id, parentSessionId: step.parentSessionId, agent: step.agent, task: PROMPT_REDACTED, runner, externalJob: external.externalJob, exitCode: external.exitCode, error: external.error, timestamp: Date.now() },
758
- })
759
- : {};
746
+ const settled = settleExternalOutput(external, outputSnapshot, { runner, externalJob: external.externalJob });
760
747
  return omitUndefinedProperties({
761
748
  agent: step.agent,
762
749
  ...(childSessionName ? { sessionName: childSessionName } : {}),
763
750
  context: step.context,
764
- output: finalizedOutput.displayOutput,
751
+ output: settled.output,
765
752
  outputState: external.output.trim() ? "present" : "absent",
766
- exitCode,
767
- error,
753
+ exitCode: settled.exitCode,
754
+ error: settled.error,
768
755
  timedOut: external.timedOut,
769
756
  stopped: external.stopped,
770
757
  artifactPaths,
771
- outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
772
- artifactOutputSaveFailed: artifactErrors.outputSaveError ? true : undefined,
773
- metadataSaveError: artifactErrors.metadataSaveError,
758
+ outputSaveError: settled.outputSaveError,
759
+ artifactOutputSaveFailed: settled.artifactOutputSaveFailed,
760
+ metadataSaveError: settled.metadataSaveError,
774
761
  runner,
775
762
  externalJob: external.externalJob,
776
763
  });
@@ -1020,7 +1007,11 @@ export async function runSingleStepInner(step, ctx) {
1020
1007
  const mutationEvidence = run.nativeMachine ? { source: "tracked-files", trackedOnly: true, changedFiles: [], attemptedMutation: remoteGitChanged === true, ...(remoteGitChanged === undefined ? { unavailable: "Remote Git before/after evidence was incomplete." } : {}) } : collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
1021
1008
  finalMutationEvidence = mutationEvidence;
1022
1009
  const mutationAttemptObserved = run.observedMutationAttempt === true || mutationEvidence.attemptedMutation === true;
1023
- const finalOutputHasPersistableFileContent = run.exitCode === 0 && !run.error && !emptyOutputError && Boolean(stripAcceptanceReport(run.finalOutput).trim());
1010
+ // Validated structured output is persistable file content even when the final text is only
1011
+ // an acceptance-report fence; otherwise the required-output precheck forces exitCode 1 before
1012
+ // resolveSingleOutput can publish the canonical JSON into the bound file-only path.
1013
+ const finalOutputHasPersistableFileContent = run.exitCode === 0 && !run.error && !emptyOutputError
1014
+ && (Boolean(stripAcceptanceReport(run.finalOutput).trim()) || validatedStructuredOutput);
1024
1015
  const requiredOutput = step.outputMode === "file-only" && step.outputPath
1025
1016
  ? { kind: "file-only", path: step.outputPath, missing: !fs.existsSync(step.outputPath) && !finalOutputHasPersistableFileContent }
1026
1017
  : effectiveStructuredOutput
@@ -1104,7 +1095,7 @@ export async function runSingleStepInner(step, ctx) {
1104
1095
  const resolvedOutput = step.outputPath && finalResult?.exitCode === 0
1105
1096
  ? resolveSingleOutput(step.outputPath, structuredText ?? replyOutput, finalOutputSnapshot, step.outputClaimPath)
1106
1097
  : { fullOutput: replyOutput };
1107
- if (resolvedOutput.fatalError) {
1098
+ if (isSettlementFatalOutputError(resolvedOutput, step.outputMode)) {
1108
1099
  if (finalResult) {
1109
1100
  finalResult.exitCode = 1;
1110
1101
  finalResult.error = finalResult.error ? `${finalResult.error}\n${resolvedOutput.saveError}` : resolvedOutput.saveError;
@@ -1114,7 +1105,7 @@ export async function runSingleStepInner(step, ctx) {
1114
1105
  const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, output) : undefined;
1115
1106
  let outputForSummary = output;
1116
1107
  if (finalResult?.stopped && !outputForSummary.trim()) {
1117
- outputForSummary = ctx.stopMessage ?? "Subagent stopped by user.";
1108
+ outputForSummary = ctx.stopMessage ?? SUBAGENT_STOPPED_MESSAGE;
1118
1109
  }
1119
1110
  // Unfinished streamed text never stands in for the child's completed reply.
1120
1111
  const outputForAcceptance = finalResult?.outputPartial ? "" : rawOutput;
@@ -1167,7 +1158,7 @@ export async function runSingleStepInner(step, ctx) {
1167
1158
  cwd: step.cwd ?? ctx.cwd,
1168
1159
  stagedIndexBaseline,
1169
1160
  signal: combinedAbortSignal([ctx.timeoutSignal, ctx.stopSignal]),
1170
- abortMessage: ctx.stopSignal?.aborted ? ctx.stopMessage ?? "Subagent stopped by user." : ctx.timeoutMessage ?? "Subagent timed out.",
1161
+ abortMessage: ctx.stopSignal?.aborted ? ctx.stopMessage ?? SUBAGENT_STOPPED_MESSAGE : ctx.timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE,
1171
1162
  reportOptional: isAgentContract(step.agentContract),
1172
1163
  artifactsDir: ctx.artifactsDir,
1173
1164
  runId: ctx.id,
@@ -1194,9 +1185,9 @@ export async function runSingleStepInner(step, ctx) {
1194
1185
  }
1195
1186
  const intercomDetachReceipt = finalResult?.finalOutput === INTERCOM_DETACH_RECEIPT;
1196
1187
  const baseFinalError = stoppedAfterAcceptance
1197
- ? ctx.stopMessage ?? "Subagent stopped by user."
1188
+ ? ctx.stopMessage ?? SUBAGENT_STOPPED_MESSAGE
1198
1189
  : timedOutAfterAcceptance
1199
- ? finalResult?.error ?? ctx.timeoutMessage ?? "Subagent timed out."
1190
+ ? finalResult?.error ?? ctx.timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE
1200
1191
  : acceptanceCanFailRun
1201
1192
  ? (finalResult?.error ? `${finalResult.error}\n${acceptanceFailure}` : acceptanceFailure)
1202
1193
  : finalResult?.error ?? (intercomDetachReceipt ? INTERCOM_DETACH_RECEIPT : undefined);
@@ -1343,8 +1334,8 @@ function markParallelGroupSetupFailure(input) {
1343
1334
  const paused = !stopped && input.statusPayload.state === "paused";
1344
1335
  const timedOut = !stopped && !paused && input.statusPayload.timedOut === true;
1345
1336
  statusStep.status = stopped ? "stopped" : paused ? "paused" : "failed";
1346
- // Mirror the run-level timeout onto the step so consumers (status readers,
1347
- // run-history recording) see timed_out rather than a bare failure — the
1337
+ // Mirror the run-level timeout onto the step so status readers see
1338
+ // timed_out rather than a bare failure — the
1348
1339
  // StepResult below already carries it, the status step must too.
1349
1340
  if (timedOut)
1350
1341
  statusStep.timedOut = true;
@@ -1518,11 +1509,6 @@ export async function runSubagent(config, childSessions) {
1518
1509
  let previousOutput = "";
1519
1510
  const outputs = {};
1520
1511
  const results = [];
1521
- // Flat indices of steps a child session was actually dispatched for — the
1522
- // ground truth for run-history: stopRunner/timeoutRunner/fail-fast/budget
1523
- // skips relabel never-launched steps to terminal statuses, so status alone
1524
- // cannot distinguish them from steps that really ran.
1525
- const launchedFlatIndices = new Set();
1526
1512
  const overallStartTime = Date.now();
1527
1513
  const shareEnabled = config.share === true;
1528
1514
  const asyncDir = config.asyncDir;
@@ -1549,7 +1535,7 @@ export async function runSubagent(config, childSessions) {
1549
1535
  let stopped = false;
1550
1536
  let usageBudgetExceeded = false;
1551
1537
  const timeoutMessage = config.timeoutMs !== undefined ? `Subagent timed out after ${config.timeoutMs}ms.` : undefined;
1552
- const stopMessage = "Subagent stopped by user.";
1538
+ const stopMessage = SUBAGENT_STOPPED_MESSAGE;
1553
1539
  const timeoutAbortController = new AbortController();
1554
1540
  const stopAbortController = new AbortController();
1555
1541
  const setupInterruptController = new AbortController();
@@ -1705,6 +1691,7 @@ export async function runSubagent(config, childSessions) {
1705
1691
  ...(config.launchResolvedExtensions ? { launchResolvedExtensions: config.launchResolvedExtensions } : {}),
1706
1692
  ...(config.capabilityCeiling ? { capabilityCeiling: config.capabilityCeiling } : {}),
1707
1693
  ...(config.runFanoutBudget ? { runFanoutBudget: getRunFanoutBudgetSnapshot(config.runFanoutBudget) } : {}),
1694
+ ...(config.revivalLease ? { revivedFrom: config.revivalLease.sourceRunId } : {}),
1708
1695
  ...(config.parentWorkflowRunId ? { parentWorkflowRunId: config.parentWorkflowRunId } : {}),
1709
1696
  ...(config.workflowKey ? { workflowKey: config.workflowKey } : {}),
1710
1697
  ...(config.lane ? { lane: config.lane } : {}),
@@ -2211,8 +2198,8 @@ export async function runSubagent(config, childSessions) {
2211
2198
  agent,
2212
2199
  sessionName,
2213
2200
  context,
2214
- output: timeoutMessage ?? "Subagent timed out.",
2215
- error: timeoutMessage ?? "Subagent timed out.",
2201
+ output: timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE,
2202
+ error: timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE,
2216
2203
  exitCode: 1,
2217
2204
  timedOut: true,
2218
2205
  });
@@ -2251,6 +2238,7 @@ export async function runSubagent(config, childSessions) {
2251
2238
  });
2252
2239
  mutatingFailureStates.push(...Array.from({ length: added.addedFlatSteps }, () => createMutatingFailureState()));
2253
2240
  pendingToolResults.push(...Array.from({ length: added.addedFlatSteps }, () => undefined));
2241
+ pendingToolResultsByCallId.push(...Array.from({ length: added.addedFlatSteps }, () => new Map()));
2254
2242
  if (config.childIntercomTargets) {
2255
2243
  config.childIntercomTargets = statusPayload.steps.map((statusStep, index) => resolveSubagentIntercomTarget(id, statusStep.agent, index));
2256
2244
  }
@@ -2347,6 +2335,9 @@ export async function runSubagent(config, childSessions) {
2347
2335
  const activeLongRunningSteps = new Set();
2348
2336
  const mutatingFailureStates = initialStatusSteps.map(() => createMutatingFailureState());
2349
2337
  const pendingToolResults = initialStatusSteps.map(() => undefined);
2338
+ // Pi's parallel tool execution emits every tool_execution_start before the first result message, so each
2339
+ // step matches results to calls by toolCallId; pendingToolResults keeps the last-started call for events without one.
2340
+ const pendingToolResultsByCallId = initialStatusSteps.map(() => new Map());
2350
2341
  const activeToolCalls = initialStatusSteps.map(() => new Map());
2351
2342
  const activeToolKeysByName = initialStatusSteps.map(() => new Map());
2352
2343
  const activeToolSequences = initialStatusSteps.map(() => 0);
@@ -2481,6 +2472,7 @@ export async function runSubagent(config, childSessions) {
2481
2472
  step.activityState = "needs_attention";
2482
2473
  statusPayload.activityState = "needs_attention";
2483
2474
  const toolDurationMs = Math.max(0, now - target.startedAt);
2475
+ const toolCallId = target.key.startsWith("id:") ? target.key.slice(3) : undefined;
2484
2476
  appendControlEvent(buildControlEvent(omitUndefinedProperties({
2485
2477
  type: "needs_attention",
2486
2478
  from: previous,
@@ -2495,7 +2487,8 @@ export async function runSubagent(config, childSessions) {
2495
2487
  tokens: step.tokens?.total,
2496
2488
  toolCount: step.toolCount,
2497
2489
  currentTool: target.tool,
2498
- toolCallId: target.key.startsWith("id:") ? target.key.slice(3) : undefined,
2490
+ toolCallId,
2491
+ hasCommandController: Boolean(toolCallId && retainsChildCommand(supervisorChannelDir(id, step.agent, flatIndex), toolCallId)),
2499
2492
  currentToolDurationMs: toolDurationMs,
2500
2493
  currentPath: target.path,
2501
2494
  })));
@@ -2698,11 +2691,72 @@ export async function runSubagent(config, childSessions) {
2698
2691
  if (!step)
2699
2692
  return;
2700
2693
  setOptionalProperty(step, "model", model);
2694
+ setOptionalProperty(step, "launchModel", undefined);
2701
2695
  setOptionalProperty(step, "thinking", thinking);
2702
2696
  setOptionalProperty(step, "contextLimit", contextLimit);
2703
2697
  statusPayload.lastUpdate = now;
2704
2698
  writeStatusPayload();
2705
2699
  };
2700
+ // Records a finished child's result on its status step; the sequential and both parallel paths share it.
2701
+ const recordStepResult = (fi, singleResult, startedAt, endedAt, recordLaunchContractDigest = false) => {
2702
+ const step = requiredStatusStep(statusPayload, fi);
2703
+ const childStopped = singleResult.stopped === true;
2704
+ const childInterrupted = singleResult.interrupted === true;
2705
+ step.status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.execution?.status === "partial" ? "partial" : singleResult.exitCode === 0 ? "complete" : "failed";
2706
+ step.endedAt = endedAt;
2707
+ step.durationMs = endedAt - startedAt;
2708
+ step.exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
2709
+ setOptionalProperty(step, "timedOut", timedOut || singleResult.timedOut ? true : undefined);
2710
+ setOptionalProperty(step, "stopped", stopped || childStopped ? true : undefined);
2711
+ setOptionalProperty(step, "toolBudget", singleResult.toolBudget);
2712
+ setOptionalProperty(step, "toolBudgetBlocked", singleResult.toolBudgetBlocked);
2713
+ if (singleResult.toolBudget)
2714
+ statusPayload.toolBudget = singleResult.toolBudget;
2715
+ if (singleResult.toolBudgetBlocked)
2716
+ statusPayload.toolBudgetBlocked = true;
2717
+ setOptionalProperty(step, "sessionName", singleResult.sessionName);
2718
+ // Keep the model the child was observed running on; a host extension can switch it
2719
+ // after launch, and singleResult.model only names the launch candidate.
2720
+ setOptionalProperty(step, "model", step.model ?? singleResult.model);
2721
+ setOptionalProperty(step, "thinking", resolveEffectiveThinking(singleResult.model, step.thinking));
2722
+ setOptionalProperty(step, "requestedModel", singleResult.requestedModel);
2723
+ setOptionalProperty(step, "contextOverflow", singleResult.contextOverflow);
2724
+ setOptionalProperty(step, "totalCost", singleResult.totalCost);
2725
+ setOptionalProperty(step, "error", stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE) : singleResult.error);
2726
+ setOptionalProperty(step, "transcriptPath", singleResult.transcriptPath ?? step.transcriptPath);
2727
+ setOptionalProperty(step, "transcriptError", singleResult.transcriptError);
2728
+ setOptionalProperty(step, "agentContract", singleResult.agentContract);
2729
+ if (recordLaunchContractDigest)
2730
+ setOptionalProperty(step, "launchContractDigest", singleResult.launchContractDigest);
2731
+ setOptionalProperty(step, "launchResolvedExtensions", singleResult.launchResolvedExtensions);
2732
+ setOptionalProperty(step, "runtimeAcknowledgedExtensions", singleResult.runtimeAcknowledgedExtensions);
2733
+ setOptionalProperty(step, "effects", singleResult.effects);
2734
+ setOptionalProperty(step, "execution", singleResult.execution);
2735
+ setOptionalProperty(step, "review", singleResult.review);
2736
+ setOptionalProperty(step, "structuredOutput", singleResult.structuredOutput);
2737
+ setOptionalProperty(step, "structuredOutputPath", singleResult.structuredOutputPath);
2738
+ setOptionalProperty(step, "structuredOutputSchemaPath", singleResult.structuredOutputSchemaPath);
2739
+ setOptionalProperty(step, "acceptance", singleResult.acceptance);
2740
+ setOptionalProperty(step, "timeoutRecovery", singleResult.timeoutRecovery);
2741
+ setOptionalProperty(step, "watchdog", singleResult.watchdog);
2742
+ setOptionalProperty(step, "capabilityCeiling", singleResult.capabilityCeiling);
2743
+ setOptionalProperty(step, "capabilityAudit", singleResult.capabilityAudit);
2744
+ if (singleResult.capabilityCeiling)
2745
+ statusPayload.capabilityCeiling = singleResult.capabilityCeiling;
2746
+ if (singleResult.capabilityAudit)
2747
+ statusPayload.capabilityAudit = singleResult.capabilityAudit;
2748
+ };
2749
+ // Adds a parallel child's cost to the in-flight usage ledger.
2750
+ const addParallelUsageCost = (totalCost) => {
2751
+ if (!totalCost)
2752
+ return;
2753
+ pendingParallelUsageCost = {
2754
+ inputTokens: pendingParallelUsageCost.inputTokens + totalCost.inputTokens,
2755
+ outputTokens: pendingParallelUsageCost.outputTokens + totalCost.outputTokens,
2756
+ costUsd: pendingParallelUsageCost.costUsd + totalCost.costUsd,
2757
+ };
2758
+ refreshUsageBudget();
2759
+ };
2706
2760
  const updateStepFromChildEvent = (flatIndex, event) => {
2707
2761
  const step = statusPayload.steps[flatIndex];
2708
2762
  if (!step)
@@ -2745,11 +2799,15 @@ export async function runSubagent(config, childSessions) {
2745
2799
  step.toolCount = (step.toolCount ?? 0) + 1;
2746
2800
  const configuredToolBudget = flatSteps[flatIndex]?.toolBudget;
2747
2801
  if (configuredToolBudget) {
2748
- step.toolBudget = toolBudgetState(configuredToolBudget, step.toolCount);
2802
+ step.toolBudget = toolBudgetState(configuredToolBudget, step.toolCount, step.toolBudget?.blockedTool);
2749
2803
  statusPayload.toolBudget = step.toolBudget;
2750
2804
  }
2751
2805
  recordActiveToolCall(flatIndex, { toolCallId: event.toolCallId, toolName: event.toolName }, { argsPreview, currentPath, blocksSupervisor, now });
2752
- pendingToolResults[flatIndex] = omitUndefinedProperties({ tool: event.toolName, path: currentPath, mutates, startedAt: now });
2806
+ const pending = omitUndefinedProperties({ tool: event.toolName, path: currentPath, mutates, startedAt: now });
2807
+ pendingToolResults[flatIndex] = pending;
2808
+ const startedCallId = event.toolCallId;
2809
+ if (typeof startedCallId === "string" && startedCallId)
2810
+ pendingToolResultsByCallId[flatIndex].set(startedCallId, pending);
2753
2811
  statusPayload.toolCount = (statusPayload.toolCount ?? 0) + 1;
2754
2812
  syncTopLevelCurrentTool();
2755
2813
  if (controlConfig.enabled && blocksSupervisor && step.activityState !== "needs_attention") {
@@ -2794,21 +2852,30 @@ export async function runSubagent(config, childSessions) {
2794
2852
  }
2795
2853
  syncTopLevelCurrentTool();
2796
2854
  }
2797
- else if (event.type === "tool_result_end" && event.message) {
2798
- const toolSnapshot = pendingToolResults[flatIndex];
2799
- pendingToolResults[flatIndex] = undefined;
2855
+ else if (event.type === "message_end" && event.message?.role === "toolResult") {
2856
+ // Pi delivers every tool result, including a blocked call, as a toolResult message after tool_execution_end.
2857
+ const resultCallId = event.message.toolCallId;
2858
+ const byCallId = pendingToolResultsByCallId[flatIndex];
2859
+ const keyedSnapshot = typeof resultCallId === "string" ? byCallId.get(resultCallId) : undefined;
2860
+ if (keyedSnapshot)
2861
+ byCallId.delete(resultCallId);
2862
+ const toolSnapshot = keyedSnapshot ?? pendingToolResults[flatIndex];
2863
+ if (!keyedSnapshot || pendingToolResults[flatIndex] === keyedSnapshot)
2864
+ pendingToolResults[flatIndex] = undefined;
2800
2865
  const resultText = extractTextFromContent(event.message.content);
2801
- if (toolSnapshot && resultText.includes("Tool budget hard limit reached")) {
2802
- const configuredToolBudget = flatSteps[flatIndex]?.toolBudget;
2803
- if (configuredToolBudget) {
2804
- step.toolBudget = toolBudgetState(configuredToolBudget, step.toolCount ?? 0, toolSnapshot.tool);
2805
- step.toolBudgetBlocked = true;
2806
- statusPayload.toolBudget = step.toolBudget;
2807
- statusPayload.toolBudgetBlocked = true;
2808
- }
2866
+ const resultToolName = event.message.toolName;
2867
+ const blockedTool = (typeof resultToolName === "string" && resultToolName.length > 0 ? resultToolName : undefined) ?? toolSnapshot?.tool;
2868
+ const configuredToolBudget = flatSteps[flatIndex]?.toolBudget;
2869
+ const budgetBlocked = configuredToolBudget !== undefined && isToolBudgetBlockedMessage(configuredToolBudget, resultText, blockedTool);
2870
+ if (budgetBlocked) {
2871
+ step.toolBudget = toolBudgetState(configuredToolBudget, step.toolCount ?? 0, blockedTool);
2872
+ step.toolBudgetBlocked = true;
2873
+ statusPayload.toolBudget = step.toolBudget;
2874
+ statusPayload.toolBudgetBlocked = true;
2809
2875
  }
2810
2876
  appendRecentStepOutput(step, resultText.split("\n").slice(-10));
2811
- if (toolSnapshot?.mutates && didMutatingToolFail(resultText)) {
2877
+ // A budget block has its own signal; it is not a failing mutation.
2878
+ if (toolSnapshot?.mutates && !budgetBlocked && didMutatingToolFail(event.message)) {
2812
2879
  const state = mutatingFailureStates[flatIndex];
2813
2880
  recordMutatingFailure(state, omitUndefinedProperties({
2814
2881
  tool: toolSnapshot.tool,
@@ -2847,6 +2914,20 @@ export async function runSubagent(config, childSessions) {
2847
2914
  else if (event.type === "message_end" && event.message?.role === "assistant") {
2848
2915
  appendRecentStepOutput(step, stripAcceptanceReport(extractTextFromContent(event.message.content)).split("\n").slice(-10));
2849
2916
  step.turnCount = (step.turnCount ?? 0) + 1;
2917
+ // The launch model is only a candidate: a host extension (e.g. a model
2918
+ // router) can switch the child's model on its first input, so report the
2919
+ // model each assistant message actually ran on. Mirrors the foreground
2920
+ // path's `progress.model = observedModel`.
2921
+ if (event.message.model) {
2922
+ const observedModel = qualifyModelWithProvider(event.message.model, event.message.provider, flatSteps[flatIndex]?.modelVerificationRegistry) ?? event.message.model;
2923
+ if (observedModel !== step.model) {
2924
+ step.launchModel ??= step.model;
2925
+ step.model = observedModel;
2926
+ }
2927
+ }
2928
+ const { provider, model } = event.message;
2929
+ if (typeof provider === "string" && provider && typeof model === "string" && model)
2930
+ step.resolvedModel = { provider, model };
2850
2931
  const usage = event.message.usage;
2851
2932
  if (config.usageBudget) {
2852
2933
  const known = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
@@ -3099,7 +3180,7 @@ export async function runSubagent(config, childSessions) {
3099
3180
  return;
3100
3181
  timedOut = true;
3101
3182
  const now = Date.now();
3102
- const message = timeoutMessage ?? "Subagent timed out.";
3183
+ const message = timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE;
3103
3184
  statusPayload.timedOut = true;
3104
3185
  statusPayload.error = message;
3105
3186
  currentActivityState = undefined;
@@ -3293,7 +3374,7 @@ export async function runSubagent(config, childSessions) {
3293
3374
  }),
3294
3375
  cwd,
3295
3376
  signal: combinedAbortSignal([timeoutAbortController.signal, stopAbortController.signal]),
3296
- abortMessage: stopAbortController.signal.aborted ? stopMessage : timeoutMessage ?? "Subagent timed out.",
3377
+ abortMessage: stopAbortController.signal.aborted ? stopMessage : timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE,
3297
3378
  reportOptional: isAgentContract(step.agentContract),
3298
3379
  }))
3299
3380
  : undefined;
@@ -3304,7 +3385,7 @@ export async function runSubagent(config, childSessions) {
3304
3385
  placeholder.acceptance = effectiveGroupAcceptance;
3305
3386
  const groupAcceptanceFailure = effectiveGroupAcceptance && (!isAgentContract(step.agentContract) || step.gateOn === "acceptance") ? acceptanceFailureMessage(effectiveGroupAcceptance) : undefined;
3306
3387
  if (groupTimedOut || groupStopped || groupAcceptanceFailure) {
3307
- const errorMessage = groupStopped ? stopMessage : groupTimedOut ? timeoutMessage ?? "Subagent timed out." : groupAcceptanceFailure;
3388
+ const errorMessage = groupStopped ? stopMessage : groupTimedOut ? timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE : groupAcceptanceFailure;
3308
3389
  statusPayload.state = groupStopped ? "stopped" : "failed";
3309
3390
  statusPayload.error = errorMessage;
3310
3391
  setOptionalProperty(statusPayload, "stopped", groupStopped ? true : statusPayload.stopped);
@@ -3352,7 +3433,6 @@ export async function runSubagent(config, childSessions) {
3352
3433
  dynamic: step.parallel.acceptanceInput === undefined,
3353
3434
  agentContract: step.parallel.agentContract ?? step.agentContract,
3354
3435
  })),
3355
- systemPrompt: step.parallel.namespaceOutputPath ? injectOutputPathSystemPrompt(step.parallel.systemPrompt ?? "", outputPath, step.parallel) : step.parallel.systemPrompt,
3356
3436
  outputPath,
3357
3437
  label: task.label ?? step.parallel.label,
3358
3438
  ...(step.sessionFiles?.[itemIndex] ? { sessionFile: step.sessionFiles[itemIndex] } : {}),
@@ -3397,6 +3477,7 @@ export async function runSubagent(config, childSessions) {
3397
3477
  }
3398
3478
  mutatingFailureStates.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps.map(() => createMutatingFailureState()));
3399
3479
  pendingToolResults.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps.map(() => undefined));
3480
+ pendingToolResultsByCallId.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps.map(() => new Map()));
3400
3481
  const materializedDelta = dynamicStatusSteps.length - 1;
3401
3482
  for (const group of statusPayload.parallelGroups) {
3402
3483
  if (group.stepIndex === stepIndex) {
@@ -3477,7 +3558,6 @@ export async function runSubagent(config, childSessions) {
3477
3558
  return omitUndefinedProperties({ agent: task.agent, ...(task.sessionName ? { sessionName: task.sessionName } : {}), context: task.context, output: "(skipped — fail-fast)", exitCode: -1, skipped: true });
3478
3559
  }
3479
3560
  const taskStartTime = Date.now();
3480
- launchedFlatIndices.add(fi);
3481
3561
  statusPayload.currentStep = fi;
3482
3562
  requiredStatusStep(statusPayload, fi).status = "running";
3483
3563
  delete requiredStatusStep(statusPayload, fi).error;
@@ -3531,54 +3611,8 @@ export async function runSubagent(config, childSessions) {
3531
3611
  const taskEndTime = Date.now();
3532
3612
  const childInterrupted = singleResult.interrupted === true;
3533
3613
  const childStopped = singleResult.stopped === true;
3534
- requiredStatusStep(statusPayload, fi).status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.execution?.status === "partial" ? "partial" : singleResult.exitCode === 0 ? "complete" : "failed";
3535
- requiredStatusStep(statusPayload, fi).endedAt = taskEndTime;
3536
- requiredStatusStep(statusPayload, fi).durationMs = taskEndTime - taskStartTime;
3537
- requiredStatusStep(statusPayload, fi).exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
3538
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "timedOut", timedOut || singleResult.timedOut ? true : undefined);
3539
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "stopped", stopped || childStopped ? true : undefined);
3540
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "toolBudget", singleResult.toolBudget);
3541
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "toolBudgetBlocked", singleResult.toolBudgetBlocked);
3542
- if (singleResult.toolBudget)
3543
- statusPayload.toolBudget = singleResult.toolBudget;
3544
- if (singleResult.toolBudgetBlocked)
3545
- statusPayload.toolBudgetBlocked = true;
3546
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "sessionName", singleResult.sessionName);
3547
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "model", singleResult.model);
3548
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "thinking", resolveEffectiveThinking(singleResult.model, requiredStatusStep(statusPayload, fi).thinking));
3549
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "requestedModel", singleResult.requestedModel);
3550
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "contextOverflow", singleResult.contextOverflow);
3551
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "totalCost", singleResult.totalCost);
3552
- if (singleResult.totalCost) {
3553
- pendingParallelUsageCost = {
3554
- inputTokens: pendingParallelUsageCost.inputTokens + singleResult.totalCost.inputTokens,
3555
- outputTokens: pendingParallelUsageCost.outputTokens + singleResult.totalCost.outputTokens,
3556
- costUsd: pendingParallelUsageCost.costUsd + singleResult.totalCost.costUsd,
3557
- };
3558
- refreshUsageBudget();
3559
- }
3560
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "error", stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error);
3561
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "transcriptPath", singleResult.transcriptPath ?? requiredStatusStep(statusPayload, fi).transcriptPath);
3562
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "transcriptError", singleResult.transcriptError);
3563
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "agentContract", singleResult.agentContract);
3564
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "launchContractDigest", singleResult.launchContractDigest);
3565
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "launchResolvedExtensions", singleResult.launchResolvedExtensions);
3566
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "runtimeAcknowledgedExtensions", singleResult.runtimeAcknowledgedExtensions);
3567
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "effects", singleResult.effects);
3568
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "execution", singleResult.execution);
3569
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "review", singleResult.review);
3570
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "structuredOutput", singleResult.structuredOutput);
3571
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "structuredOutputPath", singleResult.structuredOutputPath);
3572
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "structuredOutputSchemaPath", singleResult.structuredOutputSchemaPath);
3573
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "acceptance", singleResult.acceptance);
3574
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "timeoutRecovery", singleResult.timeoutRecovery);
3575
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "watchdog", singleResult.watchdog);
3576
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "capabilityCeiling", singleResult.capabilityCeiling);
3577
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "capabilityAudit", singleResult.capabilityAudit);
3578
- if (singleResult.capabilityCeiling)
3579
- statusPayload.capabilityCeiling = singleResult.capabilityCeiling;
3580
- if (singleResult.capabilityAudit)
3581
- statusPayload.capabilityAudit = singleResult.capabilityAudit;
3614
+ recordStepResult(fi, singleResult, taskStartTime, taskEndTime, true);
3615
+ addParallelUsageCost(singleResult.totalCost);
3582
3616
  statusPayload.lastUpdate = taskEndTime;
3583
3617
  writeStatusPayload();
3584
3618
  appendCapabilityCeilingAppliedEvent(eventsPath, id, fi, task.agent, singleResult);
@@ -3591,7 +3625,7 @@ export async function runSubagent(config, childSessions) {
3591
3625
  appendTerminalChildStatusEvent(fi, taskEndTime);
3592
3626
  if (singleResult.exitCode !== 0 && failFast && !childStopped)
3593
3627
  aborted = true;
3594
- return stopped || childStopped ? { ...singleResult, output: stopMessage, error: stopMessage, exitCode: 1, interrupted: false, timedOut: false, stopped: true, skipped: false } : timedOut ? { ...singleResult, output: singleResult.output || (timeoutMessage ?? "Subagent timed out."), error: singleResult.error ?? timeoutMessage ?? "Subagent timed out.", exitCode: 1, interrupted: false, timedOut: true, skipped: false } : { ...singleResult, skipped: false };
3628
+ return stopped || childStopped ? { ...singleResult, output: stopMessage, error: stopMessage, exitCode: 1, interrupted: false, timedOut: false, stopped: true, skipped: false } : timedOut ? { ...singleResult, output: singleResult.output || (timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE), error: singleResult.error ?? timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE, exitCode: 1, interrupted: false, timedOut: true, skipped: false } : { ...singleResult, skipped: false };
3595
3629
  }, globalSemaphore);
3596
3630
  flatIndex += dynamicSteps.length;
3597
3631
  for (const pr of parallelResults) {
@@ -3678,7 +3712,7 @@ export async function runSubagent(config, childSessions) {
3678
3712
  }),
3679
3713
  cwd,
3680
3714
  signal: combinedAbortSignal([timeoutAbortController.signal, stopAbortController.signal]),
3681
- abortMessage: stopAbortController.signal.aborted ? stopMessage : timeoutMessage ?? "Subagent timed out.",
3715
+ abortMessage: stopAbortController.signal.aborted ? stopMessage : timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE,
3682
3716
  reportOptional: isAgentContract(step.agentContract),
3683
3717
  }))
3684
3718
  : undefined;
@@ -3686,7 +3720,7 @@ export async function runSubagent(config, childSessions) {
3686
3720
  const groupTimedOut = !groupStopped && (timedOut || timeoutAbortController.signal.aborted);
3687
3721
  const effectiveGroupAcceptance = groupTimedOut || groupStopped ? undefined : groupAcceptance;
3688
3722
  const groupAcceptanceFailure = effectiveDynamicGroupAcceptance.explicit && effectiveGroupAcceptance && (!isAgentContract(step.agentContract) || step.gateOn === "acceptance") ? acceptanceFailureMessage(effectiveGroupAcceptance) : undefined;
3689
- const groupError = groupStopped ? stopMessage : groupTimedOut ? timeoutMessage ?? "Subagent timed out." : groupAcceptanceFailure;
3723
+ const groupError = groupStopped ? stopMessage : groupTimedOut ? timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE : groupAcceptanceFailure;
3690
3724
  markDynamicGraphGroup(stepIndex, groupError ? groupStopped ? "stopped" : "failed" : "completed", groupError, effectiveGroupAcceptance);
3691
3725
  if (groupError) {
3692
3726
  results.push(omitUndefinedProperties({
@@ -3801,7 +3835,7 @@ export async function runSubagent(config, childSessions) {
3801
3835
  if (config.deadlineAt !== undefined && Date.now() >= config.deadlineAt)
3802
3836
  timeoutRunner();
3803
3837
  if (setupSignal.aborted)
3804
- throw new Error(stopped ? stopMessage : timedOut ? timeoutMessage ?? "Subagent timed out." : "Subagent paused during worktree setup.");
3838
+ throw new Error(stopped ? stopMessage : timedOut ? timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE : "Subagent paused during worktree setup.");
3805
3839
  }
3806
3840
  catch (error) {
3807
3841
  if (worktreeSetup)
@@ -3896,7 +3930,6 @@ export async function runSubagent(config, childSessions) {
3896
3930
  return omitUndefinedProperties({ agent: task.agent, ...(task.sessionName ? { sessionName: task.sessionName } : {}), context: task.context, output: "(skipped — fail-fast)", exitCode: -1, skipped: true });
3897
3931
  }
3898
3932
  const taskStartTime = Date.now();
3899
- launchedFlatIndices.add(fi);
3900
3933
  statusPayload.currentStep = fi;
3901
3934
  requiredStatusStep(statusPayload, fi).status = "running";
3902
3935
  delete requiredStatusStep(statusPayload, fi).error;
@@ -3962,53 +3995,8 @@ export async function runSubagent(config, childSessions) {
3962
3995
  const taskDuration = taskEndTime - taskStartTime;
3963
3996
  const childInterrupted = singleResult.interrupted === true;
3964
3997
  const childStopped = singleResult.stopped === true;
3965
- requiredStatusStep(statusPayload, fi).status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.execution?.status === "partial" ? "partial" : singleResult.exitCode === 0 ? "complete" : "failed";
3966
- requiredStatusStep(statusPayload, fi).endedAt = taskEndTime;
3967
- requiredStatusStep(statusPayload, fi).durationMs = taskDuration;
3968
- requiredStatusStep(statusPayload, fi).exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
3969
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "timedOut", timedOut || singleResult.timedOut ? true : undefined);
3970
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "stopped", stopped || childStopped ? true : undefined);
3971
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "toolBudget", singleResult.toolBudget);
3972
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "toolBudgetBlocked", singleResult.toolBudgetBlocked);
3973
- if (singleResult.toolBudget)
3974
- statusPayload.toolBudget = singleResult.toolBudget;
3975
- if (singleResult.toolBudgetBlocked)
3976
- statusPayload.toolBudgetBlocked = true;
3977
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "sessionName", singleResult.sessionName);
3978
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "model", singleResult.model);
3979
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "thinking", resolveEffectiveThinking(singleResult.model, requiredStatusStep(statusPayload, fi).thinking));
3980
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "requestedModel", singleResult.requestedModel);
3981
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "contextOverflow", singleResult.contextOverflow);
3982
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "totalCost", singleResult.totalCost);
3983
- if (singleResult.totalCost) {
3984
- pendingParallelUsageCost = {
3985
- inputTokens: pendingParallelUsageCost.inputTokens + singleResult.totalCost.inputTokens,
3986
- outputTokens: pendingParallelUsageCost.outputTokens + singleResult.totalCost.outputTokens,
3987
- costUsd: pendingParallelUsageCost.costUsd + singleResult.totalCost.costUsd,
3988
- };
3989
- refreshUsageBudget();
3990
- }
3991
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "error", stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error);
3992
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "transcriptPath", singleResult.transcriptPath ?? requiredStatusStep(statusPayload, fi).transcriptPath);
3993
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "transcriptError", singleResult.transcriptError);
3994
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "agentContract", singleResult.agentContract);
3995
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "launchResolvedExtensions", singleResult.launchResolvedExtensions);
3996
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "runtimeAcknowledgedExtensions", singleResult.runtimeAcknowledgedExtensions);
3997
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "effects", singleResult.effects);
3998
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "execution", singleResult.execution);
3999
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "review", singleResult.review);
4000
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "structuredOutput", singleResult.structuredOutput);
4001
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "structuredOutputPath", singleResult.structuredOutputPath);
4002
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "structuredOutputSchemaPath", singleResult.structuredOutputSchemaPath);
4003
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "acceptance", singleResult.acceptance);
4004
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "timeoutRecovery", singleResult.timeoutRecovery);
4005
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "watchdog", singleResult.watchdog);
4006
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "capabilityCeiling", singleResult.capabilityCeiling);
4007
- setOptionalProperty(requiredStatusStep(statusPayload, fi), "capabilityAudit", singleResult.capabilityAudit);
4008
- if (singleResult.capabilityCeiling)
4009
- statusPayload.capabilityCeiling = singleResult.capabilityCeiling;
4010
- if (singleResult.capabilityAudit)
4011
- statusPayload.capabilityAudit = singleResult.capabilityAudit;
3998
+ recordStepResult(fi, singleResult, taskStartTime, taskEndTime);
3999
+ addParallelUsageCost(singleResult.totalCost);
4012
4000
  statusPayload.lastUpdate = taskEndTime;
4013
4001
  writeStatusPayload();
4014
4002
  appendCapabilityCeilingAppliedEvent(eventsPath, id, fi, task.agent, singleResult);
@@ -4021,7 +4009,7 @@ export async function runSubagent(config, childSessions) {
4021
4009
  appendTerminalChildStatusEvent(fi, taskEndTime);
4022
4010
  if (singleResult.exitCode !== 0 && failFast && !childStopped)
4023
4011
  aborted = true;
4024
- return stopped || childStopped ? { ...singleResult, output: stopMessage, error: stopMessage, exitCode: 1, interrupted: false, timedOut: false, stopped: true, skipped: false } : timedOut ? { ...singleResult, output: singleResult.output || (timeoutMessage ?? "Subagent timed out."), error: singleResult.error ?? timeoutMessage ?? "Subagent timed out.", exitCode: 1, interrupted: false, timedOut: true, skipped: false } : { ...singleResult, skipped: false };
4012
+ return stopped || childStopped ? { ...singleResult, output: stopMessage, error: stopMessage, exitCode: 1, interrupted: false, timedOut: false, stopped: true, skipped: false } : timedOut ? { ...singleResult, output: singleResult.output || (timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE), error: singleResult.error ?? timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE, exitCode: 1, interrupted: false, timedOut: true, skipped: false } : { ...singleResult, skipped: false };
4025
4013
  }, globalSemaphore);
4026
4014
  flatIndex += group.parallel.length;
4027
4015
  for (let t = 0; t < group.parallel.length; t++) {
@@ -4290,7 +4278,6 @@ export async function runSubagent(config, childSessions) {
4290
4278
  }
4291
4279
  const singleCwd = singleWorktreeSetup?.worktrees[0]?.agentCwd ?? cwd;
4292
4280
  const stepStartTime = Date.now();
4293
- launchedFlatIndices.add(flatIndex);
4294
4281
  statusPayload.currentStep = flatIndex;
4295
4282
  requiredStatusStep(statusPayload, flatIndex).status = "running";
4296
4283
  delete requiredStatusStep(statusPayload, flatIndex).activityState;
@@ -4372,10 +4359,10 @@ export async function runSubagent(config, childSessions) {
4372
4359
  launchContractDigest: singleResult.launchContractDigest,
4373
4360
  launchResolvedExtensions: singleResult.launchResolvedExtensions,
4374
4361
  runtimeAcknowledgedExtensions: singleResult.runtimeAcknowledgedExtensions,
4375
- output: stopped || childStopped ? stopMessage : timedOut ? singleResult.output || (timeoutMessage ?? "Subagent timed out.") : singleResult.output,
4362
+ output: stopped || childStopped ? stopMessage : timedOut ? singleResult.output || (timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE) : singleResult.output,
4376
4363
  outputState: singleResult.outputState,
4377
4364
  outputPartial: singleResult.outputPartial,
4378
- error: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error,
4365
+ error: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE) : singleResult.error,
4379
4366
  success: !stopped && !childStopped && !timedOut && singleResult.interrupted !== true && singleResult.exitCode === 0 && singleResult.execution?.status !== "partial",
4380
4367
  exitCode: stopped || childStopped ? 1 : timedOut ? 1 : singleResult.interrupted === true ? 0 : singleResult.exitCode,
4381
4368
  sessionFile: singleResult.sessionFile,
@@ -4454,45 +4441,7 @@ export async function runSubagent(config, childSessions) {
4454
4441
  }
4455
4442
  const stepEndTime = Date.now();
4456
4443
  const childInterrupted = singleResult.interrupted === true;
4457
- requiredStatusStep(statusPayload, flatIndex).status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.execution?.status === "partial" ? "partial" : singleResult.exitCode === 0 ? "complete" : "failed";
4458
- requiredStatusStep(statusPayload, flatIndex).endedAt = stepEndTime;
4459
- requiredStatusStep(statusPayload, flatIndex).durationMs = stepEndTime - stepStartTime;
4460
- requiredStatusStep(statusPayload, flatIndex).exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
4461
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "timedOut", timedOut || singleResult.timedOut ? true : undefined);
4462
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "stopped", stopped || childStopped ? true : undefined);
4463
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "toolBudget", singleResult.toolBudget);
4464
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "toolBudgetBlocked", singleResult.toolBudgetBlocked);
4465
- if (singleResult.toolBudget)
4466
- statusPayload.toolBudget = singleResult.toolBudget;
4467
- if (singleResult.toolBudgetBlocked)
4468
- statusPayload.toolBudgetBlocked = true;
4469
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "sessionName", singleResult.sessionName);
4470
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "model", singleResult.model);
4471
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "thinking", resolveEffectiveThinking(singleResult.model, requiredStatusStep(statusPayload, flatIndex).thinking));
4472
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "requestedModel", singleResult.requestedModel);
4473
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "contextOverflow", singleResult.contextOverflow);
4474
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "totalCost", singleResult.totalCost);
4475
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "error", stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error);
4476
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "transcriptPath", singleResult.transcriptPath ?? requiredStatusStep(statusPayload, flatIndex).transcriptPath);
4477
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "transcriptError", singleResult.transcriptError);
4478
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "agentContract", singleResult.agentContract);
4479
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "launchResolvedExtensions", singleResult.launchResolvedExtensions);
4480
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "runtimeAcknowledgedExtensions", singleResult.runtimeAcknowledgedExtensions);
4481
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "effects", singleResult.effects);
4482
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "execution", singleResult.execution);
4483
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "review", singleResult.review);
4484
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "structuredOutput", singleResult.structuredOutput);
4485
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "structuredOutputPath", singleResult.structuredOutputPath);
4486
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "structuredOutputSchemaPath", singleResult.structuredOutputSchemaPath);
4487
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "acceptance", singleResult.acceptance);
4488
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "timeoutRecovery", singleResult.timeoutRecovery);
4489
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "watchdog", singleResult.watchdog);
4490
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "capabilityCeiling", singleResult.capabilityCeiling);
4491
- setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "capabilityAudit", singleResult.capabilityAudit);
4492
- if (singleResult.capabilityCeiling)
4493
- statusPayload.capabilityCeiling = singleResult.capabilityCeiling;
4494
- if (singleResult.capabilityAudit)
4495
- statusPayload.capabilityAudit = singleResult.capabilityAudit;
4444
+ recordStepResult(flatIndex, singleResult, stepStartTime, stepEndTime);
4496
4445
  if (stepTokens) {
4497
4446
  requiredStatusStep(statusPayload, flatIndex).tokens = stepTokens;
4498
4447
  statusPayload.totalTokens = { ...previousCumulativeTokens };
@@ -4722,7 +4671,7 @@ export async function runSubagent(config, childSessions) {
4722
4671
  }
4723
4672
  if (timedOut) {
4724
4673
  statusPayload.timedOut = true;
4725
- statusPayload.error = timeoutMessage ?? "Subagent timed out.";
4674
+ statusPayload.error = timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE;
4726
4675
  }
4727
4676
  if (usageBudgetExceeded && statusPayload.usageBudget && !statusPayload.error) {
4728
4677
  statusPayload.error = usageBudgetExceededMessage(statusPayload.usageBudget);
@@ -4767,13 +4716,13 @@ export async function runSubagent(config, childSessions) {
4767
4716
  mode: resultMode,
4768
4717
  success: statusPayload.state === "complete",
4769
4718
  state: statusPayload.state,
4770
- summary: stopped ? stopMessage : signalTerminated ? (statusPayload.error ?? "Subagent process terminated by signal.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : usageBudgetExceeded ? (statusPayload.error ?? "Usage budget exhausted.") : interrupted ? "Paused after interrupt. Waiting for explicit next action." : statusPayload.state === "partial" ? (statusPayload.error ?? summary) : summary,
4719
+ summary: stopped ? stopMessage : signalTerminated ? (statusPayload.error ?? "Subagent process terminated by signal.") : timedOut ? timedOutRunSummary(timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE, summary, results) : usageBudgetExceeded ? (statusPayload.error ?? "Usage budget exhausted.") : interrupted ? "Paused after interrupt. Waiting for explicit next action." : statusPayload.state === "partial" ? (statusPayload.error ?? summary) : summary,
4771
4720
  ...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),
4772
4721
  ...(config.deadlineAt !== undefined ? { deadlineAt: config.deadlineAt } : {}),
4773
4722
  ...(statusPayload.toolBudget ? { toolBudget: statusPayload.toolBudget } : {}),
4774
4723
  ...(statusPayload.toolBudgetBlocked ? { toolBudgetBlocked: true } : {}),
4775
4724
  ...(statusPayload.usageBudget ? { usageBudget: statusPayload.usageBudget } : {}),
4776
- ...(stopped ? { stopped: true, error: stopMessage } : timedOut ? { timedOut: true, error: timeoutMessage ?? "Subagent timed out." } : usageBudgetExceeded ? { error: statusPayload.error ?? "Usage budget exhausted." } : {}),
4725
+ ...(stopped ? { stopped: true, error: stopMessage } : timedOut ? { timedOut: true, error: timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE } : usageBudgetExceeded ? { error: statusPayload.error ?? "Usage budget exhausted." } : {}),
4777
4726
  results: results.map((r) => omitUndefinedProperties({
4778
4727
  agent: r.agent,
4779
4728
  ...(r.sessionName ? { sessionName: r.sessionName } : {}),
@@ -4883,26 +4832,6 @@ export async function runSubagent(config, childSessions) {
4883
4832
  totalCost: finalTotalCost,
4884
4833
  usageBudget: statusPayload.usageBudget,
4885
4834
  }));
4886
- // Paused runs record an interrupted attempt; a later resume records another.
4887
- for (const historyEntry of planBackgroundRunHistory({
4888
- steps,
4889
- resultMode,
4890
- statusSteps: statusPayload.steps.map((step, index) => ({
4891
- agent: step.agent,
4892
- status: step.status,
4893
- durationMs: step.durationMs,
4894
- timedOut: step.timedOut,
4895
- stopped: step.stopped,
4896
- launched: launchedFlatIndices.has(index),
4897
- })),
4898
- stepResults: results,
4899
- runDurationMs: runEndedAt - overallStartTime,
4900
- stopped,
4901
- interrupted,
4902
- timedOut,
4903
- })) {
4904
- recordRun(historyEntry.agent, historyEntry.task, historyEntry.exitCode, historyEntry.durationMs, historyEntry.terminal);
4905
- }
4906
4835
  writeRunLog(logPath, omitUndefinedProperties({
4907
4836
  id,
4908
4837
  mode: statusPayload.mode,