pi-subagents 0.67.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 (120) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +1 -1
  3. package/docs/agents.md +37 -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 +15 -12
  11. package/docs/watchdog.md +10 -12
  12. package/docs/workflows.md +11 -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 +4 -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 +184 -71
  27. package/src/agents/chain-serializer.ts +5 -0
  28. package/src/agents/runtime-agent-registry.ts +7 -6
  29. package/src/api/preflight.ts +20 -16
  30. package/src/api/required-child-extensions.ts +6 -0
  31. package/src/extension/config.ts +10 -37
  32. package/src/extension/fanout-child.ts +3 -0
  33. package/src/extension/herdr-pi-bridge.ts +160 -0
  34. package/src/extension/index.ts +42 -31
  35. package/src/extension/public-execution.ts +3 -3
  36. package/src/extension/schemas.ts +16 -5
  37. package/src/extension/tool-description.ts +8 -7
  38. package/src/intercom/native-supervisor-channel.ts +22 -18
  39. package/src/policy/authority.ts +4 -0
  40. package/src/profiles/profiles.ts +12 -6
  41. package/src/runs/background/active-run-index.ts +17 -1
  42. package/src/runs/background/async-execution.ts +309 -126
  43. package/src/runs/background/async-job-tracker.ts +8 -6
  44. package/src/runs/background/async-resume.ts +13 -4
  45. package/src/runs/background/async-status.ts +15 -4
  46. package/src/runs/background/auto-drain.ts +20 -10
  47. package/src/runs/background/binary-bootstrap.ts +5 -0
  48. package/src/runs/background/chain-append.ts +1 -1
  49. package/src/runs/background/chain-root-attachment.ts +14 -33
  50. package/src/runs/background/notify.ts +74 -6
  51. package/src/runs/background/result-files.ts +8 -4
  52. package/src/runs/background/result-watcher.ts +19 -2
  53. package/src/runs/background/run-child-session.ts +20 -29
  54. package/src/runs/background/runner-aliases.ts +4 -33
  55. package/src/runs/background/runner-child-launch.ts +4 -1
  56. package/src/runs/background/runner-child-sessions.ts +2 -2
  57. package/src/runs/background/runner-http-dispatcher.ts +119 -0
  58. package/src/runs/background/scheduled-runs.ts +11 -5
  59. package/src/runs/background/stale-run-reconciler.ts +35 -11
  60. package/src/runs/background/subagent-runner.ts +396 -275
  61. package/src/runs/background/subagent-wait.ts +128 -23
  62. package/src/runs/background/wait-completions.ts +75 -27
  63. package/src/runs/background/wait-subscriptions.ts +9 -3
  64. package/src/runs/background/wait-tool.ts +4 -2
  65. package/src/runs/foreground/async-stop-action.ts +93 -3
  66. package/src/runs/foreground/execution.ts +91 -218
  67. package/src/runs/foreground/foreground-history.ts +2 -1
  68. package/src/runs/foreground/subagent-executor.ts +266 -80
  69. package/src/runs/shared/acceptance.ts +34 -10
  70. package/src/runs/shared/async-status-projection.ts +123 -33
  71. package/src/runs/shared/child-launch-plan.ts +15 -3
  72. package/src/runs/shared/child-launch.ts +19 -6
  73. package/src/runs/shared/child-runtime-config.ts +5 -0
  74. package/src/runs/shared/child-session.ts +94 -50
  75. package/src/runs/shared/child-tool-plan.ts +28 -16
  76. package/src/runs/shared/dynamic-fanout.ts +2 -2
  77. package/src/runs/shared/external-cli-contract.ts +11 -1
  78. package/src/runs/shared/external-cli-preflight.ts +6 -2
  79. package/src/runs/shared/herdr-connection.ts +134 -0
  80. package/src/runs/shared/herdr-external-adapters.ts +169 -0
  81. package/src/runs/shared/herdr-machine.ts +279 -0
  82. package/src/runs/shared/herdr-pi-protocol.ts +59 -0
  83. package/src/runs/shared/herdr-placed-run.ts +263 -0
  84. package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
  85. package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -237
  86. package/src/runs/shared/model-scope.ts +1 -1
  87. package/src/runs/shared/nested-events.ts +11 -2
  88. package/src/runs/shared/parallel-utils.ts +7 -2
  89. package/src/runs/shared/pi-spawn.ts +1 -1
  90. package/src/runs/shared/subagent-prompt-runtime.ts +4 -2
  91. package/src/runs/shared/worktree-setup-command.ts +27 -4
  92. package/src/runs/shared/worktree.ts +3 -3
  93. package/src/shared/child-cache-retention.ts +43 -0
  94. package/src/shared/launch-contract.ts +6 -9
  95. package/src/shared/pruned-fork.ts +1 -1
  96. package/src/shared/required-child-extensions.ts +81 -0
  97. package/src/shared/settings.ts +5 -2
  98. package/src/shared/shortcuts.ts +0 -4
  99. package/src/shared/types.ts +70 -29
  100. package/src/slash/slash-commands.ts +0 -6
  101. package/src/slash/subagents-admin.ts +13 -9
  102. package/src/tui/render.ts +20 -10
  103. package/src/watchdog/child-status.ts +28 -36
  104. package/src/watchdog/lsp-diagnostics.ts +1 -1
  105. package/src/watchdog/model-selection.ts +1 -1
  106. package/src/watchdog/register-child.ts +10 -3
  107. package/src/watchdog/register-main.ts +20 -20
  108. package/src/watchdog/render.ts +1 -1
  109. package/src/watchdog/review.ts +14 -30
  110. package/src/watchdog/rules.ts +1 -1
  111. package/src/watchdog/runtime.ts +23 -12
  112. package/src/watchdog/settings.ts +3 -6
  113. package/src/watchdog/types.ts +3 -5
  114. package/src/watchdog/warning-format.ts +1 -1
  115. package/src/workflows/scripted-workflow.ts +42 -3
  116. package/src/workflows/workflow-receipt.ts +21 -3
  117. package/src/workflows/workflow-resources.ts +13 -2
  118. package/src/runs/shared/model-exclusions.ts +0 -374
  119. package/src/runs/shared/readonly-model-continuation.ts +0 -69
  120. package/src/runs/shared/readonly-session-evidence.ts +0 -307
