pi-subagents 0.56.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 (83) hide show
  1. package/CHANGELOG.md +53 -0
  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/observability.md +8 -7
  12. package/docs/tool-reference.md +11 -1
  13. package/docs/workflows.md +20 -0
  14. package/package.json +1 -1
  15. package/skills/pi-subagents/references/execution-controls.md +1 -1
  16. package/src/agents/agent-management.ts +13 -5
  17. package/src/agents/agent-refinements.ts +4 -4
  18. package/src/agents/agents.ts +219 -48
  19. package/src/agents/builtin-names.ts +6 -0
  20. package/src/agents/runtime-agent-registry.ts +8 -2
  21. package/src/api/preflight.ts +2 -2
  22. package/src/extension/config.ts +70 -0
  23. package/src/extension/doctor.ts +3 -3
  24. package/src/extension/index.ts +18 -4
  25. package/src/extension/public-execution.ts +29 -13
  26. package/src/extension/rpc.ts +20 -5
  27. package/src/extension/schemas.ts +4 -3
  28. package/src/extension/tool-description.ts +11 -9
  29. package/src/inspectors/herdr/actions.ts +2 -1
  30. package/src/inspectors/herdr/inspector-runner.ts +2 -10
  31. package/src/inspectors/herdr/session-roots-codec.ts +42 -0
  32. package/src/integrations/herdr-status.ts +46 -3
  33. package/src/runs/background/active-async-capacity.ts +77 -10
  34. package/src/runs/background/async-execution.ts +100 -9
  35. package/src/runs/background/async-job-tracker.ts +5 -0
  36. package/src/runs/background/async-retention.ts +20 -3
  37. package/src/runs/background/async-status.ts +5 -0
  38. package/src/runs/background/chain-root-attachment.ts +15 -1
  39. package/src/runs/background/fleet-view.ts +16 -10
  40. package/src/runs/background/inspect-rpc.ts +8 -8
  41. package/src/runs/background/result-files.ts +27 -14
  42. package/src/runs/background/result-watcher.ts +9 -5
  43. package/src/runs/background/run-status.ts +33 -6
  44. package/src/runs/background/scheduled-runs.ts +7 -1
  45. package/src/runs/background/subagent-runner.ts +177 -44
  46. package/src/runs/background/wait-completions.ts +4 -0
  47. package/src/runs/foreground/execution.ts +72 -10
  48. package/src/runs/foreground/foreground-control.ts +6 -0
  49. package/src/runs/foreground/foreground-history.ts +22 -1
  50. package/src/runs/foreground/subagent-executor.ts +270 -81
  51. package/src/runs/foreground/workflow-detach-reconcile.ts +16 -1
  52. package/src/runs/shared/child-protocol.ts +21 -7
  53. package/src/runs/shared/claude-code-adapter.ts +129 -0
  54. package/src/runs/shared/codex-exec-adapter.ts +129 -0
  55. package/src/runs/shared/cursor-agent-adapter.ts +114 -0
  56. package/src/runs/shared/external-cli-contract.ts +167 -0
  57. package/src/runs/shared/external-cli-preflight.ts +122 -0
  58. package/src/runs/shared/external-cli-runner.ts +348 -55
  59. package/src/runs/shared/model-exclusions.ts +57 -7
  60. package/src/runs/shared/model-fallback.ts +23 -1
  61. package/src/runs/shared/mutation-evidence.ts +7 -2
  62. package/src/runs/shared/nested-events.ts +3 -1
  63. package/src/runs/shared/nested-render.ts +2 -2
  64. package/src/runs/shared/process-signal.ts +13 -0
  65. package/src/runs/shared/run-history.ts +21 -1
  66. package/src/shared/fork-context.ts +21 -0
  67. package/src/shared/formatters.ts +7 -1
  68. package/src/shared/pruned-fork.ts +450 -0
  69. package/src/shared/session-file-trust.ts +19 -0
  70. package/src/shared/session-tokens.ts +14 -3
  71. package/src/shared/settings.ts +10 -2
  72. package/src/shared/shortcuts.ts +17 -0
  73. package/src/shared/types.ts +118 -9
  74. package/src/shared/workflow-child-permit.ts +116 -0
  75. package/src/slash/delegation-adapters.ts +0 -1
  76. package/src/slash/slash-commands.ts +8 -6
  77. package/src/tui/fleet-status.ts +27 -10
  78. package/src/tui/fleet-transcript.ts +11 -5
  79. package/src/tui/fleet.ts +28 -13
  80. package/src/tui/render.ts +47 -21
  81. package/src/workflows/scripted-workflow.ts +248 -27
  82. package/src/workflows/workflow-child-summary.ts +117 -0
  83. package/src/workflows/workflow-receipt.ts +119 -4
