pi-subagents 0.67.0 → 0.69.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 (122) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +1 -1
  3. package/docs/agents.md +41 -12
  4. package/docs/configuration.md +61 -19
  5. package/docs/extension-api.md +5 -1
  6. package/docs/missions.md +2 -2
  7. package/docs/models.md +11 -79
  8. package/docs/observability.md +18 -8
  9. package/docs/standalone-background.md +13 -3
  10. package/docs/tool-reference.md +38 -14
  11. package/docs/watchdog.md +10 -12
  12. package/docs/workflows.md +59 -1
  13. package/index.ts +5 -2
  14. package/package.json +4 -2
  15. package/runner-peer-loader.mjs +24 -0
  16. package/runner-peer-preload.mjs +25 -11
  17. package/skills/pi-subagents/SKILL.md +18 -21
  18. package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
  19. package/skills/pi-subagents/references/execution-controls.md +6 -4
  20. package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
  21. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  22. package/skills/pi-subagents/references/prompting-and-roles.md +16 -12
  23. package/skills/pi-subagents/references/review-and-validation.md +3 -3
  24. package/src/agents/agent-management.ts +57 -58
  25. package/src/agents/agent-serializer.ts +4 -3
  26. package/src/agents/agents.ts +185 -72
  27. package/src/agents/chain-serializer.ts +5 -0
  28. package/src/agents/runtime-agent-registry.ts +7 -6
  29. package/src/agents/skills.ts +1 -1
  30. package/src/api/preflight.ts +20 -16
  31. package/src/api/required-child-extensions.ts +6 -0
  32. package/src/extension/config.ts +10 -37
  33. package/src/extension/fanout-child.ts +3 -0
  34. package/src/extension/herdr-pi-bridge.ts +160 -0
  35. package/src/extension/index.ts +42 -31
  36. package/src/extension/public-execution.ts +3 -3
  37. package/src/extension/schemas.ts +23 -6
  38. package/src/extension/tool-description.ts +8 -7
  39. package/src/inspectors/ghostty/plugin.ts +13 -1
  40. package/src/intercom/native-supervisor-channel.ts +22 -18
  41. package/src/policy/authority.ts +4 -0
  42. package/src/profiles/profiles.ts +12 -6
  43. package/src/runs/background/active-run-index.ts +17 -1
  44. package/src/runs/background/async-execution.ts +309 -126
  45. package/src/runs/background/async-job-tracker.ts +8 -6
  46. package/src/runs/background/async-resume.ts +13 -4
  47. package/src/runs/background/async-status.ts +15 -4
  48. package/src/runs/background/auto-drain.ts +20 -10
  49. package/src/runs/background/binary-bootstrap.ts +5 -0
  50. package/src/runs/background/chain-append.ts +1 -1
  51. package/src/runs/background/chain-root-attachment.ts +14 -33
  52. package/src/runs/background/notify.ts +74 -6
  53. package/src/runs/background/result-files.ts +8 -4
  54. package/src/runs/background/result-watcher.ts +19 -2
  55. package/src/runs/background/run-child-session.ts +20 -29
  56. package/src/runs/background/runner-aliases.ts +4 -33
  57. package/src/runs/background/runner-child-launch.ts +4 -1
  58. package/src/runs/background/runner-child-sessions.ts +2 -2
  59. package/src/runs/background/runner-http-dispatcher.ts +119 -0
  60. package/src/runs/background/scheduled-runs.ts +11 -5
  61. package/src/runs/background/stale-run-reconciler.ts +35 -11
  62. package/src/runs/background/subagent-runner.ts +413 -276
  63. package/src/runs/background/subagent-wait.ts +128 -23
  64. package/src/runs/background/wait-completions.ts +75 -27
  65. package/src/runs/background/wait-subscriptions.ts +9 -3
  66. package/src/runs/background/wait-tool.ts +4 -2
  67. package/src/runs/foreground/async-stop-action.ts +93 -3
  68. package/src/runs/foreground/execution.ts +115 -219
  69. package/src/runs/foreground/foreground-history.ts +2 -1
  70. package/src/runs/foreground/subagent-executor.ts +281 -80
  71. package/src/runs/shared/acceptance.ts +194 -37
  72. package/src/runs/shared/async-status-projection.ts +123 -33
  73. package/src/runs/shared/child-launch-plan.ts +15 -3
  74. package/src/runs/shared/child-launch.ts +19 -6
  75. package/src/runs/shared/child-runtime-config.ts +5 -0
  76. package/src/runs/shared/child-session.ts +94 -50
  77. package/src/runs/shared/child-tool-plan.ts +28 -16
  78. package/src/runs/shared/dynamic-fanout.ts +2 -2
  79. package/src/runs/shared/external-cli-contract.ts +11 -1
  80. package/src/runs/shared/external-cli-preflight.ts +6 -2
  81. package/src/runs/shared/herdr-connection.ts +134 -0
  82. package/src/runs/shared/herdr-external-adapters.ts +169 -0
  83. package/src/runs/shared/herdr-machine.ts +279 -0
  84. package/src/runs/shared/herdr-pi-protocol.ts +59 -0
  85. package/src/runs/shared/herdr-placed-run.ts +263 -0
  86. package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
  87. package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -237
  88. package/src/runs/shared/model-scope.ts +1 -1
  89. package/src/runs/shared/nested-events.ts +11 -2
  90. package/src/runs/shared/parallel-utils.ts +7 -2
  91. package/src/runs/shared/pi-spawn.ts +1 -1
  92. package/src/runs/shared/subagent-prompt-runtime.ts +4 -2
  93. package/src/runs/shared/worktree-setup-command.ts +27 -4
  94. package/src/runs/shared/worktree.ts +30 -8
  95. package/src/shared/child-cache-retention.ts +43 -0
  96. package/src/shared/launch-contract.ts +6 -9
  97. package/src/shared/pruned-fork.ts +1 -1
  98. package/src/shared/required-child-extensions.ts +81 -0
  99. package/src/shared/settings.ts +5 -2
  100. package/src/shared/shortcuts.ts +0 -4
  101. package/src/shared/types.ts +81 -29
  102. package/src/slash/slash-commands.ts +0 -6
  103. package/src/slash/subagents-admin.ts +13 -9
  104. package/src/tui/render.ts +20 -10
  105. package/src/watchdog/child-status.ts +28 -36
  106. package/src/watchdog/lsp-diagnostics.ts +1 -1
  107. package/src/watchdog/model-selection.ts +1 -1
  108. package/src/watchdog/register-child.ts +10 -3
  109. package/src/watchdog/register-main.ts +20 -20
  110. package/src/watchdog/render.ts +1 -1
  111. package/src/watchdog/review.ts +14 -30
  112. package/src/watchdog/rules.ts +1 -1
  113. package/src/watchdog/runtime.ts +23 -12
  114. package/src/watchdog/settings.ts +3 -6
  115. package/src/watchdog/types.ts +3 -5
  116. package/src/watchdog/warning-format.ts +1 -1
  117. package/src/workflows/scripted-workflow.ts +68 -7
  118. package/src/workflows/workflow-receipt.ts +21 -3
  119. package/src/workflows/workflow-resources.ts +13 -2
  120. package/src/runs/shared/model-exclusions.ts +0 -374
  121. package/src/runs/shared/readonly-model-continuation.ts +0 -69
  122. package/src/runs/shared/readonly-session-evidence.ts +0 -307
