pi-subagents 0.77.0 → 0.78.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 (152) hide show
  1. package/CHANGELOG.md +48 -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 +1 -1
  6. package/docs/configuration.md +21 -5
  7. package/docs/extension-api.md +5 -1
  8. package/docs/observability.md +2 -0
  9. package/docs/tool-reference.md +2 -2
  10. package/docs/workflows.md +2 -2
  11. package/package.json +1 -1
  12. package/skills/pi-subagents/SKILL.md +1 -1
  13. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  14. package/src/api/external-runs.d.ts.map +1 -1
  15. package/src/api/external-runs.js +5 -4
  16. package/src/api/external-runs.js.map +1 -1
  17. package/src/api/preflight.js +1 -1
  18. package/src/api/preflight.js.map +1 -1
  19. package/src/extension/active-runs-prompt.d.ts +8 -0
  20. package/src/extension/active-runs-prompt.d.ts.map +1 -0
  21. package/src/extension/active-runs-prompt.js +30 -0
  22. package/src/extension/active-runs-prompt.js.map +1 -0
  23. package/src/extension/config.d.ts.map +1 -1
  24. package/src/extension/config.js +6 -0
  25. package/src/extension/config.js.map +1 -1
  26. package/src/extension/declaration-pinning.d.ts +5 -0
  27. package/src/extension/declaration-pinning.d.ts.map +1 -1
  28. package/src/extension/declaration-pinning.js +9 -2
  29. package/src/extension/declaration-pinning.js.map +1 -1
  30. package/src/extension/index.d.ts +0 -1
  31. package/src/extension/index.d.ts.map +1 -1
  32. package/src/extension/index.js +56 -14
  33. package/src/extension/index.js.map +1 -1
  34. package/src/extension/tool-activation.d.ts.map +1 -1
  35. package/src/extension/tool-activation.js +4 -2
  36. package/src/extension/tool-activation.js.map +1 -1
  37. package/src/intercom/native-supervisor-channel.d.ts +16 -1
  38. package/src/intercom/native-supervisor-channel.d.ts.map +1 -1
  39. package/src/intercom/native-supervisor-channel.js +25 -6
  40. package/src/intercom/native-supervisor-channel.js.map +1 -1
  41. package/src/runs/background/active-async-capacity.d.ts +2 -1
  42. package/src/runs/background/active-async-capacity.d.ts.map +1 -1
  43. package/src/runs/background/active-async-capacity.js +10 -6
  44. package/src/runs/background/active-async-capacity.js.map +1 -1
  45. package/src/runs/background/async-execution.js +3 -3
  46. package/src/runs/background/async-execution.js.map +1 -1
  47. package/src/runs/background/async-resume.d.ts.map +1 -1
  48. package/src/runs/background/async-resume.js +3 -1
  49. package/src/runs/background/async-resume.js.map +1 -1
  50. package/src/runs/background/chain-root-attachment.js +4 -4
  51. package/src/runs/background/chain-root-attachment.js.map +1 -1
  52. package/src/runs/background/control-channel.d.ts +1 -0
  53. package/src/runs/background/control-channel.d.ts.map +1 -1
  54. package/src/runs/background/control-channel.js +27 -3
  55. package/src/runs/background/control-channel.js.map +1 -1
  56. package/src/runs/background/notify.d.ts +1 -1
  57. package/src/runs/background/notify.d.ts.map +1 -1
  58. package/src/runs/background/notify.js +8 -4
  59. package/src/runs/background/notify.js.map +1 -1
  60. package/src/runs/background/run-child-session.d.ts.map +1 -1
  61. package/src/runs/background/run-child-session.js +7 -14
  62. package/src/runs/background/run-child-session.js.map +1 -1
  63. package/src/runs/background/runner-aliases.d.ts.map +1 -1
  64. package/src/runs/background/runner-aliases.js +11 -8
  65. package/src/runs/background/runner-aliases.js.map +1 -1
  66. package/src/runs/background/subagent-runner.d.ts.map +1 -1
  67. package/src/runs/background/subagent-runner.js +198 -279
  68. package/src/runs/background/subagent-runner.js.map +1 -1
  69. package/src/runs/background/workflow-terminal-proof.d.ts +1 -1
  70. package/src/runs/background/workflow-terminal-proof.d.ts.map +1 -1
  71. package/src/runs/background/workflow-terminal-proof.js +12 -3
  72. package/src/runs/background/workflow-terminal-proof.js.map +1 -1
  73. package/src/runs/foreground/async-stop-action.d.ts.map +1 -1
  74. package/src/runs/foreground/async-stop-action.js +6 -7
  75. package/src/runs/foreground/async-stop-action.js.map +1 -1
  76. package/src/runs/foreground/execution.d.ts.map +1 -1
  77. package/src/runs/foreground/execution.js +51 -55
  78. package/src/runs/foreground/execution.js.map +1 -1
  79. package/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  80. package/src/runs/foreground/subagent-executor.js +26 -35
  81. package/src/runs/foreground/subagent-executor.js.map +1 -1
  82. package/src/runs/shared/effective-system-prompt.d.ts +4 -4
  83. package/src/runs/shared/effective-system-prompt.d.ts.map +1 -1
  84. package/src/runs/shared/effective-system-prompt.js +5 -4
  85. package/src/runs/shared/effective-system-prompt.js.map +1 -1
  86. package/src/runs/shared/external-cli-runner.d.ts.map +1 -1
  87. package/src/runs/shared/external-cli-runner.js +3 -2
  88. package/src/runs/shared/external-cli-runner.js.map +1 -1
  89. package/src/runs/shared/model-resolution-diagnostic.d.ts +7 -5
  90. package/src/runs/shared/model-resolution-diagnostic.d.ts.map +1 -1
  91. package/src/runs/shared/model-resolution-diagnostic.js +13 -10
  92. package/src/runs/shared/model-resolution-diagnostic.js.map +1 -1
  93. package/src/runs/shared/orca-progress-tabs.js +1 -1
  94. package/src/runs/shared/orca-progress-tabs.js.map +1 -1
  95. package/src/runs/shared/partial-output.d.ts +7 -0
  96. package/src/runs/shared/partial-output.d.ts.map +1 -1
  97. package/src/runs/shared/partial-output.js +7 -0
  98. package/src/runs/shared/partial-output.js.map +1 -1
  99. package/src/runs/shared/single-output.d.ts +11 -1
  100. package/src/runs/shared/single-output.d.ts.map +1 -1
  101. package/src/runs/shared/single-output.js +48 -26
  102. package/src/runs/shared/single-output.js.map +1 -1
  103. package/src/runs/shared/structured-output.d.ts +2 -0
  104. package/src/runs/shared/structured-output.d.ts.map +1 -1
  105. package/src/runs/shared/structured-output.js +9 -0
  106. package/src/runs/shared/structured-output.js.map +1 -1
  107. package/src/runs/shared/subagent-prompt-runtime.d.ts +29 -0
  108. package/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
  109. package/src/runs/shared/subagent-prompt-runtime.js +87 -2
  110. package/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
  111. package/src/runs/shared/tool-budget.js +1 -1
  112. package/src/runs/shared/tool-budget.js.map +1 -1
  113. package/src/shared/child-transcript.js +1 -1
  114. package/src/shared/child-transcript.js.map +1 -1
  115. package/src/shared/extension-context.d.ts +5 -0
  116. package/src/shared/extension-context.d.ts.map +1 -1
  117. package/src/shared/extension-context.js +7 -0
  118. package/src/shared/extension-context.js.map +1 -1
  119. package/src/shared/parent-wake.d.ts +6 -0
  120. package/src/shared/parent-wake.d.ts.map +1 -1
  121. package/src/shared/parent-wake.js +11 -0
  122. package/src/shared/parent-wake.js.map +1 -1
  123. package/src/shared/types.d.ts +7 -8
  124. package/src/shared/types.d.ts.map +1 -1
  125. package/src/shared/types.js +4 -4
  126. package/src/shared/types.js.map +1 -1
  127. package/src/shared/utils.d.ts +2 -0
  128. package/src/shared/utils.d.ts.map +1 -1
  129. package/src/shared/utils.js +2 -0
  130. package/src/shared/utils.js.map +1 -1
  131. package/src/slash/slash-commands.d.ts.map +1 -1
  132. package/src/slash/slash-commands.js +20 -9
  133. package/src/slash/slash-commands.js.map +1 -1
  134. package/src/slash/subagents-admin.d.ts.map +1 -1
  135. package/src/slash/subagents-admin.js +3 -2
  136. package/src/slash/subagents-admin.js.map +1 -1
  137. package/src/tui/fleet-status.d.ts +5 -0
  138. package/src/tui/fleet-status.d.ts.map +1 -1
  139. package/src/tui/fleet-status.js +24 -3
  140. package/src/tui/fleet-status.js.map +1 -1
  141. package/src/tui/fleet.js +2 -2
  142. package/src/tui/fleet.js.map +1 -1
  143. package/src/watchdog/change-signature.d.ts.map +1 -1
  144. package/src/watchdog/change-signature.js +1 -3
  145. package/src/watchdog/change-signature.js.map +1 -1
  146. package/src/workflows/scripted-workflow.d.ts.map +1 -1
  147. package/src/workflows/scripted-workflow.js +13 -4
  148. package/src/workflows/scripted-workflow.js.map +1 -1
  149. package/src/runs/shared/run-history.d.ts +0 -67
  150. package/src/runs/shared/run-history.d.ts.map +0 -1
  151. package/src/runs/shared/run-history.js +0 -270
  152. package/src/runs/shared/run-history.js.map +0 -1