@@ -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 =
@@ -1187,10 +1187,13 @@ export interface LaunchResolvedChildExtensions {
1187
1187
  disableAmbientExtensions: boolean;
1188
1188
  runtime: string[];
1189
1189
  configured: string[];
1190
+ /** Bounded host-supplied identities; paths are intentionally not exposed. */
1191
+ required: string[];
1190
1192
  effective: string[];
1191
1193
  omitted: {
1192
1194
  runtime: number;
1193
1195
  configured: number;
1196
+ required: number;
1194
1197
  effective: number;
1195
1198
  };
1196
1199
  }
@@ -1261,16 +1264,16 @@ export interface SingleResult {
1261
1264
  messages?: Message[];
1262
1265
  usage: Usage;
1263
1266
  model?: string;
1267
+ /** Authoritative before/after Git evidence captured by a pane-native remote machine. */
1268
+ nativeMachine?: { provider: "herdr"; machineId: string; initialGit?: HerdrRemoteGitStatus; finalGit?: HerdrRemoteGitStatus };
1264
1269
  /** Effective thinking level used by this foreground child, when known. */
1265
1270
  thinking?: string;
1266
- attemptedModels?: string[];
1267
- modelAttempts?: ModelAttempt[];
1271
+ requestedModel?: string;
1268
1272
  controlEvents?: ControlEvent[];
1269
1273
  error?: string;
1270
1274
  /**
1271
1275
  * 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
1276
+ * context window. Callers should treat this as
1274
1277
  * a signal to reduce input size or re-decompose the task.
1275
1278
  */
1276
1279
  contextOverflow?: boolean;
@@ -1381,10 +1384,11 @@ export interface AgentCapabilityRow {
1381
1384
  executable: boolean;
1382
1385
  restrictionSources?: string[];
1383
1386
  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"] };
1387
+ 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
1388
  tools: { ambient: boolean; names: string[]; excludeTools?: string[]; mcpDirectTools: string[]; mutationTools?: string[] };
1386
- model?: { value?: string; fallbackModels?: string[]; thinking?: string | false };
1389
+ model?: { value?: string; thinking?: string | false };
1387
1390
  execution?: { defaultAsync?: boolean; timeoutMs?: number };
1391
+ acceptance?: { policy?: AcceptanceInput; role?: AcceptanceRole };
1388
1392
  output?: { path?: string; mode?: OutputMode };
1389
1393
  extensions?: { names?: string[]; subagentOnly?: string[]; skills?: string[] };
1390
1394
  }
@@ -1411,6 +1415,12 @@ export interface Details {
1411
1415
  timedOut: true;
1412
1416
  activeRunIds: string[];
1413
1417
  activeProviderItems: Array<{ provider: string; id: string }>;
1418
+ } | {
1419
+ /** Non-terminal internal auto-drain yield; tracked work remains active. */
1420
+ reason: "supervisor_request";
1421
+ timedOut: false;
1422
+ activeRunIds: string[];
1423
+ activeProviderItems: Array<{ provider: string; id: string }>;
1414
1424
  };
1415
1425
  controlEvents?: ControlEvent[];
1416
1426
  steering?: SteerActionResult;
@@ -1472,6 +1482,8 @@ export interface Details {
1472
1482
  mission?: MissionRecord;
1473
1483
  workflow?: {
1474
1484
  value?: unknown;
1485
+ args?: Record<string, unknown>;
1486
+ argsDigest?: string;
1475
1487
  resource?: WorkflowResourceProvenance;
1476
1488
  preflightWarnings?: string[];
1477
1489
  trace: Array<{
@@ -1707,6 +1719,26 @@ export type AgentRunnerConfig =
1707
1719
 
1708
1720
  export type ExternalCliCapabilityNarrowing = Partial<Record<"steer" | "resume" | "structuredOutput" | "toolEvents" | "supervisor" | "forkContext" | "extensionBindings", false>>;
1709
1721
 
1722
+ export interface HerdrRemoteGitStatus {
1723
+ head?: string;
1724
+ branch?: string;
1725
+ dirty?: boolean;
1726
+ }
1727
+
1728
+ /** A Herdr saved SSH machine resolved for one launch. `cwd` is the directory on that machine. */
1729
+ export interface HerdrMachineReference {
1730
+ provider: "herdr";
1731
+ id: string;
1732
+ label?: string;
1733
+ target: string;
1734
+ session?: string;
1735
+ cwd: string;
1736
+ }
1737
+
1738
+ export interface ExternalCliMachineStatus extends HerdrMachineReference {
1739
+ remoteGit?: HerdrRemoteGitStatus;
1740
+ }
1741
+
1710
1742
  export interface ExternalCliCapabilities {
1711
1743
  stop: true;
1712
1744
  steer: false;
@@ -1721,6 +1753,7 @@ export interface ExternalCliCapabilities {
1721
1753
  export interface ExternalCliReceiptMetadata {
1722
1754
  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
1755
  capabilities: ExternalCliCapabilities;
1756
+ machine?: ExternalCliMachineStatus;
1724
1757
  safety?:
1725
1758
  | { sandbox: "read-only"; approvalPolicy: "never"; ephemeral: true }
1726
1759
  | { access: "workspace-write"; sandbox: "workspace-write"; approvalPolicy: "never"; ephemeral: true }
@@ -1745,6 +1778,7 @@ export interface ExternalCliRunnerStatus {
1745
1778
  capabilities: ExternalCliCapabilities;
1746
1779
  unsupportedReasons: Record<Exclude<keyof ExternalCliCapabilities, "stop">, string>;
1747
1780
  nonResumableReason: string;
1781
+ machine?: HerdrMachineReference;
1748
1782
  }
1749
1783
 
1750
1784
  export interface ExternalJobRunnerStatus {
@@ -1796,6 +1830,7 @@ export interface ExternalProcessStatus {
1796
1830
  stderrBytes?: number;
1797
1831
  stdoutTruncated?: boolean;
1798
1832
  stderrTruncated?: boolean;
1833
+ machine?: ExternalCliMachineStatus;
1799
1834
  }
1800
1835
 
1801
1836
  export interface AsyncStatus {
@@ -1928,8 +1963,7 @@ export interface AsyncStatus {
1928
1963
  thinking?: string;
1929
1964
  contextLimit?: number;
1930
1965
  thinkingCeiling?: ThinkingLevel;
1931
- attemptedModels?: string[];
1932
- modelAttempts?: ModelAttempt[];
1966
+ requestedModel?: string;
1933
1967
  /** True when the child input exceeded the model context window. */
1934
1968
  contextOverflow?: boolean;
1935
1969
  totalCost?: CostSummary;
@@ -2070,7 +2104,7 @@ export interface ForegroundResumeChild {
2070
2104
  /** Private bounded launch fields needed to preserve the child contract on resume. */
2071
2105
  resumeContract?: {
2072
2106
  modelResponseAliases?: Record<string, string[]>;
2073
- outputSchema?: JsonSchemaObject;
2107
+ outputSchema?: JsonSchemaObject | false;
2074
2108
  agentContract?: AgentContract;
2075
2109
  acceptance?: AcceptanceInput;
2076
2110
  output?: string | boolean;
@@ -2079,6 +2113,7 @@ export interface ForegroundResumeChild {
2079
2113
  launchContractDigest?: string;
2080
2114
  /** Private retained launch authority. Never project into status or result output. */
2081
2115
  extensionBindings?: ExtensionBindings;
2116
+ requiredExtensions?: RequiredChildExtensionSnapshot;
2082
2117
  launchResolvedExtensions?: LaunchResolvedChildExtensions;
2083
2118
  runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
2084
2119
  execution?: ExecutionProjection;
@@ -2256,8 +2291,8 @@ export interface SubagentState {
2256
2291
  lastUiContext: ExtensionContext | null;
2257
2292
  poller: NodeJS.Timeout | null;
2258
2293
  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 }>;
2294
+ /** Session-owned terminal result payloads observed by the result watcher, keyed by run id and pruned by the completion TTL. */
2295
+ completedResults?: Map<string, { sessionId: string; seenAt: number; completion: WaitCompletion }>;
2261
2296
  watcher: FSWatcher | null;
2262
2297
  watcherRestartTimer: ReturnType<typeof setTimeout> | null;
2263
2298
  resultFileCoalescer: {
@@ -2360,6 +2395,8 @@ export interface RunSyncOptions {
2360
2395
  unknownAgentDiagnosticContext?: import("../agents/agents.ts").UnknownAgentDiagnosticContext;
2361
2396
  /** Session factory for the in-process child; defaults to the process-wide factory. */
2362
2397
  childSessionFactory?: import("../runs/shared/child-session.ts").ChildSessionFactory;
2398
+ /** Invoking parent registry inherited only by its local foreground launch. */
2399
+ parentProviderRegistry?: import("../runs/shared/child-session.ts").ParentProviderRegistry;
2363
2400
  /** The launching executor's own child runtime when it is itself an in-process child. */
2364
2401
  childRuntime?: import("../runs/shared/child-runtime-config.ts").ChildRuntimeConfig;
2365
2402
  /** Fires once the child session exists and can be steered. */
@@ -2371,6 +2408,10 @@ export interface RunSyncOptions {
2371
2408
  /** Resolved launch context for this child. */
2372
2409
  context?: "fresh" | "fork";
2373
2410
  cwd?: string;
2411
+ /** Resolved pane-native saved-machine placement. */
2412
+ machine?: HerdrMachineReference;
2413
+ /** Explicit read override resolved by the remote ambient agent profile. */
2414
+ remoteReads?: string[] | false;
2374
2415
  /** Original cwd input retained for launch diagnostics. */
2375
2416
  requestedCwd?: string;
2376
2417
  signal?: AbortSignal;
@@ -2431,6 +2472,7 @@ export interface RunSyncOptions {
2431
2472
  /** Override the agent's default thinking level for this run */
2432
2473
  thinkingOverride?: AgentConfig["thinking"];
2433
2474
  thinkingCeiling?: ThinkingLevel;
2475
+ requiredExtensions?: RequiredChildExtensionSnapshot;
2434
2476
  extensionBindings?: ExtensionBindings;
2435
2477
  /** Package-internal one-use authorization for one foreground workflow child. */
2436
2478
  workflowChildPermitLaunch?: WorkflowChildPermitContext;
@@ -2443,7 +2485,7 @@ export interface RunSyncOptions {
2443
2485
  runtimeSnapshotHost?: import("../runs/shared/mcp-direct-tool-allowlist.ts").McpRuntimeSnapshotHost;
2444
2486
  /** Builtin tool names the host runtime provides; used to intersect agent-declared tools. */
2445
2487
  hostAvailableBuiltins?: readonly string[];
2446
- /** Optional subagent model-scope enforcement for fallback candidates */
2488
+ /** Optional subagent model-scope enforcement. */
2447
2489
  modelScope?: ModelScopeRule | ModelScopeRule[];
2448
2490
  /** Skills to make available (overrides agent default if provided) */
2449
2491
  skills?: string[];
@@ -2505,11 +2547,6 @@ export interface ScheduledRunsConfig {
2505
2547
  storeRoot?: string;
2506
2548
  }
2507
2549
 
2508
- export interface ModelExclusionsConfig {
2509
- /** Default duration in milliseconds. A lower configured value also shortens active cached exclusions. */
2510
- defaultTtlMs?: number;
2511
- }
2512
-
2513
2550
  export type FleetViewPlacement = "aboveEditor" | "belowEditor";
2514
2551
 
2515
2552
  export const FLEET_KEYBINDING_ACTIONS = [
@@ -2572,8 +2609,6 @@ export interface ExtensionConfig {
2572
2609
  fleetKeybindings?: FleetKeybindingsConfig;
2573
2610
  /** Show the under-editor async runs widget. Defaults to true, including when FleetView is enabled. */
2574
2611
  asyncWidget?: boolean;
2575
- /** Configure the process-wide TTL policy for persisted model exclusions. */
2576
- modelExclusions?: ModelExclusionsConfig;
2577
2612
  /** Exact provider/model candidates mapped to operator-declared equivalent response IDs. Empty arrays add no accepted IDs. */
2578
2613
  modelResponseAliases?: Record<string, string[]>;
2579
2614
  /** Tool description variant registered for the parent-facing subagent tool. Defaults to split metadata. */
@@ -2618,6 +2653,12 @@ export interface ExtensionConfig {
2618
2653
  * are rejected with an error.
2619
2654
  */
2620
2655
  toolTimeoutMs?: number;
2656
+ /**
2657
+ * Global default for the async single-agent `checkpointBeforeDeadlineMs` launch option: the runner requests that
2658
+ * the child checkpoint and stop this many milliseconds before its run deadline (best-effort). The call param wins; values that
2659
+ * leave no run time before the checkpoint disarm it.
2660
+ */
2661
+ checkpointBeforeDeadlineMs?: number;
2621
2662
  control?: ControlConfig;
2622
2663
  completionBatch?: CompletionBatchConfig;
2623
2664
  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")}`);
package/src/tui/render.ts CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  WIDGET_KEY,
27
27
  } from "../shared/types.ts";
28
28
  import { previewDisplayText, sanitizeDisplayText, truncateDisplayText } from "../shared/display-text.ts";
29
- import { FLEET_OPEN_SHORTCUT, formatShortcutLabel } from "../shared/shortcuts.ts";
29
+ import { formatShortcutLabel } from "../shared/shortcuts.ts";
30
30
  import { formatContextUsage, formatTokens, formatUsage, formatDuration, formatModelThinking, formatToolCall, formatTokenUsage, shortenPath } from "../shared/formatters.ts";
31
31
  import { getDisplayItems, getSingleResultOutput, PROMPT_REDACTED } from "../shared/utils.ts";
32
32
  import { flatToLogicalStepIndex } from "../runs/background/parallel-groups.ts";
@@ -95,11 +95,11 @@ function liveDetailKeyText(): string {
95
95
  }
96
96
 
97
97
  export function liveDetailHintText(): string {
98
- return `Press ${liveDetailKeyText()} for live detail · ${formatShortcutLabel(FLEET_OPEN_SHORTCUT)} Fleet`;
98
+ return `Press ${liveDetailKeyText()} for live detail`;
99
99
  }
100
100
 
101
101
  function workflowDetailHintText(): string {
102
- return `Press ${liveDetailKeyText()} for details · ${formatShortcutLabel(FLEET_OPEN_SHORTCUT)} Fleet`;
102
+ return `Press ${liveDetailKeyText()} for details`;
103
103
  }
104
104
 
105
105
  function foregroundSingleHintText(shortcut?: string): string {
@@ -2545,6 +2545,10 @@ function buildSingleLineWidgetLines(jobs: AsyncJobState[], theme: Theme, width:
2545
2545
  if (counts.failed.length > 0) parts.push(`${counts.failed.length} failed`);
2546
2546
  if (counts.stopped.length > 0) parts.push(`${counts.stopped.length} stopped`);
2547
2547
  if (counts.paused.length > 0) parts.push(`${counts.paused.length} paused`);
2548
+ for (const status of ["partial", "rejected"] as const) {
2549
+ const count = jobs.filter((job) => job.status === status).length;
2550
+ if (count > 0) parts.push(`${count} ${status}`);
2551
+ }
2548
2552
  if (!hasActive && counts.complete.length > 0) parts.push(`${counts.complete.length}/${jobs.length} done`);
2549
2553
  return [truncLine(`${theme.fg(hasActive ? "accent" : "dim", glyph)} ${theme.fg(hasActive ? "accent" : "dim", "subagents")} (${parts.join(", ") || `${jobs.length} total`})`, width)];
2550
2554
  }
@@ -2817,6 +2821,7 @@ function buildWidgetComponent(jobs: AsyncJobState[], ui: ExtensionContext["ui"])
2817
2821
  let cachedExpanded: boolean | undefined;
2818
2822
  let cachedLines: string[] | undefined;
2819
2823
  let cachedCoverage = "[]";
2824
+ let collapsed = false;
2820
2825
  const invalidate = (): void => {
2821
2826
  cachedLines = undefined;
2822
2827
  resetWidgetLayoutSession();
@@ -2830,6 +2835,14 @@ function buildWidgetComponent(jobs: AsyncJobState[], ui: ExtensionContext["ui"])
2830
2835
  };
2831
2836
  asyncWidgetUpdates.set(ui, update);
2832
2837
  const component = Object.assign(container, {
2838
+ // Only the mouse fields used here, without requiring newer Pi type exports.
2839
+ handleMouse(event: { type: string; button: string; y: number; shift: boolean; alt: boolean; ctrl: boolean }) {
2840
+ if (event.type !== "click" || event.button !== "left" || event.y !== 0) return undefined;
2841
+ if (event.shift || event.alt || event.ctrl) return undefined;
2842
+ collapsed = !collapsed;
2843
+ invalidate();
2844
+ return { handled: true };
2845
+ },
2833
2846
  dispose(): void {
2834
2847
  if (asyncWidgetUpdates.get(ui) === update) asyncWidgetUpdates.delete(ui);
2835
2848
  if (asyncWidgetInvalidations.get(ui) === invalidate) asyncWidgetInvalidations.delete(ui);
@@ -2872,7 +2885,10 @@ function buildWidgetComponent(jobs: AsyncJobState[], ui: ExtensionContext["ui"])
2872
2885
  cachedRenderWidth = renderWidth;
2873
2886
  cachedFrame = frame;
2874
2887
  cachedExpanded = expanded;
2875
- cachedLines = fitAdaptiveWidgetLines(roots, buildLines, theme, width, expanded, frame, projectionFor).map((line) => paddedWidgetLine(line, renderWidth));
2888
+ cachedLines = (collapsed
2889
+ ? buildSingleLineWidgetLines(jobs, theme, width, frame)
2890
+ : fitAdaptiveWidgetLines(roots, buildLines, theme, width, expanded, frame, projectionFor)
2891
+ ).map((line) => paddedWidgetLine(line, renderWidth));
2876
2892
  return cachedLines;
2877
2893
  };
2878
2894
  return component;
@@ -3468,9 +3484,6 @@ export function renderSubagentResult(
3468
3484
  if (r.skillsWarning) {
3469
3485
  c.addChild(new Text(fit(theme.fg("warning", `Warning: ${r.skillsWarning}`)), 0, 0));
3470
3486
  }
3471
- if (r.attemptedModels && r.attemptedModels.length > 1) {
3472
- c.addChild(new Text(fit(theme.fg("dim", `Fallbacks: ${r.attemptedModels.join(" → ")}`)), 0, 0));
3473
- }
3474
3487
  c.addChild(new Text(fit(theme.fg("dim", formatUsage(r.usage, r.model))), 0, 0));
3475
3488
  if (r.sessionFile) {
3476
3489
  c.addChild(new Text(fit(theme.fg("dim", `Session: ${shortenPath(r.sessionFile)}`)), 0, 0));
@@ -3647,9 +3660,6 @@ export function renderSubagentResult(
3647
3660
  if (r.skillsWarning) {
3648
3661
  c.addChild(new Text(fit(theme.fg("warning", ` Warning: ${r.skillsWarning}`)), 0, 0));
3649
3662
  }
3650
- if (r.attemptedModels && r.attemptedModels.length > 1) {
3651
- c.addChild(new Text(fit(theme.fg("dim", ` fallbacks: ${r.attemptedModels.join(" → ")}`)), 0, 0));
3652
- }
3653
3663
 
3654
3664
  if (rRunning && rProg) {
3655
3665
  if (rProg.skills?.length) {