@@ -4,7 +4,7 @@ import type { AgentConfig } from "../agents/agents.ts";
4
4
  import type { PiLaunchToolPlan } from "../runs/shared/child-tool-plan.ts";
5
5
  import type { ExtensionBindings } from "../runs/shared/extension-bindings.ts";
6
6
 
7
- export const AGENT_DEFINITION_PROJECTION_VERSION = 1 as const;
7
+ export const AGENT_DEFINITION_PROJECTION_VERSION = 2 as const;
8
8
  // v2: the Intercom bridge prompt and tools are part of the binding on every
9
9
  // path, and the bridge text no longer names the parent session.
10
10
  export const LAUNCH_BINDING_PROJECTION_VERSION = 2 as const;
@@ -50,7 +50,6 @@ export function projectAgentDefinition(agent: AgentConfig): Record<string, unkno
50
50
  inheritSkills: agent.inheritSkills,
51
51
  model: agent.model,
52
52
  modelProvider: agent.modelProvider,
53
- fallbackModels: agent.fallbackModels,
54
53
  fast: agent.fast,
55
54
  thinking: agent.thinking,
56
55
  tools: agent.tools,
@@ -63,6 +62,7 @@ export function projectAgentDefinition(agent: AgentConfig): Record<string, unkno
63
62
  skills: agent.skills,
64
63
  skillPath: agent.skillPath,
65
64
  output: agent.output,
65
+ outputSchema: agent.outputSchema,
66
66
  defaultReads: agent.defaultReads,
67
67
  defaultProgress: agent.defaultProgress,
68
68
  defaultContext: agent.defaultContext,