@@ -23,7 +23,8 @@ 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";
@@ -49,13 +50,12 @@ import { processTerminalPath, writeProcessTerminalCandidate } from "./process-te
49
50
  import { persistRunnerStartupFailure } from "./runner-startup-failure.js";
50
51
  import { currentPidNamespaceScope } from "./pid-namespace.js";
51
52
  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";
53
+ import { PROMPT_REDACTED, SUBAGENT_STOPPED_MESSAGE, SUBAGENT_TIMED_OUT_MESSAGE, detectSubagentError, extractTextFromContent, extractToolArgsPreview, formatEmptyTerminalAssistantResponseError, getAgentDir, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.js";
54
54
  import { planAbortRecovery } from "../shared/abort-recovery.js";
55
55
  import { createMutatingFailureState, didMutatingToolFail, isMutatingTool, nextLongRunningTrigger, recordMutatingFailure, resetMutatingFailureState, resolveCurrentPath, shouldEscalateMutatingFailures, summarizeRecentMutatingFailures, } from "../shared/long-running-guard.js";
56
56
  import { parseSessionTokens } from "../../shared/session-tokens.js";
57
57
  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";
58
+ import { findModelInfo, qualifyModelWithProvider, resolveEffectiveThinking } from "../../shared/model-info.js";
59
59
  import { assertThinkingWithinCeiling } from "../../shared/thinking-ceiling.js";
60
60
  import { resolveLaunchBinding } from "../../shared/launch-contract.js";
61
61
  import { writeInitialProgressFile } from "../../shared/settings.js";
@@ -65,7 +65,7 @@ import { attachContractProjections, isAgentContract } from "../shared/agent-cont
65
65
  import { waitForImportedAsyncRoot } from "./chain-root-attachment.js";
66
66
  import { appendRunnerStepsToStatus, consumeChainAppendRequests, countPendingChainAppendRequests, statusStepDescription } from "./chain-append.js";
67
67
  import { asyncStatusChildIdentity } from "../shared/child-identity.js";
68
- import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.js";
68
+ import { initialToolBudgetState, isToolBudgetBlockedMessage, toolBudgetState } from "../shared/tool-budget.js";
69
69
  import { toolTimeoutCallKey } from "../shared/tool-timeout.js";
70
70
  import { usageBudgetExceededMessage, usageBudgetState } from "../shared/usage-budget.js";
71
71
  import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writeWorktreeSetupHandoff } from "../shared/parallel-handoff.js";
@@ -467,7 +467,7 @@ export async function runSingleStepInner(step, ctx) {
467
467
  }
468
468
  const stopped = importStopped || imported.stopped === true || ctx.stopSignal?.aborted === true;
469
469
  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.";
