pi-subagents 0.66.0 → 0.68.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 (172) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +5 -4
  3. package/agents/evidence-auditor.md +34 -0
  4. package/agents/reviewer.md +3 -2
  5. package/docs/agents.md +43 -15
  6. package/docs/configuration.md +67 -23
  7. package/docs/extension-api.md +38 -19
  8. package/docs/missions.md +10 -2
  9. package/docs/models.md +11 -79
  10. package/docs/observability.md +22 -12
  11. package/docs/standalone-background.md +59 -0
  12. package/docs/tool-reference.md +33 -20
  13. package/docs/watchdog.md +39 -10
  14. package/docs/workflows.md +37 -13
  15. package/index.ts +5 -2
  16. package/inspector-runner.mjs +2 -2
  17. package/package.json +4 -2
  18. package/prompts/parallel-review.md +1 -1
  19. package/runner-peer-loader.mjs +24 -0
  20. package/runner-peer-preload.mjs +32 -0
  21. package/skills/pi-subagents/SKILL.md +32 -21
  22. package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
  23. package/skills/pi-subagents/references/execution-controls.md +11 -9
  24. package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
  25. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  26. package/skills/pi-subagents/references/prompting-and-roles.md +17 -13
  27. package/skills/pi-subagents/references/review-and-validation.md +3 -3
  28. package/src/agents/advertised-agent-prompt.ts +34 -3
  29. package/src/agents/agent-management.ts +57 -58
  30. package/src/agents/agent-serializer.ts +4 -3
  31. package/src/agents/agents.ts +190 -71
  32. package/src/agents/builtin-names.ts +1 -0
  33. package/src/agents/chain-serializer.ts +5 -0
  34. package/src/agents/runtime-agent-registry.ts +7 -6
  35. package/src/api/delegation.ts +4 -0
  36. package/src/api/preflight.ts +94 -59
  37. package/src/api/required-child-extensions.ts +6 -0
  38. package/src/api/shared-types.ts +2 -0
  39. package/src/extension/config.ts +10 -37
  40. package/src/extension/fanout-child.ts +66 -4
  41. package/src/extension/herdr-pi-bridge.ts +160 -0
  42. package/src/extension/index.ts +62 -39
  43. package/src/extension/public-execution.ts +7 -5
  44. package/src/extension/rpc.ts +4 -0
  45. package/src/extension/schemas.ts +81 -81
  46. package/src/extension/tool-description.ts +30 -82
  47. package/src/inspectors/actions.ts +148 -0
  48. package/src/inspectors/ghostty/actions.ts +74 -0
  49. package/src/inspectors/ghostty/plugin.ts +17 -0
  50. package/src/inspectors/herdr/actions.ts +99 -179
  51. package/src/inspectors/herdr/plugin.ts +20 -0
  52. package/src/inspectors/herdr/project-panes.ts +1 -1
  53. package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
  54. package/src/inspectors/plugins.ts +8 -0
  55. package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
  56. package/src/inspectors/types.ts +51 -0
  57. package/src/intercom/intercom-bridge.ts +50 -8
  58. package/src/intercom/native-supervisor-channel.ts +44 -31
  59. package/src/policy/authority.ts +4 -0
  60. package/src/profiles/profiles.ts +12 -6
  61. package/src/runs/background/active-async-capacity.ts +4 -0
  62. package/src/runs/background/active-run-index.ts +17 -1
  63. package/src/runs/background/async-execution.ts +348 -176
  64. package/src/runs/background/async-job-tracker.ts +8 -6
  65. package/src/runs/background/async-resume.ts +17 -12
  66. package/src/runs/background/async-status.ts +15 -4
  67. package/src/runs/background/auto-drain.ts +23 -10
  68. package/src/runs/background/binary-bootstrap.ts +38 -0
  69. package/src/runs/background/chain-append.ts +1 -1
  70. package/src/runs/background/chain-root-attachment.ts +14 -33
  71. package/src/runs/background/fleet-view.ts +30 -2
  72. package/src/runs/background/notify.ts +105 -7
  73. package/src/runs/background/owned-process-tree.ts +29 -2
  74. package/src/runs/background/result-files.ts +8 -4
  75. package/src/runs/background/result-watcher.ts +19 -2
  76. package/src/runs/background/run-child-session.ts +81 -33
  77. package/src/runs/background/run-status.ts +3 -0
  78. package/src/runs/background/runner-aliases.ts +12 -33
  79. package/src/runs/background/runner-child-launch.ts +6 -1
  80. package/src/runs/background/runner-child-sessions.ts +5 -4
  81. package/src/runs/background/runner-http-dispatcher.ts +119 -0
  82. package/src/runs/background/scheduled-runs.ts +51 -18
  83. package/src/runs/background/stale-run-reconciler.ts +35 -11
  84. package/src/runs/background/steering.ts +20 -2
  85. package/src/runs/background/subagent-runner.ts +441 -304
  86. package/src/runs/background/subagent-wait.ts +176 -28
  87. package/src/runs/background/wait-completions.ts +75 -27
  88. package/src/runs/background/wait-subscriptions.ts +9 -3
  89. package/src/runs/background/wait-tool.ts +5 -3
  90. package/src/runs/foreground/async-steering-action.ts +18 -7
  91. package/src/runs/foreground/async-stop-action.ts +93 -3
  92. package/src/runs/foreground/execution.ts +134 -248
  93. package/src/runs/foreground/foreground-history.ts +2 -1
  94. package/src/runs/foreground/prompt-audit.ts +3 -1
  95. package/src/runs/foreground/subagent-executor.ts +374 -178
  96. package/src/runs/foreground/workflow-detach-reconcile.ts +2 -0
  97. package/src/runs/foreground/workflow-foreground-steering.ts +2 -1
  98. package/src/runs/shared/acceptance.ts +38 -11
  99. package/src/runs/shared/async-status-projection.ts +127 -33
  100. package/src/runs/shared/capability-ceiling.ts +2 -0
  101. package/src/runs/shared/child-hooks.ts +25 -10
  102. package/src/runs/shared/child-launch-plan.ts +15 -3
  103. package/src/runs/shared/child-launch.ts +28 -5
  104. package/src/runs/shared/child-lifecycle.ts +6 -3
  105. package/src/runs/shared/child-runtime-config.ts +8 -1
  106. package/src/runs/shared/child-session.ts +127 -52
  107. package/src/runs/shared/child-tool-plan.ts +142 -11
  108. package/src/runs/shared/completion-guard.ts +5 -3
  109. package/src/runs/shared/dynamic-fanout.ts +2 -2
  110. package/src/runs/shared/effective-system-prompt.ts +33 -0
  111. package/src/runs/shared/external-cli-contract.ts +11 -1
  112. package/src/runs/shared/external-cli-preflight.ts +6 -2
  113. package/src/runs/shared/external-cli-runner.ts +9 -7
  114. package/src/runs/shared/herdr-connection.ts +134 -0
  115. package/src/runs/shared/herdr-external-adapters.ts +169 -0
  116. package/src/runs/shared/herdr-machine.ts +279 -0
  117. package/src/runs/shared/herdr-pi-protocol.ts +59 -0
  118. package/src/runs/shared/herdr-placed-run.ts +263 -0
  119. package/src/runs/shared/llm-intent-arbiter.ts +12 -3
  120. package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
  121. package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -235
  122. package/src/runs/shared/model-scope.ts +1 -1
  123. package/src/runs/shared/nested-events.ts +11 -2
  124. package/src/runs/shared/orca-progress-tabs.ts +1 -1
  125. package/src/runs/shared/parallel-utils.ts +7 -2
  126. package/src/runs/shared/pi-spawn.ts +10 -0
  127. package/src/runs/shared/subagent-prompt-runtime.ts +12 -4
  128. package/src/runs/shared/task-intent.ts +46 -13
  129. package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
  130. package/src/runs/shared/worktree-setup-command.ts +27 -4
  131. package/src/runs/shared/worktree.ts +45 -15
  132. package/src/shared/child-cache-retention.ts +43 -0
  133. package/src/shared/fork-context.ts +15 -72
  134. package/src/shared/launch-contract.ts +68 -8
  135. package/src/shared/opencode-session-headers.ts +30 -0
  136. package/src/shared/pruned-fork.ts +1 -1
  137. package/src/shared/required-child-extensions.ts +81 -0
  138. package/src/shared/settings.ts +5 -2
  139. package/src/shared/shortcuts.ts +0 -4
  140. package/src/shared/types.ts +74 -30
  141. package/src/slash/delegation-adapters.ts +3 -1
  142. package/src/slash/delegation-request.ts +14 -0
  143. package/src/slash/slash-commands.ts +2 -7
  144. package/src/slash/subagents-admin.ts +24 -13
  145. package/src/tui/fleet-status.ts +164 -19
  146. package/src/tui/fleet.ts +16 -14
  147. package/src/tui/render.ts +168 -37
  148. package/src/watchdog/child-status.ts +28 -28
  149. package/src/watchdog/lsp-diagnostics.ts +1 -1
  150. package/src/watchdog/model-selection.ts +21 -1
  151. package/src/watchdog/permission-arbiter.ts +3 -1
  152. package/src/watchdog/register-child.ts +10 -2
  153. package/src/watchdog/register-main.ts +39 -35
  154. package/src/watchdog/render.ts +1 -1
  155. package/src/watchdog/review.ts +123 -74
  156. package/src/watchdog/rules.ts +1 -1
  157. package/src/watchdog/runtime.ts +100 -27
  158. package/src/watchdog/scope.ts +1 -1
  159. package/src/watchdog/settings.ts +3 -0
  160. package/src/watchdog/tool-actions.ts +13 -12
  161. package/src/watchdog/turn-delta.ts +23 -0
  162. package/src/watchdog/types.ts +5 -3
  163. package/src/watchdog/warning-format.ts +1 -1
  164. package/src/workflows/scripted-workflow.ts +279 -10
  165. package/src/workflows/workflow-checklist.ts +2 -2
  166. package/src/workflows/workflow-receipt.ts +21 -3
  167. package/src/workflows/workflow-resources.ts +13 -2
  168. package/runner-server-preload.mjs +0 -13
  169. package/src/runs/shared/model-exclusions.ts +0 -374
  170. package/src/runs/shared/readonly-model-continuation.ts +0 -69
  171. package/src/runs/shared/readonly-session-evidence.ts +0 -307
  172. /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
