pi-subagents 0.56.0 → 0.58.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 (108) hide show
  1. package/CHANGELOG.md +92 -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 +124 -21
  9. package/docs/configuration.md +37 -0
  10. package/docs/extension-api.md +41 -2
  11. package/docs/models.md +3 -3
  12. package/docs/observability.md +8 -7
  13. package/docs/tool-reference.md +14 -3
  14. package/docs/workflows.md +44 -0
  15. package/package.json +1 -1
  16. package/skills/pi-subagents/SKILL.md +2 -0
  17. package/skills/pi-subagents/references/execution-controls.md +2 -2
  18. package/skills/pi-subagents/references/management-authoring-rpc.md +1 -0
  19. package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
  20. package/src/agents/agent-management.ts +36 -5
  21. package/src/agents/agent-refinements.ts +4 -4
  22. package/src/agents/agent-serializer.ts +5 -0
  23. package/src/agents/agents.ts +257 -51
  24. package/src/agents/builtin-names.ts +6 -0
  25. package/src/agents/runtime-agent-events.ts +70 -0
  26. package/src/agents/runtime-agent-registry.ts +18 -4
  27. package/src/api/agents.ts +10 -5
  28. package/src/api/preflight.ts +28 -3
  29. package/src/extension/config.ts +70 -0
  30. package/src/extension/doctor.ts +3 -3
  31. package/src/extension/index.ts +33 -8
  32. package/src/extension/public-execution.ts +29 -13
  33. package/src/extension/rpc.ts +55 -19
  34. package/src/extension/schemas.ts +6 -5
  35. package/src/extension/tool-description.ts +18 -12
  36. package/src/inspectors/herdr/actions.ts +2 -1
  37. package/src/inspectors/herdr/inspector-runner.ts +2 -10
  38. package/src/inspectors/herdr/session-roots-codec.ts +42 -0
  39. package/src/integrations/herdr-status.ts +51 -3
  40. package/src/runs/background/active-async-capacity.ts +77 -10
  41. package/src/runs/background/async-execution.ts +127 -19
  42. package/src/runs/background/async-job-tracker.ts +5 -0
  43. package/src/runs/background/async-resume.ts +6 -2
  44. package/src/runs/background/async-retention.ts +20 -3
  45. package/src/runs/background/async-status.ts +7 -0
  46. package/src/runs/background/chain-append.ts +2 -0
  47. package/src/runs/background/chain-root-attachment.ts +15 -1
  48. package/src/runs/background/fleet-view.ts +16 -10
  49. package/src/runs/background/inspect-rpc.ts +8 -8
  50. package/src/runs/background/notify.ts +26 -3
  51. package/src/runs/background/result-delivery-ownership.ts +45 -0
  52. package/src/runs/background/result-files.ts +27 -14
  53. package/src/runs/background/result-watcher.ts +36 -15
  54. package/src/runs/background/run-status.ts +33 -6
  55. package/src/runs/background/scheduled-runs.ts +7 -1
  56. package/src/runs/background/subagent-runner.ts +239 -56
  57. package/src/runs/background/wait-completions.ts +4 -0
  58. package/src/runs/foreground/execution.ts +92 -11
  59. package/src/runs/foreground/foreground-control.ts +6 -0
  60. package/src/runs/foreground/foreground-history.ts +22 -1
  61. package/src/runs/foreground/subagent-executor.ts +322 -102
  62. package/src/runs/foreground/workflow-detach-reconcile.ts +144 -18
  63. package/src/runs/shared/child-protocol.ts +21 -7
  64. package/src/runs/shared/claude-code-adapter.ts +129 -0
  65. package/src/runs/shared/codex-exec-adapter.ts +129 -0
  66. package/src/runs/shared/completion-guard.ts +4 -3
  67. package/src/runs/shared/cursor-agent-adapter.ts +114 -0
  68. package/src/runs/shared/dynamic-fanout.ts +3 -3
  69. package/src/runs/shared/external-cli-contract.ts +167 -0
  70. package/src/runs/shared/external-cli-preflight.ts +122 -0
  71. package/src/runs/shared/external-cli-runner.ts +348 -55
  72. package/src/runs/shared/fast-mode-extension.ts +5 -5
  73. package/src/runs/shared/launch-cwd.ts +16 -0
  74. package/src/runs/shared/long-running-guard.ts +2 -1
  75. package/src/runs/shared/mcp-config-sources.ts +386 -0
  76. package/src/runs/shared/mcp-direct-tool-allowlist.ts +155 -42
  77. package/src/runs/shared/model-exclusions.ts +69 -7
  78. package/src/runs/shared/model-fallback.ts +39 -5
  79. package/src/runs/shared/mutation-evidence.ts +7 -2
  80. package/src/runs/shared/nested-events.ts +3 -1
  81. package/src/runs/shared/nested-render.ts +2 -2
  82. package/src/runs/shared/parallel-utils.ts +8 -1
  83. package/src/runs/shared/pi-args.ts +61 -6
  84. package/src/runs/shared/process-signal.ts +13 -0
  85. package/src/runs/shared/run-history.ts +21 -1
  86. package/src/runs/shared/single-output.ts +17 -0
  87. package/src/runs/shared/subagent-prompt-runtime.ts +85 -9
  88. package/src/shared/fork-context.ts +21 -0
  89. package/src/shared/formatters.ts +13 -1
  90. package/src/shared/launch-contract.ts +4 -0
  91. package/src/shared/pruned-fork.ts +450 -0
  92. package/src/shared/session-file-trust.ts +19 -0
  93. package/src/shared/session-tokens.ts +14 -3
  94. package/src/shared/settings.ts +10 -2
  95. package/src/shared/shortcuts.ts +17 -0
  96. package/src/shared/types.ts +160 -10
  97. package/src/shared/utils.ts +6 -29
  98. package/src/shared/workflow-child-permit.ts +116 -0
  99. package/src/slash/delegation-adapters.ts +0 -1
  100. package/src/slash/slash-commands.ts +8 -6
  101. package/src/slash/subagents-admin.ts +3 -0
  102. package/src/tui/fleet-status.ts +27 -10
  103. package/src/tui/fleet-transcript.ts +11 -5
  104. package/src/tui/fleet.ts +28 -13
  105. package/src/tui/render.ts +55 -21
  106. package/src/workflows/scripted-workflow.ts +299 -31
  107. package/src/workflows/workflow-child-summary.ts +117 -0
  108. package/src/workflows/workflow-receipt.ts +155 -5
