pi-subagents 0.56.0 → 0.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/agents/claude-code-writer.md +15 -0
  3. package/agents/claude-code.md +15 -0
  4. package/agents/codex-exec-writer.md +15 -0
  5. package/agents/codex-exec.md +15 -0
  6. package/agents/cursor-agent-writer.md +14 -0
  7. package/agents/cursor-agent.md +14 -0
  8. package/docs/agents.md +124 -21
  9. package/docs/configuration.md +37 -0
  10. package/docs/extension-api.md +41 -2
  11. package/docs/models.md +3 -3
  12. package/docs/observability.md +8 -7
  13. package/docs/tool-reference.md +14 -3
  14. package/docs/workflows.md +44 -0
  15. package/package.json +1 -1
  16. package/skills/pi-subagents/SKILL.md +2 -0
  17. package/skills/pi-subagents/references/execution-controls.md +2 -2
  18. package/skills/pi-subagents/references/management-authoring-rpc.md +1 -0
  19. package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
  20. package/src/agents/agent-management.ts +36 -5
  21. package/src/agents/agent-refinements.ts +4 -4
  22. package/src/agents/agent-serializer.ts +5 -0
  23. package/src/agents/agents.ts +257 -51
  24. package/src/agents/builtin-names.ts +6 -0
  25. package/src/agents/runtime-agent-events.ts +70 -0
  26. package/src/agents/runtime-agent-registry.ts +18 -4
  27. package/src/api/agents.ts +10 -5
  28. package/src/api/preflight.ts +28 -3
  29. package/src/extension/config.ts +70 -0
  30. package/src/extension/doctor.ts +3 -3
  31. package/src/extension/index.ts +33 -8
  32. package/src/extension/public-execution.ts +29 -13
  33. package/src/extension/rpc.ts +55 -19
  34. package/src/extension/schemas.ts +6 -5
  35. package/src/extension/tool-description.ts +18 -12
  36. package/src/inspectors/herdr/actions.ts +2 -1
  37. package/src/inspectors/herdr/inspector-runner.ts +2 -10
  38. package/src/inspectors/herdr/session-roots-codec.ts +42 -0
  39. package/src/integrations/herdr-status.ts +51 -3
  40. package/src/runs/background/active-async-capacity.ts +77 -10
  41. package/src/runs/background/async-execution.ts +127 -19
  42. package/src/runs/background/async-job-tracker.ts +5 -0
  43. package/src/runs/background/async-resume.ts +6 -2
  44. package/src/runs/background/async-retention.ts +20 -3
  45. package/src/runs/background/async-status.ts +7 -0
  46. package/src/runs/background/chain-append.ts +2 -0
  47. package/src/runs/background/chain-root-attachment.ts +15 -1
  48. package/src/runs/background/fleet-view.ts +16 -10
  49. package/src/runs/background/inspect-rpc.ts +8 -8
  50. package/src/runs/background/notify.ts +26 -3
  51. package/src/runs/background/result-delivery-ownership.ts +45 -0
  52. package/src/runs/background/result-files.ts +27 -14
  53. package/src/runs/background/result-watcher.ts +36 -15
  54. package/src/runs/background/run-status.ts +33 -6
  55. package/src/runs/background/scheduled-runs.ts +7 -1
  56. package/src/runs/background/subagent-runner.ts +239 -56
  57. package/src/runs/background/wait-completions.ts +4 -0
  58. package/src/runs/foreground/execution.ts +92 -11
  59. package/src/runs/foreground/foreground-control.ts +6 -0
  60. package/src/runs/foreground/foreground-history.ts +22 -1
  61. package/src/runs/foreground/subagent-executor.ts +322 -102
  62. package/src/runs/foreground/workflow-detach-reconcile.ts +144 -18
  63. package/src/runs/shared/child-protocol.ts +21 -7
  64. package/src/runs/shared/claude-code-adapter.ts +129 -0
  65. package/src/runs/shared/codex-exec-adapter.ts +129 -0
  66. package/src/runs/shared/completion-guard.ts +4 -3
  67. package/src/runs/shared/cursor-agent-adapter.ts +114 -0
  68. package/src/runs/shared/dynamic-fanout.ts +3 -3
  69. package/src/runs/shared/external-cli-contract.ts +167 -0
  70. package/src/runs/shared/external-cli-preflight.ts +122 -0
  71. package/src/runs/shared/external-cli-runner.ts +348 -55
  72. package/src/runs/shared/fast-mode-extension.ts +5 -5
  73. package/src/runs/shared/launch-cwd.ts +16 -0
  74. package/src/runs/shared/long-running-guard.ts +2 -1
  75. package/src/runs/shared/mcp-config-sources.ts +386 -0
  76. package/src/runs/shared/mcp-direct-tool-allowlist.ts +155 -42
  77. package/src/runs/shared/model-exclusions.ts +69 -7
  78. package/src/runs/shared/model-fallback.ts +39 -5
  79. package/src/runs/shared/mutation-evidence.ts +7 -2
  80. package/src/runs/shared/nested-events.ts +3 -1
  81. package/src/runs/shared/nested-render.ts +2 -2
  82. package/src/runs/shared/parallel-utils.ts +8 -1
  83. package/src/runs/shared/pi-args.ts +61 -6
  84. package/src/runs/shared/process-signal.ts +13 -0
  85. package/src/runs/shared/run-history.ts +21 -1
  86. package/src/runs/shared/single-output.ts +17 -0
  87. package/src/runs/shared/subagent-prompt-runtime.ts +85 -9
  88. package/src/shared/fork-context.ts +21 -0
  89. package/src/shared/formatters.ts +13 -1
  90. package/src/shared/launch-contract.ts +4 -0
  91. package/src/shared/pruned-fork.ts +450 -0
  92. package/src/shared/session-file-trust.ts +19 -0
  93. package/src/shared/session-tokens.ts +14 -3
  94. package/src/shared/settings.ts +10 -2
  95. package/src/shared/shortcuts.ts +17 -0
  96. package/src/shared/types.ts +160 -10
  97. package/src/shared/utils.ts +6 -29
  98. package/src/shared/workflow-child-permit.ts +116 -0
  99. package/src/slash/delegation-adapters.ts +0 -1
  100. package/src/slash/slash-commands.ts +8 -6
  101. package/src/slash/subagents-admin.ts +3 -0
  102. package/src/tui/fleet-status.ts +27 -10
  103. package/src/tui/fleet-transcript.ts +11 -5
  104. package/src/tui/fleet.ts +28 -13
  105. package/src/tui/render.ts +55 -21
  106. package/src/workflows/scripted-workflow.ts +299 -31
  107. package/src/workflows/workflow-child-summary.ts +117 -0
  108. package/src/workflows/workflow-receipt.ts +155 -5
