pi-subagents 0.55.0 → 0.56.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 (32) hide show
  1. package/CHANGELOG.md +28 -3
  2. package/docs/models.md +6 -0
  3. package/package.json +1 -1
  4. package/src/agents/agent-serializer.ts +2 -0
  5. package/src/agents/agents.ts +23 -1
  6. package/src/api/preflight.ts +20 -1
  7. package/src/extension/schemas.ts +5 -0
  8. package/src/runs/background/async-execution.ts +48 -18
  9. package/src/runs/background/async-resume.ts +3 -0
  10. package/src/runs/background/subagent-runner.ts +83 -11
  11. package/src/runs/foreground/execution.ts +35 -6
  12. package/src/runs/foreground/foreground-history.ts +3 -2
  13. package/src/runs/foreground/subagent-executor.ts +59 -8
  14. package/src/runs/foreground/workflow-detach-reconcile.ts +15 -6
  15. package/src/runs/shared/acceptance.ts +10 -5
  16. package/src/runs/shared/agent-contract.ts +1 -1
  17. package/src/runs/shared/completion-guard.ts +3 -2
  18. package/src/runs/shared/dynamic-fanout.ts +1 -1
  19. package/src/runs/shared/extension-bindings.ts +78 -0
  20. package/src/runs/shared/external-cli-runner.ts +2 -0
  21. package/src/runs/shared/fast-mode-extension.ts +10 -0
  22. package/src/runs/shared/model-exclusions.ts +2 -1
  23. package/src/runs/shared/model-fallback.ts +2 -7
  24. package/src/runs/shared/mutation-evidence.ts +145 -0
  25. package/src/runs/shared/parallel-utils.ts +3 -0
  26. package/src/runs/shared/pi-args.ts +47 -0
  27. package/src/runs/shared/structured-output.ts +18 -4
  28. package/src/runs/shared/subagent-prompt-runtime.ts +9 -5
  29. package/src/shared/launch-contract.ts +6 -0
  30. package/src/shared/settings.ts +6 -1
  31. package/src/shared/types.ts +48 -0
  32. package/src/workflows/scripted-workflow.ts +50 -3