@@ -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
 
@@ -6,7 +6,7 @@ import { spawn } from "node:child_process";
6
6
  import { existsSync, unlinkSync } from "node:fs";
7
7
  import * as path from "node:path";
8
8
  import type { Message } from "@earendil-works/pi-ai";
9
- import type { AgentConfig } from "../../agents/agents.ts";
9
+ import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig } from "../../agents/agents.ts";
10
10
  import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
11
11
  import { alignForkedSessionCwd } from "../../shared/fork-context.ts";
12
12
  import {
@@ -58,6 +58,7 @@ import { effectiveToolTimeoutMs, formatToolTimeoutMessage, resolveToolTimeoutMs,
58
58
  import { evaluateCompletionMutationGuard, validateImplementationToolContract } from "../shared/completion-guard.ts";
59
59
  import { arbitrateCompletionGuardRescue } from "../shared/llm-intent-arbiter.ts";
60
60
  import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
61
+ import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
61
62
  import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
62
63
  import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
63
64
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
@@ -68,7 +69,7 @@ import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling,
68
69
  import { resolveEffectiveThinking } from "../../shared/model-info.ts";
69
70
  import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV } from "../../shared/thinking-ceiling.ts";
70
71
  import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput, readStructuredOutputAcceptanceReport } from "../shared/structured-output.ts";