@@ -14,6 +14,7 @@ import type { AuthorityPolicyConfig } from "../policy/authority.ts";
14
14
  import type { ThinkingLevel } from "./model-info.ts";
15
15
  import type { GlobalMissionIndexRecord, MissionRecord, MissionStoreConfig } from "../missions/types.ts";
16
16
  import type { ExtensionBindings } from "../runs/shared/extension-bindings.ts";
17
+ import type { WorkflowChildPermitContext } from "./workflow-child-permit.ts";
17
18
 
18
19
  // ============================================================================
19
20
  // Basic Types
@@ -75,16 +76,48 @@ export interface WorkflowGraphSnapshot {
75
76
 
76
77
  export type WorkflowReceiptState = "complete" | "failed" | "paused" | "stopped";
77
78
 
79
+ export type WorkflowTerminalResolution = "settled-awaiting-resume" | "failed-child" | "interrupted-child";
80
+
81
+ export interface WorkflowTerminalOutcome {
82
+ state: "partial";
83
+ reason: "budget_exhausted" | "timeout";
84
+ }
85
+
86
+ export interface WorkflowRecoveryAction {
87
+ key: string;
88
+ call: "runs.run";
89
+ resume: { workflowRunId: string; key: string; latest: true };
90
+ taskRequired: true;
91
+ }
92
+
93
+ export interface WorkflowChildSummaryV1 {
94
+ version: 1;
95
+ parentToolCallId: string;
96
+ workflowRunId: string;
97
+ inventoryComplete: boolean;
98
+ workflowState: "queued" | "running" | "completed" | "failed" | "paused" | "stopped";
99
+ children: Array<{
100
+ childId: string;
101
+ runId?: string;
102
+ agent?: string;
103
+ model?: string;
104
+ thinking?: string;
105
+ state: "pending" | "running" | "completed" | "failed" | "paused" | "stopped" | "rejected" | "detached";
106
+ }>;
107
+ }
108
+
78
109
  type WorkflowReceiptEntryResumability =
79
110
  | { latestRunId: string; resumability: { state: "resumable" } }
80
111
  | { latestRunId?: string; resumability: { state: "not-resumable"; reason: string } };
81
112
 
82
113
  export type WorkflowReceiptEntry = WorkflowReceiptEntryResumability & {
83
114
  key: string;
115
+ terminalOutcome?: WorkflowTerminalOutcome;
84
116
  agent?: string;
85
117
  requestedContext?: "fresh" | "fork";
86
118
  resolvedContext?: "fresh" | "fork" | "mixed";
87
119
  outputReference?: string;
120
+ externalAdapter?: ExternalCliReceiptMetadata;
88
121
  continuation: { runIds: string[] };
89
122
  };
90
123
 
@@ -94,6 +127,10 @@ export interface WorkflowReceipt {
94
127
  state: WorkflowReceiptState;
95
128
  createdAt: number;
96
129
  entries: Record<string, WorkflowReceiptEntry>;
130
+ workflowChildren?: WorkflowChildSummaryV1;
131
+ workflowResolution?: WorkflowTerminalResolution;
132
+ terminalOutcome?: WorkflowTerminalOutcome;
133
+ recovery?: WorkflowRecoveryAction[];
97
134
  }
98
135
 
99
136
  export interface SavedOutputReference {
@@ -166,6 +203,10 @@ export interface TokenUsage {
166
203
  input: number;
167
204
  output: number;
168
205
  total: number;
206
+ /** Input plus cache-read tokens for the latest assistant turn. */
207
+ window?: number;
208
+ /** Largest window observed in this usage scope. */
209
+ windowPeak?: number;
169
210
  }
170
211
 
171
212
  export type ActivityState = "active_long_running" | "needs_attention";
@@ -352,8 +393,24 @@ export interface FileMutationEffect {
352
393
  evidence?: TrackedMutationEvidence;
353
394
  }
354
395
 
396
+ export interface SettlementDiagnostic {
397
+ finalTextPresent: boolean;
398
+ mutation: {
399
+ expected: boolean;
400
+ attempted: boolean;
401
+ observed: boolean;
402
+ };
403
+ requiredOutput?: {
404
+ kind: "file-only" | "structured";
405
+ path: string;
406
+ missing: boolean;
407
+ };
408
+ afterCompactionSettlement?: boolean;
409
+ }
410
+
355
411
  export interface EffectsProjection {
356
412
  fileMutation?: FileMutationEffect;
413
+ settlementDiagnostic?: SettlementDiagnostic;
357
414
  }
358
415
 
359
416
  export interface TrackedMutationSnapshot {
@@ -582,9 +639,11 @@ export interface SteeringRecoveryDescriptor {
582
639
  extensions?: string[];
583
640
  subagentOnlyExtensions?: string[];
584
641
  mcpDirectTools?: string[];
642
+ mutationTools?: string[];
585
643
  systemPrompt?: string;
586
644
  systemPromptMode: "append" | "replace";
587
645
  inheritProjectContext: boolean;
646
+ inheritGlobalContext: boolean;
588
647
  inheritSkills: boolean;
589
648
  skills?: string[];
590
649
  skillPath?: string[];
@@ -703,6 +762,8 @@ export interface AgentProgress {
703
762
  thinking?: string;
704
763
  inputTokens?: number;
705
764
  outputTokens?: number;
765
+ window?: number;
766
+ windowPeak?: number;
706
767
  durationMs: number;
707
768
  error?: string;
708
769
  failedTool?: string;
@@ -1044,6 +1105,8 @@ export interface SingleResult {
1044
1105
  watchdog?: ChildWatchdogProgress;
1045
1106
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
1046
1107
  capabilityAudit?: SubagentCapabilityAudit;
1108
+ runner?: ExternalCliRunnerStatus | ExternalJobRunnerStatus;
1109
+ externalProcess?: ExternalProcessStatus;
1047
1110
  }
1048
1111
 
1049
1112
  export interface SpawnBudgetGrant {
@@ -1090,6 +1153,7 @@ export interface WaitCompletion {
1090
1153
  /** Versioned bounded output archive retained with the durable completion replay. */
1091
1154
  archivePath?: string;
1092
1155
  results?: WaitCompletionChild[];
1156
+ workflowChildren?: WorkflowChildSummaryV1;
1093
1157
  }
1094
1158
 
1095
1159
  export interface Details {
@@ -1100,6 +1164,7 @@ export interface Details {
1100
1164
  /** Run-level context summary. "mixed" when children resolved to different modes. */
1101
1165
  context?: "fresh" | "fork" | "mixed";
1102
1166
  results: SingleResult[];
1167
+ workflowChildren?: WorkflowChildSummaryV1;
1103
1168
  /**
1104
1169
  * Terminal completion payloads for runs this subagent_wait call observed
1105
1170
  * finishing. Async completions travel as result files that are consumed and
@@ -1375,9 +1440,11 @@ export type AgentRunnerConfig =
1375
1440
  | { type: "pi" }
1376
1441
  | {
1377
1442
  type: "external-cli";
1443
+ adapter?: "codex-exec" | "codex-exec-writer" | "claude-code" | "claude-code-writer" | "cursor-agent" | "cursor-agent-writer";
1378
1444
  command: string;
1379
1445
  args?: string[];
1380
1446
  promptDelivery?: "stdin";
1447
+ capabilities?: ExternalCliCapabilityNarrowing;
1381
1448
  }
1382
1449
  | {
1383
1450
  type: "external-job";
@@ -1385,18 +1452,46 @@ export type AgentRunnerConfig =
1385
1452
  options?: Record<string, unknown>;
1386
1453
  };
1387
1454
 
1455
+ export type ExternalCliCapabilityNarrowing = Partial<Record<"steer" | "resume" | "structuredOutput" | "toolEvents" | "supervisor" | "forkContext" | "extensionBindings", false>>;
1456
+
1457
+ export interface ExternalCliCapabilities {
1458
+ stop: true;
1459
+ steer: false;
1460
+ resume: false;
1461
+ structuredOutput: false;
1462
+ toolEvents: false;
1463
+ supervisor: "unsupported";
1464
+ forkContext: false;
1465
+ extensionBindings: false;
1466
+ }
1467
+
1468
+ export interface ExternalCliReceiptMetadata {
1469
+ 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" };
1470
+ capabilities: ExternalCliCapabilities;
1471
+ safety?:
1472
+ | { sandbox: "read-only"; approvalPolicy: "never"; ephemeral: true }
1473
+ | { access: "workspace-write"; sandbox: "workspace-write"; approvalPolicy: "never"; ephemeral: true }
1474
+ | { permissionMode: "plan"; tools: "none"; mcp: "empty-strict"; settingSources: "none"; sessionPersistence: false }
1475
+ | { access: "read-only"; authentication: "existing-cli-required"; permissionMode: "plan"; tools: "none"; mcp: "empty-strict"; settingSources: "user"; userSettingsTrust: "required"; sessionPersistence: false }
1476
+ | { access: "workspace-write"; authentication: "existing-cli-required"; permissionMode: "acceptEdits"; tools: "Read,Write,Edit,Glob,Grep"; mcp: "empty-strict"; settingSources: "user"; userSettingsTrust: "required"; sessionPersistence: false }
1477
+ | { access: "read-only"; authentication: "cursor-api-key-or-existing-login"; mode: "ask"; sandbox: "enabled"; workspaceTrust: "existing-required"; sessionReuse: false }
1478
+ | { access: "workspace-write"; authentication: "cursor-api-key-or-existing-login"; mode: "print"; sandbox: "enabled"; workspaceTrust: "existing-required"; sessionReuse: false };
1479
+ outputArtifacts?: { stdoutPath?: string; stderrPath?: string; finalOutputPath?: string };
1480
+ handoff: { mode: "fresh" };
1481
+ supervisor: { mode: "unsupported"; reason: string };
1482
+ nonResumableReason: string;
1483
+ }
1484
+
1388
1485
  export interface ExternalCliRunnerStatus {
1389
1486
  type: "external-cli";
1390
1487
  command: string;
1391
1488
  args: string[];
1392
- promptDelivery: "stdin";
1393
- capabilities: {
1394
- stop: true;
1395
- steer: false;
1396
- resume: false;
1397
- structuredOutput: false;
1398
- toolEvents: false;
1399
- };
1489
+ promptDelivery: "stdin" | "prompt-file";
1490
+ adapter: ExternalCliReceiptMetadata["adapter"];
1491
+ safety?: ExternalCliReceiptMetadata["safety"];
1492
+ capabilities: ExternalCliCapabilities;
1493
+ unsupportedReasons: Record<Exclude<keyof ExternalCliCapabilities, "stop">, string>;
1494
+ nonResumableReason: string;
1400
1495
  }
1401
1496
 
1402
1497
  export interface ExternalJobRunnerStatus {
@@ -1443,6 +1538,11 @@ export interface ExternalProcessStatus {
1443
1538
  processSignal?: string | null;
1444
1539
  stdoutPath: string;
1445
1540
  stderrPath: string;
1541
+ finalOutputPath?: string;
1542
+ stdoutBytes?: number;
1543
+ stderrBytes?: number;
1544
+ stdoutTruncated?: boolean;
1545
+ stderrTruncated?: boolean;
1446
1546
  }
1447
1547
 
1448
1548
  export interface AsyncStatus {
@@ -1499,6 +1599,7 @@ export interface AsyncStatus {
1499
1599
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
1500
1600
  capabilityAudit?: SubagentCapabilityAudit;
1501
1601
  workflow?: Details["workflow"];
1602
+ workflowChildren?: WorkflowChildSummaryV1;
1502
1603
  parentWorkflowRunId?: string;
1503
1604
  workflowKey?: string;
1504
1605
  /** Set when a durable schedule launched this run, so completions can name their origin. */
@@ -1557,6 +1658,7 @@ export interface AsyncStatus {
1557
1658
  skills?: string[];
1558
1659
  model?: string;
1559
1660
  thinking?: string;
1661
+ contextLimit?: number;
1560
1662
  thinkingCeiling?: ThinkingLevel;
1561
1663
  attemptedModels?: string[];
1562
1664
  modelAttempts?: ModelAttempt[];
@@ -1654,6 +1756,7 @@ export interface AsyncJobState {
1654
1756
  parentWorkflowRunId?: string;
1655
1757
  workflowKey?: string;
1656
1758
  workflow?: Details["workflow"];
1759
+ workflowChildren?: WorkflowChildSummaryV1;
1657
1760
  }
1658
1761
 
1659
1762
  export interface ForegroundResumeChild {
@@ -1671,6 +1774,8 @@ export interface ForegroundResumeChild {
1671
1774
  currentPath?: string;
1672
1775
  turnCount?: number;
1673
1776
  tokens?: number;
1777
+ window?: number;
1778
+ windowPeak?: number;
1674
1779
  toolCount?: number;
1675
1780
  exitCode?: number;
1676
1781
  error?: string;
@@ -1685,6 +1790,14 @@ export interface ForegroundResumeChild {
1685
1790
  detachedReason?: string;
1686
1791
  acceptance?: AcceptanceLedger;
1687
1792
  agentContract?: AgentContract;
1793
+ /** Private bounded launch fields needed to preserve the child contract on resume. */
1794
+ resumeContract?: {
1795
+ outputSchema?: JsonSchemaObject;
1796
+ agentContract?: AgentContract;
1797
+ acceptance?: AcceptanceInput;
1798
+ output?: string | boolean;
1799
+ outputMode?: OutputMode;
1800
+ };
1688
1801
  launchContractDigest?: string;
1689
1802
  /** Private retained launch authority. Never project into status or result output. */
1690
1803
  extensionBindings?: ExtensionBindings;
@@ -1723,6 +1836,8 @@ export interface ForegroundChildControl {
1723
1836
  tokens?: number;
1724
1837
  inputTokens?: number;
1725
1838
  outputTokens?: number;
1839
+ window?: number;
1840
+ windowPeak?: number;
1726
1841
  model?: string;
1727
1842
  thinking?: string;
1728
1843
  toolCount?: number;
@@ -1758,6 +1873,8 @@ export interface ForegroundRunControl {
1758
1873
  tokens?: number;
1759
1874
  inputTokens?: number;
1760
1875
  outputTokens?: number;
1876
+ window?: number;
1877
+ windowPeak?: number;
1761
1878
  model?: string;
1762
1879
  thinking?: string;
1763
1880
  toolCount?: number;
@@ -1808,6 +1925,8 @@ export interface SubagentState {
1808
1925
  parentSessionFile?: string | null;
1809
1926
  /** Extension-owned roots trusted for child session transcript reads. */
1810
1927
  trustedSessionRoots?: string[];
1928
+ /** Pi sessions base that caps exact runtime-recorded session file trust. */
1929
+ trustedSessionFileRoot?: string;
1811
1930
  /** Live async session roots created by this parent executor, keyed by run id. */
1812
1931
  liveAsyncSessionRoots?: Map<string, string>;
1813
1932
  /** Last valid parent session model observed for this session; used when continuation contexts omit ctx.model. */
@@ -1932,6 +2051,8 @@ export interface SubagentChildStatusEvent {
1932
2051
  // ============================================================================
1933
2052
 
1934
2053
  export interface RunSyncOptions {
2054
+ /** Exact discovery provenance for an unknown-agent error; omission uses defensive fallback discovery. */
2055
+ unknownAgentDiagnosticContext?: import("../agents/agents.ts").UnknownAgentDiagnosticContext;
1935
2056
  /** Opt-in global permission rules; missing tools remain allowed. */
1936
2057
  permissions?: import("../runs/shared/permissions.ts").PermissionConfig;
1937
2058
  /** Session id of the direct parent session for permission-system ask forwarding. */
@@ -1943,6 +2064,8 @@ export interface RunSyncOptions {
1943
2064
  /** Resolved launch context for this child. */
1944
2065
  context?: "fresh" | "fork";
1945
2066
  cwd?: string;
2067
+ /** Original cwd input retained for launch diagnostics. */
2068
+ requestedCwd?: string;
1946
2069
  signal?: AbortSignal;
1947
2070
  interruptSignal?: AbortSignal;
1948
2071
  timeoutMs?: number;
@@ -1998,10 +2121,14 @@ export interface RunSyncOptions {
1998
2121
  thinkingOverride?: AgentConfig["thinking"];
1999
2122
  thinkingCeiling?: ThinkingLevel;
2000
2123
  extensionBindings?: ExtensionBindings;
2124
+ /** Package-internal one-use authorization for one foreground workflow child. */
2125
+ workflowChildPermitLaunch?: WorkflowChildPermitContext;
2001
2126
  /** Registry models available for heuristic bare-model resolution */
2002
- availableModels?: Array<{ provider: string; id: string; fullId: string }>;
2127
+ availableModels?: Array<{ provider: string; id: string; fullId: string; contextWindow?: number }>;
2003
2128
  /** Current parent-session provider to prefer for ambiguous bare model ids */
2004
2129
  preferredModelProvider?: string;
2130
+ /** Parent Pi event host used to snapshot runtime-registered MCP servers before child launch. */
2131
+ runtimeSnapshotHost?: import("../runs/shared/mcp-direct-tool-allowlist.ts").McpRuntimeSnapshotHost;
2005
2132
  /** Optional subagent model-scope enforcement for fallback candidates */
2006
2133
  modelScope?: ModelScopeRule | ModelScopeRule[];
2007
2134
  /** Skills to make available (overrides agent default if provided) */
@@ -2063,6 +2190,11 @@ export interface ScheduledRunsConfig {
2063
2190
  storeRoot?: string;
2064
2191
  }
2065
2192
 
2193
+ export interface ModelExclusionsConfig {
2194
+ /** Default duration in milliseconds. A lower configured value also shortens active cached exclusions. */
2195
+ defaultTtlMs?: number;
2196
+ }
2197
+
2066
2198
  export type FleetViewPlacement = "aboveEditor" | "belowEditor";
2067
2199
 
2068
2200
  export const FLEET_KEYBINDING_ACTIONS = [
@@ -2097,10 +2229,24 @@ export interface MainWindowRendererConfig {
2097
2229
  compactResultMaxLines?: number;
2098
2230
  }
2099
2231
 
2232
+ export interface ForkContextConfig {
2233
+ /** Keep the complete fork by default, or summarize large text-only tool results before launch. */
2234
+ mode?: "full" | "pruned";
2235
+ /** Required pruning model when mode is "pruned". */
2236
+ model?: string;
2237
+ }
2238
+
2239
+ export interface ActiveAsyncCapacityConfig {
2240
+ /** Reclaim failed runner slots after this age when process proof is unknown; false keeps strict retention. */
2241
+ abandonedSlotReleaseAfterMs?: number | false;
2242
+ }
2243
+
2100
2244
  export interface ExtensionConfig {
2101
2245
  asyncByDefault?: boolean;
2102
2246
  /** Set the context for launches that omit an explicit context. */
2103
2247
  defaultSubagentContext?: "fresh" | "fork";
2248
+ /** Configure how every resolved fork session is prepared before child spawn. */
2249
+ forkContext?: ForkContextConfig;
2104
2250
  /** Optional shortcut that detaches the active foreground single-subagent run. */
2105
2251
  foregroundDetachShortcut?: string;
2106
2252
  /** Show the Claude Code-style navigable fleet. Defaults to true. */
@@ -2111,6 +2257,8 @@ export interface ExtensionConfig {
2111
2257
  fleetKeybindings?: FleetKeybindingsConfig;
2112
2258
  /** Show the under-editor async runs widget. Defaults to true, including when FleetView is enabled. */
2113
2259
  asyncWidget?: boolean;
2260
+ /** Configure the process-wide TTL policy for persisted model exclusions. */
2261
+ modelExclusions?: ModelExclusionsConfig;
2114
2262
  /** Tool description variant registered for the parent-facing subagent tool. Defaults to split metadata. */
2115
2263
  toolDescriptionMode?: ToolDescriptionMode;
2116
2264
  /** Inline chat rendering for the subagent tool. Defaults to rich. */
@@ -2130,6 +2278,8 @@ export interface ExtensionConfig {
2130
2278
  maxSubagentSpawnsPerRun?: number;
2131
2279
  /** Optional active top-level async run cap per parent session. Unset or 0 means unlimited. */
2132
2280
  maxActiveAsyncRunsPerSession?: number;
2281
+ /** Active-capacity cleanup policy. */
2282
+ capacity?: ActiveAsyncCapacityConfig;
2133
2283
  /** Global cap on simultaneously-running subagent tasks within a single run. Defaults to 20. */
2134
2284
  globalConcurrencyLimit?: number;
2135
2285
  /**
@@ -2285,7 +2435,7 @@ export const POLL_INTERVAL_MS = 250;
2285
2435
  export const WIDGET_ANIMATION_INTERVAL_MS = 1000;
2286
2436
  export const MAX_WIDGET_JOBS = 4;
2287
2437
  export const DEFAULT_SUBAGENT_MAX_DEPTH = 2;
2288
- export const SUBAGENT_ACTIONS = ["list", "get", "models", "children.list", "guide", "create", "update", "delete", "eject", "disable", "enable", "reset", "mission.create", "mission.list", "mission.show", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "worktree.discard", "refine", "refine.show", "refine.rollback", "inspector.open", "inspector.status", "inspector.close", "project.open", "project.status", "project.close", "status", "debug.run", "grant-spawn-budget", "interrupt", "resume", "steer", "stop", "dismiss", "doctor", "watchdog.status", "watchdog.check", "watchdog.configure", "watchdog.recommend-model", "schedule.create", "schedule.list", "schedule.show", "schedule.history", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"] as const;
2438
+ export const SUBAGENT_ACTIONS = ["list", "get", "models", "children.list", "guide", "validate", "create", "update", "delete", "eject", "disable", "enable", "reset", "mission.create", "mission.list", "mission.show", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "worktree.discard", "refine", "refine.show", "refine.rollback", "inspector.open", "inspector.status", "inspector.close", "project.open", "project.status", "project.close", "status", "debug.run", "grant-spawn-budget", "interrupt", "resume", "steer", "stop", "dismiss", "doctor", "watchdog.status", "watchdog.check", "watchdog.configure", "watchdog.recommend-model", "schedule.create", "schedule.list", "schedule.show", "schedule.history", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"] as const;
2289
2439
 
2290
2440
  export const DEFAULT_FORK_PREAMBLE =
2291
2441
  "You are a delegated subagent running from a fork of the parent session. " +
@@ -1,7 +1,3 @@
1
- /**
2
- * General utility functions for the subagent extension
3
- */
4
-
5
1
  import * as fs from "node:fs";
6
2
  import * as os from "node:os";
7
3
  import * as path from "node:path";
@@ -10,10 +6,6 @@ import { previewDisplayText, sanitizeDisplayText, truncateDisplayText } from "./
10
6
  import { formatToolCall } from "./formatters.ts";
11
7
  import type { AgentProgress, AsyncStatus, Details, DisplayItem, ErrorInfo, NestedRunSummary, SingleResult, ToolCallSummary, Usage } from "./types.ts";
12
8
 
13
- // ============================================================================
14
- // File System Utilities
15
- // ============================================================================
16
-
17
9
  const DEFAULT_CONFIG_DIR_NAME = ".pi";
18
10
  const PI_CODING_AGENT_PACKAGE_NAME = "@earendil-works/pi-coding-agent";
19
11
  export const PI_CODING_AGENT_PACKAGE_ROOT_ENV = "PI_SUBAGENTS_PI_CODING_AGENT_PACKAGE_ROOT";
@@ -96,9 +88,10 @@ export function getProjectConfigDir(projectRoot: string): string {
96
88
 
97
89
  export function getAgentDir(): string {
98
90
  const configured = process.env.PI_CODING_AGENT_DIR;
99
- if (configured === "~") return os.homedir();
100
- if (configured?.startsWith("~/")) return path.join(os.homedir(), configured.slice(2));
101
- return configured || path.join(os.homedir(), getConfigDirName(), "agent");
91
+ const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
92
+ if (configured === "~") return home;
93
+ if (configured?.startsWith("~/") || configured?.startsWith("~\\")) return path.join(home, configured.slice(2));
94
+ return configured || path.join(home, getConfigDirName(), "agent");
102
95
  }
103
96
 
104
97
  const statusCache = new Map<string, { mtime: number; ctime: number; size: number; ino: number; status: AsyncStatus }>();
@@ -253,10 +246,7 @@ function getOutputTail(outputFile: string | undefined, maxLines: number = 3): st
253
246
  }
254
247
  }
255
248
 
256
- /**
257
- * Get human-readable last activity time for a file
258
- */
259
- export function getLastActivity(outputFile: string | undefined): string {
249
+ export function getLastActivity(outputFile: string | undefined): string {
260
250
  if (!outputFile) return "";
261
251
  try {
262
252
  const stat = fs.statSync(outputFile);
@@ -265,14 +255,11 @@ function getOutputTail(outputFile: string | undefined, maxLines: number = 3): st
265
255
  if (ago < 60000) return `active ${Math.floor(ago / 1000)}s ago`;
266
256
  return `active ${Math.floor(ago / 60000)}m ago`;
267
257
  } catch {
268
- // Last-activity text is best effort; missing files should simply omit the hint.
258
+ // Last-activity text is best effort; missing files should omit the hint.
269
259
  return "";
270
260
  }
271
261
  }
272
262
 
273
- /**
274
- * Find the latest session file in a directory
275
- */
276
263
  export function findLatestSessionFile(sessionDir: string): string | null {
277
264
  if (!fs.existsSync(sessionDir)) return null;
278
265
  const files = fs.readdirSync(sessionDir)
@@ -289,9 +276,6 @@ export function findLatestSessionFile(sessionDir: string): string | null {
289
276
  return latest ? latest.path : null;
290
277
  }
291
278
 
292
- /**
293
- * Write a prompt to a temporary file
294
- */
295
279
  function writePrompt(agent: string, prompt: string): { dir: string; path: string } {
296
280
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
297
281
  const p = path.join(dir, `${agent.replace(/[^\w.-]/g, "_")}.md`);
@@ -299,13 +283,6 @@ function writePrompt(agent: string, prompt: string): { dir: string; path: string
299
283
  return { dir, path: p };
300
284
  }
301
285
 
302
- // ============================================================================
303
- // Message Parsing Utilities
304
- // ============================================================================
305
-
306
- /**
307
- * Get the final text output from a list of messages
308
- */
309
286
  export function getFinalOutput(messages: Message[]): string {
310
287
  const validTextParts: string[] = [];
311
288
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -0,0 +1,116 @@
1
+ import { stableJsonDigest } from "./launch-contract.ts";
2
+
3
+ export interface WorkflowChildPermitInput {
4
+ issuerPackage: string;
5
+ workflowRunId: string;
6
+ childKey: string;
7
+ agent: string;
8
+ launchContractDigest: string;
9
+ context: "fresh" | "fork";
10
+ }
11
+
12
+ export interface WorkflowChildPermitLaunch {
13
+ workflowRunId: string;
14
+ childKey: string;
15
+ agent: string;
16
+ launchContractDigest: string;
17
+ context: "fresh" | "fork";
18
+ runner: "pi";
19
+ }
20
+
21
+ export interface WorkflowChildPermitContext {
22
+ permit: WorkflowChildPermit;
23
+ workflowRunId: string;
24
+ childKey: string;
25
+ }
26
+
27
+ export interface WorkflowChildPermit {
28
+ readonly __workflowChildPermit: unique symbol;
29
+ }
30
+
31
+ interface WorkflowChildPermitRecord {
32
+ issuerPackage: string;
33
+ workflowRunId: string;
34
+ childKey: string;
35
+ agent: string;
36
+ expectedProjectionDigest: string;
37
+ state: "available" | "claimed" | "consumed";
38
+ }
39
+
40
+ const records = new WeakMap<object, WorkflowChildPermitRecord>();
41
+
42
+ function projectionDigest(input: Omit<WorkflowChildPermitLaunch, "workflowRunId">): string {
43
+ return stableJsonDigest({
44
+ version: 1,
45
+ childKey: input.childKey,
46
+ agent: input.agent,
47
+ launchContractDigest: input.launchContractDigest,
48
+ context: input.context,
49
+ runner: input.runner,
50
+ });
51
+ }
52
+
53
+ function required(value: string, label: string): string {
54
+ if (!value.trim() || value !== value.trim()) throw new Error(`${label} must be a non-empty trimmed string.`);
55
+ return value;
56
+ }
57
+
58
+ /** Package-internal first-slice permit. It is opaque, in-memory, and not serializable. */
59
+ export function createWorkflowChildPermit(input: WorkflowChildPermitInput): WorkflowChildPermit {
60
+ const permit = Object.freeze(Object.create(null)) as WorkflowChildPermit;
61
+ const record: WorkflowChildPermitRecord = {
62
+ issuerPackage: required(input.issuerPackage, "issuerPackage"),
63
+ workflowRunId: required(input.workflowRunId, "workflowRunId"),
64
+ childKey: required(input.childKey, "childKey"),
65
+ agent: required(input.agent, "agent"),
66
+ expectedProjectionDigest: projectionDigest({
67
+ childKey: input.childKey,
68
+ agent: input.agent,
69
+ launchContractDigest: required(input.launchContractDigest, "launchContractDigest"),
70
+ context: input.context,
71
+ runner: "pi",
72
+ }),
73
+ state: "available",
74
+ };
75
+ records.set(permit as object, record);
76
+ return permit;
77
+ }
78
+
79
+ export function validateWorkflowChildPermitRoot(permit: WorkflowChildPermit, workflowRunId: string): string | undefined {
80
+ const record = records.get(permit as object);
81
+ if (!record) return "Workflow child permit is invalid.";
82
+ if (record.state !== "available") return "Workflow child permit is already consumed.";
83
+ if (record.workflowRunId !== workflowRunId) return "Workflow child permit does not match this workflow root.";
84
+ return undefined;
85
+ }
86
+
87
+ /** Claim the first distinct launch attempt before validating its model-authored shape. */
88
+ export function claimWorkflowChildPermit(permit: WorkflowChildPermit, workflowRunId: string, childKey: string): string | undefined {
89
+ const record = records.get(permit as object);
90
+ if (!record) return "Workflow child permit is invalid.";
91
+ if (record.state !== "available") return "Workflow child permit is already consumed.";
92
+ if (record.workflowRunId !== workflowRunId) return "Workflow child permit does not match this workflow root.";
93
+ record.state = record.childKey === childKey ? "claimed" : "consumed";
94
+ if (record.childKey !== childKey) return "Workflow child permit child key mismatch.";
95
+ return undefined;
96
+ }
97
+
98
+ /** Verify and permanently consume the permit before the one native process spawn. */
99
+ export function consumeWorkflowChildPermit(permit: WorkflowChildPermit, launch: WorkflowChildPermitLaunch): string | undefined {
100
+ const record = records.get(permit as object);
101
+ if (!record) return "Workflow child permit is invalid.";
102
+ if (record.state === "available") return "Workflow child permit launch was not claimed.";
103
+ if (record.state === "consumed") return "Workflow child permit is already consumed.";
104
+ if (record.workflowRunId !== launch.workflowRunId) return "Workflow child permit does not match this workflow root.";
105
+ record.state = "consumed";
106
+ if (record.childKey !== launch.childKey) return "Workflow child permit child key mismatch.";
107
+ if (record.agent !== launch.agent) return "Workflow child permit agent mismatch.";
108
+ if (launch.runner !== "pi") return "Workflow child permit supports native Pi children only.";
109
+ if (record.expectedProjectionDigest !== projectionDigest(launch)) return "Workflow child permit does not match the final launch projection.";
110
+ return undefined;
111
+ }
112
+
113
+ export function workflowChildPermitConsumed(permit: WorkflowChildPermit): boolean {
114
+ const state = records.get(permit as object)?.state;
115
+ return state === "claimed" || state === "consumed";
116
+ }
@@ -298,7 +298,6 @@ export function toSubagentDelegationExecutionParams(request: SubagentDelegationR
298
298
  enforceHardTurnLimit: true,
299
299
  toolBudget: request.toolBudget,
300
300
  skill: request.skill,
301
- output: false,
302
301
  ...(request.result.kind === "structured" ? { outputSchema: request.result.schema } : {}),
303
302
  acceptance: false,
304
303
  artifacts: request.artifacts,
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { keyText, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
5
5
  import { Key, matchesKey, truncateToWidth, type Component, type KeyId, type TUI } from "@earendil-works/pi-tui";
6
- import { BUILTIN_AGENT_NAMES, discoverAgents, discoverAgentsAll, findBlockingAgentDiagnostic, resolveAgentName, type AgentConfig, type AgentDiscoveryDiagnostic, type AgentScope } from "../agents/agents.ts";
6
+ import { BUILTIN_AGENT_NAMES, discoverAgents, discoverAgentsAll, findBlockingAgentDiagnostic, formatUnknownAgentError, resolveAgentName, unknownAgentDiagnosticContext, type AgentConfig, type AgentDiscoveryDiagnostic, type AgentScope, type UnknownAgentDiagnosticContext } from "../agents/agents.ts";
7
7
  import { listRuntimeAgentConfigs, mergeRuntimeAgents } from "../agents/runtime-agent-registry.ts";
8
8
  import { resolveExistingReadPaths } from "../shared/settings.ts";
9
9
  import {
@@ -22,6 +22,7 @@ import { listAsyncRuns, formatAsyncRunProgressLabel, type AsyncRunSummary } from
22
22
  import { encodeInspectReply, handleInspectRpcArgs, INSPECT_WIDGET_KEY } from "../runs/background/inspect-rpc.ts";
23
23
  import { listScheduledRunSummaries } from "../runs/background/scheduled-runs.ts";
24
24
  import { SUBAGENT_FANOUT_CHILD_ENV } from "../runs/shared/pi-args.ts";
25
+ import { FLEET_OPEN_SHORTCUT } from "../shared/shortcuts.ts";
25
26
  import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.ts";
26
27
  import { registerPromptWorkflowCommands } from "./prompt-workflows.ts";
27
28
  import { openSubagentsAdmin } from "./subagents-admin.ts";
@@ -108,9 +109,9 @@ const extractExecutionFlags = (rawArgs: string): { args: string; bg: boolean; fo
108
109
  return { args, bg, fork };
109
110
  };
110
111
 
111
- function discoverSlashAgents(pi: ExtensionAPI, cwd: string, scope: AgentScope): { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[] } {
112
+ function discoverSlashAgents(pi: ExtensionAPI, cwd: string, scope: AgentScope): { agents: AgentConfig[]; agentDiagnostics?: AgentDiscoveryDiagnostic[]; unknownAgentDiagnosticContext: UnknownAgentDiagnosticContext } {
112
113
  const discovered = discoverAgents(cwd, scope);
113
- if (listRuntimeAgentConfigs(pi).length === 0) return discovered;
114
+ if (listRuntimeAgentConfigs(pi).length === 0) return { ...discovered, unknownAgentDiagnosticContext: unknownAgentDiagnosticContext(discovered) };
114
115
  const all = discoverAgentsAll(cwd);
115
116
  const configuredAgents: AgentConfig[] = [
116
117
  ...all.builtin,
@@ -118,7 +119,8 @@ function discoverSlashAgents(pi: ExtensionAPI, cwd: string, scope: AgentScope):
118
119
  ...(scope !== "project" ? all.user : []),
119
120
  ...(scope !== "user" ? all.project : []),
120
121
  ];
121
- return mergeRuntimeAgents(pi, discovered, configuredAgents);
122
+ const merged = mergeRuntimeAgents(pi, discovered, configuredAgents);
123
+ return { ...merged, unknownAgentDiagnosticContext: { ...unknownAgentDiagnosticContext(discovered), agents: merged.agents } };
122
124
  }
123
125
 
124
126
  const makeAgentCompletions = (pi: ExtensionAPI, state: SubagentState) => (prefix: string) => {
@@ -693,7 +695,7 @@ export function registerSlashCommands(
693
695
  : resolvedAgent.agent;
694
696
  const diagnostic = findBlockingAgentDiagnostic(agentName, candidates, discovered.agentDiagnostics);
695
697
  if (diagnostic || resolvedAgent.error || !resolvedAgent.agent) {
696
- ctx.ui.notify(diagnostic ? `Agent '${agentName}' has invalid configuration: ${diagnostic.error}` : resolvedAgent.error ?? `Unknown agent: ${agentName}`, "error");
698
+ ctx.ui.notify(diagnostic ? `Agent '${agentName}' has invalid configuration: ${diagnostic.error}` : resolvedAgent.error ?? formatUnknownAgentError(agentName, discovered.unknownAgentDiagnosticContext), "error");
697
699
  return;
698
700
  }
699
701
 
@@ -776,7 +778,7 @@ export function registerSlashCommands(
776
778
  handler: async (_args, ctx) => showFleet(ctx),
777
779
  });
778
780
 
779
- pi.registerShortcut(Key.ctrlAlt("f"), {
781
+ pi.registerShortcut(FLEET_OPEN_SHORTCUT, {
780
782
  description: "Open subagent fleet inspector",
781
783
  handler: async (ctx) => showFleet(ctx),
782
784
  });