@@ -80,9 +80,10 @@ 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";
83
+ import { createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
84
84
  import { formatProcessSignalError, 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";
@@ -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";
@@ -229,6 +231,7 @@ interface StepResult {
229
231
  timedOut?: boolean;
230
232
  stopped?: boolean;
231
233
  processSignal?: string | null;
234
+ timeoutRecovery?: import("../../shared/types.ts").TimeoutRecoverySummary;
232
235
  turnBudget?: TurnBudgetState;
233
236
  turnBudgetExceeded?: boolean;
234
237
  wrapUpRequested?: boolean;
@@ -540,6 +543,9 @@ interface RunPiStreamingResult {
540
543
  processCloseObservedAt?: number;
541
544
  processSignal?: string | null;
542
545
  processTree: ProcessTreeTerminalV1;
546
+ currentTool?: string;
547
+ currentToolArgs?: string;
548
+ currentPath?: string;
543
549
  }
544
550
 
545
551
  function runPiStreaming(
@@ -610,6 +616,9 @@ function runPiStreaming(
610
616
  let observedMutationAttempt = false;
611
617
  let structuredOutputToolInvoked = false;
612
618
  let structuredOutputMessageStartIndex: number | undefined;
619
+ let currentTool: string | undefined;
620
+ let currentToolArgs: string | undefined;
621
+ let currentPath: string | undefined;
613
622
  let toolCount = 0;
614
623
  const childWatchdogConfig = decodeChildWatchdogConfig(env?.[CHILD_WATCHDOG_CONFIG_ENV]);
615
624
  let childWatchdogState: ChildWatchdogStateSnapshot | undefined;
@@ -701,12 +710,18 @@ function runPiStreaming(
701
710
 
702
711
  if (event.type === "tool_execution_end") {
703
712
  clearActiveToolTimeout(event);
713
+ currentTool = undefined;
714
+ currentToolArgs = undefined;
715
+ currentPath = undefined;
704
716
  return;
705
717
  }
706
718
 
707
719
  if (event.type === "tool_execution_start" && event.toolName) {
708
720
  toolCount += 1;
709
721
  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);
710
725
  if (event.toolName === "structured_output") {
711
726
  structuredOutputToolInvoked = true;
712
727
  structuredOutputMessageStartIndex = messages.length;
@@ -1029,6 +1044,9 @@ function runPiStreaming(
1029
1044
  processCloseObservedAt,
1030
1045
  processSignal: signal,
1031
1046
  processTree,
1047
+ currentTool,
1048
+ currentToolArgs,
1049
+ currentPath,
1032
1050
  }));
1033
1051
  });
1034
1052
 
@@ -1207,6 +1225,8 @@ interface SingleStepContext {
1207
1225
  onExternalProcess?: (process: ExternalProcessStatus) => void;
1208
1226
  onExternalJob?: (status: ExternalJobStatus) => void;
1209
1227
  skipAcceptance?: () => boolean;
1228
+ /** False when sibling work in the same Git worktree could have caused the tracked diff. */
1229
+ trackedMutationEvidenceForCompletionGuard?: boolean;
1210
1230
  orcaProgressTab?: OrcaProgressTab;
1211
1231
  }
1212
1232
 
@@ -1297,6 +1317,9 @@ async function runSingleStepInner(
1297
1317
  tools: step.tools,
1298
1318
  extensions: step.extensions,
1299
1319
  subagentOnlyExtensions: step.subagentOnlyExtensions,
1320
+ fast: step.fast,
1321
+ model: step.model,
1322
+ modelCandidates: step.modelCandidates,
1300
1323
  mcpDirectTools: step.mcpDirectTools,
1301
1324
  cwd: step.cwd ?? ctx.cwd,
1302
1325
  requireReadTool: Boolean(step.skills?.length),
@@ -1329,7 +1352,7 @@ async function runSingleStepInner(
1329
1352
  }
1330
1353
  }
1331
1354
  if (step.effectiveAcceptance) {
1332
- const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance, { reportOptional: isAgentContractV1(step.agentContract) });
1355
+ const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance, { reportOptional: isAgentContractV1(step.agentContract), structuredOutput: Boolean(step.structuredOutput?.acceptanceReportPath) });
1333
1356
  if (acceptancePrompt) task = `${task}\n${acceptancePrompt}`;
1334
1357
  }
1335
1358
  const sessionEnabled = Boolean(step.sessionFile) || ctx.sessionEnabled;
@@ -1500,11 +1523,15 @@ async function runSingleStepInner(
1500
1523
  const eventsPath = path.join(path.dirname(ctx.outputFile), "events.jsonl");
1501
1524
  let finalResult: RunPiStreamingResult | undefined;
1502
1525
  let finalOutputSnapshot: SingleOutputSnapshot | undefined;
1526
+ let structuredAcceptanceReport: unknown;
1527
+ let structuredAcceptanceReportError: string | undefined;
1503
1528
  let completionGuardTriggeredFinal = false;
1504
1529
  let turnBudget = ctx.turnBudget ? initialTurnBudgetState(ctx.turnBudget) : undefined;
1505
1530
  let toolBudget = step.toolBudget ? initialToolBudgetState(step.toolBudget) : undefined;
1506
1531
  let toolBudgetBlocked = false;
1507
1532
  let actualLaunchContractDigest = step.launchContractDigest;
1533
+ const mutationSnapshot = snapshotTrackedMutations(step.cwd ?? ctx.cwd);
1534
+ let finalMutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
1508
1535
 
1509
1536
  let modelIndex = 0;
1510
1537
  let startupAttemptIndex = 0;
@@ -1533,6 +1560,7 @@ async function runSingleStepInner(
1533
1560
  }
1534
1561
  }
1535
1562
  const watchdogConfig = resolveWatchdogConfig(step.cwd ?? ctx.cwd);
1563
+ const extensionBindings = normalizeExtensionBindings(step.extensionBindings)?.value;
1536
1564
  const childWatchdog = watchdogConfig.ok
1537
1565
  ? resolveChildWatchdogConfig({
1538
1566
  config: watchdogConfig.config,
@@ -1556,6 +1584,8 @@ async function runSingleStepInner(
1556
1584
  tools: step.tools,
1557
1585
  extensions: step.extensions,
1558
1586
  subagentOnlyExtensions: step.subagentOnlyExtensions,
1587
+ fast: step.fast,
1588
+ modelCandidates: step.modelCandidates,
1559
1589
  systemPrompt: appendTurnBudgetSystemPrompt(step.systemPrompt ?? "", ctx.turnBudget),
1560
1590
  systemPromptMode: step.systemPromptMode,
1561
1591
  mcpDirectTools: step.mcpDirectTools,
@@ -1587,6 +1617,7 @@ async function runSingleStepInner(
1587
1617
  childWatchdog,
1588
1618
  waitToolEnabled: step.waitToolEnabled,
1589
1619
  thinkingCeiling: step.thinkingCeiling,
1620
+ extensionBindings,
1590
1621
  }));
1591
1622
  if (!launchWarningsEmitted && warnings.length > 0) {
1592
1623
  for (const warning of warnings) console.warn(`[pi-subagents] ${warning}`);
@@ -1597,6 +1628,9 @@ async function runSingleStepInner(
1597
1628
  tools: step.tools,
1598
1629
  extensions: step.extensions,
1599
1630
  subagentOnlyExtensions: step.subagentOnlyExtensions,
1631
+ fast: step.fast,
1632
+ model: step.model,
1633
+ modelCandidates: step.modelCandidates,
1600
1634
  mcpDirectTools: step.mcpDirectTools,
1601
1635
  cwd: step.cwd ?? ctx.cwd,
1602
1636
  requireReadTool: Boolean(step.skills?.length),
@@ -1611,6 +1645,7 @@ async function runSingleStepInner(
1611
1645
  task: step.launchBindingTask ?? task,
1612
1646
  ...(candidate ? { model: candidate } : {}),
1613
1647
  modelCandidates: candidates as string[],
1648
+ ...(step.fast !== undefined ? { fast: step.fast } : {}),
1614
1649
  ...(resolveEffectiveThinking(candidate, step.thinking) ? { thinking: resolveEffectiveThinking(candidate, step.thinking) } : {}),
1615
1650
  ...(step.thinkingCeiling ? { thinkingCeiling: step.thinkingCeiling } : {}),
1616
1651
  systemPrompt: appendTurnBudgetSystemPrompt(step.systemPrompt ?? "", ctx.turnBudget),
@@ -1624,6 +1659,7 @@ async function runSingleStepInner(
1624
1659
  ...(step.outputPath ? { outputPath: step.outputPath } : {}),
1625
1660
  ...(step.outputMode ? { outputMode: step.outputMode } : {}),
1626
1661
  ...(step.structuredOutputSchema ? { structuredOutputSchema: step.structuredOutputSchema } : {}),
1662
+ ...(extensionBindings ? { extensionBindings } : {}),
1627
1663
  }));
1628
1664
  }
1629
1665
  capabilityAudit = attemptCapabilityAudit;
@@ -1703,6 +1739,9 @@ async function runSingleStepInner(
1703
1739
  if (structured.error) structuredError = structured.error;
1704
1740
  else {
1705
1741
  structuredOutput = structured.value;
1742
+ const acceptanceReport = readStructuredOutputAcceptanceReport(effectiveStructuredOutput);
1743
+ structuredAcceptanceReport = acceptanceReport.value;
1744
+ structuredAcceptanceReportError = acceptanceReport.error;
1706
1745
  validatedStructuredOutput = true;
1707
1746
  }
1708
1747
  }
@@ -1724,6 +1763,9 @@ async function runSingleStepInner(
1724
1763
  : undefined;
1725
1764
  const completionGuardEnabled = isAgentContractV1(step.agentContract) ? step.completionGuard === true : step.completionGuard !== false;
1726
1765
  const completionToolPlan = resolvedTaskToolPlan;
1766
+ const mutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
1767
+ finalMutationEvidence = mutationEvidence;
1768
+ const completionMutationEvidence = ctx.trackedMutationEvidenceForCompletionGuard === false ? undefined : mutationEvidence;
1727
1769
  const completionGuard = run.exitCode === 0 && !run.error && !structuredError && !hiddenError?.hasError && !emptyOutputError && completionGuardEnabled
1728
1770
  ? evaluateCompletionMutationGuard(omitUndefinedProperties({
1729
1771
  agent: step.agent,
@@ -1732,15 +1774,18 @@ async function runSingleStepInner(
1732
1774
  tools: completionToolPlan ? (completionToolPlan.explicitToolAllowlist ? completionToolPlan.effectiveToolAllowlist : undefined) : step.tools,
1733
1775
  mcpDirectTools: completionToolPlan?.effectiveMcpTools ?? step.mcpDirectTools,
1734
1776
  toolAvailabilityError,
1777
+ mutationEvidence: completionMutationEvidence,
1735
1778
  }))
1736
1779
  : undefined;
1737
- const completionGuardTriggered = completionGuard?.triggered === true && !run.observedMutationAttempt;
1780
+ const mutationAttemptObserved = run.observedMutationAttempt === true || completionMutationEvidence?.attemptedMutation === true;
1781
+ const completionGuardTriggered = completionGuard?.triggered === true && !mutationAttemptObserved;
1738
1782
  const completionGuardBlocked = completionGuard?.blocked === true;
1739
1783
  const fileMutationEffect = completionGuard
1740
1784
  ? {
1741
1785
  status: completionGuardBlocked ? "blocked" as const : completionGuard.expectedMutation ? completionGuardTriggered ? "missing" as const : "observed" as const : "not-applicable" as const,
1742
1786
  expected: completionGuard.expectedMutation,
1743
- attempted: completionGuardBlocked ? false : completionGuard.attemptedMutation || run.observedMutationAttempt === true,
1787
+ attempted: completionGuardBlocked ? false : completionGuard.attemptedMutation || mutationAttemptObserved,
1788
+ ...(completionMutationEvidence ? { evidence: completionMutationEvidence } : {}),
1744
1789
  ...(completionGuardBlocked && completionGuard.message ? { message: completionGuard.message } : {}),
1745
1790
  ...(completionGuardTriggered ? { message: "Subagent completed without making edits for an implementation task." } : {}),
1746
1791
  }
@@ -1890,6 +1935,21 @@ async function runSingleStepInner(
1890
1935
  : resolvedOutput.savedPath
1891
1936
  ? "unknown"
1892
1937
  : finalResult?.outputState ?? "unknown";
1938
+ const timeoutRecovery = finalResult?.timedOut === true || ctx.timeoutSignal?.aborted === true
1939
+ ? buildTimeoutRecoverySummary({
1940
+ termination: "timed-out",
1941
+ evidence: finalMutationEvidence,
1942
+ currentTool: finalResult?.currentTool,
1943
+ currentToolArgs: finalResult?.currentToolArgs,
1944
+ currentPath: finalResult?.currentPath,
1945
+ sessionFile: step.sessionFile,
1946
+ transcriptPath: transcriptWriter ? artifactPaths?.transcriptPath : undefined,
1947
+ artifactPaths,
1948
+ })
1949
+ : undefined;
1950
+ if (timeoutRecovery) outputForSummary = outputForSummary.trim()
1951
+ ? `${outputForSummary}\n\n${timeoutRecovery.message}`
1952
+ : timeoutRecovery.message;
1893
1953
  const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
1894
1954
  fullOutput: outputForSummary,
1895
1955
  outputPath: step.outputPath,
@@ -1904,6 +1964,8 @@ async function runSingleStepInner(
1904
1964
  ? await evaluateAcceptance(omitUndefinedProperties({
1905
1965
  acceptance: step.effectiveAcceptance,
1906
1966
  output: outputForAcceptance,
1967
+ report: structuredAcceptanceReport as import("../../shared/types.ts").AcceptanceReport | undefined,
1968
+ reportError: structuredAcceptanceReportError,
1907
1969
  fileOutput: childWrittenOutput !== undefined && step.outputPath
1908
1970
  ? { content: childWrittenOutput, path: step.outputPath, authoritative: step.outputMode === "file-only" }
1909
1971
  : undefined,
@@ -1999,6 +2061,7 @@ async function runSingleStepInner(
1999
2061
  timedOut: timedOutAfterAcceptance ? true : finalResult?.timedOut,
2000
2062
  stopped: stoppedAfterAcceptance ? true : finalResult?.stopped,
2001
2063
  processSignal: finalResult?.processSignal,
2064
+ timeoutRecovery,
2002
2065
  turnBudget,
2003
2066
  turnBudgetExceeded: turnBudgetExceeded || undefined,
2004
2067
  wrapUpRequested: finalResult?.wrapUpRequested || turnBudget?.outcome === "wrap-up-requested" || turnBudget?.outcome === "termination-deferred" || turnBudgetExceeded || undefined,
@@ -4013,6 +4076,7 @@ async function runSubagent(
4013
4076
  registerTurnBudgetAbort: (abort) => registerStepTurnBudgetAbort(fi, abort),
4014
4077
  timeoutSignal: timeoutAbortController.signal,
4015
4078
  stopSignal: stopAbortController.signal,
4079
+ trackedMutationEvidenceForCompletionGuard: false,
4016
4080
  timeoutMessage,
4017
4081
  stopMessage,
4018
4082
  turnBudget: config.turnBudget,
@@ -4072,6 +4136,7 @@ async function runSubagent(
4072
4136
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "structuredOutputPath", singleResult.structuredOutputPath);
4073
4137
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "structuredOutputSchemaPath", singleResult.structuredOutputSchemaPath);
4074
4138
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "acceptance", singleResult.acceptance);
4139
+ setOptionalProperty(requiredStatusStep(statusPayload, fi), "timeoutRecovery", singleResult.timeoutRecovery);
4075
4140
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "watchdog", singleResult.watchdog);
4076
4141
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "capabilityCeiling", singleResult.capabilityCeiling);
4077
4142
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "capabilityAudit", singleResult.capabilityAudit);
@@ -4087,7 +4152,7 @@ async function runSubagent(
4087
4152
  }));
4088
4153
  if (stopped || childStopped) appendTerminalChildStatusEvent(fi, taskEndTime);
4089
4154
  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 };
4155
+ 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
4156
  }, globalSemaphore);
4092
4157
 
4093
4158
  flatIndex += dynamicSteps.length;
@@ -4127,6 +4192,7 @@ async function runSubagent(
4127
4192
  effects: pr.effects,
4128
4193
  execution: pr.execution,
4129
4194
  review: pr.review,
4195
+ timeoutRecovery: pr.timeoutRecovery,
4130
4196
  structuredOutput: pr.structuredOutput,
4131
4197
  structuredOutputPath: pr.structuredOutputPath,
4132
4198
  structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
@@ -4406,6 +4472,7 @@ async function runSubagent(
4406
4472
  registerTurnBudgetAbort: (abort) => registerStepTurnBudgetAbort(fi, abort),
4407
4473
  timeoutSignal: timeoutAbortController.signal,
4408
4474
  stopSignal: stopAbortController.signal,
4475
+ trackedMutationEvidenceForCompletionGuard: Boolean(worktreeSetup),
4409
4476
  timeoutMessage,
4410
4477
  stopMessage,
4411
4478
  turnBudget: config.turnBudget,
@@ -4470,6 +4537,7 @@ async function runSubagent(
4470
4537
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "structuredOutputPath", singleResult.structuredOutputPath);
4471
4538
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "structuredOutputSchemaPath", singleResult.structuredOutputSchemaPath);
4472
4539
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "acceptance", singleResult.acceptance);
4540
+ setOptionalProperty(requiredStatusStep(statusPayload, fi), "timeoutRecovery", singleResult.timeoutRecovery);
4473
4541
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "watchdog", singleResult.watchdog);
4474
4542
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "capabilityCeiling", singleResult.capabilityCeiling);
4475
4543
  setOptionalProperty(requiredStatusStep(statusPayload, fi), "capabilityAudit", singleResult.capabilityAudit);