71
- import { formatProcessSignalError, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
72
+ import { formatMidToolExitError, formatProcessSignalError, isOrdinaryToolForMidToolExit, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
72
73
  import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
73
74
  import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
74
75
  import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
@@ -77,7 +78,7 @@ import {
77
78
  formatSubagentModelVerificationError,
78
79
  formatModelAttemptNote,
79
80
  isContextOverflow,
80
- isRetryableModelFailure,
81
+ isRetryableModelFailureAttempt,
81
82
  recordRetryableModelFailure,
82
83
  } from "../shared/model-fallback.ts";
83
84
  import {
@@ -106,7 +107,8 @@ import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudget
106
107
  import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
107
108
  import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
108
109
  import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
109
- import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
110
+ import { consumeWorkflowChildPermit } from "../../shared/workflow-child-permit.ts";
111
+ import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ChildLifecycleState, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
110
112
  import {
111
113
  acceptChildWatchdogEvent,
112
114
  childWatchdogIsActive,
@@ -345,6 +347,7 @@ async function runSingleAttempt(
345
347
  thinking: effectiveThinking,
346
348
  systemPromptMode: agent.systemPromptMode,
347
349
  inheritProjectContext: agent.inheritProjectContext,
350
+ inheritGlobalContext: agent.inheritGlobalContext,
348
351
  inheritSkills: agent.inheritSkills,
349
352
  requireReadTool: Boolean(shared.resolvedSkillNames?.length),
350
353
  tools: agent.tools,
@@ -380,6 +383,7 @@ async function runSingleAttempt(
380
383
  capabilityCeiling: options.capabilityCeiling,
381
384
  thinkingCeiling: options.thinkingCeiling,
382
385
  extensionBindings: options.extensionBindings,
386
+ runtimeSnapshotHost: options.runtimeSnapshotHost,
383
387
  });
384
388
  if (!shared.launchWarnings.emitted && warnings.length > 0) {
385
389
  for (const warning of warnings) console.warn(`[pi-subagents] ${warning}`);
@@ -402,6 +406,7 @@ async function runSingleAttempt(
402
406
  inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
403
407
  agentName: agent.name,
404
408
  permissionRules,
409
+ runtimeSnapshotHost: options.runtimeSnapshotHost,
405
410
  });
406
411
  const contractTools = toolPlan.explicitToolAllowlist ? toolPlan.effectiveToolAllowlist : undefined;
407
412
  const contractError = validateImplementationToolContract({
@@ -445,6 +450,7 @@ async function runSingleAttempt(
445
450
  systemPrompt: effectiveSystemPrompt,
446
451
  systemPromptMode: agent.systemPromptMode,
447
452
  inheritProjectContext: agent.inheritProjectContext,
453
+ inheritGlobalContext: agent.inheritGlobalContext,
448
454
  inheritSkills: agent.inheritSkills,
449
455
  skills: shared.resolvedSkillNames ?? [],
450
456
  tools: toolPlan.effectiveToolAllowlist,
@@ -538,9 +544,29 @@ async function runSingleAttempt(
538
544
  let structuredOutputToolInvoked = false;
539
545
  let structuredOutputMessageStartIndex: number | undefined;
540
546
  let toolAvailabilityError: string | undefined;
547
+ let abortedBySignal = options.signal?.aborted === true;
541
548
 
549
+ const spawnSpec = getPiSpawnCommand(args);
550
+ if (options.workflowChildPermitLaunch) {
551
+ const permitError = consumeWorkflowChildPermit(options.workflowChildPermitLaunch.permit, {
552
+ workflowRunId: options.workflowChildPermitLaunch.workflowRunId,
553
+ childKey: options.workflowChildPermitLaunch.childKey,
554
+ agent: agent.name,
555
+ launchContractDigest,
556
+ context: options.context ?? "fresh",
557
+ runner: "pi",
558
+ });
559
+ if (permitError) {
560
+ cleanupTempDir(tempDir);
561
+ result.exitCode = 1;
562
+ result.error = permitError;
563
+ result.finalOutput = permitError;
564
+ progress.status = "failed";
565
+ progress.error = permitError;
566
+ return result;
567
+ }
568
+ }
542
569
  const exitCode = await new Promise<number>((resolve) => {
543
- const spawnSpec = getPiSpawnCommand(args);
544
570
  const proc = spawn(spawnSpec.command, spawnSpec.args, {
545
571
  cwd: options.cwd ?? runtimeCwd,
546
572
  env: spawnEnv,
@@ -707,6 +733,7 @@ async function runSingleAttempt(
707
733
  }
708
734
  if (action === "start-drain") startFinalDrain();
709
735
  };
736
+ const childLifecycleState: ChildLifecycleState = { compactionRetryActive: false };
710
737
 
711
738
  const unsubscribeIntercomDetach = options.intercomEvents?.on?.(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {
712
739
  if (!options.allowIntercomDetach || processClosed) return;
@@ -994,8 +1021,9 @@ async function runSingleAttempt(
994
1021
  }
995
1022
  shared.transcriptWriter?.writeChildEvent(evt);
996
1023
  shared.orcaProgressTab?.event(evt);
997
- if (evt.type === "agent_settled") agentSettledReceived = true;
998
- applyChildLifecycle(projectChildLifecycle(evt));
1024
+ const lifecycleAction = projectChildLifecycle(evt, false, childLifecycleState);
1025
+ if (evt.type === "agent_settled" && lifecycleAction === "start-drain") agentSettledReceived = true;
1026
+ applyChildLifecycle(lifecycleAction);
999
1027
 
1000
1028
  if (isChildWatchdogStatusEvent(evt)) {
1001
1029
  if (!childWatchdog) return;
@@ -1041,7 +1069,7 @@ async function runSingleAttempt(
1041
1069
  if (options.toolBudget) {
1042
1070
  result.toolBudget = toolBudgetState(options.toolBudget, progress.toolCount);
1043
1071
  }
1044
- const mutates = isMutatingTool(evt.toolName, toolArgs);
1072
+ const mutates = isMutatingTool(evt.toolName, toolArgs, agent.mutationTools);
1045
1073
  observedMutationAttempt = observedMutationAttempt || mutates;
1046
1074
  pendingToolResult = { tool: evt.toolName ?? "tool", path: activeTool?.path, mutates, startedAt: now };
1047
1075
  fireUpdate();
@@ -1078,6 +1106,7 @@ async function runSingleAttempt(
1078
1106
  updateTurnBudget(result.usage.turns, terminalAssistantStop || terminalStructuredOutputCall, hasToolCall || Boolean(progress.currentTool));
1079
1107
  const u = evt.message.usage;
1080
1108
  if (u) {
1109
+ const window = (u.input || 0) + (u.cacheRead || 0);
1081
1110
  result.usage.input += u.input || 0;
1082
1111
  result.usage.output += u.output || 0;
1083
1112
  result.usage.cacheRead += u.cacheRead || 0;
@@ -1086,6 +1115,8 @@ async function runSingleAttempt(
1086
1115
  progress.tokens = result.usage.input + result.usage.output;
1087
1116
  progress.inputTokens = result.usage.input;
1088
1117
  progress.outputTokens = result.usage.output;
1118
+ progress.window = window;
1119
+ progress.windowPeak = Math.max(progress.windowPeak ?? 0, window);
1089
1120
  }
1090
1121
  if (evt.message.model) {
1091
1122
  progress.model = evt.message.model;
@@ -1102,7 +1133,11 @@ async function runSingleAttempt(
1102
1133
  if (terminalAssistantStop) {
1103
1134
  if (!evt.message.errorMessage && assistantText.trim()) assistantError = undefined;
1104
1135
  cleanTerminalAssistantStopReceived ||= !evt.message.errorMessage;
1105
- applyChildLifecycle(projectChildLifecycle(evt, true));
1136
+ clearAllToolTimeouts();
1137
+ activeToolCalls.clear();
1138
+ activeToolKeysByName.clear();
1139
+ refreshCurrentTool();
1140
+ applyChildLifecycle(projectChildLifecycle(evt, true, childLifecycleState));
1106
1141
  }
1107
1142
  }
1108
1143
  updateActivityState(now);
@@ -1369,6 +1404,7 @@ async function runSingleAttempt(
1369
1404
  if (options.signal) {
1370
1405
  const kill = () => {
1371
1406
  if (processClosed || lifecycleFinished) return;
1407
+ abortedBySignal = true;
1372
1408
  proc.kill("SIGTERM");
1373
1409
  setTimeout(() => !proc.killed && proc.kill("SIGKILL"), 3000);
1374
1410
  };
@@ -1431,6 +1467,22 @@ async function runSingleAttempt(
1431
1467
  };
1432
1468
  return result;
1433
1469
  }
1470
+ if (progress.currentTool
1471
+ && isOrdinaryToolForMidToolExit(progress.currentTool)
1472
+ && !abortedBySignal
1473
+ && !result.timedOut
1474
+ && !result.stopped
1475
+ && !result.turnBudgetExceeded
1476
+ && !result.protocolError
1477
+ && !toolAvailabilityError) {
1478
+ const processExitCode = result.exitCode;
1479
+ result.exitCode = 1;
1480
+ result.error = formatMidToolExitError({
1481
+ toolName: progress.currentTool,
1482
+ exitCode: processExitCode,
1483
+ processSignal: result.processSignal,
1484
+ });
1485
+ }
1434
1486
  result.error = formatSubagentExtensionConflictError(result.error, {
1435
1487
  agent: agent.name,
1436
1488
  ambientExtensionsEnabled: !launchResolvedExtensions.disableAmbientExtensions,
@@ -1536,6 +1588,7 @@ async function runSingleAttempt(
1536
1588
  messages: result.messages ?? [],
1537
1589
  tools: contractTools,
1538
1590
  mcpDirectTools: toolPlan.effectiveMcpTools,
1591
+ mutationTools: agent.mutationTools,
1539
1592
  toolAvailabilityError,
1540
1593
  mutationEvidence,
1541
1594
  })
@@ -1641,12 +1694,27 @@ async function runSyncCompletionInner(
1641
1694
  task: string,
1642
1695
  options: RunSyncOptions,
1643
1696
  ): Promise<SingleResult> {
1697
+ const effectiveCwd = options.cwd ?? runtimeCwd;
1698
+ const cwdError = preflightLaunchCwd(options.requestedCwd ?? effectiveCwd, effectiveCwd);
1699
+ if (cwdError) {
1700
+ return redactResultPrompt(withRunContext({
1701
+ index: options.index ?? 0,
1702
+ agent: agentName,
1703
+ task,
1704
+ exitCode: 1,
1705
+ messages: [],
1706
+ usage: emptyUsage(),
1707
+ error: cwdError,
1708
+ }, options.context));
1709
+ }
1644
1710
  options = {
1645
1711
  ...options,
1646
1712
  capabilityCeiling: intersectSubagentCapabilityCeilings(options.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(options.parentSessionId), decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV])),
1647
1713
  };
1648
1714
  const agent = agents.find((a) => a.name === agentName);
1649
1715
  if (!agent) {
1716
+ const diagnosticContext = options.unknownAgentDiagnosticContext
1717
+ ?? unknownAgentDiagnosticContext(discoverAgents(path.resolve(options.cwd ?? runtimeCwd), "both"));
1650
1718
  return redactResultPrompt(withRunContext({
1651
1719
  index: options.index ?? 0,
1652
1720
  agent: agentName,
@@ -1654,7 +1722,7 @@ async function runSyncCompletionInner(
1654
1722
  exitCode: 1,
1655
1723
  messages: [],
1656
1724
  usage: emptyUsage(),
1657
- error: `Unknown agent: ${agentName}`,
1725
+ error: formatUnknownAgentError(agentName, diagnosticContext),
1658
1726
  }, options.context));
1659
1727
  }
1660
1728
  options = {
@@ -1781,6 +1849,18 @@ async function runSyncCompletionInner(
1781
1849
  agent.modelProvider ?? options.preferredModelProvider,
1782
1850
  { scope: options.modelScope, primaryModelFromParent: options.modelOverrideFromParent },
1783
1851
  );
1852
+ if (options.workflowChildPermitLaunch && candidates.length > 1) {
1853
+ const error = "Workflow child permit does not support model fallback.";
1854
+ return redactResultPrompt(withRunContext({
1855
+ index: options.index ?? 0,
1856
+ agent: agent.name,
1857
+ task,
1858
+ exitCode: 1,
1859
+ messages: [],
1860
+ usage: emptyUsage(),
1861
+ error,
1862
+ }, options.context));
1863
+ }
1784
1864
  try {
1785
1865
  for (const candidate of candidates) {
1786
1866
  const model = applyThinkingSuffix(candidate, options.thinkingOverride ?? agent.thinking, options.thinkingOverride !== undefined);
@@ -1914,6 +1994,7 @@ async function runSyncCompletionInner(
1914
1994
  usage: { ...result.usage },
1915
1995
  };
1916
1996
  modelAttempts.push(attempt);
1997
+ if (options.workflowChildPermitLaunch && !attemptSucceeded) break modelAttemptsLoop;
1917
1998
  // Preserve the legacy intercom handoff contract: once this logical run has
1918
1999
  // been handed to a supervisor, terminating that attempt must not launch a
1919
2000
  // startup retry or model fallback. Explicit user detach retains fallback.
@@ -1986,7 +2067,7 @@ async function runSyncCompletionInner(
1986
2067
  attempt.error = startupError;
1987
2068
  break modelAttemptsLoop;
1988
2069
  }
1989
- const retryableModelFailure = isRetryableModelFailure(result.error);
2070
+ const retryableModelFailure = isRetryableModelFailureAttempt({ error: result.error, messages: result.messages, toolCount: result.progressSummary?.toolCount });
1990
2071
  if (retryableModelFailure) recordRetryableModelFailure(result.model ?? candidate, result.error);
1991
2072
  if (isContextOverflow(result.error)) {
1992
2073
  result.contextOverflow = true;
@@ -27,6 +27,8 @@ function copyProgress(target: ForegroundChildControl, progress: AgentProgress |
27
27
  target.tokens = progress.tokens;
28
28
  target.inputTokens = progress.inputTokens;
29
29
  target.outputTokens = progress.outputTokens;
30
+ target.window = progress.window;
31
+ target.windowPeak = progress.windowPeak;
30
32
  target.model = progress.model;
31
33
  target.thinking = progress.thinking;
32
34
  target.toolCount = progress.toolCount;
@@ -45,6 +47,8 @@ function syncCurrentChild(control: ForegroundRunControl, child: ForegroundChildC
45
47
  control.tokens = child.tokens;
46
48
  control.inputTokens = child.inputTokens;
47
49
  control.outputTokens = child.outputTokens;
50
+ control.window = child.window;
51
+ control.windowPeak = child.windowPeak;
48
52
  control.model = child.model;
49
53
  control.thinking = child.thinking;
50
54
  control.toolCount = child.toolCount;
@@ -65,6 +69,8 @@ function clearCurrentChild(control: ForegroundRunControl): void {
65
69
  control.tokens = undefined;
66
70
  control.inputTokens = undefined;
67
71
  control.outputTokens = undefined;
72
+ control.window = undefined;
73
+ control.windowPeak = undefined;
68
74
  control.model = undefined;
69
75
  control.thinking = undefined;
70
76
  control.toolCount = undefined;
@@ -4,10 +4,12 @@ import type { ForegroundResumeChild, ForegroundResumeRun, SubagentState } from "
4
4
  import { DIRS } from "../../shared/types.ts";
5
5
  import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
6
6
  import { utf8Tail } from "../../shared/utf8.ts";
7
+ import { validateAcceptanceInput } from "../shared/acceptance.ts";
7
8
 
8
9
  export const MAX_REMEMBERED_FOREGROUND_RUNS = 50;
9
10
  const HISTORY_VERSION = 1;
10
11
  const MAX_INLINE_OUTPUT_BYTES = 64 * 1024;
12
+ const MAX_RESUME_CONTRACT_BYTES = 64 * 1024;
11
13
 
12
14
  interface ForegroundHistoryIndex {
13
15
  version: 1;
@@ -51,6 +53,7 @@ function compactChild(child: ForegroundResumeChild): ForegroundResumeChild {
51
53
  ...(child.transcriptPath ? { transcriptPath: child.transcriptPath } : {}),
52
54
  ...(child.transcriptError ? { transcriptError: child.transcriptError } : {}),
53
55
  ...(child.acceptance ? { acceptance: child.acceptance } : {}),
56
+ ...(child.resumeContract ? { resumeContract: child.resumeContract } : {}),
54
57
  ...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
55
58
  ...(child.extensionBindings ? { extensionBindings: child.extensionBindings } : {}),
56
59
  ...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
@@ -62,9 +65,26 @@ function isRestorableForegroundStatus(status: unknown): status is ForegroundResu
62
65
  return status === "completed" || status === "failed" || status === "paused" || status === "stopped";
63
66
  }
64
67
 
68
+ function isRestorableResumeContract(value: unknown): boolean {
69
+ if (value === undefined) return true;
70
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
71
+ try {
72
+ if (Buffer.byteLength(JSON.stringify(value), "utf8") > MAX_RESUME_CONTRACT_BYTES) return false;
73
+ } catch {
74
+ return false;
75
+ }
76
+ const contract = value as NonNullable<ForegroundResumeChild["resumeContract"]>;
77
+ if (Object.keys(contract).some((key) => !["outputSchema", "agentContract", "acceptance", "output", "outputMode"].includes(key))) return false;
78
+ if (contract.outputSchema !== undefined && (!contract.outputSchema || typeof contract.outputSchema !== "object" || Array.isArray(contract.outputSchema))) return false;
79
+ if (contract.agentContract !== undefined && (!contract.agentContract || typeof contract.agentContract !== "object" || Array.isArray(contract.agentContract) || contract.agentContract.version !== 1)) return false;
80
+ if (validateAcceptanceInput(contract.acceptance).length > 0) return false;
81
+ if (contract.output !== undefined && typeof contract.output !== "string" && typeof contract.output !== "boolean") return false;
82
+ return contract.outputMode === undefined || contract.outputMode === "inline" || contract.outputMode === "file-only";
83
+ }
84
+
65
85
  function compactRun(run: ForegroundResumeRun): ForegroundResumeRun | undefined {
66
86
  if (!run.sessionId) return undefined;
67
- if (run.children.length === 0 || !run.children.every((child) => isRestorableForegroundStatus(child.status))) return undefined;
87
+ if (run.children.length === 0 || !run.children.every((child) => isRestorableForegroundStatus(child.status) && isRestorableResumeContract(child.resumeContract))) return undefined;
68
88
  return {
69
89
  runId: run.runId,
70
90
  mode: run.mode,
@@ -102,6 +122,7 @@ function isRestorableRun(value: unknown): value is ForegroundResumeRun {
102
122
  && run.children.every((child) => Boolean(child && typeof child === "object" && !Array.isArray(child)
103
123
  && typeof (child as Partial<ForegroundResumeChild>).agent === "string"
104
124
  && typeof (child as Partial<ForegroundResumeChild>).index === "number"
125
+ && isRestorableResumeContract((child as Partial<ForegroundResumeChild>).resumeContract)
105
126
  && isRestorableForegroundStatus((child as Partial<ForegroundResumeChild>).status)));
106
127
  }
107
128