@@ -81,13 +81,13 @@ import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolved
81
81
  import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
82
82
  import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
83
83
  import { createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
84
- import { formatProcessSignalError, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
84
+ import { formatMidToolExitError, formatProcessSignalError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
85
85
  import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
86
86
  import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
87
87
  import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
88
88
  import { claimRunFanoutBatch, getRunFanoutBudgetSnapshot } from "../shared/run-fanout-budget.ts";
89
89
  import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent } from "../shared/nested-events.ts";
90
- import { formatModelAttemptNote, formatSubagentModelVerificationError, isContextOverflow, isRetryableModelFailure, recordRetryableModelFailure } from "../shared/model-fallback.ts";
90
+ import { formatModelAttemptNote, formatSubagentModelVerificationError, isContextOverflow, isRetryableModelFailureAttempt, recordRetryableModelFailure } from "../shared/model-fallback.ts";
91
91
  import {
92
92
  SUBAGENT_STARTUP_RETRY_DELAYS_MS,
93
93
  formatSubagentExtensionConflictError,
@@ -141,9 +141,13 @@ import { effectiveToolTimeoutMs, formatToolTimeoutMessage, toolTimeoutCallKey }
141
141
  import { usageBudgetExceededMessage, usageBudgetState } from "../shared/usage-budget.ts";
142
142
  import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
143
143
  import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
144
- import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
144
+ import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ChildLifecycleState, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
145
145
  import { acquireSessionLease, type SessionLeaseRequest } from "../shared/session-lease.ts";
146
146
  import { buildExternalCliPrompt, runExternalCli } from "../shared/external-cli-runner.ts";
147
+ import { resolveClaudeCodeLaunch } from "../shared/claude-code-adapter.ts";
148
+ import { resolveCodexExecLaunch } from "../shared/codex-exec-adapter.ts";
149
+ import { resolveCursorAgentLaunch } from "../shared/cursor-agent-adapter.ts";
150
+ import { resolveExternalCliRunnerStatus } from "../shared/external-cli-contract.ts";
147
151
  import { runExternalJob } from "../shared/external-job-runner.ts";
148
152
  import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
149
153
  import { decodeSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
@@ -206,6 +210,7 @@ interface SubagentRunConfig {
206
210
  launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
207
211
  runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
208
212
  runnerProcessInstanceId?: string;
213
+ launchBarrierToken?: string;
209
214
  parentWorkflowRunId?: string;
210
215
  workflowKey?: string;
211
216
  }
@@ -496,6 +501,7 @@ interface ChildUsage {
496
501
  output?: number;
497
502
  outputTokens?: number;
498
503
  cacheRead?: number;
504
+ cacheReadTokens?: number;
499
505
  cacheWrite?: number;
500
506
  cost?: { total?: number };
501
507
  }
@@ -546,6 +552,25 @@ interface RunPiStreamingResult {
546
552
  currentTool?: string;
547
553
  currentToolArgs?: string;
548
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}` : ""}`;
549
574
  }
550
575
 
551
576
  function runPiStreaming(
@@ -620,8 +645,50 @@ function runPiStreaming(
620
645
  let currentToolArgs: string | undefined;
621
646
  let currentPath: string | undefined;
622
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
+ };
623
689
  const childWatchdogConfig = decodeChildWatchdogConfig(env?.[CHILD_WATCHDOG_CONFIG_ENV]);
624
690
  let childWatchdogState: ChildWatchdogStateSnapshot | undefined;
691
+ const childLifecycleState: ChildLifecycleState = { compactionRetryActive: false };
625
692
  let applyChildLifecycle = (_action: ChildLifecycleAction): void => {};
626
693
  const updateChildWatchdogState = (snapshot: ChildWatchdogStateSnapshot): void => {
627
694
  childWatchdogState = snapshot;
@@ -672,8 +739,10 @@ function runPiStreaming(
672
739
 
673
740
  appendChildEvent(event as unknown as Record<string, unknown>);
674
741
  transcriptWriter?.writeChildEvent(event);
675
- if (event.type === "agent_settled") agentSettledReceived = true;
676
- 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);
677
746
 
678
747
  if (isChildWatchdogStatusEvent(event)) {
679
748
  if (!childWatchdogConfig) return;
@@ -710,18 +779,14 @@ function runPiStreaming(
710
779
 
711
780
  if (event.type === "tool_execution_end") {
712
781
  clearActiveToolTimeout(event);
713
- currentTool = undefined;
714
- currentToolArgs = undefined;
715
- currentPath = undefined;
782
+ removeActiveToolCall(event);
716
783
  return;
717
784
  }
718
785
 
719
786
  if (event.type === "tool_execution_start" && event.toolName) {
720
787
  toolCount += 1;
721
788
  armToolTimeout({ toolCallId: (event as { toolCallId?: unknown }).toolCallId, toolName: event.toolName });
722
- currentTool = event.toolName;
723
- currentToolArgs = extractToolArgsPreview(event.args ?? {});
724
- currentPath = resolveCurrentPath(event.toolName, event.args);
789
+ recordActiveToolCall({ toolCallId: (event as { toolCallId?: unknown }).toolCallId, toolName: event.toolName, args: event.args });
725
790
  if (event.toolName === "structured_output") {
726
791
  structuredOutputToolInvoked = true;
727
792
  structuredOutputMessageStartIndex = messages.length;
@@ -733,6 +798,13 @@ function runPiStreaming(
733
798
  }
734
799
 
735
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
+ }
736
808
  messages.push(event.message);
737
809
  const text = extractTextFromContent(event.message.content);
738
810
  if (text) writeOutputText(text);
@@ -759,7 +831,11 @@ function runPiStreaming(
759
831
  if (isTerminalAssistantStop(event.message)) {
760
832
  if (!event.message.errorMessage && extractTextFromContent(event.message.content).trim()) assistantError = undefined;
761
833
  cleanTerminalAssistantStopReceived ||= !event.message.errorMessage;
762
- applyChildLifecycle(projectChildLifecycle(event, true));
834
+ clearAllToolTimeouts();
835
+ activeToolCalls.clear();
836
+ activeToolKeysByName.clear();
837
+ refreshCurrentTool();
838
+ applyChildLifecycle(projectChildLifecycle(event, true, childLifecycleState));
763
839
  }
764
840
  }
765
841
  };
@@ -772,6 +848,7 @@ function runPiStreaming(
772
848
  let forcedTerminationSignal = false;
773
849
  let cleanTerminalAssistantStopReceived = false;
774
850
  let agentSettledReceived = false;
851
+ let compactionStartedReceived = false;
775
852
  let finalDrainTimer: NodeJS.Timeout | undefined;
776
853
  let finalHardKillTimer: NodeJS.Timeout | undefined;
777
854
  let watchdogTailTimer: NodeJS.Timeout | undefined;
@@ -1047,6 +1124,7 @@ function runPiStreaming(
1047
1124
  currentTool,
1048
1125
  currentToolArgs,
1049
1126
  currentPath,
1127
+ afterCompactionSettlement: (compactionStartedReceived && agentSettledReceived) || undefined,
1050
1128
  }));
1051
1129
  });
1052
1130
 
@@ -1381,21 +1459,29 @@ async function runSingleStepInner(
1381
1459
  transcriptWriter?.writeInitialUserMessage(`${PROMPT_REDACTED}; live Prompt Audit only.`);
1382
1460
 
1383
1461
  if (step.runner?.type === "external-cli") {
1384
- const runner: ExternalCliRunnerStatus = {
1385
- type: "external-cli",
1386
- command: step.runner.command,
1387
- args: step.runner.args ?? [],
1388
- promptDelivery: step.runner.promptDelivery ?? "stdin",
1389
- capabilities: { stop: true, steer: false, resume: false, structuredOutput: false, toolEvents: false },
1390
- };
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 } : {}) });
1391
1471
  const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
1392
1472
  const external = await runExternalCli(omitUndefinedProperties({
1393
- command: runner.command,
1394
- args: runner.args,
1395
- cwd: step.cwd ?? ctx.cwd,
1473
+ command: adapterLaunch?.command ?? runner.command,
1474
+ args: adapterLaunch?.args ?? runner.args,
1475
+ cwd: externalCwd,
1396
1476
  prompt: buildExternalCliPrompt(step.systemPrompt ?? "", task),
1397
1477
  asyncDir: path.dirname(ctx.outputFile),
1398
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,
1399
1485
  registerTimeout: ctx.registerTimeout,
1400
1486
  registerStop: ctx.registerStop,
1401
1487
  timeoutMessage: ctx.timeoutMessage,
@@ -1723,11 +1809,25 @@ async function runSingleStepInner(
1723
1809
  : undefined;
1724
1810
  const runtimeAcknowledgedExtensions = readRuntimeAcknowledgedExtensions(runtimeAcknowledgedExtensionsPath);
1725
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;
1726
1826
 
1727
1827
  let structuredOutput: unknown;
1728
1828
  let structuredError: string | undefined;
1729
1829
  let validatedStructuredOutput = false;
1730
- if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !toolAvailabilityError) {
1830
+ if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !toolAvailabilityError && !midToolExitError) {
1731
1831
  if (!run.structuredOutputToolInvoked) {
1732
1832
  structuredError = MISSING_STRUCTURED_OUTPUT_CALL_ERROR;
1733
1833
  } else {
@@ -1749,7 +1849,7 @@ async function runSingleStepInner(
1749
1849
  const errorMessages = validatedStructuredOutput
1750
1850
  ? run.messages.slice(run.structuredOutputMessageStartIndex ?? run.messages.length)
1751
1851
  : run.messages;
1752
- const hiddenError = run.exitCode === 0 && !run.error && !toolAvailabilityError && !structuredError
1852
+ const hiddenError = run.exitCode === 0 && !run.error && !toolAvailabilityError && !structuredError && !midToolExitError
1753
1853
  ? detectSubagentError(errorMessages)
1754
1854
  : null;
1755
1855
  const emptyOutputError = run.exitCode === 0
@@ -1766,7 +1866,7 @@ async function runSingleStepInner(
1766
1866
  const mutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
1767
1867
  finalMutationEvidence = mutationEvidence;
1768
1868
  const completionMutationEvidence = ctx.trackedMutationEvidenceForCompletionGuard === false ? undefined : mutationEvidence;
1769
- const completionGuard = run.exitCode === 0 && !run.error && !structuredError && !hiddenError?.hasError && !emptyOutputError && completionGuardEnabled
1869
+ const completionGuard = run.exitCode === 0 && !run.error && !structuredError && !hiddenError?.hasError && !midToolExitError && !emptyOutputError && completionGuardEnabled
1770
1870
  ? evaluateCompletionMutationGuard(omitUndefinedProperties({
1771
1871
  agent: step.agent,
1772
1872
  task: taskForCompletionGuard,
@@ -1793,7 +1893,7 @@ async function runSingleStepInner(
1793
1893
  const completionGuardError = completionGuardTriggered && !isAgentContractV1(step.agentContract)
1794
1894
  ? "Subagent completed without making edits for an implementation task.\nIt appears to have returned planning or scratchpad output instead of applying changes."
1795
1895
  : undefined;
1796
- const effectiveExitCode = toolAvailabilityError || (completionGuardTriggered && !isAgentContractV1(step.agentContract)) || structuredError || emptyOutputError
1896
+ const effectiveExitCode = toolAvailabilityError || (completionGuardTriggered && !isAgentContractV1(step.agentContract)) || midToolExitError || structuredError || emptyOutputError
1797
1897
  ? 1
1798
1898
  : hiddenError?.hasError
1799
1899
  ? (hiddenError.exitCode ?? 1)
@@ -1810,6 +1910,7 @@ async function runSingleStepInner(
1810
1910
  const error = formatSubagentExtensionConflictError(
1811
1911
  toolAvailabilityError
1812
1912
  ?? completionGuardError
1913
+ ?? midToolExitError
1813
1914
  ?? structuredError
1814
1915
  ?? emptyOutputError
1815
1916
  ?? (hiddenError?.hasError
@@ -1889,7 +1990,7 @@ async function runSingleStepInner(
1889
1990
  finalResult.finalOutput = startupError;
1890
1991
  break modelAttemptsLoop;
1891
1992
  }
1892
- const retryableModelFailure = isRetryableModelFailure(error);
1993
+ const retryableModelFailure = isRetryableModelFailureAttempt({ error, messages: run.messages, toolCount: run.toolCount });
1893
1994
  if (retryableModelFailure) recordRetryableModelFailure(candidate ?? run.model ?? step.model, error);
1894
1995
  if (isContextOverflow(error)) {
1895
1996
  contextOverflow = true;
@@ -1993,7 +2094,7 @@ async function runSingleStepInner(
1993
2094
  const acceptanceCanFailRun = acceptanceFailure && effectiveAcceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted && !timedOutAfterAcceptance && !stoppedAfterAcceptance && !turnBudgetExceeded && !isAgentContractV1(step.agentContract);
1994
2095
  const effectiveFinalExitCode = timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? 1 : acceptanceCanFailRun ? 1 : finalResult?.exitCode ?? 1;
1995
2096
  const intercomDetachReceipt = finalResult?.finalOutput === INTERCOM_DETACH_RECEIPT;
1996
- const effectiveFinalError = stoppedAfterAcceptance
2097
+ const baseFinalError = stoppedAfterAcceptance
1997
2098
  ? ctx.stopMessage ?? "Subagent stopped by user."
1998
2099
  : timedOutAfterAcceptance
1999
2100
  ? finalResult?.error ?? ctx.timeoutMessage ?? "Subagent timed out."
@@ -2002,6 +2103,17 @@ async function runSingleStepInner(
2002
2103
  : acceptanceCanFailRun
2003
2104
  ? (finalResult?.error ? `${finalResult.error}\n${acceptanceFailure}` : acceptanceFailure)
2004
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
+ });
2005
2117
 
2006
2118
  const artifactErrors = artifactPaths && ctx.artifactConfig?.enabled !== false
2007
2119
  ? persistStepArtifacts({
@@ -2098,13 +2210,7 @@ type RunnerStatusStep = NonNullable<AsyncStatus["steps"]>[number] & {
2098
2210
 
2099
2211
  function externalRunnerStatus(runner: SubagentStep["runner"]): ExternalCliRunnerStatus | ExternalJobRunnerStatus | undefined {
2100
2212
  if (runner?.type === "external-cli") {
2101
- return {
2102
- type: "external-cli",
2103
- command: runner.command,
2104
- args: runner.args ?? [],
2105
- promptDelivery: runner.promptDelivery ?? "stdin",
2106
- capabilities: { stop: true, steer: false, resume: false, structuredOutput: false, toolEvents: false },
2107
- };
2213
+ return resolveExternalCliRunnerStatus(runner);
2108
2214
  }
2109
2215
  if (runner?.type === "external-job") {
2110
2216
  return {
@@ -3516,12 +3622,13 @@ async function runSubagent(
3516
3622
  if (usage) {
3517
3623
  const input = usage.input ?? usage.inputTokens ?? 0;
3518
3624
  const output = usage.output ?? usage.outputTokens ?? 0;
3625
+ const window = input + (usage.cacheRead ?? usage.cacheReadTokens ?? 0);
3519
3626
  const previousInput = step.tokens?.input ?? 0;
3520
3627
  const previousOutput = step.tokens?.output ?? 0;
3521
- 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) };
3522
3629
  const totalInput = statusPayload.totalTokens?.input ?? 0;
3523
3630
  const totalOutput = statusPayload.totalTokens?.output ?? 0;
3524
- 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) };
3525
3632
  refreshUsageBudget();
3526
3633
  }
3527
3634
  statusPayload.turnCount = Math.max(statusPayload.turnCount ?? 0, step.turnCount);
@@ -4580,13 +4687,21 @@ async function runSubagent(
4580
4687
  const sessionTokens = config.sessionDir
4581
4688
  ? parseSessionTokens(path.join(config.sessionDir, `parallel-${t}`))
4582
4689
  : null;
4583
- 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);
4584
4695
  if (!taskTokens) continue;
4585
4696
  requiredStatusStep(statusPayload, fi).tokens = taskTokens;
4586
4697
  previousCumulativeTokens = {
4587
4698
  input: previousCumulativeTokens.input + taskTokens.input,
4588
4699
  output: previousCumulativeTokens.output + taskTokens.output,
4589
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
+ : {}),
4590
4705
  };
4591
4706
  }
4592
4707
  statusPayload.totalTokens = { ...previousCumulativeTokens };
@@ -4903,17 +5018,27 @@ async function runSubagent(
4903
5018
  input: cumulativeTokens.input - previousCumulativeTokens.input,
4904
5019
  output: cumulativeTokens.output - previousCumulativeTokens.output,
4905
5020
  total: cumulativeTokens.total - previousCumulativeTokens.total,
5021
+ ...(cumulativeTokens.window !== undefined ? { window: cumulativeTokens.window } : {}),
5022
+ ...(cumulativeTokens.windowPeak !== undefined ? { windowPeak: cumulativeTokens.windowPeak } : {}),
4906
5023
  }
4907
5024
  : null;
4908
5025
  if (cumulativeTokens) {
4909
5026
  previousCumulativeTokens = cumulativeTokens;
4910
5027
  } else {
4911
- 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;
4912
5033
  if (stepTokens) {
4913
5034
  previousCumulativeTokens = {
4914
5035
  input: previousCumulativeTokens.input + stepTokens.input,
4915
5036
  output: previousCumulativeTokens.output + stepTokens.output,
4916
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
+ : {}),
4917
5042
  };
4918
5043
  }
4919
5044
  }
@@ -5048,7 +5173,7 @@ async function runSubagent(
5048
5173
  }
5049
5174
  }
5050
5175
 
5051
- 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");
5052
5177
  let truncated = false;
5053
5178
 
5054
5179
  if (maxOutput) {
@@ -5359,7 +5484,7 @@ async function waitForStartupControl(
5359
5484
  } catch (error) {
5360
5485
  throw new Error(`Failed to read runner startup control '${controlPath}': ${error instanceof Error ? error.message : String(error)}`);
5361
5486
  }
5362
- 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.");
5363
5488
  if (payload.action === action) return;
5364
5489
  if (payload.action !== "ack" && payload.action !== "proceed") throw new Error("Runner startup control action is invalid.");
5365
5490
  }
@@ -5370,7 +5495,7 @@ async function waitForStartupControl(
5370
5495
 
5371
5496
  async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
5372
5497
  let lease: ReturnType<typeof acquireSessionLease> | undefined;
5373
- let startupCommitted = config.revivalLease === undefined;
5498
+ let startupCommitted = config.revivalLease === undefined && config.launchBarrierToken === undefined;
5374
5499
  const startupPath = path.join(config.asyncDir, "runner-startup.json");
5375
5500
  const startupAckPath = path.join(config.asyncDir, "runner-startup-ack.json");
5376
5501
  const startupProceedPath = path.join(config.asyncDir, "runner-startup-proceed.json");
@@ -5383,7 +5508,15 @@ async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
5383
5508
  };
5384
5509
  process.once("exit", releaseOnExit);
5385
5510
  try {
5386
- 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) {
5387
5520
  lease = acquireSessionLease(config.revivalLease);
5388
5521
  config.revivalLeaseToken = lease.owner.token;
5389
5522
  writeAtomicJson(startupPath, { state: "ready", token: lease.owner.token, pid: process.pid, owner: lease.owner });
@@ -5401,7 +5534,7 @@ async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
5401
5534
  }
5402
5535
  await runSubagent(config, lease ? (writer) => lease!.updateWriter(writer) : undefined);
5403
5536
  } catch (error) {
5404
- if (config.revivalLease && !startupCommitted) {
5537
+ if (!startupCommitted) {
5405
5538
  try {
5406
5539
  writeAtomicJson(startupPath, { state: "error", pid: process.pid, error: error instanceof Error ? error.message : String(error) });
5407
5540
  } catch {
@@ -3,6 +3,7 @@ import type { ArtifactPaths, SubagentState, WaitCompletion, WaitCompletionChild
3
3
  import type { AsyncRunSummary } from "./async-status.ts";
4
4
  import { readCompletionReplay, writeCompletionReplay } from "./completion-replay.ts";
5
5
  import { fallbackResultPayloadPathForSessionRun, resultFilePath, resultPayloadPathForSessionRun } from "./result-files.ts";
6
+ import { parseWorkflowChildSummary } from "../../workflows/workflow-child-summary.ts";
6
7
 
7
8
  function asNonEmptyString(value: unknown): string | undefined {
8
9
  return typeof value === "string" && value ? value : undefined;
@@ -60,6 +61,8 @@ export function toWaitCompletion(data: Record<string, unknown>, runId: string):
60
61
  const agent = asNonEmptyString(data.agent);
61
62
  const mode = asNonEmptyString(data.mode);
62
63
  const state = asNonEmptyString(data.state);
64
+ const workflowChildren = parseWorkflowChildSummary(data.workflowChildren);
65
+ if (workflowChildren && workflowChildren.workflowRunId !== runId) throw new Error("workflowChildren.workflowRunId does not match its completion run id.");
63
66
  return {
64
67
  runId,
65
68
  ...(agent ? { agent } : {}),
@@ -67,6 +70,7 @@ export function toWaitCompletion(data: Record<string, unknown>, runId: string):
67
70
  ...(state ? { state } : {}),
68
71
  ...(typeof data.success === "boolean" ? { success: data.success } : {}),
69
72
  ...(results && results.length > 0 ? { results } : {}),
73
+ ...(workflowChildren ? { workflowChildren } : {}),
70
74
  };
71
75
  }
72
76