@@ -7,6 +7,7 @@ export const BUILTIN_AGENT_NAMES = [
7
7
  "cursor-agent",
8
8
  "cursor-agent-writer",
9
9
  "delegate",
10
+ "evidence-auditor",
10
11
  "oracle",
11
12
  "researcher",
12
13
  "reviewer",
@@ -22,6 +22,10 @@ function parseStepBody(agent: string, sectionBody: string): ChainStepConfig {
22
22
  const key = keyValue.trim().toLowerCase();
23
23
  const rawValue = rawValueValue.trim();
24
24
 
25
+ if (key === "machine") {
26
+ if (rawValue) step.machine = rawValue;
27
+ continue;
28
+ }
25
29
  if (key === "output") {
26
30
  if (rawValue === "false") step.output = false;
27
31
  else if (rawValue) step.output = rawValue;
@@ -257,6 +261,7 @@ export function serializeChain(config: ChainConfig): string {
257
261
  for (let i = 0; i < config.steps.length; i++) {
258
262
  const step = config.steps[i]!;
259
263
  lines.push(`## ${step.agent}`);
264
+ if (step.machine) lines.push(`machine: ${step.machine}`);
260
265
  if (step.output === false) lines.push("output: false");
261
266
  else if (step.output) lines.push(`output: ${step.output}`);
262
267
  if (step.phase) lines.push(`phase: ${step.phase}`);
@@ -24,7 +24,6 @@ export interface RuntimeAgentDefinition {
24
24
  allowNestedSubagents?: boolean;
25
25
  mcpDirectTools?: readonly string[];
26
26
  model?: string;
27
- fallbackModels?: readonly string[];
28
27
  thinking?: string | false;
29
28
  systemPromptMode?: "append" | "replace";
30
29
  inheritProjectContext?: boolean;
@@ -37,6 +36,7 @@ export interface RuntimeAgentDefinition {
37
36
  defaultAcceptance?: AcceptanceInput;
38
37
  acceptanceRole?: AcceptanceRole;
39
38
  runner?: AgentRunnerConfig;
39
+ machine?: string;
40
40
  skills?: readonly string[];
41
41
  skillPath?: readonly string[];
42
42
  extensions?: readonly string[];
@@ -199,9 +199,9 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
199
199
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Runtime agent definition must be an object.");
200
200
  const definition = value as Record<string, unknown>;
201
201
  const supported = new Set([
202
- "description", "systemPrompt", "aliases", "tools", "excludeTools", "allowNestedSubagents", "mcpDirectTools", "model", "fallbackModels", "thinking",
202
+ "description", "systemPrompt", "aliases", "tools", "excludeTools", "allowNestedSubagents", "mcpDirectTools", "model", "thinking",
203
203
  "systemPromptMode", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "defaultContext", "defaultAsync", "defaultTimeoutMs",
204
- "defaultToolTimeoutMs", "defaultAcceptance", "acceptanceRole", "runner", "skills", "skillPath",
204
+ "defaultToolTimeoutMs", "defaultAcceptance", "acceptanceRole", "runner", "machine", "skills", "skillPath",
205
205
  "extensions", "subagentOnlyExtensions", "mutationTools", "output", "outputMode", "defaultReads", "defaultProgress", "interactive",
206
206
  "maxSubagentDepth", "completionGuard", "toolBudget", "permissions",
207
207
  ]);
@@ -223,7 +223,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
223
223
  const allowNestedSubagents = validateBoolean(definition.allowNestedSubagents, "Runtime agent definition allowNestedSubagents");
224
224
  const mcpDirectTools = validateStringList(definition.mcpDirectTools, "Runtime agent definition mcpDirectTools");
225
225
  const model = validateOptionalString(definition.model, "Runtime agent definition model");
226
- const fallbackModels = validateStringList(definition.fallbackModels, "Runtime agent definition fallbackModels");
226
+ if ((definition as Record<string, unknown>).fallbackModels !== undefined) throw new Error("Runtime agent definition fallbackModels was removed; configure one model instead.");
227
227
  const inheritProjectContext = validateBoolean(definition.inheritProjectContext, "Runtime agent definition inheritProjectContext");
228
228
  const inheritGlobalContext = validateBoolean(definition.inheritGlobalContext, "Runtime agent definition inheritGlobalContext");
229
229
  const inheritSkills = validateBoolean(definition.inheritSkills, "Runtime agent definition inheritSkills");
@@ -237,6 +237,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
237
237
  const extensions = validateStringList(definition.extensions, "Runtime agent definition extensions");
238
238
  const subagentOnlyExtensions = validateStringList(definition.subagentOnlyExtensions, "Runtime agent definition subagentOnlyExtensions");
239
239
  const mutationTools = validateStringList(definition.mutationTools, "Runtime agent definition mutationTools");
240
+ const machine = validateOptionalString(definition.machine, "Runtime agent definition machine");
240
241
  const output = validateOptionalString(definition.output, "Runtime agent definition output");
241
242
  const defaultReads = validateStringList(definition.defaultReads, "Runtime agent definition defaultReads");
242
243
  const defaultProgress = validateBoolean(definition.defaultProgress, "Runtime agent definition defaultProgress");
@@ -254,7 +255,6 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
254
255
  ...(allowNestedSubagents !== undefined ? { allowNestedSubagents } : {}),
255
256
  ...(mcpDirectTools ? { mcpDirectTools } : {}),
256
257
  ...(model ? { model } : {}),
257
- ...(fallbackModels ? { fallbackModels } : {}),
258
258
  ...(thinking !== undefined ? { thinking: thinking as string | false } : {}),
259
259
  ...(systemPromptMode !== undefined ? { systemPromptMode: systemPromptMode as "append" | "replace" } : {}),
260
260
  ...(inheritProjectContext !== undefined ? { inheritProjectContext } : {}),
@@ -272,6 +272,7 @@ function validateDefinition(value: unknown): RuntimeAgentDefinition {
272
272
  ...(extensions ? { extensions } : {}),
273
273
  ...(subagentOnlyExtensions ? { subagentOnlyExtensions } : {}),
274
274
  ...(mutationTools ? { mutationTools } : {}),
275
+ ...(machine ? { machine } : {}),
275
276
  ...(output ? { output } : {}),
276
277
  ...(outputMode !== undefined ? { outputMode: outputMode as OutputMode } : {}),
277
278
  ...(defaultReads ? { defaultReads } : {}),
@@ -334,7 +335,6 @@ function toAgentConfig(name: string, definition: RuntimeAgentDefinition): AgentC
334
335
  ...(definition.allowNestedSubagents !== undefined ? { allowNestedSubagents: definition.allowNestedSubagents } : {}),
335
336
  ...(definition.mcpDirectTools !== undefined ? { mcpDirectTools: [...definition.mcpDirectTools] } : {}),
336
337
  ...(definition.model !== undefined ? { model: definition.model } : {}),
337
- ...(definition.fallbackModels !== undefined ? { fallbackModels: [...definition.fallbackModels] } : {}),
338
338
  ...(definition.thinking !== undefined ? { thinking: definition.thinking } : {}),
339
339
  systemPromptMode: definition.systemPromptMode ?? defaultSystemPromptMode(name),
340
340
  inheritProjectContext: definition.inheritProjectContext ?? defaultInheritProjectContext(name),
@@ -354,6 +354,7 @@ function toAgentConfig(name: string, definition: RuntimeAgentDefinition): AgentC
354
354
  ...(definition.extensions !== undefined ? { extensions: [...definition.extensions] } : {}),
355
355
  ...(definition.subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions: [...definition.subagentOnlyExtensions] } : {}),
356
356
  ...(definition.mutationTools !== undefined ? { mutationTools: [...definition.mutationTools] } : {}),
357
+ ...(definition.machine !== undefined ? { machine: definition.machine } : {}),
357
358
  ...(definition.output !== undefined ? { output: definition.output } : {}),
358
359
  ...(definition.outputMode !== undefined ? { outputMode: definition.outputMode } : {}),
359
360
  ...(definition.defaultReads !== undefined ? { defaultReads: [...definition.defaultReads] } : {}),
@@ -1,3 +1,5 @@
1
+ import type { IntercomBridgeConfig } from "../shared/types.ts";
2
+
1
3
  // This is the established extension-to-extension transport. The structured
2
4
  // delegation API intentionally reuses it instead of adding a second event
3
5
  // protocol. Unstructured legacy direct payloads are rejected.
@@ -35,6 +37,8 @@ export interface SubagentDelegationRequest {
35
37
  toolBudget?: SubagentDelegationToolBudget;
36
38
  skill?: string | string[] | boolean;
37
39
  artifacts?: boolean;
40
+ /** Per-launch bridge config; replaces the global `intercomBridge` config. Pass the same value to preflight to compare digests. */
41
+ intercomBridge?: IntercomBridgeConfig;
38
42
  result: SubagentDelegationResultRequest;
39
43
  }
40
44
 
@@ -3,30 +3,37 @@ import * as path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { discoverAgentSnapshot, findBlockingAgentDiagnostic, formatUnknownAgentError, resolveAgentName, unknownAgentDiagnosticContext, type AgentConfig, type AgentDiscoveryAllResult, type AgentScope, type AgentSource } from "../agents/agents.ts";
5
5
  import { resolveExecutionAgentScope } from "../agents/agent-scope.ts";
6
- import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../agents/skills.ts";
7
- import { buildAgentMemoryInjection } from "../agents/agent-memory.ts";
8
- import { buildModelCandidates, inheritsParentModel, resolveEffectiveSubagentModel, resolveModelOrigin, type AvailableModelInfo, type ParentModel } from "../runs/shared/model-fallback.ts";
6
+ import { normalizeSkillInput, resolveSkillsWithFallback } from "../agents/skills.ts";
7
+ import { inheritsParentModel, resolveEffectiveSubagentModel, resolveModelOrigin, resolveModelSelection, type AvailableModelInfo, type ParentModel } from "../runs/shared/model-resolution.ts";
9
8
  import { resolveModelScopesForAgent } from "../runs/shared/model-scope.ts";
10
9
  import { applyThinkingSuffix, resolvePiLaunchToolPlan, type PiLaunchToolPlan } from "../runs/shared/child-tool-plan.ts";
11
- import { injectOutputPathSystemPrompt, normalizeSingleOutputOverride, resolveSingleOutputPath } from "../runs/shared/single-output.ts";
10
+ import { buildEffectiveSystemPrompt } from "../runs/shared/effective-system-prompt.ts";
11
+ import { normalizeSingleOutputOverride, resolveSingleOutputPath } from "../runs/shared/single-output.ts";
12
12
  import { getArtifactPaths, getArtifactsDir } from "../shared/artifacts.ts";
13
13
  import { resolveEffectiveThinking } from "../shared/model-info.ts";
14
14
  import { assertThinkingWithinCeiling, intersectThinkingCeilings, type ThinkingLevel } from "../shared/thinking-ceiling.ts";
15
- import { SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, type ArtifactDirPreference, type ArtifactPaths, type JsonSchemaObject, type OutputMode } from "../shared/types.ts";
15
+ import { SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, type ArtifactDirPreference, type ArtifactPaths, type IntercomBridgeConfig, type IntercomBridgeMode, type JsonSchemaObject, type OutputMode } from "../shared/types.ts";
16
16
  import { capabilityCeilingAgentRestrictionMessage, intersectSubagentCapabilityCeilings, type ResolvedSubagentCapabilityCeiling, type SubagentCapabilityAudit } from "../runs/shared/capability-ceiling.ts";
17
17
  import { resolvePermissionRules } from "../runs/shared/permissions.ts";
18
18
  import type { ResolvedMcpDirectToolSelection } from "../runs/shared/mcp-direct-tool-allowlist.ts";
19
19
  import { resolveStepBehavior } from "../shared/settings.ts";
20
20
  import { canPreferForkFromSnapshot, resolveSubagentLaunchContext } from "../shared/fork-context.ts";
21
21
  import { loadConfig } from "../extension/config.ts";
22
- import { agentDefinitionDigest, AGENT_DEFINITION_PROJECTION_VERSION, launchBindingDigest, stableJsonDigest } from "../shared/launch-contract.ts";
22
+ import { applyIntercomBridgeToAgent, resolveIntercomBridge, validateIntercomBridgeConfig } from "../intercom/intercom-bridge.ts";
23
+ import { AGENT_DEFINITION_PROJECTION_VERSION, resolveLaunchBinding, stableJsonDigest } from "../shared/launch-contract.ts";
23
24
  import { DIRS, TEMP_ROOT_DIR } from "../shared/types.ts";
24
25
  import { processTerminalCandidatePath, processTerminalPath } from "../runs/background/process-terminal.ts";
25
26
  import { resultFilePath } from "../runs/background/result-files.ts";
26
27
  import { nestedResultsPath } from "../runs/shared/nested-events.ts";
27
28
  import { normalizeExtensionBindings, type ExtensionBindings } from "../runs/shared/extension-bindings.ts";
29
+ import { resolveRequiredChildExtensions } from "../shared/required-child-extensions.ts";
28
30
 
29
- export const SUBAGENT_LAUNCH_CONTRACT_VERSION = 2 as const;
31
+ // v3: the contract reports the resolved Intercom bridge state and binds its
32
+ // prompt and tools into launchContractDigest, matching execution (#2127).
33
+ export const SUBAGENT_LAUNCH_CONTRACT_VERSION = 3 as const;
34
+
35
+ /** Stands in for the parent session target when the host does not supply one; only custom templates that name the session read it. */
36
+ const PREFLIGHT_ORCHESTRATOR_TARGET = "preflight";
30
37
 
31
38
  export type SubagentLaunchContractReasonCode =
32
39
  | "missing_agent"
@@ -38,7 +45,8 @@ export type SubagentLaunchContractReasonCode =
38
45
  | "unsupported_mode"
39
46
  | "restricted_agent"
40
47
  | "thinking_ceiling"
41
- | "invalid_extension_bindings";
48
+ | "invalid_extension_bindings"
49
+ | "invalid_intercom_bridge";
42
50
 
43
51
  export type SubagentLaunchContractDiagnosticCode = SubagentLaunchContractReasonCode | "host_required" | "snapshot_warning" | "workspace_scope_authority";
44
52
 
@@ -65,11 +73,13 @@ export interface SubagentLaunchContractInput {
65
73
  skill?: string | string[] | boolean;
66
74
  output?: string | boolean;
67
75
  outputMode?: OutputMode;
68
- outputSchema?: JsonSchemaObject;
76
+ outputSchema?: JsonSchemaObject | false;
69
77
  extensionBindings?: ExtensionBindings;
70
78
  artifacts?: boolean;
71
79
  artifactDir?: ArtifactDirPreference;
72
80
  parentSessionFile?: string | null;
81
+ /** Parent session whose host-required child extension snapshot is preflighted. */
82
+ parentSessionId?: string;
73
83
  /** Current parent leaf required before an implicit `defaultContext: fork` stays `fork`. */
74
84
  parentLeafId?: string | null;
75
85
  sessionRoot?: string;
@@ -80,8 +90,23 @@ export interface SubagentLaunchContractInput {
80
90
  nestedRootRunId?: string;
81
91
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
82
92
  inheritedCapabilityCeiling?: ResolvedSubagentCapabilityCeiling;
93
+ /** Builtin tool names the host runtime provides; used to intersect agent-declared tools. */
94
+ hostAvailableBuiltins?: readonly string[];
95
+ /** Per-launch bridge config; replaces the global `intercomBridge` config exactly as the tool and delegation overrides do. */
96
+ intercomBridge?: IntercomBridgeConfig;
97
+ /**
98
+ * Supervisor session target the host will hand to the child. Only a custom
99
+ * bridge instruction file that names the session needs it; the default
100
+ * template is session-independent.
101
+ */
102
+ orchestratorTarget?: string;
83
103
  }
84
104
 
105
+ /** Bridge activation before tool capability ceilings are applied. */
106
+ export type SubagentLaunchContractIntercomBridge =
107
+ | { active: true; mode: Exclude<IntercomBridgeMode, "off"> }
108
+ | { active: false; mode: IntercomBridgeMode };
109
+
85
110
  export interface SubagentLaunchContractAgentCandidate {
86
111
  name: string;
87
112
  localName?: string;
@@ -122,6 +147,7 @@ export interface SubagentLaunchContractTools {
122
147
  toolExtensionPaths: string[];
123
148
  runtimeExtensions: string[];
124
149
  configuredExtensions: string[];
150
+ requiredExtensionIds: string[];
125
151
  extensionArgs: string[];
126
152
  disableAmbientExtensions: boolean;
127
153
  fanoutAuthorized: boolean;
@@ -153,7 +179,6 @@ export interface SubagentLaunchContract {
153
179
  agent: SubagentLaunchContractAgent;
154
180
  context: "fresh" | "fork";
155
181
  model?: string;
156
- modelCandidates: string[];
157
182
  thinking?: string;
158
183
  thinkingCeiling?: ThinkingLevel;
159
184
  systemPromptMode: AgentConfig["systemPromptMode"];
@@ -162,6 +187,7 @@ export interface SubagentLaunchContract {
162
187
  inheritSkills: boolean;
163
188
  skills: SubagentLaunchContractSkills;
164
189
  tools: SubagentLaunchContractTools;
190
+ intercomBridge: SubagentLaunchContractIntercomBridge;
165
191
  roots: SubagentLaunchContractRoots;
166
192
  protocol: {
167
193
  lifecycleArtifactVersion: number;
@@ -257,6 +283,15 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
257
283
  if (input.artifactDir !== undefined && input.artifactDir !== "project" && input.artifactDir !== "session" && input.artifactDir !== "temp") {
258
284
  return { ok: false, code: "invalid_artifact_dir", message: `Unsupported artifactDir '${String(input.artifactDir)}'; expected 'project', 'session', or 'temp'.`, diagnostics };
259
285
  }
286
+ const bridgeOverride = input.intercomBridge === undefined ? undefined : validateIntercomBridgeConfig({ value: input.intercomBridge, label: "intercomBridge" });
287
+ if (bridgeOverride && !bridgeOverride.ok) {
288
+ return { ok: false, code: "invalid_intercom_bridge", message: bridgeOverride.error, diagnostics };
289
+ }
290
+ // Execution always derives a non-empty target, so an empty one here would
291
+ // silently deactivate the bridge and break parity instead of proving it.
292
+ if (input.orchestratorTarget !== undefined && (typeof input.orchestratorTarget !== "string" || !input.orchestratorTarget.trim())) {
293
+ return { ok: false, code: "invalid_intercom_bridge", message: "orchestratorTarget must be a non-empty string when provided.", diagnostics };
294
+ }
260
295
  const scope = resolveExecutionAgentScope(input.agentScope);
261
296
  const parentProvider = input.preferredProvider ?? input.parentModel?.provider;
262
297
  const discovery = discoverAgentSnapshot(effectiveCwd, scope, parentProvider, { includeChains: false });
@@ -276,20 +311,32 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
276
311
  if (!resolvedAgent.agent) {
277
312
  return { ok: false, code: "missing_agent", message: formatUnknownAgentError(input.agent, unknownAgentDiagnosticContext(discovered)), diagnostics };
278
313
  }
279
- const agent = resolvedAgent.agent;
314
+ const definitionAgent = resolvedAgent.agent;
280
315
  let extensionBindings: ExtensionBindings | undefined;
281
316
  try {
282
317
  extensionBindings = normalizeExtensionBindings(input.extensionBindings)?.value;
283
318
  } catch (error) {
284
319
  return { ok: false, code: "invalid_extension_bindings", message: error instanceof Error ? error.message : String(error), diagnostics };
285
320
  }
286
- if (extensionBindings !== undefined && (agent.runner?.type === "external-cli" || agent.runner?.type === "external-job")) {
287
- return { ok: false, code: "unsupported_mode", message: `extensionBindings is not supported for runner.type='${agent.runner.type}'.`, diagnostics };
321
+ if (extensionBindings !== undefined && (definitionAgent.runner?.type === "external-cli" || definitionAgent.runner?.type === "external-job")) {
322
+ return { ok: false, code: "unsupported_mode", message: `extensionBindings is not supported for runner.type='${definitionAgent.runner.type}'.`, diagnostics };
288
323
  }
289
- const context = resolveLaunchContractContext(input, agent);
324
+ const context = resolveLaunchContractContext(input, definitionAgent);
290
325
  if (context === "fork") {
291
- diagnostics.push({ code: "host_required", severity: "host-required", message: "Exact fork session branching and fork-thinking downgrade checks require Pi host session and model-registry snapshots." });
326
+ diagnostics.push({ code: "host_required", severity: "host-required", message: "Exact fork session branching requires Pi host session snapshots." });
327
+ }
328
+ // Execution rewrites the discovered agent through the bridge before any
329
+ // other launch resolution, so preflight must hash the same rewritten agent.
330
+ const bridge = resolveIntercomBridge({
331
+ config: loadConfig().intercomBridge,
332
+ ...(bridgeOverride ? { override: bridgeOverride.value } : {}),
333
+ context,
334
+ orchestratorTarget: input.orchestratorTarget ?? PREFLIGHT_ORCHESTRATOR_TARGET,
335
+ });
336
+ if (bridge.active && bridge.interpolatesOrchestratorTarget && input.orchestratorTarget === undefined) {
337
+ diagnostics.push({ code: "host_required", severity: "host-required", message: "The intercomBridge instruction file names the supervisor session; supply orchestratorTarget to bind the exact child prompt." });
292
338
  }
339
+ const agent = applyIntercomBridgeToAgent(definitionAgent, bridge);
293
340
  const effectiveCapabilityCeiling = intersectSubagentCapabilityCeilings(input.capabilityCeiling, input.inheritedCapabilityCeiling);
294
341
  const restrictionMessage = capabilityCeilingAgentRestrictionMessage(agent.name, effectiveCapabilityCeiling);
295
342
  if (restrictionMessage) return { ok: false, code: "restricted_agent", message: restrictionMessage, diagnostics };
@@ -301,6 +348,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
301
348
  ...(input.outputMode !== undefined ? { outputMode: input.outputMode } : {}),
302
349
  ...(skillInput !== undefined ? { skills: skillInput } : {}),
303
350
  ...(input.model !== undefined ? { model: input.model } : {}),
351
+ ...(input.outputSchema !== undefined ? { outputSchema: input.outputSchema } : {}),
304
352
  });
305
353
  const requestedSkills = behavior.skills === false ? [] : behavior.skills;
306
354
  const resolvedSkills = resolveSkillsWithFallback(
@@ -316,6 +364,9 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
316
364
  if (resolvedSkills.missing.length > 0) diagnostics.push({ code: "missing_skill", severity: "error", message: `Missing skills: ${resolvedSkills.missing.join(", ")}` });
317
365
 
318
366
  const externalRunner = agent.runner?.type === "external-cli" || agent.runner?.type === "external-job";
367
+ if (externalRunner && behavior.outputSchema) {
368
+ return { ok: false, code: "unsupported_mode", message: `Agent '${agent.name}' uses runner.type='${agent.runner?.type}' and does not support: structured output.`, diagnostics };
369
+ }
319
370
  const availableModels = normalizeAvailableModels(input.availableModels);
320
371
  const preferredProvider = agent.modelProvider ?? input.preferredProvider ?? input.parentModel?.provider;
321
372
  const modelScopes = resolveModelScopesForAgent(discovered.modelScope, agent.name, input.parentModel);
@@ -332,19 +383,14 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
332
383
  input.thinkingCeiling,
333
384
  input.inheritedThinkingCeiling,
334
385
  );
335
- const model = externalRunner ? undefined : applyThinkingSuffix(primaryModel, effectiveThinkingConfig, input.thinking !== undefined);
336
- const modelCandidates = externalRunner
337
- ? []
338
- : buildModelCandidates(primaryModel, agent.fallbackModels, availableModels, preferredProvider, {
386
+ const model = externalRunner ? undefined : applyThinkingSuffix(resolveModelSelection(primaryModel, availableModels, preferredProvider, {
339
387
  scope: modelScopes,
340
388
  primaryModelFromParent: modelOrigin === "inherited" || inheritsParentModel(input.model, agent.model, input.parentModel),
341
389
  origin: modelOrigin,
342
- })
343
- .map((candidate) => applyThinkingSuffix(candidate, effectiveThinkingConfig, input.thinking !== undefined) ?? candidate);
390
+ }).model, effectiveThinkingConfig, input.thinking !== undefined);
344
391
  if (!externalRunner) {
345
392
  try {
346
393
  assertThinkingWithinCeiling({ model, configThinking: effectiveThinkingConfig, ceiling: thinkingCeiling, agent: agent.name, runId });
347
- for (const candidate of modelCandidates) assertThinkingWithinCeiling({ model: candidate, configThinking: effectiveThinkingConfig, ceiling: thinkingCeiling, agent: agent.name, runId });
348
394
  } catch (error) {
349
395
  const message = error instanceof Error ? error.message : String(error);
350
396
  diagnostics.push({ code: "thinking_ceiling", severity: "error", message });
@@ -354,6 +400,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
354
400
  let toolPlan: PiLaunchToolPlan;
355
401
  const permissionRules = resolvePermissionRules(loadConfig().permissions, agent.permissions);
356
402
  const fast = input.fast ?? agent.fast;
403
+ const requiredExtensions = externalRunner ? [] : resolveRequiredChildExtensions(input.parentSessionId);
357
404
  try {
358
405
  toolPlan = resolvePiLaunchToolPlan({
359
406
  tools: agent.tools,
@@ -361,16 +408,17 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
361
408
  allowNestedSubagents: agent.allowNestedSubagents,
362
409
  extensions: agent.extensions,
363
410
  subagentOnlyExtensions: agent.subagentOnlyExtensions,
411
+ requiredExtensions,
364
412
  mcpDirectTools: agent.mcpDirectTools,
365
413
  cwd: effectiveCwd,
366
414
  requireReadTool: resolvedSkills.resolved.length > 0,
367
- structuredOutput: Boolean(input.outputSchema),
415
+ structuredOutput: Boolean(behavior.outputSchema),
368
416
  fast,
369
417
  model,
370
- modelCandidates,
371
418
  capabilityCeiling: effectiveCapabilityCeiling,
372
419
  agentName: agent.name,
373
420
  permissionRules,
421
+ hostAvailableBuiltins: input.hostAvailableBuiltins,
374
422
  });
375
423
  } catch (error) {
376
424
  const message = error instanceof Error ? error.message : String(error);
@@ -399,17 +447,23 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
399
447
  if (resolvedSkills.missing.length > 0) {
400
448
  return { ok: false, code: "missing_skill", message: `Missing skills: ${resolvedSkills.missing.join(", ")}`, diagnostics };
401
449
  }
402
- let effectiveSystemPrompt = agent.systemPrompt?.trim() ?? "";
403
- if (resolvedSkills.resolved.length > 0) {
404
- const skillInjection = buildSkillInjection(resolvedSkills.resolved);
405
- effectiveSystemPrompt = effectiveSystemPrompt ? `${effectiveSystemPrompt}\n\n${skillInjection}` : skillInjection;
406
- }
407
- const memoryInjection = buildAgentMemoryInjection(agent, effectiveCwd);
408
- if (memoryInjection) effectiveSystemPrompt = effectiveSystemPrompt ? `${effectiveSystemPrompt}\n\n${memoryInjection}` : memoryInjection;
409
- effectiveSystemPrompt = injectOutputPathSystemPrompt(effectiveSystemPrompt, outputPath, agent);
450
+ const effectiveThinking = resolveEffectiveThinking(model, effectiveThinkingConfig);
451
+ const binding = resolveLaunchBinding({
452
+ agent,
453
+ task: input.task ?? "",
454
+ model,
455
+ ...(fast !== undefined ? { fast } : {}),
456
+ ...(effectiveThinking ? { thinking: effectiveThinking } : {}),
457
+ systemPrompt: buildEffectiveSystemPrompt({ agent, resolvedSkills: resolvedSkills.resolved, cwd: effectiveCwd, ...(outputPath ? { outputPath } : {}) }),
458
+ skills: requestedSkills,
459
+ toolPlan,
460
+ ...(outputPath ? { outputPath } : {}),
461
+ outputMode: behavior.outputMode,
462
+ ...(behavior.outputSchema ? { structuredOutputSchema: behavior.outputSchema } : {}),
463
+ ...(extensionBindings ? { extensionBindings } : {}),
464
+ });
410
465
  const candidates = candidateList(input.agent, agent, discovery.all);
411
466
  const shadowedCandidates = candidates.filter((candidate) => !candidate.selected);
412
- const definitionDigest = agentDefinitionDigest(agent);
413
467
  const contractBase: Omit<SubagentLaunchContract, "digest"> = {
414
468
  version: SUBAGENT_LAUNCH_CONTRACT_VERSION,
415
469
  runId,
@@ -420,13 +474,12 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
420
474
  source: agent.source,
421
475
  filePath: agent.filePath,
422
476
  definitionProjectionVersion: AGENT_DEFINITION_PROJECTION_VERSION,
423
- definitionDigest,
477
+ definitionDigest: binding.definitionDigest,
424
478
  shadowedCandidates,
425
479
  },
426
480
  context,
427
481
  ...(model ? { model } : {}),
428
- modelCandidates,
429
- ...(resolveEffectiveThinking(model, effectiveThinkingConfig) ? { thinking: resolveEffectiveThinking(model, effectiveThinkingConfig) } : {}),
482
+ ...(effectiveThinking ? { thinking: effectiveThinking } : {}),
430
483
  ...(thinkingCeiling ? { thinkingCeiling } : {}),
431
484
  systemPromptMode: agent.systemPromptMode,
432
485
  inheritProjectContext: agent.inheritProjectContext,
@@ -450,12 +503,15 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
450
503
  toolExtensionPaths: toolPlan.toolExtensionPaths,
451
504
  runtimeExtensions: toolPlan.runtimeExtensions,
452
505
  configuredExtensions: toolPlan.configuredExtensions,
453
- extensionArgs: toolPlan.extensionArgs,
506
+ requiredExtensionIds: toolPlan.requiredExtensions.map(({ id }) => id),
507
+ // Required paths are private launch authority; preflight exposes their safe IDs above.
508
+ extensionArgs: toolPlan.extensionArgs.filter((extensionPath) => !requiredExtensions.some(({ path }) => path === extensionPath)),
454
509
  disableAmbientExtensions: toolPlan.disableAmbientExtensions,
455
510
  fanoutAuthorized: toolPlan.fanoutAuthorized,
456
511
  ...(toolPlan.capabilityCeiling ? { capabilityCeiling: toolPlan.capabilityCeiling } : {}),
457
512
  ...(toolPlan.capabilityAudit ? { capabilityAudit: toolPlan.capabilityAudit } : {}),
458
513
  },
514
+ intercomBridge: bridge.active && bridge.mode !== "off" ? { active: true, mode: bridge.mode } : { active: false, mode: bridge.mode },
459
515
  roots: {
460
516
  cwd: effectiveCwd,
461
517
  ...(sessionRoot ? { sessionRoot } : {}),
@@ -477,28 +533,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
477
533
  packageVersion: packageVersion(),
478
534
  },
479
535
  diagnostics,
480
- launchContractDigest: launchBindingDigest({
481
- task: input.task ?? "",
482
- definitionDigest,
483
- ...(model ? { model } : {}),
484
- modelCandidates,
485
- ...(fast !== undefined ? { fast } : {}),
486
- ...(resolveEffectiveThinking(model, effectiveThinkingConfig) ? { thinking: resolveEffectiveThinking(model, effectiveThinkingConfig) } : {}),
487
- systemPrompt: effectiveSystemPrompt,
488
- systemPromptMode: agent.systemPromptMode,
489
- inheritProjectContext: agent.inheritProjectContext,
490
- inheritGlobalContext: agent.inheritGlobalContext,
491
- inheritSkills: agent.inheritSkills,
492
- skills: requestedSkills,
493
- tools: toolPlan.effectiveToolAllowlist,
494
- ...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
495
- extensions: toolPlan.extensionArgs,
496
- mcpDirectTools: toolPlan.effectiveMcpTools,
497
- ...(outputPath ? { outputPath } : {}),
498
- outputMode: behavior.outputMode,
499
- ...(input.outputSchema ? { structuredOutputSchema: input.outputSchema } : {}),
500
- ...(extensionBindings ? { extensionBindings } : {}),
501
- }),
536
+ launchContractDigest: binding.launchContractDigest,
502
537
  };
503
538
  return { ok: true, contract: { ...contractBase, digest: digestContract(contractBase) } };
504
539
  }
@@ -0,0 +1,6 @@
1
+ export {
2
+ registerRequiredChildExtensions,
3
+ type RegisterRequiredChildExtensionsInput,
4
+ type RequiredChildExtension,
5
+ type RequiredChildExtensionRegistration,
6
+ } from "../shared/required-child-extensions.ts";
@@ -13,6 +13,8 @@ export {
13
13
  type ExecutionProjection,
14
14
  type ExternalJobRunnerStatus,
15
15
  type ExternalJobStatus,
16
+ type IntercomBridgeConfig,
17
+ type IntercomBridgeMode,
16
18
  type JsonSchemaObject,
17
19
  type OutputMode,
18
20
  type ReviewProjection,
@@ -6,7 +6,6 @@ import { FLEET_KEYBINDING_ACTIONS, type ArtifactDirPreference, type ExtensionCon
6
6
  import { validateMissionStoreConfig } from "../missions/store.ts";
7
7
  import { validateAuthorityPolicy } from "../policy/authority.ts";
8
8
  import { getAgentDir } from "../shared/utils.ts";
9
- import { DEFAULT_MODEL_EXCLUSION_TTL_MS, MAX_MODEL_EXCLUSION_TTL_MS, setDefaultTTL } from "../runs/shared/model-exclusions.ts";
10
9
  import { validatePermissionConfig } from "../runs/shared/permissions.ts";
11
10
  import { MAX_ABANDONED_SLOT_RELEASE_AFTER_MS, MIN_ABANDONED_SLOT_RELEASE_AFTER_MS } from "../runs/background/active-async-capacity.ts";
12
11
  import { normalizeWorktreeBranchPrefix } from "../runs/shared/worktree.ts";
@@ -97,17 +96,6 @@ function validateCapacityConfig(value: unknown): void {
97
96
  }
98
97
  }
99
98
 
100
- /** Validate the user-controlled TTL policy before it reaches the exclusion store. */
101
- // TEST:test/unit/pi-coding-agent-dir.test.ts[loads and applies model exclusion TTL config]
102
- function validateModelExclusionsConfig(value: unknown): void {
103
- if (value === undefined) return;
104
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("config.modelExclusions must be a JSON object");
105
- const defaultTtlMs = (value as Record<string, unknown>).defaultTtlMs;
106
- if (defaultTtlMs !== undefined && (typeof defaultTtlMs !== "number" || !Number.isFinite(defaultTtlMs) || defaultTtlMs <= 0 || defaultTtlMs > MAX_MODEL_EXCLUSION_TTL_MS)) {
107
- throw new Error(`config.modelExclusions.defaultTtlMs must be a finite positive number no greater than ${MAX_MODEL_EXCLUSION_TTL_MS}`);
108
- }
109
- }
110
-
111
99
  function validateOrcaProgressTabsConfig(value: unknown): void {
112
100
  if (value === undefined) return;
113
101
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("config.orcaProgressTabs must be a JSON object");
@@ -154,6 +142,13 @@ function validateConfig(config: Record<string, unknown>): void {
154
142
  throw new Error('config.defaultSubagentContext must be "fresh" or "fork"');
155
143
  }
156
144
  validateForkContextConfig(config.forkContext);
145
+ if (config.checkpointBeforeDeadlineMs !== undefined
146
+ && (typeof config.checkpointBeforeDeadlineMs !== "number"
147
+ || !Number.isInteger(config.checkpointBeforeDeadlineMs)
148
+ || config.checkpointBeforeDeadlineMs <= 0
149
+ || config.checkpointBeforeDeadlineMs > 2_147_483_647)) {
150
+ throw new Error("config.checkpointBeforeDeadlineMs must be a positive integer no larger than 2147483647");
151
+ }
157
152
  if (config.foregroundDetachShortcut !== undefined
158
153
  && (typeof config.foregroundDetachShortcut !== "string" || !isValidKeyId(config.foregroundDetachShortcut))) {
159
154
  throw new Error("config.foregroundDetachShortcut must be a valid keybinding string such as \"ctrl+b\"");
@@ -177,7 +172,7 @@ function validateConfig(config: Record<string, unknown>): void {
177
172
  validateFleetKeybindingsConfig(config.fleetKeybindings);
178
173
  validateArtifactConfig(config.artifactConfig);
179
174
  validateCapacityConfig(config.capacity);
180
- validateModelExclusionsConfig(config.modelExclusions);
175
+ if (config.modelExclusions !== undefined) throw new Error("config.modelExclusions was removed; model failures are no longer persisted or used for automatic switching");
181
176
  validateModelResponseAliases(config.modelResponseAliases);
182
177
  validateMainWindowRendererConfig(config.mainWindowRenderer);
183
178
  validateOrcaProgressTabsConfig(config.orcaProgressTabs);
@@ -210,28 +205,6 @@ export function updateConfig(updater: (config: ExtensionConfig) => ExtensionConf
210
205
  return next;
211
206
  }
212
207
 
213
- /**
214
- * Resolve the default TTL that the process-wide exclusion store should use.
215
- *
216
- * @param config Extension configuration after validation.
217
- * @returns The configured TTL in milliseconds, or the built-in 24-hour default.
218
- */
219
- // TEST:test/unit/pi-coding-agent-dir.test.ts[loads and applies model exclusion TTL config]
220
- export function resolveModelExclusionTTL(config: Pick<ExtensionConfig, "modelExclusions">): number {
221
- return config.modelExclusions?.defaultTtlMs ?? DEFAULT_MODEL_EXCLUSION_TTL_MS;
222
- }
223
-
224
- /**
225
- * Apply the configured exclusion policy to the process-wide model store.
226
- *
227
- * @param config Extension configuration after validation.
228
- * @returns Nothing.
229
- */
230
- // TEST:test/unit/pi-coding-agent-dir.test.ts[loads and applies model exclusion TTL config]
231
- export function applyModelExclusionsConfig(config: Pick<ExtensionConfig, "modelExclusions">): void {
232
- setDefaultTTL(resolveModelExclusionTTL(config), { shortenExisting: config.modelExclusions?.defaultTtlMs !== undefined });
233
- }
234
-
235
208
  export function resolveAsyncByDefault(config: Pick<ExtensionConfig, "asyncByDefault">): boolean {
236
209
  return config.asyncByDefault !== false;
237
210
  }
@@ -242,12 +215,12 @@ export function loadConfig(): ExtensionConfig {
242
215
  return readConfigForUpdate(configPath);
243
216
  } catch (error) {
244
217
  if (error instanceof PrunedForkConfigError) throw error;
245
- // Explicit route identity and worktree policies must not be silently
218
+ // Explicit route identity, worktree, and checkpoint policies must not be silently
246
219
  // discarded and replaced by the built-in defaults after validation fails.
247
220
  try {
248
221
  const raw = JSON.parse(fs.readFileSync(configPath, "utf-8")) as unknown;
249
222
  if (raw && typeof raw === "object" && !Array.isArray(raw)
250
- && (Object.hasOwn(raw, "worktreeProvider") || Object.hasOwn(raw, "worktreeBranchPrefix") || Object.hasOwn(raw, "modelResponseAliases"))) throw error;
223
+ && (Object.hasOwn(raw, "worktreeProvider") || Object.hasOwn(raw, "worktreeBranchPrefix") || Object.hasOwn(raw, "modelResponseAliases") || Object.hasOwn(raw, "modelExclusions") || Object.hasOwn(raw, "checkpointBeforeDeadlineMs"))) throw error;
251
224
  } catch (readError) {
252
225
  if (readError === error) throw error;
253
226
  }