@@ -4500,7 +4568,7 @@ async function runSubagent(
4500
4568
  }
4501
4569
 
4502
4570
  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 };
4571
+ 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
4572
  },
4505
4573
  globalSemaphore,
4506
4574
  );
@@ -4557,10 +4625,11 @@ async function runSubagent(
4557
4625
  artifactPaths: pr.artifactPaths,
4558
4626
  transcriptPath: pr.transcriptPath,
4559
4627
  transcriptError: pr.transcriptError,
4560
- effects: pr.effects,
4561
- execution: pr.execution,
4562
- review: pr.review,
4563
- structuredOutput: pr.structuredOutput,
4628
+ effects: pr.effects,
4629
+ execution: pr.execution,
4630
+ review: pr.review,
4631
+ timeoutRecovery: pr.timeoutRecovery,
4632
+ structuredOutput: pr.structuredOutput,
4564
4633
  structuredOutputPath: pr.structuredOutputPath,
4565
4634
  structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
4566
4635
  acceptance: pr.acceptance,
@@ -4780,7 +4849,7 @@ async function runSubagent(
4780
4849
  launchContractDigest: singleResult.launchContractDigest,
4781
4850
  launchResolvedExtensions: singleResult.launchResolvedExtensions,
4782
4851
  runtimeAcknowledgedExtensions: singleResult.runtimeAcknowledgedExtensions,
4783
- output: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.output,
4852
+ output: stopped || childStopped ? stopMessage : timedOut ? singleResult.output || (timeoutMessage ?? "Subagent timed out.") : singleResult.output,
4784
4853
  outputState: singleResult.outputState,
4785
4854
  error: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error,
4786
4855
  protocolError: singleResult.protocolError,
@@ -4799,6 +4868,7 @@ async function runSubagent(
4799
4868
  effects: singleResult.effects,
4800
4869
  execution: singleResult.execution,
4801
4870
  review: singleResult.review,
4871
+ timeoutRecovery: singleResult.timeoutRecovery,
4802
4872
  structuredOutput: singleResult.structuredOutput,
4803
4873
  structuredOutputPath: singleResult.structuredOutputPath,
4804
4874
  structuredOutputSchemaPath: singleResult.structuredOutputSchemaPath,
@@ -4885,6 +4955,7 @@ async function runSubagent(
4885
4955
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "structuredOutputPath", singleResult.structuredOutputPath);
4886
4956
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "structuredOutputSchemaPath", singleResult.structuredOutputSchemaPath);
4887
4957
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "acceptance", singleResult.acceptance);
4958
+ setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "timeoutRecovery", singleResult.timeoutRecovery);
4888
4959
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "watchdog", singleResult.watchdog);
4889
4960
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "capabilityCeiling", singleResult.capabilityCeiling);
4890
4961
  setOptionalProperty(requiredStatusStep(statusPayload, flatIndex), "capabilityAudit", singleResult.capabilityAudit);