@@ -88,7 +88,6 @@ export interface LaunchBindingInput {
88
88
  /** Caller task; runtime acceptance/output task annotations are explicitly outside the preflight-known subset. */
89
89
  task?: string;
90
90
  model?: string;
91
- modelCandidates?: string[];
92
91
  fast?: boolean;
93
92
  thinking?: string;
94
93
  systemPrompt?: string | null;
@@ -114,9 +113,7 @@ export function projectLaunchBinding(input: LaunchBindingInput): Record<string,
114
113
  version: LAUNCH_BINDING_PROJECTION_VERSION,
115
114
  definitionDigest: input.definitionDigest,
116
115
  taskDigest: input.task === undefined ? undefined : stableJsonDigest(input.task),
117
- // The ordered candidate set already contains each attempted model; keeping only
118
- // this set makes retries correlate to the same preflight binding.
119
- modelCandidates: input.modelCandidates,
116
+ model: input.model,
120
117
  fast: input.fast,
121
118
  thinking: input.thinking,
122
119
  systemPromptDigest: input.systemPrompt === undefined || input.systemPrompt === null ? undefined : stableJsonDigest(input.systemPrompt),
@@ -155,7 +152,7 @@ export type LaunchBindingSource = LaunchBindingIdentity
155
152
  & Pick<LaunchBindingInput, "fast" | "thinking" | "skills" | "outputPath" | "outputMode" | "structuredOutputSchema" | "extensionBindings">
156
153
  & {
157
154
  task: string;
158
- modelCandidates: string[];
155
+ model?: string;
159
156
  /** Effective child system prompt before runtime acceptance prose. */
160
157
  systemPrompt: string;
161
158
  toolPlan: Pick<PiLaunchToolPlan, "effectiveToolAllowlist" | "excludeTools" | "extensionArgs" | "effectiveMcpTools">;
@@ -183,7 +180,7 @@ export function resolveLaunchBinding(source: LaunchBindingSource): LaunchBinding
183
180
  launchContractDigest: launchBindingDigest({
184
181
  ...identity,
185
182
  task: source.task,
186
- modelCandidates: source.modelCandidates,
183
+ model: source.model,
187
184
  fast: source.fast,
188
185
  thinking: source.thinking || undefined,
189
186
  systemPrompt: source.systemPrompt,
@@ -194,7 +191,7 @@ export function resolveLaunchBinding(source: LaunchBindingSource): LaunchBinding
194
191
  mcpDirectTools: source.toolPlan.effectiveMcpTools,
195
192
  outputPath: source.outputPath || undefined,
196
193
  outputMode: source.outputMode,
197
- structuredOutputSchema: source.structuredOutputSchema || undefined,
194
+ structuredOutputSchema: source.structuredOutputSchema,
198
195
  extensionBindings: source.extensionBindings || undefined,
199
196
  }),
200
197
  };
@@ -5,7 +5,7 @@ import * as path from "node:path";
5
5
  import type { TextContent } from "@earendil-works/pi-ai";
6
6
  import { completeSimple } from "@earendil-works/pi-ai/compat";
7
7
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
8
- import { resolveModelCandidate } from "../runs/shared/model-fallback.ts";
8
+ import { resolveModelCandidate } from "../runs/shared/model-resolution.ts";
9
9
  import { splitKnownThinkingSuffix, toModelInfo } from "./model-info.ts";
10
10
  import type { ForkContextConfig } from "./types.ts";
11
11
 
@@ -0,0 +1,81 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
5
+ const MAX_REQUIRED_EXTENSIONS = 32;
6
+ const MAX_PATH_BYTES = 4096;
7
+ const EMPTY_SNAPSHOT: RequiredChildExtensionSnapshot = Object.freeze([]);
8
+
9
+ export interface RequiredChildExtension {
10
+ /** Safe host-owned identity exposed in launch evidence. */
11
+ id: string;
12
+ /** Existing importable module path. Snapshotted to its canonical absolute path. */
13
+ path: string;
14
+ }
15
+
16
+ export interface RegisterRequiredChildExtensionsInput {
17
+ sessionId: string;
18
+ extensions: readonly RequiredChildExtension[];
19
+ }
20
+
21
+ export interface RequiredChildExtensionRegistration { dispose(): void }
22
+ export type RequiredChildExtensionSnapshot = ReadonlyArray<Readonly<RequiredChildExtension>>;
23
+
24
+ /** Validate and freeze a snapshot; registration canonicalizes once, while retained launches preserve that identity. */
25
+ export function snapshotRequiredChildExtensions(value: unknown, label = "Required child extensions", canonicalizeFiles = false): RequiredChildExtensionSnapshot {
26
+ if (!Array.isArray(value) || value.length > MAX_REQUIRED_EXTENSIONS) throw new Error(`${label} must be an array of at most ${MAX_REQUIRED_EXTENSIONS} entries.`);
27
+ const ids = new Set<string>();
28
+ const paths = new Set<string>();
29
+ return Object.freeze(value.map((entry, index) => {
30
+ if (!entry || typeof entry !== "object" || Array.isArray(entry) || Object.keys(entry).some((key) => key !== "id" && key !== "path")) throw new Error(`${label} entry ${index} requires only id and path.`);
31
+ const id = "id" in entry ? entry.id : undefined;
32
+ const rawPath = "path" in entry ? entry.path : undefined;
33
+ if (typeof id !== "string" || !ID_PATTERN.test(id)) throw new Error(`${label} entry ${index} requires a safe id of at most 128 characters.`);
34
+ if (ids.has(id)) throw new Error(`${label} id '${id}' is duplicated.`);
35
+ if (typeof rawPath !== "string" || !rawPath.trim() || rawPath.includes("\0") || Buffer.byteLength(rawPath, "utf8") > MAX_PATH_BYTES || (!canonicalizeFiles && !path.isAbsolute(rawPath))) throw new Error(`${label} '${id}' requires ${canonicalizeFiles ? "a" : "an absolute"} non-empty path of at most ${MAX_PATH_BYTES} bytes without NUL.`);
36
+ let extensionPath = rawPath;
37
+ if (canonicalizeFiles) {
38
+ try {
39
+ extensionPath = fs.realpathSync(path.resolve(rawPath));
40
+ if (!fs.statSync(extensionPath).isFile()) throw new Error("not a file");
41
+ } catch (error) {
42
+ throw new Error(`${label} '${id}' is not an importable file: ${error instanceof Error ? error.message : String(error)}`);
43
+ }
44
+ }
45
+ if (paths.has(extensionPath)) throw new Error(`${label} path '${extensionPath}' is duplicated.`);
46
+ ids.add(id);
47
+ paths.add(extensionPath);
48
+ return Object.freeze({ id, path: extensionPath });
49
+ }));
50
+ }
51
+
52
+ interface Registry { version: 1; bySession: Map<string, RequiredChildExtensionSnapshot> }
53
+
54
+ function registry(): Registry {
55
+ const key = Symbol.for("pi-subagents.required-child-extensions.v1");
56
+ const root = globalThis as Record<PropertyKey, unknown>;
57
+ const existing = root[key];
58
+ if (existing === undefined) {
59
+ const created: Registry = { version: 1, bySession: new Map() };
60
+ root[key] = created;
61
+ return created;
62
+ }
63
+ if (!existing || typeof existing !== "object" || !("version" in existing) || existing.version !== 1 || !("bySession" in existing) || !(existing.bySession instanceof Map)) throw new Error("Malformed or unsupported required child extension registry.");
64
+ return { version: 1, bySession: existing.bySession };
65
+ }
66
+
67
+ /** Register one immutable host-required extension snapshot for a parent session. */
68
+ export function registerRequiredChildExtensions(input: RegisterRequiredChildExtensionsInput): RequiredChildExtensionRegistration {
69
+ if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key) => key !== "sessionId" && key !== "extensions")) throw new Error("Required child extension registration requires only sessionId and extensions.");
70
+ if (typeof input.sessionId !== "string" || !input.sessionId || input.sessionId.trim() !== input.sessionId || input.sessionId.length > 256 || input.sessionId.includes("\0")) throw new Error("Required child extension registration requires a non-empty trimmed sessionId of at most 256 characters without NUL.");
71
+ const frozen = snapshotRequiredChildExtensions(input.extensions, "Required child extensions", true);
72
+ const store = registry();
73
+ if (store.bySession.has(input.sessionId)) throw new Error(`Required child extensions are already registered for session '${input.sessionId}'; dispose them first.`);
74
+ store.bySession.set(input.sessionId, frozen);
75
+ return { dispose() { if (store.bySession.get(input.sessionId) === frozen) store.bySession.delete(input.sessionId); } };
76
+ }
77
+
78
+ export function resolveRequiredChildExtensions(sessionId: string | undefined): RequiredChildExtensionSnapshot {
79
+ if (!sessionId) return EMPTY_SNAPSHOT;
80
+ return registry().bySession.get(sessionId) ?? EMPTY_SNAPSHOT;
81
+ }
@@ -32,8 +32,9 @@ export interface SequentialStep {
32
32
  phase?: string;
33
33
  label?: string;
34
34
  as?: string;
35
- outputSchema?: JsonSchemaObject;
35
+ outputSchema?: JsonSchemaObject | false;
36
36
  cwd?: string;
37
+ machine?: string;
37
38
  output?: OutputOverrideInput;
38
39
  outputMode?: OutputMode;
39
40
  reads?: string[] | false;
@@ -56,8 +57,9 @@ export interface ParallelTaskItem {
56
57
  phase?: string;
57
58
  label?: string;
58
59
  as?: string;
59
- outputSchema?: JsonSchemaObject;
60
+ outputSchema?: JsonSchemaObject | false;
60
61
  cwd?: string;
62
+ machine?: string;
61
63
  count?: number;
62
64
  output?: OutputOverrideInput;
63
65
  outputMode?: OutputMode;
@@ -110,6 +112,7 @@ export interface ParallelStep {
110
112
  failFast?: boolean;
111
113
  worktree?: boolean;
112
114
  cwd?: string;
115
+ machine?: string;
113
116
  agentContract?: AgentContract;
114
117
  gateOn?: ChainGateLayer;
115
118
  }
@@ -1,7 +1,3 @@
1
- import { Key, type KeyId } from "@earendil-works/pi-tui";
2
-
3
- export const FLEET_OPEN_SHORTCUT: KeyId = Key.ctrlAlt("f");
4
-
5
1
  export function formatShortcutLabel(shortcut: string): string {
6
2
  return shortcut
7
3
  .split("+")
@@ -16,6 +16,7 @@ import type { GlobalMissionIndexRecord, MissionRecord, MissionStoreConfig } from
16
16
  import type { ExtensionBindings } from "../runs/shared/extension-bindings.ts";
17
17
  import type { WorkflowChildPermitContext } from "./workflow-child-permit.ts";
18
18
  import type { WatchdogWarningDetails } from "../watchdog/types.ts";
19
+ import type { RequiredChildExtensionSnapshot } from "./required-child-extensions.ts";
19
20
 
20
21
  // ============================================================================
21
22
  // Basic Types
@@ -230,6 +231,7 @@ export interface WorkflowReceipt {
230
231
  state: WorkflowReceiptState;
231
232
  createdAt: number;
232
233
  entries: Record<string, WorkflowReceiptEntry>;
234
+ argsDigest?: string;
233
235
  resource?: WorkflowResourceProvenance;
234
236
  hostSteps?: HostStepNode[];
235
237
  workflowChildren?: WorkflowChildSummary;
@@ -655,6 +657,12 @@ export type ProcessTreeTerminal =
655
657
  processGroupId: number;
656
658
  verifiedAt: number;
657
659
  }
660
+ | {
661
+ state: "observed";
662
+ mechanism: "windows-taskkill";
663
+ pid: number;
664
+ verifiedAt: number;
665
+ }
658
666
  | {
659
667
  state: "unknown";
660
668
  reason: "unsupported-platform" | "signal-failed" | "verification-failed";
@@ -800,6 +808,7 @@ export interface SteeringRecoveryDescriptor {
800
808
  version: 1;
801
809
  launchContractDigest?: string;
802
810
  extensionBindings?: ExtensionBindings;
811
+ requiredExtensions?: RequiredChildExtensionSnapshot;
803
812
  runFanoutBudget: RunFanoutBudgetDescriptor;
804
813
  sourceRunId: string;
805
814
  agentContract?: AgentContract;
@@ -812,7 +821,6 @@ export interface SteeringRecoveryDescriptor {
812
821
  modelProvider?: string;
813
822
  modelOverrideFromParent?: boolean;
814
823
  modelOrigin?: "explicit" | "inherited" | "configured";
815
- fallbackModels?: string[];
816
824
  fast?: boolean;
817
825
  thinking?: string;
818
826
  thinkingCeiling?: ThinkingLevel;
@@ -916,7 +924,7 @@ export interface SubagentResultIntercomPayload {
916
924
  // Progress Tracking
917
925
  // ============================================================================
918
926
 
919
- export interface ChildWatchdogWarningSummary extends Pick<WatchdogWarningDetails, "severity" | "category" | "summary" | "evidence" | "recommendedAction" | "displayedAt"> {
927
+ export interface ChildWatchdogWarningSummary extends Pick<WatchdogWarningDetails, "severity" | "importance" | "category" | "summary" | "evidence" | "recommendedAction" | "displayedAt"> {
920
928
  /** True when a later assistant turn in the child followed the warning. */
921
929
  addressed: boolean;
922
930
  stalemate: boolean;
@@ -993,14 +1001,6 @@ interface ProgressSummary {
993
1001
  // Results
994
1002
  // ============================================================================
995
1003
 
996
- export interface ModelAttempt {
997
- model: string;
998
- success: boolean;
999
- exitCode?: number | null;
1000
- error?: string;
1001
- usage?: Usage;
1002
- }
1003
-
1004
1004
  export type AcceptanceLevel = "auto" | "none" | "attested" | "checked" | "verified";
1005
1005
 
1006
1006
  export type AcceptanceEvidenceKind =
@@ -1028,6 +1028,10 @@ export interface AcceptanceVerifyCommand {
1028
1028
  cwd?: string;
1029
1029
  env?: Record<string, string>;
1030
1030
  allowFailure?: boolean;
1031
+ /** When "json", a passing command's stdout is parsed and becomes the run's structured output. */
1032
+ output?: "json";
1033
+ /** Optional JSON Schema the parsed stdout must satisfy; only meaningful with `output: "json"`. */
1034
+ schema?: JsonSchemaObject;
1031
1035
  }
1032
1036
 
1033
1037
  export interface AcceptanceReviewGate {
@@ -1039,6 +1043,8 @@ export interface AcceptanceReviewGate {
1039
1043
  export interface AcceptanceConfig {
1040
1044
  level?: AcceptanceLevel;
1041
1045
  report?: "on" | "off";
1046
+ /** Preserve an intentional launch-time staged index, while rejecting any terminal index change. */
1047
+ preserveStagedIndex?: true;
1042
1048
  criteria?: Array<string | AcceptanceGate>;
1043
1049
  evidence?: AcceptanceEvidenceKind[];
1044
1050
  verify?: AcceptanceVerifyCommand[];
@@ -1063,6 +1069,7 @@ export interface ResolvedAcceptanceConfig {
1063
1069
  inferredReason: string[];
1064
1070
  criteria: ResolvedAcceptanceGate[];
1065
1071
  evidence: AcceptanceEvidenceKind[];
1072
+ preserveStagedIndex?: true;
1066
1073
  verify: AcceptanceVerifyCommand[];
1067
1074
  review?: AcceptanceReviewGate | false;
1068
1075
  stopRules: string[];
@@ -1121,6 +1128,10 @@ export interface AcceptanceVerifyResult {
1121
1128
  diffHash: string;
1122
1129
  };
1123
1130
  artifactError?: string;
1131
+ /** Parsed stdout of a passing `output: "json"` command. */
1132
+ structuredOutput?: unknown;
1133
+ /** Why a passing `output: "json"` command still failed: invalid JSON, truncated stdout, or schema mismatch. */
1134
+ structuredOutputError?: string;
1124
1135
  }
1125
1136
 
1126
1137
  export interface AcceptanceReviewResult {
@@ -1187,10 +1198,13 @@ export interface LaunchResolvedChildExtensions {
1187
1198
  disableAmbientExtensions: boolean;
1188
1199
  runtime: string[];
1189
1200
  configured: string[];
1201
+ /** Bounded host-supplied identities; paths are intentionally not exposed. */
1202
+ required: string[];
1190
1203
  effective: string[];
1191
1204
  omitted: {
1192
1205
  runtime: number;
1193
1206
  configured: number;
1207
+ required: number;
1194
1208
  effective: number;
1195
1209
  };
1196
1210
  }
@@ -1261,16 +1275,16 @@ export interface SingleResult {
1261
1275
  messages?: Message[];
1262
1276
  usage: Usage;
1263
1277
  model?: string;
1278
+ /** Authoritative before/after Git evidence captured by a pane-native remote machine. */
1279
+ nativeMachine?: { provider: "herdr"; machineId: string; initialGit?: HerdrRemoteGitStatus; finalGit?: HerdrRemoteGitStatus };
1264
1280
  /** Effective thinking level used by this foreground child, when known. */
1265
1281
  thinking?: string;
1266
- attemptedModels?: string[];
1267
- modelAttempts?: ModelAttempt[];
1282
+ requestedModel?: string;
1268
1283
  controlEvents?: ControlEvent[];
1269
1284
  error?: string;
1270
1285
  /**
1271
1286
  * True when the dispatch failed because the input exceeded the model's
1272
- * context window. The model fallback loop stops immediately (retrying the
1273
- * same input on another model cannot succeed). Callers should treat this as
1287
+ * context window. Callers should treat this as
1274
1288
  * a signal to reduce input size or re-decompose the task.
1275
1289
  */
1276
1290
  contextOverflow?: boolean;
@@ -1381,10 +1395,11 @@ export interface AgentCapabilityRow {
1381
1395
  executable: boolean;
1382
1396
  restrictionSources?: string[];
1383
1397
  aliases?: string[];
1384
- runner: { type: "pi" } | { type: "external-cli"; adapter?: string; command: string; available: boolean; unavailableReason?: string; capabilities: ExternalCliCapabilities } | { type: "external-job"; provider: string; available?: boolean; capabilities: ExternalJobRunnerStatus["capabilities"] };
1398
+ runner: { type: "pi" } | { type: "external-cli"; adapter?: string; command: string; machine?: string; available: boolean; unavailableReason?: string; capabilities: ExternalCliCapabilities } | { type: "external-job"; provider: string; available?: boolean; capabilities: ExternalJobRunnerStatus["capabilities"] };
1385
1399
  tools: { ambient: boolean; names: string[]; excludeTools?: string[]; mcpDirectTools: string[]; mutationTools?: string[] };
1386
- model?: { value?: string; fallbackModels?: string[]; thinking?: string | false };
1400
+ model?: { value?: string; thinking?: string | false };
1387
1401
  execution?: { defaultAsync?: boolean; timeoutMs?: number };
1402
+ acceptance?: { policy?: AcceptanceInput; role?: AcceptanceRole };
1388
1403
  output?: { path?: string; mode?: OutputMode };
1389
1404
  extensions?: { names?: string[]; subagentOnly?: string[]; skills?: string[] };
1390
1405
  }
@@ -1411,6 +1426,12 @@ export interface Details {
1411
1426
  timedOut: true;
1412
1427
  activeRunIds: string[];
1413
1428
  activeProviderItems: Array<{ provider: string; id: string }>;
1429
+ } | {
1430
+ /** Non-terminal internal auto-drain yield; tracked work remains active. */
1431
+ reason: "supervisor_request";
1432
+ timedOut: false;
1433
+ activeRunIds: string[];
1434
+ activeProviderItems: Array<{ provider: string; id: string }>;
1414
1435
  };
1415
1436
  controlEvents?: ControlEvent[];
1416
1437
  steering?: SteerActionResult;
@@ -1472,6 +1493,8 @@ export interface Details {
1472
1493
  mission?: MissionRecord;
1473
1494
  workflow?: {
1474
1495
  value?: unknown;
1496
+ args?: Record<string, unknown>;
1497
+ argsDigest?: string;
1475
1498
  resource?: WorkflowResourceProvenance;
1476
1499
  preflightWarnings?: string[];
1477
1500
  trace: Array<{
@@ -1707,6 +1730,26 @@ export type AgentRunnerConfig =
1707
1730
 
1708
1731
  export type ExternalCliCapabilityNarrowing = Partial<Record<"steer" | "resume" | "structuredOutput" | "toolEvents" | "supervisor" | "forkContext" | "extensionBindings", false>>;
1709
1732
 
1733
+ export interface HerdrRemoteGitStatus {
1734
+ head?: string;
1735
+ branch?: string;
1736
+ dirty?: boolean;
1737
+ }
1738
+
1739
+ /** A Herdr saved SSH machine resolved for one launch. `cwd` is the directory on that machine. */
1740
+ export interface HerdrMachineReference {
1741
+ provider: "herdr";
1742
+ id: string;
1743
+ label?: string;
1744
+ target: string;
1745
+ session?: string;
1746
+ cwd: string;
1747
+ }
1748
+
1749
+ export interface ExternalCliMachineStatus extends HerdrMachineReference {
1750
+ remoteGit?: HerdrRemoteGitStatus;
1751
+ }
1752
+
1710
1753
  export interface ExternalCliCapabilities {
1711
1754
  stop: true;
1712
1755
  steer: false;
@@ -1721,6 +1764,7 @@ export interface ExternalCliCapabilities {
1721
1764
  export interface ExternalCliReceiptMetadata {
1722
1765
  adapter: { id: "external-cli" | "codex-exec" | "codex-exec-writer" | "claude-code" | "claude-code-writer" | "cursor-agent" | "cursor-agent-writer" | "grok-build"; version: 1; executionMode: "one-shot-stdin" | "one-shot-prompt-file" };
1723
1766
  capabilities: ExternalCliCapabilities;
1767
+ machine?: ExternalCliMachineStatus;
1724
1768
  safety?:
1725
1769
  | { sandbox: "read-only"; approvalPolicy: "never"; ephemeral: true }
1726
1770
  | { access: "workspace-write"; sandbox: "workspace-write"; approvalPolicy: "never"; ephemeral: true }
@@ -1745,6 +1789,7 @@ export interface ExternalCliRunnerStatus {
1745
1789
  capabilities: ExternalCliCapabilities;
1746
1790
  unsupportedReasons: Record<Exclude<keyof ExternalCliCapabilities, "stop">, string>;
1747
1791
  nonResumableReason: string;
1792
+ machine?: HerdrMachineReference;
1748
1793
  }
1749
1794
 
1750
1795
  export interface ExternalJobRunnerStatus {
@@ -1796,6 +1841,7 @@ export interface ExternalProcessStatus {
1796
1841
  stderrBytes?: number;
1797
1842
  stdoutTruncated?: boolean;
1798
1843
  stderrTruncated?: boolean;
1844
+ machine?: ExternalCliMachineStatus;
1799
1845
  }
1800
1846
 
1801
1847
  export interface AsyncStatus {
@@ -1928,8 +1974,7 @@ export interface AsyncStatus {
1928
1974
  thinking?: string;
1929
1975
  contextLimit?: number;
1930
1976
  thinkingCeiling?: ThinkingLevel;
1931
- attemptedModels?: string[];
1932
- modelAttempts?: ModelAttempt[];
1977
+ requestedModel?: string;
1933
1978
  /** True when the child input exceeded the model context window. */
1934
1979
  contextOverflow?: boolean;
1935
1980
  totalCost?: CostSummary;
@@ -2070,7 +2115,7 @@ export interface ForegroundResumeChild {
2070
2115
  /** Private bounded launch fields needed to preserve the child contract on resume. */
2071
2116
  resumeContract?: {
2072
2117
  modelResponseAliases?: Record<string, string[]>;
2073
- outputSchema?: JsonSchemaObject;
2118
+ outputSchema?: JsonSchemaObject | false;
2074
2119
  agentContract?: AgentContract;
2075
2120
  acceptance?: AcceptanceInput;
2076
2121
  output?: string | boolean;
@@ -2079,6 +2124,7 @@ export interface ForegroundResumeChild {
2079
2124
  launchContractDigest?: string;
2080
2125
  /** Private retained launch authority. Never project into status or result output. */
2081
2126
  extensionBindings?: ExtensionBindings;
2127
+ requiredExtensions?: RequiredChildExtensionSnapshot;
2082
2128
  launchResolvedExtensions?: LaunchResolvedChildExtensions;
2083
2129
  runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
2084
2130
  execution?: ExecutionProjection;
@@ -2256,8 +2302,8 @@ export interface SubagentState {
2256
2302
  lastUiContext: ExtensionContext | null;
2257
2303
  poller: NodeJS.Timeout | null;
2258
2304
  completionSeen: Map<string, number>;
2259
- /** Terminal result payloads observed by the result watcher, keyed by run id and pruned by the completion TTL. */
2260
- completedResults?: Map<string, { seenAt: number; completion: WaitCompletion }>;
2305
+ /** Session-owned terminal result payloads observed by the result watcher, keyed by run id and pruned by the completion TTL. */
2306
+ completedResults?: Map<string, { sessionId: string; seenAt: number; completion: WaitCompletion }>;
2261
2307
  watcher: FSWatcher | null;
2262
2308
  watcherRestartTimer: ReturnType<typeof setTimeout> | null;
2263
2309
  resultFileCoalescer: {
@@ -2360,6 +2406,8 @@ export interface RunSyncOptions {
2360
2406
  unknownAgentDiagnosticContext?: import("../agents/agents.ts").UnknownAgentDiagnosticContext;
2361
2407
  /** Session factory for the in-process child; defaults to the process-wide factory. */
2362
2408
  childSessionFactory?: import("../runs/shared/child-session.ts").ChildSessionFactory;
2409
+ /** Invoking parent registry inherited only by its local foreground launch. */
2410
+ parentProviderRegistry?: import("../runs/shared/child-session.ts").ParentProviderRegistry;
2363
2411
  /** The launching executor's own child runtime when it is itself an in-process child. */
2364
2412
  childRuntime?: import("../runs/shared/child-runtime-config.ts").ChildRuntimeConfig;
2365
2413
  /** Fires once the child session exists and can be steered. */
@@ -2371,6 +2419,10 @@ export interface RunSyncOptions {
2371
2419
  /** Resolved launch context for this child. */
2372
2420
  context?: "fresh" | "fork";
2373
2421
  cwd?: string;
2422
+ /** Resolved pane-native saved-machine placement. */
2423
+ machine?: HerdrMachineReference;
2424
+ /** Explicit read override resolved by the remote ambient agent profile. */
2425
+ remoteReads?: string[] | false;
2374
2426
  /** Original cwd input retained for launch diagnostics. */
2375
2427
  requestedCwd?: string;
2376
2428
  signal?: AbortSignal;
@@ -2431,6 +2483,7 @@ export interface RunSyncOptions {
2431
2483
  /** Override the agent's default thinking level for this run */
2432
2484
  thinkingOverride?: AgentConfig["thinking"];
2433
2485
  thinkingCeiling?: ThinkingLevel;
2486
+ requiredExtensions?: RequiredChildExtensionSnapshot;
2434
2487
  extensionBindings?: ExtensionBindings;
2435
2488
  /** Package-internal one-use authorization for one foreground workflow child. */
2436
2489
  workflowChildPermitLaunch?: WorkflowChildPermitContext;
@@ -2443,7 +2496,7 @@ export interface RunSyncOptions {
2443
2496
  runtimeSnapshotHost?: import("../runs/shared/mcp-direct-tool-allowlist.ts").McpRuntimeSnapshotHost;
2444
2497
  /** Builtin tool names the host runtime provides; used to intersect agent-declared tools. */
2445
2498
  hostAvailableBuiltins?: readonly string[];
2446
- /** Optional subagent model-scope enforcement for fallback candidates */
2499
+ /** Optional subagent model-scope enforcement. */
2447
2500
  modelScope?: ModelScopeRule | ModelScopeRule[];
2448
2501
  /** Skills to make available (overrides agent default if provided) */
2449
2502
  skills?: string[];
@@ -2505,11 +2558,6 @@ export interface ScheduledRunsConfig {
2505
2558
  storeRoot?: string;
2506
2559
  }
2507
2560
 
2508
- export interface ModelExclusionsConfig {
2509
- /** Default duration in milliseconds. A lower configured value also shortens active cached exclusions. */
2510
- defaultTtlMs?: number;
2511
- }
2512
-
2513
2561
  export type FleetViewPlacement = "aboveEditor" | "belowEditor";
2514
2562
 
2515
2563
  export const FLEET_KEYBINDING_ACTIONS = [
@@ -2572,8 +2620,6 @@ export interface ExtensionConfig {
2572
2620
  fleetKeybindings?: FleetKeybindingsConfig;
2573
2621
  /** Show the under-editor async runs widget. Defaults to true, including when FleetView is enabled. */
2574
2622
  asyncWidget?: boolean;
2575
- /** Configure the process-wide TTL policy for persisted model exclusions. */
2576
- modelExclusions?: ModelExclusionsConfig;
2577
2623
  /** Exact provider/model candidates mapped to operator-declared equivalent response IDs. Empty arrays add no accepted IDs. */
2578
2624
  modelResponseAliases?: Record<string, string[]>;
2579
2625
  /** Tool description variant registered for the parent-facing subagent tool. Defaults to split metadata. */
@@ -2618,6 +2664,12 @@ export interface ExtensionConfig {
2618
2664
  * are rejected with an error.
2619
2665
  */
2620
2666
  toolTimeoutMs?: number;
2667
+ /**
2668
+ * Global default for the async single-agent `checkpointBeforeDeadlineMs` launch option: the runner requests that
2669
+ * the child checkpoint and stop this many milliseconds before its run deadline (best-effort). The call param wins; values that
2670
+ * leave no run time before the checkpoint disarm it.
2671
+ */
2672
+ checkpointBeforeDeadlineMs?: number;
2621
2673
  control?: ControlConfig;
2622
2674
  completionBatch?: CompletionBatchConfig;
2623
2675
  toolBudget?: ToolBudgetConfig;
@@ -25,7 +25,6 @@ import { resolveAsyncStatusChild } from "../runs/shared/child-identity.ts";
25
25
  import { readStatus } from "../shared/utils.ts";
26
26
  import { getArtifactPaths, getArtifactsDir } from "../shared/artifacts.ts";
27
27
  import { readWorkflowReceipt } from "../workflows/workflow-receipt.ts";
28
- import { FLEET_OPEN_SHORTCUT } from "../shared/shortcuts.ts";
29
28
  import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.ts";
30
29
  import { registerPromptWorkflowCommands } from "./prompt-workflows.ts";
31
30
  import { openSubagentsAdmin } from "./subagents-admin.ts";
@@ -976,11 +975,6 @@ export function registerSlashCommands(
976
975
  handler: async (_args, ctx) => showFleet(ctx),
977
976
  });
978
977
 
979
- pi.registerShortcut(FLEET_OPEN_SHORTCUT, {
980
- description: "Open subagent fleet inspector",
981
- handler: async (ctx) => showFleet(ctx),
982
- });
983
-
984
978
  const detachForegroundRun = (args: string, ctx: ExtensionContext): void => {
985
979
  const id = args.trim();
986
980
  let control: ReturnType<typeof selectForegroundDetachControl>;
@@ -11,6 +11,7 @@ import {
11
11
  type BuiltinAgentOverrideBase,
12
12
  } from "../agents/agents.ts";
13
13
  import { serializeAgent } from "../agents/agent-serializer.ts";
14
+ import { mergeRuntimeAgents, type RuntimeAgentOwner } from "../agents/runtime-agent-registry.ts";
14
15
  import { editableAgentConfig, preservedAgentFrontmatterFields } from "../agents/agent-management.ts";
15
16
  import { findModelInfo, getSupportedThinkingLevels, toModelInfo } from "../shared/model-info.ts";
16
17
  import { SelectorComponent, type SelectorItem, type SelectorResult } from "./selector.ts";
@@ -28,11 +29,13 @@ function sourceRank(source: AgentConfig["source"]): number {
28
29
  return 3;
29
30
  }
30
31
 
31
- function allVisibleAgents(cwd: string): AgentConfig[] {
32
+ function allVisibleAgents(pi: RuntimeAgentOwner, cwd: string): AgentConfig[] {
32
33
  const d = discoverAgentsAll(cwd);
33
- return [...d.project, ...d.user, ...d.package, ...d.builtin]
34
- .filter((agent) => !agent.disabled)
35
- .sort((a, b) => a.name.localeCompare(b.name) || sourceRank(a.source) - sourceRank(b.source));
34
+ const allConfigured = [...d.project, ...d.user, ...d.package, ...d.builtin];
35
+ const visibleConfigured = allConfigured.filter((agent) => !agent.disabled);
36
+ // Disabled definitions remain in collision checks even though the panel hides them.
37
+ const agents = mergeRuntimeAgents(pi, { agents: visibleConfigured }, allConfigured).agents;
38
+ return agents.sort((a, b) => a.name.localeCompare(b.name) || sourceRank(a.source) - sourceRank(b.source));
36
39
  }
37
40
 
38
41
  function agentLabel(agent: AgentConfig): string {
@@ -91,7 +94,6 @@ async function liveAvailableModels(ctx: ExtensionContext) {
91
94
  function buildBuiltinBase(agent: AgentConfig): BuiltinAgentOverrideBase {
92
95
  return {
93
96
  ...(agent.model !== undefined ? { model: agent.model } : {}),
94
- ...(agent.fallbackModels !== undefined ? { fallbackModels: [...agent.fallbackModels] } : {}),
95
97
  ...(agent.thinking !== undefined ? { thinking: agent.thinking } : {}),
96
98
  systemPromptMode: agent.systemPromptMode,
97
99
  inheritProjectContext: agent.inheritProjectContext,
@@ -146,6 +148,9 @@ function isReadOnlyExtraAgent(agent: AgentConfig): boolean {
146
148
  }
147
149
 
148
150
  function readOnlyAgentMessage(agent: AgentConfig, field: EditableOverrideField): string | undefined {
151
+ if (agent.source === "runtime") {
152
+ return `Cannot update '${agent.name}' ${field} because that agent is runtime-registered by an extension; edit its source definition instead.`;
153
+ }
149
154
  if (agent.source === "package") {
150
155
  return `Cannot update '${agent.name}' ${field} because that field is owned by its read-only package definition.`;
151
156
  }
@@ -154,8 +159,8 @@ function readOnlyAgentMessage(agent: AgentConfig, field: EditableOverrideField):
154
159
  : undefined;
155
160
  }
156
161
 
157
- async function selectAgent(ctx: ExtensionContext, args: string): Promise<AgentSelection> {
158
- const agents = allVisibleAgents(ctx.cwd);
162
+ async function selectAgent(pi: RuntimeAgentOwner, ctx: ExtensionContext, args: string): Promise<AgentSelection> {
163
+ const agents = allVisibleAgents(pi, ctx.cwd);
159
164
  const requestedName = args.trim().split(/\s+/)[0] ?? "";
160
165
  if (agents.length === 0) return { kind: "not-found", agents, requestedName: requestedName || undefined };
161
166
 
@@ -189,7 +194,6 @@ function metadataFor(agent: AgentConfig): string {
189
194
  lines.push(`Package: ${agent.packageName}`);
190
195
  }
191
196
  lines.push(`Model: ${agent.model ?? "default / inherit"}`);
192
- if (agent.fallbackModels?.length) lines.push(`Fallback models: ${agent.fallbackModels.join(", ")}`);
193
197
  if (agent.thinking !== undefined) lines.push(`Thinking: ${agent.thinking === false ? "off" : agent.thinking}`);
194
198
  if (tools.length) lines.push(`Tools: ${tools.join(", ")}`);
195
199
  if (agent.excludeTools?.length) lines.push(`Excluded tools: ${agent.excludeTools.join(", ")}`);
@@ -390,7 +394,7 @@ async function editSystemPrompt(ctx: ExtensionContext, agent: AgentConfig): Prom
390
394
  }
391
395
 
392
396
  export async function openSubagentsAdmin(pi: ExtensionAPI, ctx: ExtensionContext, args = ""): Promise<void> {
393
- const selection = await selectAgent(ctx, args);
397
+ const selection = await selectAgent(pi, ctx, args);
394
398
  if (selection.kind === "cancelled") return;
395
399
  if (selection.kind === "ambiguous") {
396
400
  sendAdminMessage(pi, `Subagent '${selection.requestedName}' is ambiguous. Choose a scope in interactive mode:\n${selection.matches.map((agent) => `- ${agent.source}: ${agent.filePath}`).join("\n")}`);