pi-subagents 0.55.0 → 0.57.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 (97) hide show
  1. package/CHANGELOG.md +81 -3
  2. package/agents/claude-code-writer.md +15 -0
  3. package/agents/claude-code.md +15 -0
  4. package/agents/codex-exec-writer.md +15 -0
  5. package/agents/codex-exec.md +15 -0
  6. package/agents/cursor-agent-writer.md +14 -0
  7. package/agents/cursor-agent.md +14 -0
  8. package/docs/agents.md +111 -15
  9. package/docs/configuration.md +37 -0
  10. package/docs/extension-api.md +2 -2
  11. package/docs/models.md +6 -0
  12. package/docs/observability.md +8 -7
  13. package/docs/tool-reference.md +11 -1
  14. package/docs/workflows.md +20 -0
  15. package/package.json +1 -1
  16. package/skills/pi-subagents/references/execution-controls.md +1 -1
  17. package/src/agents/agent-management.ts +13 -5
  18. package/src/agents/agent-refinements.ts +4 -4
  19. package/src/agents/agent-serializer.ts +2 -0
  20. package/src/agents/agents.ts +242 -49
  21. package/src/agents/builtin-names.ts +6 -0
  22. package/src/agents/runtime-agent-registry.ts +8 -2
  23. package/src/api/preflight.ts +22 -3
  24. package/src/extension/config.ts +70 -0
  25. package/src/extension/doctor.ts +3 -3
  26. package/src/extension/index.ts +18 -4
  27. package/src/extension/public-execution.ts +29 -13
  28. package/src/extension/rpc.ts +20 -5
  29. package/src/extension/schemas.ts +9 -3
  30. package/src/extension/tool-description.ts +11 -9
  31. package/src/inspectors/herdr/actions.ts +2 -1
  32. package/src/inspectors/herdr/inspector-runner.ts +2 -10
  33. package/src/inspectors/herdr/session-roots-codec.ts +42 -0
  34. package/src/integrations/herdr-status.ts +46 -3
  35. package/src/runs/background/active-async-capacity.ts +77 -10
  36. package/src/runs/background/async-execution.ts +148 -27
  37. package/src/runs/background/async-job-tracker.ts +5 -0
  38. package/src/runs/background/async-resume.ts +3 -0
  39. package/src/runs/background/async-retention.ts +20 -3
  40. package/src/runs/background/async-status.ts +5 -0
  41. package/src/runs/background/chain-root-attachment.ts +15 -1
  42. package/src/runs/background/fleet-view.ts +16 -10
  43. package/src/runs/background/inspect-rpc.ts +8 -8
  44. package/src/runs/background/result-files.ts +27 -14
  45. package/src/runs/background/result-watcher.ts +9 -5
  46. package/src/runs/background/run-status.ts +33 -6
  47. package/src/runs/background/scheduled-runs.ts +7 -1
  48. package/src/runs/background/subagent-runner.ts +254 -49
  49. package/src/runs/background/wait-completions.ts +4 -0
  50. package/src/runs/foreground/execution.ts +107 -16
  51. package/src/runs/foreground/foreground-control.ts +6 -0
  52. package/src/runs/foreground/foreground-history.ts +25 -3
  53. package/src/runs/foreground/subagent-executor.ts +324 -84
  54. package/src/runs/foreground/workflow-detach-reconcile.ts +31 -7
  55. package/src/runs/shared/acceptance.ts +10 -5
  56. package/src/runs/shared/agent-contract.ts +1 -1
  57. package/src/runs/shared/child-protocol.ts +21 -7
  58. package/src/runs/shared/claude-code-adapter.ts +129 -0
  59. package/src/runs/shared/codex-exec-adapter.ts +129 -0
  60. package/src/runs/shared/completion-guard.ts +3 -2
  61. package/src/runs/shared/cursor-agent-adapter.ts +114 -0
  62. package/src/runs/shared/dynamic-fanout.ts +1 -1
  63. package/src/runs/shared/extension-bindings.ts +78 -0
  64. package/src/runs/shared/external-cli-contract.ts +167 -0
  65. package/src/runs/shared/external-cli-preflight.ts +122 -0
  66. package/src/runs/shared/external-cli-runner.ts +349 -54
  67. package/src/runs/shared/fast-mode-extension.ts +10 -0
  68. package/src/runs/shared/model-exclusions.ts +59 -8
  69. package/src/runs/shared/model-fallback.ts +25 -8
  70. package/src/runs/shared/mutation-evidence.ts +150 -0
  71. package/src/runs/shared/nested-events.ts +3 -1
  72. package/src/runs/shared/nested-render.ts +2 -2
  73. package/src/runs/shared/parallel-utils.ts +3 -0
  74. package/src/runs/shared/pi-args.ts +47 -0
  75. package/src/runs/shared/process-signal.ts +13 -0
  76. package/src/runs/shared/run-history.ts +21 -1
  77. package/src/runs/shared/structured-output.ts +18 -4
  78. package/src/runs/shared/subagent-prompt-runtime.ts +9 -5
  79. package/src/shared/fork-context.ts +21 -0
  80. package/src/shared/formatters.ts +7 -1
  81. package/src/shared/launch-contract.ts +6 -0
  82. package/src/shared/pruned-fork.ts +450 -0
  83. package/src/shared/session-file-trust.ts +19 -0
  84. package/src/shared/session-tokens.ts +14 -3
  85. package/src/shared/settings.ts +16 -3
  86. package/src/shared/shortcuts.ts +17 -0
  87. package/src/shared/types.ts +166 -9
  88. package/src/shared/workflow-child-permit.ts +116 -0
  89. package/src/slash/delegation-adapters.ts +0 -1
  90. package/src/slash/slash-commands.ts +8 -6
  91. package/src/tui/fleet-status.ts +27 -10
  92. package/src/tui/fleet-transcript.ts +11 -5
  93. package/src/tui/fleet.ts +28 -13
  94. package/src/tui/render.ts +47 -21
  95. package/src/workflows/scripted-workflow.ts +298 -30
  96. package/src/workflows/workflow-child-summary.ts +117 -0
  97. package/src/workflows/workflow-receipt.ts +119 -4
@@ -80,13 +80,14 @@ import {
80
80
  import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, type SubagentTaskDelivery } from "../shared/pi-args.ts";
81
81
  import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
82
82
  import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