@@ -5169,6 +5240,7 @@ async function runSubagent(
5169
5240
  structuredOutputSchemaPath: r.structuredOutputSchemaPath,
5170
5241
  acceptance: r.acceptance,
5171
5242
  watchdog: r.watchdog,
5243
+ timeoutRecovery: r.timeoutRecovery,
5172
5244
  capabilityCeiling: r.capabilityCeiling,
5173
5245
  capabilityAudit: r.capabilityAudit,
5174
5246
  })),
@@ -67,9 +67,10 @@ import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledge
67
67
  import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.ts";
68
68
  import { resolveEffectiveThinking } from "../../shared/model-info.ts";
69
69
  import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV } from "../../shared/thinking-ceiling.ts";
70
- import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput } from "../shared/structured-output.ts";
70
+ import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
71
71
  import { formatProcessSignalError, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
72
72
  import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
73
+ import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
73
74
  import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
74
75
  import {
75
76
  buildModelCandidates,
@@ -368,6 +369,8 @@ async function runSingleAttempt(
368
369
  steerCapabilityPath: options.steerCapabilityPath,
369
370
  steerAckDir: options.steerAckDir,
370
371
  structuredOutput: options.structuredOutput,
372
+ fast: options.fast ?? agent.fast,
373
+ modelCandidates: shared.modelCandidates,
371
374
  toolBudget: options.toolBudget,
372
375
  allowZeroToolBudget: options.allowZeroToolBudget,
373
376
  permissionRules,
@@ -376,6 +379,7 @@ async function runSingleAttempt(
376
379
  waitToolEnabled: options.waitToolEnabled,
377
380
  capabilityCeiling: options.capabilityCeiling,
378
381
  thinkingCeiling: options.thinkingCeiling,
382
+ extensionBindings: options.extensionBindings,
379
383
  });
380
384
  if (!shared.launchWarnings.emitted && warnings.length > 0) {
381
385
  for (const warning of warnings) console.warn(`[pi-subagents] ${warning}`);
@@ -391,6 +395,9 @@ async function runSingleAttempt(
391
395
  cwd: options.cwd ?? runtimeCwd,
392
396
  requireReadTool: Boolean(shared.resolvedSkillNames?.length),
393
397
  structuredOutput: Boolean(options.structuredOutput),
398
+ fast: options.fast ?? agent.fast,
399
+ model: modelArg,
400
+ modelCandidates: shared.modelCandidates,
394
401
  capabilityCeiling: options.capabilityCeiling,
395
402
  inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
396
403
  agentName: agent.name,
@@ -432,6 +439,7 @@ async function runSingleAttempt(
432
439
  task: shared.originalTask ?? task,
433
440
  ...(modelArg ? { model: modelArg } : {}),
434
441
  modelCandidates: shared.modelCandidates,
442
+ ...((options.fast ?? agent.fast) !== undefined ? { fast: options.fast ?? agent.fast } : {}),
435
443
  ...(resolvedThinking ? { thinking: resolvedThinking } : {}),
436
444
  ...(options.thinkingCeiling ? { thinkingCeiling: options.thinkingCeiling } : {}),
437
445
  systemPrompt: effectiveSystemPrompt,
@@ -445,6 +453,7 @@ async function runSingleAttempt(
445
453
  ...(options.outputPath ? { outputPath: options.outputPath } : {}),
446
454
  outputMode: options.outputMode ?? "inline",
447
455
  ...(options.structuredOutput ? { structuredOutputSchema: options.structuredOutput.schema } : {}),
456
+ ...(options.extensionBindings ? { extensionBindings: options.extensionBindings } : {}),
448
457
  });
449
458
  const result: SingleResult = withRunContext({
450
459
  index: options.index ?? 0,
@@ -524,6 +533,7 @@ async function runSingleAttempt(
524
533
  return result;
525
534
  }
526
535
  const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };
536
+ const mutationSnapshot = snapshotTrackedMutations(options.cwd ?? runtimeCwd);
527
537
  let observedMutationAttempt = false;
528
538
  let structuredOutputToolInvoked = false;
529
539
  let structuredOutputMessageStartIndex: number | undefined;
@@ -1448,6 +1458,9 @@ async function runSingleAttempt(
1448
1458
  result.structuredOutputFailed = true;
1449
1459
  } else {
1450
1460
  result.structuredOutput = structured.value;
1461
+ const acceptanceReport = readStructuredOutputAcceptanceReport(options.structuredOutput);
1462
+ (result as SingleResult & { structuredAcceptanceReport?: unknown; structuredAcceptanceReportError?: string }).structuredAcceptanceReport = acceptanceReport.value;
1463
+ (result as SingleResult & { structuredAcceptanceReport?: unknown; structuredAcceptanceReportError?: string }).structuredAcceptanceReportError = acceptanceReport.error;
1451
1464
  validatedStructuredOutput = true;
1452
1465
  }
1453
1466
  }
@@ -1485,6 +1498,7 @@ async function runSingleAttempt(
1485
1498
  tokens: progress.tokens,
1486
1499
  durationMs: progress.durationMs,
1487
1500
  };
1501
+ const mutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, options.cwd ?? runtimeCwd);
1488
1502
 
1489
1503
  const acceptanceOutput = getFinalOutput(result.messages ?? []);
1490
1504
  let fullOutput = stripAcceptanceReport(acceptanceOutput);
@@ -1492,9 +1506,19 @@ async function runSingleAttempt(
1492
1506
  result.outputState = fullOutput.trim() || result.structuredOutput !== undefined ? "present" : "absent";
1493
1507
  if (result.timedOut) {
1494
1508
  const timeoutMessage = formatTimeoutMessage(options.timeoutMs ?? 0);
1509
+ result.timeoutRecovery = buildTimeoutRecoverySummary({
1510
+ termination: "timed-out",
1511
+ evidence: mutationEvidence,
1512
+ currentTool: progress.currentTool,
1513
+ currentToolArgs: progress.currentToolArgs,
1514
+ currentPath: progress.currentPath,
1515
+ sessionFile: options.sessionFile,
1516
+ transcriptPath: shared.transcriptWriter ? shared.artifactPaths?.transcriptPath : undefined,
1517
+ artifactPaths: shared.artifactPaths,
1518
+ });
1495
1519
  fullOutput = fullOutput.trim()
1496
- ? `${timeoutMessage}\n\nPartial output before timeout:\n${fullOutput}`
1497
- : timeoutMessage;
1520
+ ? `${timeoutMessage}\n\n${result.timeoutRecovery.message}\n\nPartial output before timeout:\n${fullOutput}`
1521
+ : `${timeoutMessage}\n\n${result.timeoutRecovery.message}`;
1498
1522
  } else if (result.turnBudgetExceeded && result.turnBudget) {
1499
1523
  fullOutput = formatTurnBudgetOutput(turnBudgetExceededMessage(result.turnBudget, result.turnBudget.turnCount), fullOutput);
1500
1524
  } else if (result.turnBudget?.outcome === "termination-deferred") {
@@ -1513,9 +1537,11 @@ async function runSingleAttempt(
1513
1537
  tools: contractTools,
1514
1538
  mcpDirectTools: toolPlan.effectiveMcpTools,
1515
1539
  toolAvailabilityError,
1540
+ mutationEvidence,
1516
1541
  })
1517
1542
  : undefined;
1518
- let completionGuardTriggered = completionGuard?.triggered === true && !observedMutationAttempt;
1543
+ const mutationAttemptObserved = observedMutationAttempt || mutationEvidence.attemptedMutation;
1544
+ let completionGuardTriggered = completionGuard?.triggered === true && !mutationAttemptObserved;
1519
1545
  const completionGuardBlocked = completionGuard?.blocked === true;
1520
1546
  // The classifier is deliberately narrow, so a read-only review task can
1521
1547
  // still be misread as implementation. Arbitrate BEFORE any failure side
@@ -1547,7 +1573,8 @@ async function runSingleAttempt(
1547
1573
  : "observed"
1548
1574
  : "not-applicable",
1549
1575
  expected: completionGuard.expectedMutation,
1550
- attempted: completionGuardBlocked ? false : completionGuard.attemptedMutation || observedMutationAttempt,
1576
+ attempted: completionGuardBlocked ? false : completionGuard.attemptedMutation || mutationAttemptObserved,
1577
+ evidence: mutationEvidence,
1551
1578
  ...(completionGuardBlocked && completionGuard.message ? { message: completionGuard.message } : {}),
1552
1579
  ...(completionGuardTriggered ? { message: "Subagent completed without making edits for an implementation task." } : {}),
1553
1580
  ...(arbiterRescued ? { resolvedBy: "llm-intent-arbiter" } : {}),
@@ -1708,7 +1735,7 @@ async function runSyncCompletionInner(
1708
1735
  dynamicGroup: options.acceptanceContext?.dynamicGroup,
1709
1736
  agentContract: options.agentContract,
1710
1737
  });
1711
- const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance, { reportOptional: isAgentContractV1(options.agentContract) });
1738
+ const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance, { reportOptional: isAgentContractV1(options.agentContract), structuredOutput: Boolean(options.structuredOutput?.acceptanceReportPath) });
1712
1739
  const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
1713
1740
  options.onEffectivePrompt?.(taskWithAcceptance);
1714
1741
  const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
@@ -2045,6 +2072,8 @@ async function runSyncCompletionInner(
2045
2072
  result.acceptance = await evaluateAcceptance({
2046
2073
  acceptance: effectiveAcceptance,
2047
2074
  output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
2075
+ report: (result as SingleResult & { structuredAcceptanceReport?: import("../../shared/types.ts").AcceptanceReport; structuredAcceptanceReportError?: string }).structuredAcceptanceReport,
2076
+ reportError: (result as SingleResult & { structuredAcceptanceReport?: import("../../shared/types.ts").AcceptanceReport; structuredAcceptanceReportError?: string }).structuredAcceptanceReportError,
2048
2077
  fileOutput: childWrittenOutput !== undefined && options.outputPath
2049
2078
  ? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only" }
2050
2079
  : undefined,
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import type { ForegroundResumeChild, ForegroundResumeRun, SubagentState } from "../../shared/types.ts";
4
4
  import { DIRS } from "../../shared/types.ts";
5
- import { writeAtomicJson } from "../../shared/atomic-json.ts";
5
+ import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
6
6
  import { utf8Tail } from "../../shared/utf8.ts";
7
7
 
8
8
  export const MAX_REMEMBERED_FOREGROUND_RUNS = 50;
@@ -52,6 +52,7 @@ function compactChild(child: ForegroundResumeChild): ForegroundResumeChild {
52
52
  ...(child.transcriptError ? { transcriptError: child.transcriptError } : {}),
53
53
  ...(child.acceptance ? { acceptance: child.acceptance } : {}),
54
54
  ...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
55
+ ...(child.extensionBindings ? { extensionBindings: child.extensionBindings } : {}),
55
56
  ...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
56
57
  ...(child.updatedAt !== undefined ? { updatedAt: child.updatedAt } : {}),
57
58
  };
@@ -118,7 +119,7 @@ export function persistForegroundRunHistory(state: SubagentState, options: { res
118
119
  if (compact) merged.set(compact.runId, compact);
119
120
  }
120
121
  const runs = sortAndBound([...merged.values()], limit);
121
- writeAtomicJson(historyPath(resultsDir), { version: HISTORY_VERSION, runs });
122
+ writePrivateAtomicJson(historyPath(resultsDir), { version: HISTORY_VERSION, runs });
122
123
  }
123
124
 
124
125
  export function restoreForegroundRunHistory(state: SubagentState, options: { resultsDir?: string; sessionId?: string | null; limit?: number } = {}): number {