470
+ const message = stopped ? ctx.stopMessage ?? SUBAGENT_STOPPED_MESSAGE : ctx.timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE;
471
471
  return omitUndefinedProperties({
472
472
  agent: imported.agent,
473
473
  output: timedOut || stopped ? message : imported.output,
@@ -554,6 +554,47 @@ export async function runSingleStepInner(step, ctx) {
554
554
  }
555
555
  }
556
556
  transcriptWriter?.writeInitialUserMessage(`${PROMPT_REDACTED}; live Prompt Audit only.`);
557
+ // External CLI and external job runs settle their output the same way; only their runner metadata differs.
558
+ const settleExternalOutput = (external, outputSnapshot, runnerMetadata) => {
559
+ try {
560
+ fs.writeFileSync(ctx.outputFile, external.output, "utf-8");
561
+ }
562
+ catch { /* Observability output is best-effort. */ }
563
+ const resolvedOutput = step.outputPath && external.exitCode === 0
564
+ ? resolveSingleOutput(step.outputPath, external.output, outputSnapshot, step.outputClaimPath)
565
+ : { fullOutput: external.output };
566
+ const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, resolvedOutput.fullOutput) : undefined;
567
+ const settlementFatalOutputError = isSettlementFatalOutputError(resolvedOutput, step.outputMode);
568
+ const exitCode = settlementFatalOutputError ? 1 : external.exitCode;
569
+ const error = settlementFatalOutputError && resolvedOutput.saveError
570
+ ? external.error ? `${external.error}\n${resolvedOutput.saveError}` : resolvedOutput.saveError
571
+ : external.error;
572
+ const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
573
+ fullOutput: resolvedOutput.fullOutput,
574
+ outputPath: step.outputPath,
575
+ outputMode: step.outputMode,
576
+ exitCode: exitCode ?? 1,
577
+ savedPath: resolvedOutput.savedPath,
578
+ outputReference,
579
+ saveError: resolvedOutput.saveError,
580
+ }));
581
+ const artifactErrors = artifactPaths && ctx.artifactConfig?.enabled !== false
582
+ ? persistStepArtifacts({
583
+ artifactPaths,
584
+ artifactConfig: ctx.artifactConfig,
585
+ output: formatOutputArtifactContent(omitUndefinedProperties({ output: resolvedOutput.fullOutput, error: error, metadataPath: ctx.artifactConfig?.includeMetadata === false ? undefined : artifactPaths.metadataPath })),
586
+ metadata: { runId: ctx.id, parentSessionId: step.parentSessionId, agent: step.agent, task: PROMPT_REDACTED, ...runnerMetadata, exitCode: exitCode, error: error, timestamp: Date.now() },
587
+ })
588
+ : {};
589
+ return {
590
+ output: finalizedOutput.displayOutput,
591
+ exitCode,
592
+ error,
593
+ outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
594
+ artifactOutputSaveFailed: artifactErrors.outputSaveError ? true : undefined,
595
+ metadataSaveError: artifactErrors.metadataSaveError,
596
+ };
597
+ };
557
598
  if (step.runner?.type === "external-cli") {
558
599
  const externalCwd = step.machine?.cwd ?? step.cwd ?? ctx.cwd;
559
600
  const externalAbortSignal = combinedAbortSignal([ctx.timeoutSignal, ctx.stopSignal]);
@@ -561,7 +602,7 @@ export async function runSingleStepInner(step, ctx) {
561
602
  await ctx.prepareExternalActivity?.(externalCwd, externalAbortSignal);
562
603
  if (externalAbortSignal?.aborted) {
563
604
  const stopped = ctx.stopSignal?.aborted === true;
564
- const message = stopped ? ctx.stopMessage ?? "Subagent stopped by user." : ctx.timeoutMessage ?? "Subagent timed out.";
605
+ const message = stopped ? ctx.stopMessage ?? SUBAGENT_STOPPED_MESSAGE : ctx.timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE;
565
606
  return omitUndefinedProperties({ agent: step.agent, context: step.context, output: message, error: message, exitCode: 1, stopped: stopped || undefined, timedOut: stopped ? undefined : true });
566
607
  }
567
608
  if (step.machine) {
@@ -584,7 +625,7 @@ export async function runSingleStepInner(step, ctx) {
584
625
  else if (raced.kind === "timeout")
585
626
  settled = createHerdrExternalAdapter(step.runner.adapter).normalize(evidenceInput, "Placed external run timed out; truthful pane retained for inspection.");
586
627
  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 } };
628
+ 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
629
  }
589
630
  catch (error) {
590
631
  retain = true;
@@ -599,7 +640,7 @@ export async function runSingleStepInner(step, ctx) {
599
640
  const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, resolvedOutput.fullOutput) : undefined;
600
641
  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
642
  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 } });
643
+ 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
644
  }
604
645
  catch (error) {
605
646
  // Any post-allocation uncertainty retains the pane; only explicit stop
@@ -655,50 +696,22 @@ export async function runSingleStepInner(step, ctx) {
655
696
  const ran = await runExternalCli(preparedExternal.input);
656
697
  const externalProcess = preparedExternal.decorateProcess(ran.externalProcess);
657
698
  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
- : {};
699
+ const settled = settleExternalOutput(external, outputSnapshot, { runner, externalProcess });
687
700
  return omitUndefinedProperties({
688
701
  agent: step.agent,
689
702
  ...(childSessionName ? { sessionName: childSessionName } : {}),
690
703
  context: step.context,
691
- output: finalizedOutput.displayOutput,
704
+ output: settled.output,
692
705
  outputState: external.output.trim() ? "present" : "absent",
693
- exitCode,
694
- error,
706
+ exitCode: settled.exitCode,
707
+ error: settled.error,
695
708
  timedOut: external.timedOut,
696
709
  stopped: external.stopped,
697
710
  processSignal: external.processSignal,
698
711
  artifactPaths,
699
- outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
700
- artifactOutputSaveFailed: artifactErrors.outputSaveError ? true : undefined,
701
- metadataSaveError: artifactErrors.metadataSaveError,
712
+ outputSaveError: settled.outputSaveError,
713
+ artifactOutputSaveFailed: settled.artifactOutputSaveFailed,
714
+ metadataSaveError: settled.metadataSaveError,
702
715
  runner,
703
716
  externalProcess: externalProcess,
704
717
  });
@@ -728,49 +741,21 @@ export async function runSingleStepInner(step, ctx) {
728
741
  onExternalJob: ctx.onExternalJob,
729
742
  followUp: step.externalJobFollowUp,
730
743
  }));
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
- : {};
744
+ const settled = settleExternalOutput(external, outputSnapshot, { runner, externalJob: external.externalJob });
760
745
  return omitUndefinedProperties({
761
746
  agent: step.agent,
762
747
  ...(childSessionName ? { sessionName: childSessionName } : {}),
763
748
  context: step.context,
764
- output: finalizedOutput.displayOutput,
749
+ output: settled.output,
765
750
  outputState: external.output.trim() ? "present" : "absent",
766
- exitCode,
767
- error,
751
+ exitCode: settled.exitCode,
752
+ error: settled.error,
768
753
  timedOut: external.timedOut,
769
754
  stopped: external.stopped,
770
755
  artifactPaths,
771
- outputSaveError: [resolvedOutput.saveError, artifactErrors.outputSaveError].filter(Boolean).join("\n") || undefined,
772
- artifactOutputSaveFailed: artifactErrors.outputSaveError ? true : undefined,
773
- metadataSaveError: artifactErrors.metadataSaveError,
756
+ outputSaveError: settled.outputSaveError,
757
+ artifactOutputSaveFailed: settled.artifactOutputSaveFailed,
758
+ metadataSaveError: settled.metadataSaveError,
774
759
  runner,
775
760
  externalJob: external.externalJob,
776
761
  });