83
- import { createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput } from "../shared/structured-output.ts";
84
- import { formatProcessSignalError, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
83
+ import { createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
84
+ import { formatMidToolExitError, formatProcessSignalError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
85
85
  import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
86
+ import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
86
87
  import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
87
88
  import { claimRunFanoutBatch, getRunFanoutBudgetSnapshot } from "../shared/run-fanout-budget.ts";
88
89
  import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent } from "../shared/nested-events.ts";
89
- import { formatModelAttemptNote, formatSubagentModelVerificationError, isContextOverflow, isRetryableModelFailure, recordRetryableModelFailure } from "../shared/model-fallback.ts";
90
+ import { formatModelAttemptNote, formatSubagentModelVerificationError, isContextOverflow, isRetryableModelFailureAttempt, recordRetryableModelFailure } from "../shared/model-fallback.ts";
90
91
  import {
91
92
  SUBAGENT_STARTUP_RETRY_DELAYS_MS,
92
93
  formatSubagentExtensionConflictError,
@@ -131,6 +132,7 @@ import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts
131
132
  import { acceptanceFailureMessage, aggregateAcceptanceReport, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
132
133
  import { attachContractProjections, isAgentContractV1 } from "../shared/agent-contract.ts";
133
134
  import { waitForImportedAsyncRoot } from "./chain-root-attachment.ts";
135
+ import { normalizeExtensionBindings } from "../shared/extension-bindings.ts";
134
136
  import { appendRunnerStepsToStatus, consumeChainAppendRequests, countPendingChainAppendRequests, statusStepDescription } from "./chain-append.ts";
135
137
  import { asyncStatusChildIdentity } from "../shared/child-identity.ts";
136
138
  import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, turnBudgetDecision, turnBudgetDeferredNote, turnBudgetDeferredState, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
@@ -139,9 +141,13 @@ import { effectiveToolTimeoutMs, formatToolTimeoutMessage, toolTimeoutCallKey }
139
141
  import { usageBudgetExceededMessage, usageBudgetState } from "../shared/usage-budget.ts";
140
142
  import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
141
143
  import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
142
- import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
144
+ import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ChildLifecycleState, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
143
145
  import { acquireSessionLease, type SessionLeaseRequest } from "../shared/session-lease.ts";
144
146
  import { buildExternalCliPrompt, runExternalCli } from "../shared/external-cli-runner.ts";
147
+ import { resolveClaudeCodeLaunch } from "../shared/claude-code-adapter.ts";
148
+ import { resolveCodexExecLaunch } from "../shared/codex-exec-adapter.ts";
149
+ import { resolveCursorAgentLaunch } from "../shared/cursor-agent-adapter.ts";
150
+ import { resolveExternalCliRunnerStatus } from "../shared/external-cli-contract.ts";
145
151
  import { runExternalJob } from "../shared/external-job-runner.ts";
146
152
  import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
147
153
  import { decodeSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
@@ -204,6 +210,7 @@ interface SubagentRunConfig {
204
210
  launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
205
211
  runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
206
212
  runnerProcessInstanceId?: string;
213
+ launchBarrierToken?: string;
207
214
  parentWorkflowRunId?: string;
208
215
  workflowKey?: string;
209
216
  }
@@ -229,6 +236,7 @@ interface StepResult {
229
236
  timedOut?: boolean;
230
237
  stopped?: boolean;
231
238
  processSignal?: string | null;
239
+ timeoutRecovery?: import("../../shared/types.ts").TimeoutRecoverySummary;
232
240
  turnBudget?: TurnBudgetState;
233
241
  turnBudgetExceeded?: boolean;
234
242
  wrapUpRequested?: boolean;
@@ -493,6 +501,7 @@ interface ChildUsage {
493
501
  output?: number;
494
502
  outputTokens?: number;
495
503
  cacheRead?: number;
504
+ cacheReadTokens?: number;
496
505
  cacheWrite?: number;
497
506
  cost?: { total?: number };
498
507
  }
@@ -540,6 +549,28 @@ interface RunPiStreamingResult {
540
549
  processCloseObservedAt?: number;
541
550
  processSignal?: string | null;
542
551
  processTree: ProcessTreeTerminalV1;
552
+ currentTool?: string;
553
+ currentToolArgs?: string;
554
+ currentPath?: string;
555
+ afterCompactionSettlement?: boolean;
556
+ }
557
+
558
+ const MAX_CHILD_FAILURE_DIAGNOSTIC_CHARS = 8_192;
559
+
560
+ function formatChildFailureDiagnostic(input: {
561
+ error: string | undefined;
562
+ afterCompactionSettlement?: boolean;
563
+ missingFileOnlyOutput?: string;
564
+ }): string | undefined {
565
+ if (!input.error) return undefined;
566
+ const notes = [
567
+ input.afterCompactionSettlement ? "Child failure followed session compaction and agent settlement." : undefined,
568
+ input.missingFileOnlyOutput ? `Required file-only output was not produced: ${input.missingFileOnlyOutput.slice(0, 2_048)}` : undefined,
569
+ ].filter((note): note is string => Boolean(note));
570
+ if (notes.length === 0) return input.error;
571
+ const context = notes.join("\n");
572
+ const errorLimit = MAX_CHILD_FAILURE_DIAGNOSTIC_CHARS - (context ? context.length + 1 : 0);
573
+ return `${input.error.slice(0, Math.max(0, errorLimit))}${context ? `\n${context}` : ""}`;
543
574
  }
544
575
 
545
576
  function runPiStreaming(
@@ -610,9 +641,54 @@ function runPiStreaming(
610
641
  let observedMutationAttempt = false;
611
642
  let structuredOutputToolInvoked = false;
612
643
  let structuredOutputMessageStartIndex: number | undefined;
644
+ let currentTool: string | undefined;
645
+ let currentToolArgs: string | undefined;
646
+ let currentPath: string | undefined;
613
647
  let toolCount = 0;
648
+ type ActiveToolCall = { key: string; tool: string; args?: string; path?: string };
649
+ let activeToolSequence = 0;
650
+ const activeToolCalls = new Map<string, ActiveToolCall>();
651
+ const activeToolKeysByName = new Map<string, string[]>();
652
+ const refreshCurrentTool = (): void => {
653
+ const active = [...activeToolCalls.values()].at(-1);
654
+ currentTool = active?.tool;
655
+ currentToolArgs = active?.args;
656
+ currentPath = active?.path;
657
+ };
658
+ const recordActiveToolCall = (event: { toolCallId?: unknown; toolName: string; args?: Record<string, unknown> }): void => {
659
+ const key = toolTimeoutCallKey(event, ++activeToolSequence);
660
+ const active = omitUndefinedProperties({
661
+ key,
662
+ tool: event.toolName,
663
+ args: extractToolArgsPreview(event.args ?? {}),
664
+ path: resolveCurrentPath(event.toolName, event.args),
665
+ });
666
+ activeToolCalls.set(key, active);
667
+ const keys = activeToolKeysByName.get(active.tool) ?? [];
668
+ keys.push(key);
669
+ activeToolKeysByName.set(active.tool, keys);
670
+ refreshCurrentTool();
671
+ };
672
+ const removeActiveToolCall = (event: { toolCallId?: unknown; toolName?: unknown }): void => {
673
+ const key = typeof event.toolCallId === "string" && event.toolCallId.length > 0
674
+ ? `id:${event.toolCallId}`
675
+ : typeof event.toolName === "string"
676
+ ? activeToolKeysByName.get(event.toolName)?.[0]
677
+ : activeToolCalls.size === 1
678
+ ? [...activeToolCalls.keys()][0]
679
+ : undefined;
680
+ if (!key) return;
681
+ const active = activeToolCalls.get(key);
682
+ if (!active) return;
683
+ activeToolCalls.delete(key);
684
+ const keys = activeToolKeysByName.get(active.tool)?.filter((candidate) => candidate !== key) ?? [];
685
+ if (keys.length > 0) activeToolKeysByName.set(active.tool, keys);
686
+ else activeToolKeysByName.delete(active.tool);
687
+ refreshCurrentTool();
688
+ };
614
689
  const childWatchdogConfig = decodeChildWatchdogConfig(env?.[CHILD_WATCHDOG_CONFIG_ENV]);
615
690
  let childWatchdogState: ChildWatchdogStateSnapshot | undefined;
691
+ const childLifecycleState: ChildLifecycleState = { compactionRetryActive: false };
616
692
  let applyChildLifecycle = (_action: ChildLifecycleAction): void => {};
617
693
  const updateChildWatchdogState = (snapshot: ChildWatchdogStateSnapshot): void => {
618
694
  childWatchdogState = snapshot;
@@ -663,8 +739,10 @@ function runPiStreaming(
663
739
 
664
740
  appendChildEvent(event as unknown as Record<string, unknown>);
665
741
  transcriptWriter?.writeChildEvent(event);
666
- if (event.type === "agent_settled") agentSettledReceived = true;
667
- applyChildLifecycle(projectChildLifecycle(event));
742
+ if (event.type === "compaction_start") compactionStartedReceived = true;
743
+ const lifecycleAction = projectChildLifecycle(event, false, childLifecycleState);
744
+ if (event.type === "agent_settled" && lifecycleAction === "start-drain") agentSettledReceived = true;
745
+ applyChildLifecycle(lifecycleAction);
668
746
 
669
747
  if (isChildWatchdogStatusEvent(event)) {
670
748
  if (!childWatchdogConfig) return;
@@ -701,12 +779,14 @@ function runPiStreaming(
701
779
 
702
780
  if (event.type === "tool_execution_end") {
703
781
  clearActiveToolTimeout(event);
782
+ removeActiveToolCall(event);
704
783
  return;
705
784
  }
706
785
 
707
786
  if (event.type === "tool_execution_start" && event.toolName) {
708
787
  toolCount += 1;
709
788
  armToolTimeout({ toolCallId: (event as { toolCallId?: unknown }).toolCallId, toolName: event.toolName });
789
+ recordActiveToolCall({ toolCallId: (event as { toolCallId?: unknown }).toolCallId, toolName: event.toolName, args: event.args });
710
790
  if (event.toolName === "structured_output") {
711
791
  structuredOutputToolInvoked = true;
712
792
  structuredOutputMessageStartIndex = messages.length;
@@ -718,6 +798,13 @@ function runPiStreaming(
718
798
  }
719
799
 
720
800
  if ((event.type === "message_end" || event.type === "tool_result_end") && event.message) {
801
+ if (event.type === "tool_result_end") {
802
+ clearActiveToolTimeout(event);
803
+ removeActiveToolCall({
804
+ toolCallId: (event.message as { toolCallId?: unknown }).toolCallId ?? (event as { toolCallId?: unknown }).toolCallId,
805
+ toolName: (event.message as { toolName?: unknown }).toolName ?? event.toolName,
806
+ });
807
+ }
721
808
  messages.push(event.message);
722
809
  const text = extractTextFromContent(event.message.content);
723
810
  if (text) writeOutputText(text);
@@ -744,7 +831,11 @@ function runPiStreaming(
744
831
  if (isTerminalAssistantStop(event.message)) {
745
832
  if (!event.message.errorMessage && extractTextFromContent(event.message.content).trim()) assistantError = undefined;
746
833
  cleanTerminalAssistantStopReceived ||= !event.message.errorMessage;
747
- applyChildLifecycle(projectChildLifecycle(event, true));
834
+ clearAllToolTimeouts();
835
+ activeToolCalls.clear();
836
+ activeToolKeysByName.clear();
837
+ refreshCurrentTool();
838
+ applyChildLifecycle(projectChildLifecycle(event, true, childLifecycleState));
748
839
  }
749
840
  }
750
841
  };
@@ -757,6 +848,7 @@ function runPiStreaming(
757
848
  let forcedTerminationSignal = false;
758
849
  let cleanTerminalAssistantStopReceived = false;
759
850
  let agentSettledReceived = false;
851
+ let compactionStartedReceived = false;
760
852
  let finalDrainTimer: NodeJS.Timeout | undefined;
761
853
  let finalHardKillTimer: NodeJS.Timeout | undefined;
762
854
  let watchdogTailTimer: NodeJS.Timeout | undefined;
@@ -1029,6 +1121,10 @@ function runPiStreaming(
1029
1121
  processCloseObservedAt,
1030
1122
  processSignal: signal,
1031
1123
  processTree,
1124
+ currentTool,
1125
+ currentToolArgs,
1126
+ currentPath,
1127
+ afterCompactionSettlement: (compactionStartedReceived && agentSettledReceived) || undefined,
1032
1128
  }));
1033
1129
  });
1034
1130
 
@@ -1207,6 +1303,8 @@ interface SingleStepContext {
1207
1303
  onExternalProcess?: (process: ExternalProcessStatus) => void;
1208
1304
  onExternalJob?: (status: ExternalJobStatus) => void;
1209
1305
  skipAcceptance?: () => boolean;
1306
+ /** False when sibling work in the same Git worktree could have caused the tracked diff. */
1307
+ trackedMutationEvidenceForCompletionGuard?: boolean;
1210
1308
  orcaProgressTab?: OrcaProgressTab;
1211
1309
  }
1212
1310
 
@@ -1297,6 +1395,9 @@ async function runSingleStepInner(
1297
1395
  tools: step.tools,
1298
1396
  extensions: step.extensions,
1299
1397
  subagentOnlyExtensions: step.subagentOnlyExtensions,
1398
+ fast: step.fast,
1399
+ model: step.model,
1400
+ modelCandidates: step.modelCandidates,
1300
1401
  mcpDirectTools: step.mcpDirectTools,
1301
1402
  cwd: step.cwd ?? ctx.cwd,
1302
1403
  requireReadTool: Boolean(step.skills?.length),
@@ -1329,7 +1430,7 @@ async function runSingleStepInner(
1329
1430
  }
1330
1431
  }
1331
1432
  if (step.effectiveAcceptance) {
1332
- const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance, { reportOptional: isAgentContractV1(step.agentContract) });
1433
+ const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance, { reportOptional: isAgentContractV1(step.agentContract), structuredOutput: Boolean(step.structuredOutput?.acceptanceReportPath) });
1333
1434
  if (acceptancePrompt) task = `${task}\n${acceptancePrompt}`;
1334
1435
  }
1335
1436
  const sessionEnabled = Boolean(step.sessionFile) || ctx.sessionEnabled;
@@ -1358,21 +1459,29 @@ async function runSingleStepInner(
1358
1459
  transcriptWriter?.writeInitialUserMessage(`${PROMPT_REDACTED}; live Prompt Audit only.`);
1359
1460
 
1360
1461
  if (step.runner?.type === "external-cli") {
1361
- const runner: ExternalCliRunnerStatus = {
1362
- type: "external-cli",
1363
- command: step.runner.command,
1364
- args: step.runner.args ?? [],
1365
- promptDelivery: step.runner.promptDelivery ?? "stdin",
1366
- capabilities: { stop: true, steer: false, resume: false, structuredOutput: false, toolEvents: false },
1367
- };
1462
+ const externalCwd = step.cwd ?? ctx.cwd;
1463
+ const adapterLaunch = step.runner.adapter === "codex-exec" || step.runner.adapter === "codex-exec-writer"
1464
+ ? resolveCodexExecLaunch({ adapter: step.runner.adapter, command: step.runner.command, asyncDir: path.dirname(ctx.outputFile), stepIndex: ctx.flatIndex })
1465
+ : step.runner.adapter === "claude-code" || step.runner.adapter === "claude-code-writer"
1466
+ ? resolveClaudeCodeLaunch({ adapter: step.runner.adapter, command: step.runner.command })
1467
+ : step.runner.adapter === "cursor-agent" || step.runner.adapter === "cursor-agent-writer"
1468
+ ? resolveCursorAgentLaunch({ adapter: step.runner.adapter, command: step.runner.command, cwd: externalCwd, asyncDir: path.dirname(ctx.outputFile), stepIndex: ctx.flatIndex })
1469
+ : undefined;
1470
+ const runner = resolveExternalCliRunnerStatus({ ...step.runner, ...(adapterLaunch ? { args: adapterLaunch.args } : {}) });
1368
1471
  const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
1369
1472
  const external = await runExternalCli(omitUndefinedProperties({
1370
- command: runner.command,
1371
- args: runner.args,
1372
- cwd: step.cwd ?? ctx.cwd,
1473
+ command: adapterLaunch?.command ?? runner.command,
1474
+ args: adapterLaunch?.args ?? runner.args,
1475
+ cwd: externalCwd,
1373
1476
  prompt: buildExternalCliPrompt(step.systemPrompt ?? "", task),
1374
1477
  asyncDir: path.dirname(ctx.outputFile),
1375
1478
  stepIndex: ctx.flatIndex,
1479
+ environment: adapterLaunch?.environment,
1480
+ preflight: adapterLaunch?.preflight,
1481
+ parser: adapterLaunch?.parser,
1482
+ finalOutputPath: adapterLaunch?.finalOutputPath,
1483
+ promptFilePath: adapterLaunch?.promptFilePath,
1484
+ temporaryDirectories: adapterLaunch?.temporaryDirectories,
1376
1485
  registerTimeout: ctx.registerTimeout,
1377
1486
  registerStop: ctx.registerStop,
1378
1487
  timeoutMessage: ctx.timeoutMessage,
@@ -1500,11 +1609,15 @@ async function runSingleStepInner(
1500
1609
  const eventsPath = path.join(path.dirname(ctx.outputFile), "events.jsonl");
1501
1610
  let finalResult: RunPiStreamingResult | undefined;
1502
1611
  let finalOutputSnapshot: SingleOutputSnapshot | undefined;
1612
+ let structuredAcceptanceReport: unknown;
1613
+ let structuredAcceptanceReportError: string | undefined;
1503
1614
  let completionGuardTriggeredFinal = false;
1504
1615
  let turnBudget = ctx.turnBudget ? initialTurnBudgetState(ctx.turnBudget) : undefined;
1505
1616
  let toolBudget = step.toolBudget ? initialToolBudgetState(step.toolBudget) : undefined;
1506
1617
  let toolBudgetBlocked = false;
1507
1618
  let actualLaunchContractDigest = step.launchContractDigest;
1619
+ const mutationSnapshot = snapshotTrackedMutations(step.cwd ?? ctx.cwd);
1620
+ let finalMutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
1508
1621
 
1509
1622
  let modelIndex = 0;
1510
1623
  let startupAttemptIndex = 0;
@@ -1533,6 +1646,7 @@ async function runSingleStepInner(
1533
1646
  }
1534
1647
  }
1535
1648
  const watchdogConfig = resolveWatchdogConfig(step.cwd ?? ctx.cwd);
1649
+ const extensionBindings = normalizeExtensionBindings(step.extensionBindings)?.value;
1536
1650
  const childWatchdog = watchdogConfig.ok
1537
1651
  ? resolveChildWatchdogConfig({
1538
1652
  config: watchdogConfig.config,
@@ -1556,6 +1670,8 @@ async function runSingleStepInner(
1556
1670
  tools: step.tools,
1557
1671
  extensions: step.extensions,
1558
1672
  subagentOnlyExtensions: step.subagentOnlyExtensions,
1673
+ fast: step.fast,
1674
+ modelCandidates: step.modelCandidates,
1559
1675
  systemPrompt: appendTurnBudgetSystemPrompt(step.systemPrompt ?? "", ctx.turnBudget),
1560
1676
  systemPromptMode: step.systemPromptMode,
1561
1677
  mcpDirectTools: step.mcpDirectTools,
@@ -1587,6 +1703,7 @@ async function runSingleStepInner(
1587
1703
  childWatchdog,
1588
1704
  waitToolEnabled: step.waitToolEnabled,
1589
1705
  thinkingCeiling: step.thinkingCeiling,
1706
+ extensionBindings,
1590
1707
  }));
1591
1708
  if (!launchWarningsEmitted && warnings.length > 0) {
1592
1709
  for (const warning of warnings) console.warn(`[pi-subagents] ${warning}`);
@@ -1597,6 +1714,9 @@ async function runSingleStepInner(
1597
1714
  tools: step.tools,
1598
1715
  extensions: step.extensions,
1599
1716
  subagentOnlyExtensions: step.subagentOnlyExtensions,
1717
+ fast: step.fast,
1718
+ model: step.model,
1719
+ modelCandidates: step.modelCandidates,
1600
1720
  mcpDirectTools: step.mcpDirectTools,
1601
1721
  cwd: step.cwd ?? ctx.cwd,
1602
1722
  requireReadTool: Boolean(step.skills?.length),
@@ -1611,6 +1731,7 @@ async function runSingleStepInner(
1611
1731
  task: step.launchBindingTask ?? task,
1612
1732
  ...(candidate ? { model: candidate } : {}),
1613
1733
  modelCandidates: candidates as string[],
1734
+ ...(step.fast !== undefined ? { fast: step.fast } : {}),
1614
1735
  ...(resolveEffectiveThinking(candidate, step.thinking) ? { thinking: resolveEffectiveThinking(candidate, step.thinking) } : {}),
1615
1736
  ...(step.thinkingCeiling ? { thinkingCeiling: step.thinkingCeiling } : {}),
1616
1737
  systemPrompt: appendTurnBudgetSystemPrompt(step.systemPrompt ?? "", ctx.turnBudget),
@@ -1624,6 +1745,7 @@ async function runSingleStepInner(
1624
1745
  ...(step.outputPath ? { outputPath: step.outputPath } : {}),
1625
1746
  ...(step.outputMode ? { outputMode: step.outputMode } : {}),
1626
1747
  ...(step.structuredOutputSchema ? { structuredOutputSchema: step.structuredOutputSchema } : {}),
1748
+ ...(extensionBindings ? { extensionBindings } : {}),
1627
1749
  }));
1628
1750
  }
1629
1751
  capabilityAudit = attemptCapabilityAudit;
@@ -1687,11 +1809,25 @@ async function runSingleStepInner(
1687
1809
  : undefined;
1688
1810
  const runtimeAcknowledgedExtensions = readRuntimeAcknowledgedExtensions(runtimeAcknowledgedExtensionsPath);
1689
1811
  cleanupTempDir(tempDir);
1812
+ const midToolExitError = run.currentTool
1813
+ && isOrdinaryToolForMidToolExit(run.currentTool)
1814
+ && !run.interrupted
1815
+ && !run.timedOut
1816
+ && !run.stopped
1817
+ && !run.turnBudgetExceeded
1818
+ && !run.protocolError
1819
+ && !toolAvailabilityError
1820
+ ? formatMidToolExitError({
1821
+ toolName: run.currentTool,
1822
+ exitCode: run.exitCode,
1823
+ processSignal: run.processSignal,
1824
+ })
1825
+ : undefined;
1690
1826
 
1691
1827
  let structuredOutput: unknown;
1692
1828
  let structuredError: string | undefined;
1693
1829
  let validatedStructuredOutput = false;
1694
- if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !toolAvailabilityError) {
1830
+ if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !toolAvailabilityError && !midToolExitError) {
1695
1831
  if (!run.structuredOutputToolInvoked) {
1696
1832
  structuredError = MISSING_STRUCTURED_OUTPUT_CALL_ERROR;
1697
1833
  } else {
@@ -1703,6 +1839,9 @@ async function runSingleStepInner(
1703
1839
  if (structured.error) structuredError = structured.error;
1704
1840
  else {
1705
1841
  structuredOutput = structured.value;
1842
+ const acceptanceReport = readStructuredOutputAcceptanceReport(effectiveStructuredOutput);
1843
+ structuredAcceptanceReport = acceptanceReport.value;
1844
+ structuredAcceptanceReportError = acceptanceReport.error;
1706
1845
  validatedStructuredOutput = true;
1707
1846
  }
1708
1847
  }
@@ -1710,7 +1849,7 @@ async function runSingleStepInner(
1710
1849
  const errorMessages = validatedStructuredOutput
1711
1850
  ? run.messages.slice(run.structuredOutputMessageStartIndex ?? run.messages.length)
1712
1851
  : run.messages;
1713
- const hiddenError = run.exitCode === 0 && !run.error && !toolAvailabilityError && !structuredError
1852
+ const hiddenError = run.exitCode === 0 && !run.error && !toolAvailabilityError && !structuredError && !midToolExitError
1714
1853
  ? detectSubagentError(errorMessages)
1715
1854
  : null;
1716
1855
  const emptyOutputError = run.exitCode === 0
@@ -1724,7 +1863,10 @@ async function runSingleStepInner(
1724
1863
  : undefined;
1725
1864
  const completionGuardEnabled = isAgentContractV1(step.agentContract) ? step.completionGuard === true : step.completionGuard !== false;
1726
1865
  const completionToolPlan = resolvedTaskToolPlan;
1727
- const completionGuard = run.exitCode === 0 && !run.error && !structuredError && !hiddenError?.hasError && !emptyOutputError && completionGuardEnabled
1866
+ const mutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
1867
+ finalMutationEvidence = mutationEvidence;
1868
+ const completionMutationEvidence = ctx.trackedMutationEvidenceForCompletionGuard === false ? undefined : mutationEvidence;
1869
+ const completionGuard = run.exitCode === 0 && !run.error && !structuredError && !hiddenError?.hasError && !midToolExitError && !emptyOutputError && completionGuardEnabled
1728
1870
  ? evaluateCompletionMutationGuard(omitUndefinedProperties({
1729
1871
  agent: step.agent,
1730
1872
  task: taskForCompletionGuard,
@@ -1732,15 +1874,18 @@ async function runSingleStepInner(
1732
1874
  tools: completionToolPlan ? (completionToolPlan.explicitToolAllowlist ? completionToolPlan.effectiveToolAllowlist : undefined) : step.tools,
1733
1875
  mcpDirectTools: completionToolPlan?.effectiveMcpTools ?? step.mcpDirectTools,
1734
1876
  toolAvailabilityError,
1877
+ mutationEvidence: completionMutationEvidence,
1735
1878
  }))
1736
1879
  : undefined;
1737
- const completionGuardTriggered = completionGuard?.triggered === true && !run.observedMutationAttempt;
1880
+ const mutationAttemptObserved = run.observedMutationAttempt === true || completionMutationEvidence?.attemptedMutation === true;
1881
+ const completionGuardTriggered = completionGuard?.triggered === true && !mutationAttemptObserved;
1738
1882
  const completionGuardBlocked = completionGuard?.blocked === true;
1739
1883
  const fileMutationEffect = completionGuard
1740
1884
  ? {
1741
1885
  status: completionGuardBlocked ? "blocked" as const : completionGuard.expectedMutation ? completionGuardTriggered ? "missing" as const : "observed" as const : "not-applicable" as const,
1742
1886
  expected: completionGuard.expectedMutation,
1743
- attempted: completionGuardBlocked ? false : completionGuard.attemptedMutation || run.observedMutationAttempt === true,
1887
+ attempted: completionGuardBlocked ? false : completionGuard.attemptedMutation || mutationAttemptObserved,
1888
+ ...(completionMutationEvidence ? { evidence: completionMutationEvidence } : {}),
1744
1889
  ...(completionGuardBlocked && completionGuard.message ? { message: completionGuard.message } : {}),
1745
1890
  ...(completionGuardTriggered ? { message: "Subagent completed without making edits for an implementation task." } : {}),
1746
1891
  }
@@ -1748,7 +1893,7 @@ async function runSingleStepInner(
1748
1893
  const completionGuardError = completionGuardTriggered && !isAgentContractV1(step.agentContract)
1749
1894
  ? "Subagent completed without making edits for an implementation task.\nIt appears to have returned planning or scratchpad output instead of applying changes."
1750
1895
  : undefined;
1751
- const effectiveExitCode = toolAvailabilityError || (completionGuardTriggered && !isAgentContractV1(step.agentContract)) || structuredError || emptyOutputError
1896
+ const effectiveExitCode = toolAvailabilityError || (completionGuardTriggered && !isAgentContractV1(step.agentContract)) || midToolExitError || structuredError || emptyOutputError
1752
1897
  ? 1
1753
1898
  : hiddenError?.hasError
1754
1899
  ? (hiddenError.exitCode ?? 1)
@@ -1765,6 +1910,7 @@ async function runSingleStepInner(
1765
1910
  const error = formatSubagentExtensionConflictError(
1766
1911
  toolAvailabilityError
1767
1912
  ?? completionGuardError
1913
+ ?? midToolExitError
1768
1914
  ?? structuredError
1769
1915
  ?? emptyOutputError
1770
1916
  ?? (hiddenError?.hasError
@@ -1844,7 +1990,7 @@ async function runSingleStepInner(
1844
1990
  finalResult.finalOutput = startupError;
1845
1991
  break modelAttemptsLoop;
1846
1992
  }
1847
- const retryableModelFailure = isRetryableModelFailure(error);
1993
+ const retryableModelFailure = isRetryableModelFailureAttempt({ error, messages: run.messages, toolCount: run.toolCount });
1848
1994
  if (retryableModelFailure) recordRetryableModelFailure(candidate ?? run.model ?? step.model, error);
1849
1995
  if (isContextOverflow(error)) {
1850
1996
  contextOverflow = true;
@@ -1890,6 +2036,21 @@ async function runSingleStepInner(
1890
2036
  : resolvedOutput.savedPath
1891
2037
  ? "unknown"
1892
2038
  : finalResult?.outputState ?? "unknown";
2039
+ const timeoutRecovery = finalResult?.timedOut === true || ctx.timeoutSignal?.aborted === true
2040
+ ? buildTimeoutRecoverySummary({
2041
+ termination: "timed-out",
2042
+ evidence: finalMutationEvidence,
2043
+ currentTool: finalResult?.currentTool,
2044
+ currentToolArgs: finalResult?.currentToolArgs,
2045
+ currentPath: finalResult?.currentPath,
2046
+ sessionFile: step.sessionFile,
2047
+ transcriptPath: transcriptWriter ? artifactPaths?.transcriptPath : undefined,
2048
+ artifactPaths,
2049
+ })
2050
+ : undefined;
2051
+ if (timeoutRecovery) outputForSummary = outputForSummary.trim()
2052
+ ? `${outputForSummary}\n\n${timeoutRecovery.message}`
2053
+ : timeoutRecovery.message;
1893
2054
  const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
1894
2055
  fullOutput: outputForSummary,
1895
2056
  outputPath: step.outputPath,
@@ -1904,6 +2065,8 @@ async function runSingleStepInner(
1904
2065
  ? await evaluateAcceptance(omitUndefinedProperties({
1905
2066
  acceptance: step.effectiveAcceptance,
1906
2067
  output: outputForAcceptance,
2068
+ report: structuredAcceptanceReport as import("../../shared/types.ts").AcceptanceReport | undefined,
2069
+ reportError: structuredAcceptanceReportError,
1907
2070
  fileOutput: childWrittenOutput !== undefined && step.outputPath
1908
2071
  ? { content: childWrittenOutput, path: step.outputPath, authoritative: step.outputMode === "file-only" }
1909
2072
  : undefined,
@@ -1931,7 +2094,7 @@ async function runSingleStepInner(
1931
2094
  const acceptanceCanFailRun = acceptanceFailure && effectiveAcceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted && !timedOutAfterAcceptance && !stoppedAfterAcceptance && !turnBudgetExceeded && !isAgentContractV1(step.agentContract);
1932
2095
  const effectiveFinalExitCode = timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? 1 : acceptanceCanFailRun ? 1 : finalResult?.exitCode ?? 1;
1933
2096
  const intercomDetachReceipt = finalResult?.finalOutput === INTERCOM_DETACH_RECEIPT;
1934
- const effectiveFinalError = stoppedAfterAcceptance
2097
+ const baseFinalError = stoppedAfterAcceptance
1935
2098
  ? ctx.stopMessage ?? "Subagent stopped by user."
1936
2099
  : timedOutAfterAcceptance
1937
2100
  ? finalResult?.error ?? ctx.timeoutMessage ?? "Subagent timed out."
@@ -1940,6 +2103,17 @@ async function runSingleStepInner(
1940
2103
  : acceptanceCanFailRun
1941
2104
  ? (finalResult?.error ? `${finalResult.error}\n${acceptanceFailure}` : acceptanceFailure)
1942
2105
  : finalResult?.error ?? (intercomDetachReceipt ? INTERCOM_DETACH_RECEIPT : undefined);
2106
+ const effectiveFinalError = formatChildFailureDiagnostic({
2107
+ error: baseFinalError,
2108
+ afterCompactionSettlement: finalResult?.afterCompactionSettlement,
2109
+ missingFileOnlyOutput: effectiveFinalExitCode !== 0
2110
+ && finalResult?.afterCompactionSettlement
2111
+ && step.outputMode === "file-only"
2112
+ && step.outputPath
2113
+ && !fs.existsSync(step.outputPath)
2114
+ ? step.outputPath
2115
+ : undefined,
2116
+ });
1943
2117
 
1944
2118
  const artifactErrors = artifactPaths && ctx.artifactConfig?.enabled !== false
1945
2119
  ? persistStepArtifacts({
@@ -1999,6 +2173,7 @@ async function runSingleStepInner(
1999
2173
  timedOut: timedOutAfterAcceptance ? true : finalResult?.timedOut,
2000
2174
  stopped: stoppedAfterAcceptance ? true : finalResult?.stopped,
2001
2175
  processSignal: finalResult?.processSignal,
2176
+ timeoutRecovery,
2002
2177
  turnBudget,
2003
2178
  turnBudgetExceeded: turnBudgetExceeded || undefined,
2004
2179
  wrapUpRequested: finalResult?.wrapUpRequested || turnBudget?.outcome === "wrap-up-requested" || turnBudget?.outcome === "termination-deferred" || turnBudgetExceeded || undefined,
@@ -2035,13 +2210,7 @@ type RunnerStatusStep = NonNullable<AsyncStatus["steps"]>[number] & {
2035
2210
 
2036
2211
  function externalRunnerStatus(runner: SubagentStep["runner"]): ExternalCliRunnerStatus | ExternalJobRunnerStatus | undefined {
2037
2212
  if (runner?.type === "external-cli") {
2038
- return {
2039
- type: "external-cli",
2040
- command: runner.command,
2041
- args: runner.args ?? [],
2042
- promptDelivery: runner.promptDelivery ?? "stdin",
2043
- capabilities: { stop: true, steer: false, resume: false, structuredOutput: false, toolEvents: false },
2044
- };
2213
+ return resolveExternalCliRunnerStatus(runner);
2045
2214
  }
2046
2215
  if (runner?.type === "external-job") {
2047
2216
  return {
@@ -3453,12 +3622,13 @@ async function runSubagent(
3453
3622
  if (usage) {
3454
3623
  const input = usage.input ?? usage.inputTokens ?? 0;
3455
3624
  const output = usage.output ?? usage.outputTokens ?? 0;
3625
+ const window = input + (usage.cacheRead ?? usage.cacheReadTokens ?? 0);
3456
3626
  const previousInput = step.tokens?.input ?? 0;
3457
3627
  const previousOutput = step.tokens?.output ?? 0;
3458
- step.tokens = { input: previousInput + input, output: previousOutput + output, total: previousInput + previousOutput + input + output };
3628
+ step.tokens = { input: previousInput + input, output: previousOutput + output, total: previousInput + previousOutput + input + output, window, windowPeak: Math.max(step.tokens?.windowPeak ?? 0, window) };
3459
3629
  const totalInput = statusPayload.totalTokens?.input ?? 0;
3460
3630
  const totalOutput = statusPayload.totalTokens?.output ?? 0;
3461
- statusPayload.totalTokens = { input: totalInput + input, output: totalOutput + output, total: totalInput + totalOutput + input + output };
3631
+ statusPayload.totalTokens = { input: totalInput + input, output: totalOutput + output, total: totalInput + totalOutput + input + output, window, windowPeak: Math.max(statusPayload.totalTokens?.windowPeak ?? 0, window) };
3462
3632
  refreshUsageBudget();
3463
3633
  }
3464
3634
  statusPayload.turnCount = Math.max(statusPayload.turnCount ?? 0, step.turnCount);
@@ -4013,6 +4183,7 @@ async function runSubagent(
4013
4183
  registerTurnBudgetAbort: (abort) => registerStepTurnBudgetAbort(fi, abort),
4014
4184
  timeoutSignal: timeoutAbortController.signal,
4015
4185
  stopSignal: stopAbortController.signal,
4186
+ trackedMutationEvidenceForCompletionGuard: false,
4016
4187
  timeoutMessage,
4017
4188
  stopMessage,
4018
4189
  turnBudget: config.turnBudget,
@@ -4072,6 +4243,7 @@ async function runSubagent(
4072
4243
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "structuredOutputPath", singleResult.structuredOutputPath);
4073
4244
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "structuredOutputSchemaPath", singleResult.structuredOutputSchemaPath);
4074
4245
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "acceptance", singleResult.acceptance);
4246
+ setOptionalProperty(requiredStatusStep(statusPayload, fi), "timeoutRecovery", singleResult.timeoutRecovery);
4075
4247
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "watchdog", singleResult.watchdog);
4076
4248
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "capabilityCeiling", singleResult.capabilityCeiling);
4077
4249
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "capabilityAudit", singleResult.capabilityAudit);
@@ -4087,7 +4259,7 @@ async function runSubagent(
4087
4259
  }));
4088
4260
  if (stopped || childStopped) appendTerminalChildStatusEvent(fi, taskEndTime);
4089
4261
  if (singleResult.exitCode !== 0 && failFast && !childStopped) aborted = true;
4090
- return stopped || childStopped ? { ...singleResult, output: stopMessage, error: stopMessage, exitCode: 1, interrupted: false, timedOut: false, stopped: true, skipped: false } : timedOut ? { ...singleResult, output: timeoutMessage ?? "Subagent timed out.", error: timeoutMessage ?? "Subagent timed out.", exitCode: 1, interrupted: false, timedOut: true, skipped: false } : { ...singleResult, skipped: false };
4262
+ 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 };
4091
4263
  }, globalSemaphore);
4092
4264
 
4093
4265
  flatIndex += dynamicSteps.length;
@@ -4127,6 +4299,7 @@ async function runSubagent(
4127
4299
  effects: pr.effects,
4128
4300
  execution: pr.execution,
4129
4301
  review: pr.review,
4302
+ timeoutRecovery: pr.timeoutRecovery,
4130
4303
  structuredOutput: pr.structuredOutput,
4131
4304
  structuredOutputPath: pr.structuredOutputPath,
4132
4305
  structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
@@ -4406,6 +4579,7 @@ async function runSubagent(
4406
4579
  registerTurnBudgetAbort: (abort) => registerStepTurnBudgetAbort(fi, abort),
4407
4580
  timeoutSignal: timeoutAbortController.signal,
4408
4581
  stopSignal: stopAbortController.signal,
4582
+ trackedMutationEvidenceForCompletionGuard: Boolean(worktreeSetup),
4409
4583
  timeoutMessage,
4410
4584
  stopMessage,
4411
4585
  turnBudget: config.turnBudget,
@@ -4470,6 +4644,7 @@ async function runSubagent(
4470
4644
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "structuredOutputPath", singleResult.structuredOutputPath);
4471
4645
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "structuredOutputSchemaPath", singleResult.structuredOutputSchemaPath);
4472
4646
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "acceptance", singleResult.acceptance);
4647
+ setOptionalProperty(requiredStatusStep(statusPayload, fi), "timeoutRecovery", singleResult.timeoutRecovery);
4473
4648
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "watchdog", singleResult.watchdog);
4474
4649
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "capabilityCeiling", singleResult.capabilityCeiling);
4475
4650
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "capabilityAudit", singleResult.capabilityAudit);
@@ -4500,7 +4675,7 @@ async function runSubagent(
4500
4675
  }
4501
4676
 
4502
4677
  if (singleResult.exitCode !== 0 && failFast && !childStopped) aborted = true;
4503
- return stopped || childStopped ? { ...singleResult, output: stopMessage, error: stopMessage, exitCode: 1, interrupted: false, timedOut: false, stopped: true, skipped: false } : timedOut ? { ...singleResult, output: timeoutMessage ?? "Subagent timed out.", error: timeoutMessage ?? "Subagent timed out.", exitCode: 1, interrupted: false, timedOut: true, skipped: false } : { ...singleResult, skipped: false };
4678
+ 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 };
4504
4679
  },
4505
4680
  globalSemaphore,
4506
4681
  );
@@ -4512,13 +4687,21 @@ async function runSubagent(
4512
4687
  const sessionTokens = config.sessionDir
4513
4688
  ? parseSessionTokens(path.join(config.sessionDir, `parallel-${t}`))
4514
4689
  : null;
4515
- const taskTokens = sessionTokens ?? tokenUsageFromAttempts(parallelResults[t]?.modelAttempts);
4690
+ const fallbackTokens = tokenUsageFromAttempts(parallelResults[t]?.modelAttempts);
4691
+ const observedTokens = requiredStatusStep(statusPayload, fi).tokens;
4692
+ const taskTokens = sessionTokens ?? (fallbackTokens
4693
+ ? { ...fallbackTokens, ...(observedTokens?.window !== undefined ? { window: observedTokens.window } : {}), ...(observedTokens?.windowPeak !== undefined ? { windowPeak: observedTokens.windowPeak } : {}) }
4694
+ : null);
4516
4695
  if (!taskTokens) continue;
4517
4696
  requiredStatusStep(statusPayload, fi).tokens = taskTokens;
4518
4697
  previousCumulativeTokens = {
4519
4698
  input: previousCumulativeTokens.input + taskTokens.input,
4520
4699
  output: previousCumulativeTokens.output + taskTokens.output,
4521
4700
  total: previousCumulativeTokens.total + taskTokens.total,
4701
+ ...(taskTokens.window !== undefined ? { window: taskTokens.window } : {}),
4702
+ ...(previousCumulativeTokens.windowPeak !== undefined || taskTokens.windowPeak !== undefined
4703
+ ? { windowPeak: Math.max(previousCumulativeTokens.windowPeak ?? 0, taskTokens.windowPeak ?? 0) }
4704
+ : {}),
4522
4705
  };
4523
4706
  }
4524
4707
  statusPayload.totalTokens = { ...previousCumulativeTokens };
@@ -4557,10 +4740,11 @@ async function runSubagent(
4557
4740
  artifactPaths: pr.artifactPaths,
4558
4741
  transcriptPath: pr.transcriptPath,
4559
4742
  transcriptError: pr.transcriptError,
4560
- effects: pr.effects,
4561
- execution: pr.execution,
4562
- review: pr.review,
4563
- structuredOutput: pr.structuredOutput,
4743
+ effects: pr.effects,
4744
+ execution: pr.execution,
4745
+ review: pr.review,
4746
+ timeoutRecovery: pr.timeoutRecovery,
4747
+ structuredOutput: pr.structuredOutput,
4564
4748
  structuredOutputPath: pr.structuredOutputPath,
4565
4749
  structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
4566
4750
  acceptance: pr.acceptance,
@@ -4780,7 +4964,7 @@ async function runSubagent(
4780
4964
  launchContractDigest: singleResult.launchContractDigest,
4781
4965
  launchResolvedExtensions: singleResult.launchResolvedExtensions,
4782
4966
  runtimeAcknowledgedExtensions: singleResult.runtimeAcknowledgedExtensions,
4783
- output: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.output,
4967
+ output: stopped || childStopped ? stopMessage : timedOut ? singleResult.output || (timeoutMessage ?? "Subagent timed out.") : singleResult.output,
4784
4968
  outputState: singleResult.outputState,
4785
4969
  error: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error,
4786
4970
  protocolError: singleResult.protocolError,
@@ -4799,6 +4983,7 @@ async function runSubagent(
4799
4983
  effects: singleResult.effects,
4800
4984
  execution: singleResult.execution,
4801
4985
  review: singleResult.review,
4986
+ timeoutRecovery: singleResult.timeoutRecovery,
4802
4987
  structuredOutput: singleResult.structuredOutput,
4803
4988
  structuredOutputPath: singleResult.structuredOutputPath,
4804
4989
  structuredOutputSchemaPath: singleResult.structuredOutputSchemaPath,
@@ -4833,17 +5018,27 @@ async function runSubagent(
4833
5018
  input: cumulativeTokens.input - previousCumulativeTokens.input,
4834
5019
  output: cumulativeTokens.output - previousCumulativeTokens.output,
4835
5020
  total: cumulativeTokens.total - previousCumulativeTokens.total,
5021
+ ...(cumulativeTokens.window !== undefined ? { window: cumulativeTokens.window } : {}),
5022
+ ...(cumulativeTokens.windowPeak !== undefined ? { windowPeak: cumulativeTokens.windowPeak } : {}),
4836
5023
  }
4837
5024
  : null;
4838
5025
  if (cumulativeTokens) {
4839
5026
  previousCumulativeTokens = cumulativeTokens;
4840
5027
  } else {
4841
- stepTokens = tokenUsageFromAttempts(singleResult.modelAttempts);
5028
+ const fallbackTokens = tokenUsageFromAttempts(singleResult.modelAttempts);
5029
+ const observedTokens = requiredStatusStep(statusPayload, flatIndex).tokens;
5030
+ stepTokens = fallbackTokens
5031
+ ? { ...fallbackTokens, ...(observedTokens?.window !== undefined ? { window: observedTokens.window } : {}), ...(observedTokens?.windowPeak !== undefined ? { windowPeak: observedTokens.windowPeak } : {}) }
5032
+ : null;
4842
5033
  if (stepTokens) {
4843
5034
  previousCumulativeTokens = {
4844
5035
  input: previousCumulativeTokens.input + stepTokens.input,
4845
5036
  output: previousCumulativeTokens.output + stepTokens.output,
4846
5037
  total: previousCumulativeTokens.total + stepTokens.total,
5038
+ ...(stepTokens.window !== undefined ? { window: stepTokens.window } : {}),
5039
+ ...(previousCumulativeTokens.windowPeak !== undefined || stepTokens.windowPeak !== undefined
5040
+ ? { windowPeak: Math.max(previousCumulativeTokens.windowPeak ?? 0, stepTokens.windowPeak ?? 0) }
5041
+ : {}),
4847
5042
  };
4848
5043
  }
4849
5044
  }
@@ -4885,6 +5080,7 @@ async function runSubagent(
4885
5080
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "structuredOutputPath", singleResult.structuredOutputPath);
4886
5081
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "structuredOutputSchemaPath", singleResult.structuredOutputSchemaPath);
4887
5082
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "acceptance", singleResult.acceptance);
5083
+ setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "timeoutRecovery", singleResult.timeoutRecovery);
4888
5084
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "watchdog", singleResult.watchdog);
4889
5085
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "capabilityCeiling", singleResult.capabilityCeiling);
4890
5086
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "capabilityAudit", singleResult.capabilityAudit);
@@ -4977,7 +5173,7 @@ async function runSubagent(
4977
5173
  }
4978
5174
  }
4979
5175
 
4980
- let summary = results.map((r) => `${r.agent}:\n${r.output}`).join("\n\n");
5176
+ let summary = results.map((r) => `${r.agent}:\n${r.output || (r.exitCode !== 0 ? r.error : undefined) || "(no output)"}`).join("\n\n");
4981
5177
  let truncated = false;
4982
5178
 
4983
5179
  if (maxOutput) {
@@ -5169,6 +5365,7 @@ async function runSubagent(
5169
5365
  structuredOutputSchemaPath: r.structuredOutputSchemaPath,
5170
5366
  acceptance: r.acceptance,
5171
5367
  watchdog: r.watchdog,
5368
+ timeoutRecovery: r.timeoutRecovery,
5172
5369
  capabilityCeiling: r.capabilityCeiling,
5173
5370
  capabilityAudit: r.capabilityAudit,
5174
5371
  })),
@@ -5287,7 +5484,7 @@ async function waitForStartupControl(
5287
5484
  } catch (error) {
5288
5485
  throw new Error(`Failed to read runner startup control '${controlPath}': ${error instanceof Error ? error.message : String(error)}`);
5289
5486
  }
5290
- if (payload.token !== token) throw new Error("Runner startup control token does not match the acquired session lease.");
5487
+ if (payload.token !== token) throw new Error("Runner startup control token does not match.");
5291
5488
  if (payload.action === action) return;
5292
5489
  if (payload.action !== "ack" && payload.action !== "proceed") throw new Error("Runner startup control action is invalid.");
5293
5490
  }
@@ -5298,7 +5495,7 @@ async function waitForStartupControl(
5298
5495
 
5299
5496
  async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
5300
5497
  let lease: ReturnType<typeof acquireSessionLease> | undefined;
5301
- let startupCommitted = config.revivalLease === undefined;
5498
+ let startupCommitted = config.revivalLease === undefined && config.launchBarrierToken === undefined;
5302
5499
  const startupPath = path.join(config.asyncDir, "runner-startup.json");
5303
5500
  const startupAckPath = path.join(config.asyncDir, "runner-startup-ack.json");
5304
5501
  const startupProceedPath = path.join(config.asyncDir, "runner-startup-proceed.json");
@@ -5311,7 +5508,15 @@ async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
5311
5508
  };
5312
5509
  process.once("exit", releaseOnExit);
5313
5510
  try {
5314
- if (config.revivalLease) {
5511
+ if (config.launchBarrierToken) {
5512
+ await waitForStartupControl(startupProceedPath, config.launchBarrierToken, "proceed");
5513
+ startupCommitted = true;
5514
+ try {
5515
+ fs.rmSync(startupProceedPath, { force: true });
5516
+ } catch {
5517
+ // Startup control cleanup is best effort after the parent commits the run.
5518
+ }
5519
+ } else if (config.revivalLease) {
5315
5520
  lease = acquireSessionLease(config.revivalLease);
5316
5521
  config.revivalLeaseToken = lease.owner.token;
5317
5522
  writeAtomicJson(startupPath, { state: "ready", token: lease.owner.token, pid: process.pid, owner: lease.owner });
@@ -5329,7 +5534,7 @@ async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
5329
5534
  }
5330
5535
  await runSubagent(config, lease ? (writer) => lease!.updateWriter(writer) : undefined);
5331
5536
  } catch (error) {
5332
- if (config.revivalLease && !startupCommitted) {
5537
+ if (!startupCommitted) {
5333
5538
  try {
5334
5539
  writeAtomicJson(startupPath, { state: "error", pid: process.pid, error: error instanceof Error ? error.message : String(error) });
5335
5540
  } catch {