@@ -1020,7 +1005,11 @@ export async function runSingleStepInner(step, ctx) {
1020
1005
  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
1006
  finalMutationEvidence = mutationEvidence;
1022
1007
  const mutationAttemptObserved = run.observedMutationAttempt === true || mutationEvidence.attemptedMutation === true;
1023
- const finalOutputHasPersistableFileContent = run.exitCode === 0 && !run.error && !emptyOutputError && Boolean(stripAcceptanceReport(run.finalOutput).trim());
1008
+ // Validated structured output is persistable file content even when the final text is only
1009
+ // an acceptance-report fence; otherwise the required-output precheck forces exitCode 1 before
1010
+ // resolveSingleOutput can publish the canonical JSON into the bound file-only path.
1011
+ const finalOutputHasPersistableFileContent = run.exitCode === 0 && !run.error && !emptyOutputError
1012
+ && (Boolean(stripAcceptanceReport(run.finalOutput).trim()) || validatedStructuredOutput);
1024
1013
  const requiredOutput = step.outputMode === "file-only" && step.outputPath
1025
1014
  ? { kind: "file-only", path: step.outputPath, missing: !fs.existsSync(step.outputPath) && !finalOutputHasPersistableFileContent }
1026
1015
  : effectiveStructuredOutput
@@ -1104,7 +1093,7 @@ export async function runSingleStepInner(step, ctx) {
1104
1093
  const resolvedOutput = step.outputPath && finalResult?.exitCode === 0
1105
1094
  ? resolveSingleOutput(step.outputPath, structuredText ?? replyOutput, finalOutputSnapshot, step.outputClaimPath)
1106
1095
  : { fullOutput: replyOutput };
1107
- if (resolvedOutput.fatalError) {
1096
+ if (isSettlementFatalOutputError(resolvedOutput, step.outputMode)) {
1108
1097
  if (finalResult) {
1109
1098
  finalResult.exitCode = 1;
1110
1099
  finalResult.error = finalResult.error ? `${finalResult.error}\n${resolvedOutput.saveError}` : resolvedOutput.saveError;
@@ -1114,7 +1103,7 @@ export async function runSingleStepInner(step, ctx) {
1114
1103
  const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, output) : undefined;
1115
1104
  let outputForSummary = output;
1116
1105
  if (finalResult?.stopped && !outputForSummary.trim()) {
1117
- outputForSummary = ctx.stopMessage ?? "Subagent stopped by user.";
1106
+ outputForSummary = ctx.stopMessage ?? SUBAGENT_STOPPED_MESSAGE;
1118
1107
  }
1119
1108
  // Unfinished streamed text never stands in for the child's completed reply.
1120
1109
  const outputForAcceptance = finalResult?.outputPartial ? "" : rawOutput;
@@ -1167,7 +1156,7 @@ export async function runSingleStepInner(step, ctx) {
1167
1156
  cwd: step.cwd ?? ctx.cwd,
1168
1157
  stagedIndexBaseline,
1169
1158
  signal: combinedAbortSignal([ctx.timeoutSignal, ctx.stopSignal]),
1170
- abortMessage: ctx.stopSignal?.aborted ? ctx.stopMessage ?? "Subagent stopped by user." : ctx.timeoutMessage ?? "Subagent timed out.",
1159
+ abortMessage: ctx.stopSignal?.aborted ? ctx.stopMessage ?? SUBAGENT_STOPPED_MESSAGE : ctx.timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE,
1171
1160
  reportOptional: isAgentContract(step.agentContract),
1172
1161
  artifactsDir: ctx.artifactsDir,
1173
1162
  runId: ctx.id,
@@ -1194,9 +1183,9 @@ export async function runSingleStepInner(step, ctx) {
1194
1183
  }
1195
1184
  const intercomDetachReceipt = finalResult?.finalOutput === INTERCOM_DETACH_RECEIPT;
1196
1185
  const baseFinalError = stoppedAfterAcceptance
1197
- ? ctx.stopMessage ?? "Subagent stopped by user."
1186
+ ? ctx.stopMessage ?? SUBAGENT_STOPPED_MESSAGE
1198
1187
  : timedOutAfterAcceptance
1199
- ? finalResult?.error ?? ctx.timeoutMessage ?? "Subagent timed out."
1188
+ ? finalResult?.error ?? ctx.timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE
1200
1189
  : acceptanceCanFailRun
1201
1190
  ? (finalResult?.error ? `${finalResult.error}\n${acceptanceFailure}` : acceptanceFailure)
1202
1191
  : finalResult?.error ?? (intercomDetachReceipt ? INTERCOM_DETACH_RECEIPT : undefined);
@@ -1343,8 +1332,8 @@ function markParallelGroupSetupFailure(input) {
1343
1332
  const paused = !stopped && input.statusPayload.state === "paused";
1344
1333
  const timedOut = !stopped && !paused && input.statusPayload.timedOut === true;
1345
1334
  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
1335
+ // Mirror the run-level timeout onto the step so status readers see
1336
+ // timed_out rather than a bare failure — the
1348
1337
  // StepResult below already carries it, the status step must too.
1349
1338
  if (timedOut)
1350
1339
  statusStep.timedOut = true;
@@ -1518,11 +1507,6 @@ export async function runSubagent(config, childSessions) {
1518
1507
  let previousOutput = "";
1519
1508
  const outputs = {};
1520
1509
  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
1510
  const overallStartTime = Date.now();
1527
1511
  const shareEnabled = config.share === true;
1528
1512
  const asyncDir = config.asyncDir;
@@ -1549,7 +1533,7 @@ export async function runSubagent(config, childSessions) {
1549
1533
  let stopped = false;
1550
1534
  let usageBudgetExceeded = false;
1551
1535
  const timeoutMessage = config.timeoutMs !== undefined ? `Subagent timed out after ${config.timeoutMs}ms.` : undefined;
1552
- const stopMessage = "Subagent stopped by user.";
1536
+ const stopMessage = SUBAGENT_STOPPED_MESSAGE;
1553
1537
  const timeoutAbortController = new AbortController();
1554
1538
  const stopAbortController = new AbortController();
1555
1539
  const setupInterruptController = new AbortController();
@@ -2211,8 +2195,8 @@ export async function runSubagent(config, childSessions) {
2211
2195
  agent,
2212
2196
  sessionName,
2213
2197
  context,
2214
- output: timeoutMessage ?? "Subagent timed out.",
2215
- error: timeoutMessage ?? "Subagent timed out.",
2198
+ output: timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE,
2199
+ error: timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE,
2216
2200
  exitCode: 1,
2217
2201
  timedOut: true,
2218
2202
  });
@@ -2251,6 +2235,7 @@ export async function runSubagent(config, childSessions) {
2251
2235
  });
2252
2236
  mutatingFailureStates.push(...Array.from({ length: added.addedFlatSteps }, () => createMutatingFailureState()));
2253
2237
  pendingToolResults.push(...Array.from({ length: added.addedFlatSteps }, () => undefined));
2238
+ pendingToolResultsByCallId.push(...Array.from({ length: added.addedFlatSteps }, () => new Map()));
2254
2239
  if (config.childIntercomTargets) {
2255
2240
  config.childIntercomTargets = statusPayload.steps.map((statusStep, index) => resolveSubagentIntercomTarget(id, statusStep.agent, index));
2256
2241
  }
@@ -2347,6 +2332,9 @@ export async function runSubagent(config, childSessions) {
2347
2332
  const activeLongRunningSteps = new Set();
2348
2333
  const mutatingFailureStates = initialStatusSteps.map(() => createMutatingFailureState());
2349
2334
  const pendingToolResults = initialStatusSteps.map(() => undefined);
2335
+ // Pi's parallel tool execution emits every tool_execution_start before the first result message, so each
2336
+ // step matches results to calls by toolCallId; pendingToolResults keeps the last-started call for events without one.
2337
+ const pendingToolResultsByCallId = initialStatusSteps.map(() => new Map());
2350
2338
  const activeToolCalls = initialStatusSteps.map(() => new Map());
2351
2339
  const activeToolKeysByName = initialStatusSteps.map(() => new Map());
2352
2340
  const activeToolSequences = initialStatusSteps.map(() => 0);
@@ -2698,11 +2686,72 @@ export async function runSubagent(config, childSessions) {
2698
2686
  if (!step)
2699
2687
  return;
2700
2688
  setOptionalProperty(step, "model", model);
2689
+ setOptionalProperty(step, "launchModel", undefined);
2701
2690
  setOptionalProperty(step, "thinking", thinking);
2702
2691
  setOptionalProperty(step, "contextLimit", contextLimit);
2703
2692
  statusPayload.lastUpdate = now;
2704
2693
  writeStatusPayload();
2705
2694
  };
2695
+ // Records a finished child's result on its status step; the sequential and both parallel paths share it.
2696
+ const recordStepResult = (fi, singleResult, startedAt, endedAt, recordLaunchContractDigest = false) => {
2697
+ const step = requiredStatusStep(statusPayload, fi);
2698
+ const childStopped = singleResult.stopped === true;
2699
+ const childInterrupted = singleResult.interrupted === true;
2700
+ step.status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.execution?.status === "partial" ? "partial" : singleResult.exitCode === 0 ? "complete" : "failed";
2701
+ step.endedAt = endedAt;
2702
+ step.durationMs = endedAt - startedAt;
2703
+ step.exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
2704
+ setOptionalProperty(step, "timedOut", timedOut || singleResult.timedOut ? true : undefined);
2705
+ setOptionalProperty(step, "stopped", stopped || childStopped ? true : undefined);
2706
+ setOptionalProperty(step, "toolBudget", singleResult.toolBudget);
2707
+ setOptionalProperty(step, "toolBudgetBlocked", singleResult.toolBudgetBlocked);
2708
+ if (singleResult.toolBudget)
2709
+ statusPayload.toolBudget = singleResult.toolBudget;
2710
+ if (singleResult.toolBudgetBlocked)
2711
+ statusPayload.toolBudgetBlocked = true;
2712
+ setOptionalProperty(step, "sessionName", singleResult.sessionName);
2713
+ // Keep the model the child was observed running on; a host extension can switch it
2714
+ // after launch, and singleResult.model only names the launch candidate.
2715
+ setOptionalProperty(step, "model", step.model ?? singleResult.model);
2716
+ setOptionalProperty(step, "thinking", resolveEffectiveThinking(singleResult.model, step.thinking));
2717
+ setOptionalProperty(step, "requestedModel", singleResult.requestedModel);
2718
+ setOptionalProperty(step, "contextOverflow", singleResult.contextOverflow);
2719
+ setOptionalProperty(step, "totalCost", singleResult.totalCost);
2720
+ setOptionalProperty(step, "error", stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE) : singleResult.error);
2721
+ setOptionalProperty(step, "transcriptPath", singleResult.transcriptPath ?? step.transcriptPath);
2722
+ setOptionalProperty(step, "transcriptError", singleResult.transcriptError);
2723
+ setOptionalProperty(step, "agentContract", singleResult.agentContract);
2724
+ if (recordLaunchContractDigest)
2725
+ setOptionalProperty(step, "launchContractDigest", singleResult.launchContractDigest);
2726
+ setOptionalProperty(step, "launchResolvedExtensions", singleResult.launchResolvedExtensions);
2727
+ setOptionalProperty(step, "runtimeAcknowledgedExtensions", singleResult.runtimeAcknowledgedExtensions);
2728
+ setOptionalProperty(step, "effects", singleResult.effects);
2729
+ setOptionalProperty(step, "execution", singleResult.execution);
2730
+ setOptionalProperty(step, "review", singleResult.review);
2731
+ setOptionalProperty(step, "structuredOutput", singleResult.structuredOutput);
2732
+ setOptionalProperty(step, "structuredOutputPath", singleResult.structuredOutputPath);
2733
+ setOptionalProperty(step, "structuredOutputSchemaPath", singleResult.structuredOutputSchemaPath);
2734
+ setOptionalProperty(step, "acceptance", singleResult.acceptance);
2735
+ setOptionalProperty(step, "timeoutRecovery", singleResult.timeoutRecovery);
2736
+ setOptionalProperty(step, "watchdog", singleResult.watchdog);
2737
+ setOptionalProperty(step, "capabilityCeiling", singleResult.capabilityCeiling);
2738
+ setOptionalProperty(step, "capabilityAudit", singleResult.capabilityAudit);
2739
+ if (singleResult.capabilityCeiling)
2740
+ statusPayload.capabilityCeiling = singleResult.capabilityCeiling;
2741
+ if (singleResult.capabilityAudit)
2742
+ statusPayload.capabilityAudit = singleResult.capabilityAudit;
2743
+ };
2744
+ // Adds a parallel child's cost to the in-flight usage ledger.
2745
+ const addParallelUsageCost = (totalCost) => {
2746
+ if (!totalCost)
2747
+ return;
2748
+ pendingParallelUsageCost = {
2749
+ inputTokens: pendingParallelUsageCost.inputTokens + totalCost.inputTokens,
2750
+ outputTokens: pendingParallelUsageCost.outputTokens + totalCost.outputTokens,
2751
+ costUsd: pendingParallelUsageCost.costUsd + totalCost.costUsd,
2752
+ };
2753
+ refreshUsageBudget();
2754
+ };
2706
2755
  const updateStepFromChildEvent = (flatIndex, event) => {
2707
2756
  const step = statusPayload.steps[flatIndex];
2708
2757
  if (!step)
@@ -2745,11 +2794,15 @@ export async function runSubagent(config, childSessions) {
2745
2794
  step.toolCount = (step.toolCount ?? 0) + 1;
2746
2795
  const configuredToolBudget = flatSteps[flatIndex]?.toolBudget;
2747
2796
  if (configuredToolBudget) {
2748
- step.toolBudget = toolBudgetState(configuredToolBudget, step.toolCount);
2797
+ step.toolBudget = toolBudgetState(configuredToolBudget, step.toolCount, step.toolBudget?.blockedTool);
2749
2798
  statusPayload.toolBudget = step.toolBudget;
2750
2799
  }
2751
2800
  recordActiveToolCall(flatIndex, { toolCallId: event.toolCallId, toolName: event.toolName }, { argsPreview, currentPath, blocksSupervisor, now });
2752
- pendingToolResults[flatIndex] = omitUndefinedProperties({ tool: event.toolName, path: currentPath, mutates, startedAt: now });
2801
+ const pending = omitUndefinedProperties({ tool: event.toolName, path: currentPath, mutates, startedAt: now });
2802
+ pendingToolResults[flatIndex] = pending;
2803
+ const startedCallId = event.toolCallId;
2804
+ if (typeof startedCallId === "string" && startedCallId)
2805
+ pendingToolResultsByCallId[flatIndex].set(startedCallId, pending);
2753
2806
  statusPayload.toolCount = (statusPayload.toolCount ?? 0) + 1;
2754
2807
  syncTopLevelCurrentTool();
2755
2808
  if (controlConfig.enabled && blocksSupervisor && step.activityState !== "needs_attention") {
@@ -2794,18 +2847,25 @@ export async function runSubagent(config, childSessions) {
2794
2847
  }
2795
2848
  syncTopLevelCurrentTool();
2796
2849
  }
2797
- else if (event.type === "tool_result_end" && event.message) {
2798
- const toolSnapshot = pendingToolResults[flatIndex];
2799
- pendingToolResults[flatIndex] = undefined;
2850
+ else if (event.type === "message_end" && event.message?.role === "toolResult") {
2851
+ // Pi delivers every tool result, including a blocked call, as a toolResult message after tool_execution_end.
2852
+ const resultCallId = event.message.toolCallId;
2853
+ const byCallId = pendingToolResultsByCallId[flatIndex];
2854
+ const keyedSnapshot = typeof resultCallId === "string" ? byCallId.get(resultCallId) : undefined;
2855
+ if (keyedSnapshot)
2856
+ byCallId.delete(resultCallId);
2857
+ const toolSnapshot = keyedSnapshot ?? pendingToolResults[flatIndex];
2858
+ if (!keyedSnapshot || pendingToolResults[flatIndex] === keyedSnapshot)
2859
+ pendingToolResults[flatIndex] = undefined;
2800
2860
  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
- }
2861
+ const resultToolName = event.message.toolName;
2862
+ const blockedTool = (typeof resultToolName === "string" && resultToolName.length > 0 ? resultToolName : undefined) ?? toolSnapshot?.tool;
2863
+ const configuredToolBudget = flatSteps[flatIndex]?.toolBudget;
2864
+ if (configuredToolBudget && isToolBudgetBlockedMessage(configuredToolBudget, resultText, blockedTool)) {
2865
+ step.toolBudget = toolBudgetState(configuredToolBudget, step.toolCount ?? 0, blockedTool);
2866
+ step.toolBudgetBlocked = true;
2867
+ statusPayload.toolBudget = step.toolBudget;
2868
+ statusPayload.toolBudgetBlocked = true;
2809
2869
  }
2810
2870
  appendRecentStepOutput(step, resultText.split("\n").slice(-10));
2811
2871
  if (toolSnapshot?.mutates && didMutatingToolFail(resultText)) {
@@ -2847,6 +2907,17 @@ export async function runSubagent(config, childSessions) {
2847
2907
  else if (event.type === "message_end" && event.message?.role === "assistant") {
2848
2908
  appendRecentStepOutput(step, stripAcceptanceReport(extractTextFromContent(event.message.content)).split("\n").slice(-10));
2849
2909
  step.turnCount = (step.turnCount ?? 0) + 1;
2910
+ // The launch model is only a candidate: a host extension (e.g. a model
2911
+ // router) can switch the child's model on its first input, so report the
2912
+ // model each assistant message actually ran on. Mirrors the foreground
2913
+ // path's `progress.model = observedModel`.
2914
+ if (event.message.model) {
2915
+ const observedModel = qualifyModelWithProvider(event.message.model, event.message.provider, flatSteps[flatIndex]?.modelVerificationRegistry) ?? event.message.model;
2916
+ if (observedModel !== step.model) {
2917
+ step.launchModel ??= step.model;
2918
+ step.model = observedModel;
2919
+ }
2920
+ }
2850
2921
  const usage = event.message.usage;
2851
2922
  if (config.usageBudget) {
2852
2923
  const known = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
@@ -3099,7 +3170,7 @@ export async function runSubagent(config, childSessions) {
3099
3170
  return;
3100
3171
  timedOut = true;
3101
3172
  const now = Date.now();
3102
- const message = timeoutMessage ?? "Subagent timed out.";
3173
+ const message = timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE;
3103
3174
  statusPayload.timedOut = true;
3104
3175
  statusPayload.error = message;
3105
3176
  currentActivityState = undefined;
@@ -3293,7 +3364,7 @@ export async function runSubagent(config, childSessions) {
3293
3364
  }),
3294
3365
  cwd,
3295
3366
  signal: combinedAbortSignal([timeoutAbortController.signal, stopAbortController.signal]),
3296
- abortMessage: stopAbortController.signal.aborted ? stopMessage : timeoutMessage ?? "Subagent timed out.",
3367
+ abortMessage: stopAbortController.signal.aborted ? stopMessage : timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE,
3297
3368
  reportOptional: isAgentContract(step.agentContract),
3298
3369
  }))
3299
3370
  : undefined;
@@ -3304,7 +3375,7 @@ export async function runSubagent(config, childSessions) {
3304
3375
  placeholder.acceptance = effectiveGroupAcceptance;
3305
3376
  const groupAcceptanceFailure = effectiveGroupAcceptance && (!isAgentContract(step.agentContract) || step.gateOn === "acceptance") ? acceptanceFailureMessage(effectiveGroupAcceptance) : undefined;
3306
3377
  if (groupTimedOut || groupStopped || groupAcceptanceFailure) {
3307
- const errorMessage = groupStopped ? stopMessage : groupTimedOut ? timeoutMessage ?? "Subagent timed out." : groupAcceptanceFailure;
3378
+ const errorMessage = groupStopped ? stopMessage : groupTimedOut ? timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE : groupAcceptanceFailure;
3308
3379
  statusPayload.state = groupStopped ? "stopped" : "failed";
3309
3380
  statusPayload.error = errorMessage;
3310
3381
  setOptionalProperty(statusPayload, "stopped", groupStopped ? true : statusPayload.stopped);
@@ -3352,7 +3423,6 @@ export async function runSubagent(config, childSessions) {
3352
3423
  dynamic: step.parallel.acceptanceInput === undefined,
3353
3424
  agentContract: step.parallel.agentContract ?? step.agentContract,
3354
3425
  })),
3355
- systemPrompt: step.parallel.namespaceOutputPath ? injectOutputPathSystemPrompt(step.parallel.systemPrompt ?? "", outputPath, step.parallel) : step.parallel.systemPrompt,
3356
3426
  outputPath,
3357
3427
  label: task.label ?? step.parallel.label,
3358
3428
  ...(step.sessionFiles?.[itemIndex] ? { sessionFile: step.sessionFiles[itemIndex] } : {}),
@@ -3397,6 +3467,7 @@ export async function runSubagent(config, childSessions) {
3397
3467
  }
3398
3468
  mutatingFailureStates.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps.map(() => createMutatingFailureState()));
3399
3469
  pendingToolResults.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps.map(() => undefined));
3470
+ pendingToolResultsByCallId.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps.map(() => new Map()));
3400
3471
  const materializedDelta = dynamicStatusSteps.length - 1;
3401
3472
  for (const group of statusPayload.parallelGroups) {
3402
3473
  if (group.stepIndex === stepIndex) {
@@ -3477,7 +3548,6 @@ export async function runSubagent(config, childSessions) {
3477
3548
  return omitUndefinedProperties({ agent: task.agent, ...(task.sessionName ? { sessionName: task.sessionName } : {}), context: task.context, output: "(skipped — fail-fast)", exitCode: -1, skipped: true });
3478
3549
  }
3479
3550
  const taskStartTime = Date.now();
3480
- launchedFlatIndices.add(fi);
3481
3551
  statusPayload.currentStep = fi;
3482
3552
  requiredStatusStep(statusPayload, fi).status = "running";
3483
3553
  delete requiredStatusStep(statusPayload, fi).error;
@@ -3531,54 +3601,8 @@ export async function runSubagent(config, childSessions) {
3531
3601
  const taskEndTime = Date.now();
3532
3602
  const childInterrupted = singleResult.interrupted === true;
3533
3603
  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;
3604
+ recordStepResult(fi, singleResult, taskStartTime, taskEndTime, true);
3605
+ addParallelUsageCost(singleResult.totalCost);
3582
3606
  statusPayload.lastUpdate = taskEndTime;
3583
3607
  writeStatusPayload();
3584
3608
  appendCapabilityCeilingAppliedEvent(eventsPath, id, fi, task.agent, singleResult);
@@ -3591,7 +3615,7 @@ export async function runSubagent(config, childSessions) {
3591
3615
  appendTerminalChildStatusEvent(fi, taskEndTime);
3592
3616
  if (singleResult.exitCode !== 0 && failFast && !childStopped)
3593
3617
  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 };
3618
+ 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
3619
  }, globalSemaphore);
3596
3620
  flatIndex += dynamicSteps.length;
3597
3621
  for (const pr of parallelResults) {
@@ -3678,7 +3702,7 @@ export async function runSubagent(config, childSessions) {
3678
3702
  }),
3679
3703
  cwd,
3680
3704
  signal: combinedAbortSignal([timeoutAbortController.signal, stopAbortController.signal]),
3681
- abortMessage: stopAbortController.signal.aborted ? stopMessage : timeoutMessage ?? "Subagent timed out.",
3705
+ abortMessage: stopAbortController.signal.aborted ? stopMessage : timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE,
3682
3706
  reportOptional: isAgentContract(step.agentContract),
3683
3707
  }))
3684
3708
  : undefined;
@@ -3686,7 +3710,7 @@ export async function runSubagent(config, childSessions) {
3686
3710
  const groupTimedOut = !groupStopped && (timedOut || timeoutAbortController.signal.aborted);
3687
3711
  const effectiveGroupAcceptance = groupTimedOut || groupStopped ? undefined : groupAcceptance;
3688
3712
  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;
3713
+ const groupError = groupStopped ? stopMessage : groupTimedOut ? timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE : groupAcceptanceFailure;
3690
3714
  markDynamicGraphGroup(stepIndex, groupError ? groupStopped ? "stopped" : "failed" : "completed", groupError, effectiveGroupAcceptance);
3691
3715
  if (groupError) {
3692
3716
  results.push(omitUndefinedProperties({
@@ -3801,7 +3825,7 @@ export async function runSubagent(config, childSessions) {
3801
3825
  if (config.deadlineAt !== undefined && Date.now() >= config.deadlineAt)
3802
3826
  timeoutRunner();
3803
3827
  if (setupSignal.aborted)
3804
- throw new Error(stopped ? stopMessage : timedOut ? timeoutMessage ?? "Subagent timed out." : "Subagent paused during worktree setup.");
3828
+ throw new Error(stopped ? stopMessage : timedOut ? timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE : "Subagent paused during worktree setup.");
3805
3829
  }
3806
3830
  catch (error) {
3807
3831
  if (worktreeSetup)
@@ -3896,7 +3920,6 @@ export async function runSubagent(config, childSessions) {
3896
3920
  return omitUndefinedProperties({ agent: task.agent, ...(task.sessionName ? { sessionName: task.sessionName } : {}), context: task.context, output: "(skipped — fail-fast)", exitCode: -1, skipped: true });
3897
3921
  }
3898
3922
  const taskStartTime = Date.now();
3899
- launchedFlatIndices.add(fi);
3900
3923
  statusPayload.currentStep = fi;
3901
3924
  requiredStatusStep(statusPayload, fi).status = "running";
3902
3925
  delete requiredStatusStep(statusPayload, fi).error;
@@ -3962,53 +3985,8 @@ export async function runSubagent(config, childSessions) {
3962
3985
  const taskDuration = taskEndTime - taskStartTime;
3963
3986
  const childInterrupted = singleResult.interrupted === true;
3964
3987
  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;
3988
+ recordStepResult(fi, singleResult, taskStartTime, taskEndTime);
3989
+ addParallelUsageCost(singleResult.totalCost);
4012
3990
  statusPayload.lastUpdate = taskEndTime;
4013
3991
  writeStatusPayload();
4014
3992
  appendCapabilityCeilingAppliedEvent(eventsPath, id, fi, task.agent, singleResult);
@@ -4021,7 +3999,7 @@ export async function runSubagent(config, childSessions) {
4021
3999
  appendTerminalChildStatusEvent(fi, taskEndTime);
4022
4000
  if (singleResult.exitCode !== 0 && failFast && !childStopped)
4023
4001
  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 };
4002
+ 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
4003
  }, globalSemaphore);
4026
4004
  flatIndex += group.parallel.length;
4027
4005
  for (let t = 0; t < group.parallel.length; t++) {
@@ -4290,7 +4268,6 @@ export async function runSubagent(config, childSessions) {
4290
4268
  }
4291
4269
  const singleCwd = singleWorktreeSetup?.worktrees[0]?.agentCwd ?? cwd;
4292
4270
  const stepStartTime = Date.now();
4293
- launchedFlatIndices.add(flatIndex);
4294
4271
  statusPayload.currentStep = flatIndex;
4295
4272
  requiredStatusStep(statusPayload, flatIndex).status = "running";
4296
4273
  delete requiredStatusStep(statusPayload, flatIndex).activityState;
@@ -4372,10 +4349,10 @@ export async function runSubagent(config, childSessions) {
4372
4349
  launchContractDigest: singleResult.launchContractDigest,
4373
4350
  launchResolvedExtensions: singleResult.launchResolvedExtensions,
4374
4351
  runtimeAcknowledgedExtensions: singleResult.runtimeAcknowledgedExtensions,
4375
- output: stopped || childStopped ? stopMessage : timedOut ? singleResult.output || (timeoutMessage ?? "Subagent timed out.") : singleResult.output,
4352
+ output: stopped || childStopped ? stopMessage : timedOut ? singleResult.output || (timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE) : singleResult.output,
4376
4353
  outputState: singleResult.outputState,
4377
4354
  outputPartial: singleResult.outputPartial,
4378
- error: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error,
4355
+ error: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE) : singleResult.error,
4379
4356
  success: !stopped && !childStopped && !timedOut && singleResult.interrupted !== true && singleResult.exitCode === 0 && singleResult.execution?.status !== "partial",
4380
4357
  exitCode: stopped || childStopped ? 1 : timedOut ? 1 : singleResult.interrupted === true ? 0 : singleResult.exitCode,
4381
4358
  sessionFile: singleResult.sessionFile,
@@ -4454,45 +4431,7 @@ export async function runSubagent(config, childSessions) {
4454
4431
  }
4455
4432
  const stepEndTime = Date.now();
4456
4433
  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;
4434
+ recordStepResult(flatIndex, singleResult, stepStartTime, stepEndTime);
4496
4435
  if (stepTokens) {
4497
4436
  requiredStatusStep(statusPayload, flatIndex).tokens = stepTokens;
4498
4437
  statusPayload.totalTokens = { ...previousCumulativeTokens };
@@ -4722,7 +4661,7 @@ export async function runSubagent(config, childSessions) {
4722
4661
  }
4723
4662
  if (timedOut) {
4724
4663
  statusPayload.timedOut = true;
4725
- statusPayload.error = timeoutMessage ?? "Subagent timed out.";
4664
+ statusPayload.error = timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE;
4726
4665
  }
4727
4666
  if (usageBudgetExceeded && statusPayload.usageBudget && !statusPayload.error) {
4728
4667
  statusPayload.error = usageBudgetExceededMessage(statusPayload.usageBudget);
@@ -4767,13 +4706,13 @@ export async function runSubagent(config, childSessions) {
4767
4706
  mode: resultMode,
4768
4707
  success: statusPayload.state === "complete",
4769
4708
  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,
4709
+ 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
4710
  ...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),
4772
4711
  ...(config.deadlineAt !== undefined ? { deadlineAt: config.deadlineAt } : {}),
4773
4712
  ...(statusPayload.toolBudget ? { toolBudget: statusPayload.toolBudget } : {}),
4774
4713
  ...(statusPayload.toolBudgetBlocked ? { toolBudgetBlocked: true } : {}),
4775
4714
  ...(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." } : {}),
4715
+ ...(stopped ? { stopped: true, error: stopMessage } : timedOut ? { timedOut: true, error: timeoutMessage ?? SUBAGENT_TIMED_OUT_MESSAGE } : usageBudgetExceeded ? { error: statusPayload.error ?? "Usage budget exhausted." } : {}),
4777
4716
  results: results.map((r) => omitUndefinedProperties({
4778
4717
  agent: r.agent,
4779
4718
  ...(r.sessionName ? { sessionName: r.sessionName } : {}),
@@ -4883,26 +4822,6 @@ export async function runSubagent(config, childSessions) {
4883
4822
  totalCost: finalTotalCost,
4884
4823
  usageBudget: statusPayload.usageBudget,
4885
4824
  }));
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
4825
  writeRunLog(logPath, omitUndefinedProperties({
4907
4826
  id,
4908
4827
  mode: statusPayload.mode,