pi-subagents 0.65.0 → 0.66.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 (104) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/README.md +1 -1
  3. package/agents/researcher.md +23 -13
  4. package/docs/agents.md +17 -3
  5. package/docs/configuration.md +34 -0
  6. package/docs/extension-api.md +94 -0
  7. package/docs/models.md +58 -1
  8. package/docs/observability.md +42 -2
  9. package/docs/tool-reference.md +11 -5
  10. package/docs/workflows.md +22 -7
  11. package/package.json +4 -1
  12. package/runner-server-preload.mjs +13 -0
  13. package/skills/pi-subagents/SKILL.md +2 -1
  14. package/skills/pi-subagents/references/execution-controls.md +19 -2
  15. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
  16. package/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
  17. package/src/agents/advertised-agent-prompt.ts +63 -0
  18. package/src/agents/agent-management.ts +14 -1
  19. package/src/agents/agent-serializer.ts +2 -0
  20. package/src/agents/agents.ts +8 -0
  21. package/src/api/preflight.ts +5 -1
  22. package/src/api/shared-types.ts +1 -1
  23. package/src/api/workflow-resources.ts +6 -0
  24. package/src/extension/config.ts +4 -2
  25. package/src/extension/index.ts +71 -4
  26. package/src/extension/public-execution.ts +0 -1
  27. package/src/extension/rpc.ts +4 -21
  28. package/src/extension/schemas.ts +9 -7
  29. package/src/extension/tool-description.ts +10 -5
  30. package/src/integrations/pi-web-session-liveness.ts +73 -0
  31. package/src/intercom/native-supervisor-channel.ts +102 -88
  32. package/src/intercom/supervisor-ui.ts +3 -2
  33. package/src/missions/workflow-state.ts +37 -16
  34. package/src/runs/background/active-async-capacity.ts +18 -18
  35. package/src/runs/background/async-execution.ts +8 -1
  36. package/src/runs/background/async-job-tracker.ts +35 -3
  37. package/src/runs/background/async-resume.ts +3 -1
  38. package/src/runs/background/async-retention.ts +9 -0
  39. package/src/runs/background/async-status-snapshot.ts +10 -12
  40. package/src/runs/background/async-status.ts +17 -9
  41. package/src/runs/background/auto-drain.ts +40 -29
  42. package/src/runs/background/chain-root-attachment.ts +8 -0
  43. package/src/runs/background/control-channel.ts +78 -44
  44. package/src/runs/background/notify.ts +88 -12
  45. package/src/runs/background/owned-process-tree.ts +6 -6
  46. package/src/runs/background/process-terminal.ts +23 -23
  47. package/src/runs/background/retained-nested-route-tracker.ts +96 -0
  48. package/src/runs/background/run-child-session.ts +62 -33
  49. package/src/runs/background/run-status.ts +75 -5
  50. package/src/runs/background/runner-aliases.ts +46 -8
  51. package/src/runs/background/runner-child-launch.ts +86 -0
  52. package/src/runs/background/stale-run-reconciler.ts +3 -1
  53. package/src/runs/background/subagent-runner.ts +430 -208
  54. package/src/runs/background/subagent-wait.ts +3 -0
  55. package/src/runs/background/wait-completions.ts +4 -0
  56. package/src/runs/foreground/async-steering-action.ts +19 -0
  57. package/src/runs/foreground/execution.ts +116 -26
  58. package/src/runs/foreground/foreground-history.ts +3 -1
  59. package/src/runs/foreground/prompt-audit.ts +9 -5
  60. package/src/runs/foreground/subagent-executor.ts +531 -227
  61. package/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
  62. package/src/runs/foreground/workflow-foreground-steering.ts +56 -2
  63. package/src/runs/shared/acceptance.ts +16 -3
  64. package/src/runs/shared/agent-contract.ts +1 -1
  65. package/src/runs/shared/async-status-projection.ts +47 -47
  66. package/src/runs/shared/child-hooks.ts +151 -2
  67. package/src/runs/shared/child-launch.ts +18 -13
  68. package/src/runs/shared/child-session.ts +55 -24
  69. package/src/runs/shared/child-tool-plan.ts +2 -2
  70. package/src/runs/shared/completion-evidence.ts +2 -2
  71. package/src/runs/shared/completion-guard.ts +1 -0
  72. package/src/runs/shared/host-step-status.ts +11 -11
  73. package/src/runs/shared/llm-intent-arbiter.ts +30 -20
  74. package/src/runs/shared/model-exclusions.ts +2 -1
  75. package/src/runs/shared/model-fallback.ts +41 -8
  76. package/src/runs/shared/nested-events.ts +8 -8
  77. package/src/runs/shared/orca-progress-tabs.ts +6 -0
  78. package/src/runs/shared/parallel-handoff.ts +57 -12
  79. package/src/runs/shared/parallel-utils.ts +3 -2
  80. package/src/runs/shared/readonly-drain-observation.ts +42 -0
  81. package/src/runs/shared/readonly-model-continuation.ts +69 -0
  82. package/src/runs/shared/readonly-session-evidence.ts +307 -0
  83. package/src/runs/shared/run-fanout-budget.ts +8 -8
  84. package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
  85. package/src/runs/shared/subagent-prompt-runtime.ts +13 -3
  86. package/src/runs/shared/worktree-cleanup-plan.ts +6 -3
  87. package/src/runs/shared/worktree-setup-command.ts +190 -0
  88. package/src/runs/shared/worktree.ts +403 -210
  89. package/src/shared/model-response-aliases.ts +13 -0
  90. package/src/shared/types.ts +89 -60
  91. package/src/shared/utils.ts +10 -2
  92. package/src/shared/watch-strategy.ts +2 -0
  93. package/src/shared/workflow-child-permit.ts +18 -13
  94. package/src/tui/fleet-status.ts +1 -1
  95. package/src/tui/fleet.ts +11 -5
  96. package/src/tui/render.ts +44 -15
  97. package/src/workflows/chat-progress.ts +3 -3
  98. package/src/workflows/scripted-workflow.ts +70 -16
  99. package/src/workflows/workflow-checklist.ts +15 -18
  100. package/src/workflows/workflow-child-summary.ts +57 -8
  101. package/src/workflows/workflow-preflight.ts +19 -19
  102. package/src/workflows/workflow-receipt.ts +3 -3
  103. package/src/workflows/workflow-resources.ts +96 -21
  104. package/src/workflows/workflow-settlement.ts +3 -0
@@ -0,0 +1,13 @@
1
+ export function validateModelResponseAliases(value: unknown, label = "config.modelResponseAliases"): void {
2
+ if (value === undefined) return;
3
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be a JSON object`);
4
+ for (const [candidate, aliases] of Object.entries(value)) {
5
+ const slash = candidate.indexOf("/");
6
+ if (slash <= 0 || !candidate.slice(0, slash).trim() || !candidate.slice(slash + 1).trim()) {
7
+ throw new Error(`${label} key ${JSON.stringify(candidate)} must be a non-empty provider/model ID`);
8
+ }
9
+ if (!Array.isArray(aliases) || aliases.some((alias) => typeof alias !== "string" || !alias.trim())) {
10
+ throw new Error(`${label}[${JSON.stringify(candidate)}] must be an array of non-empty response ID strings`);
11
+ }
12
+ }
13
+ }
@@ -47,14 +47,14 @@ export type HostStepMonitorKind = "command" | "ci" | "gate";
47
47
  export type HostStepState = "pending" | "running" | "done" | "cancelled" | "error";
48
48
  export type HostStepVerdict = "pass" | "fail" | "inconclusive";
49
49
 
50
- export interface HostStepFreshnessV1 {
50
+ export interface HostStepFreshness {
51
51
  expectedRef: string;
52
52
  observedRef?: string;
53
53
  stale?: boolean;
54
54
  }
55
55
 
56
56
  /** Bounded, provider-agnostic status for a host-owned workflow monitor. */
57
- export interface HostStepNodeV1 {
57
+ export interface HostStepNode {
58
58
  version: 1;
59
59
  kind: "host-step";
60
60
  /** Explicit monitor category; never inferred from labels or commands. */
@@ -68,7 +68,7 @@ export interface HostStepNodeV1 {
68
68
  reasonCode?: string;
69
69
  detail?: string;
70
70
  target?: string;
71
- freshness?: HostStepFreshnessV1;
71
+ freshness?: HostStepFreshness;
72
72
  reportPath?: string;
73
73
  exitCode?: number | null;
74
74
  updatedAt: number;
@@ -97,7 +97,7 @@ export interface WorkflowGraphNode {
97
97
  structured?: boolean;
98
98
  acceptanceStatus?: AcceptanceLedgerStatus;
99
99
  error?: string;
100
- hostStep?: HostStepNodeV1;
100
+ hostStep?: HostStepNode;
101
101
  }
102
102
 
103
103
  export interface WorkflowGraphSnapshot {
@@ -112,7 +112,7 @@ export type WorkflowPreflightCoverage = "complete" | "partial";
112
112
  export type WorkflowPreflightMode = "mutation" | "review" | "scout" | "gate";
113
113
 
114
114
  /** Bounded, display-only lane hints supplied alongside a workflowScript launch. */
115
- export interface WorkflowPreflightLaneV1 {
115
+ export interface WorkflowPreflightLane {
116
116
  key: string;
117
117
  mode?: WorkflowPreflightMode;
118
118
  decision?: string;
@@ -122,10 +122,10 @@ export interface WorkflowPreflightLaneV1 {
122
122
  }
123
123
 
124
124
  /** Versioned, display-only workflow launch plan; it never grants launch authority. */
125
- export interface WorkflowPreflightV1 {
125
+ export interface WorkflowPreflight {
126
126
  version: 1;
127
127
  coverage: WorkflowPreflightCoverage;
128
- lanes: WorkflowPreflightLaneV1[];
128
+ lanes: WorkflowPreflightLane[];
129
129
  }
130
130
 
131
131
  export type WorkflowReceiptState = "complete" | "failed" | "paused" | "stopped";
@@ -149,7 +149,7 @@ export interface WorkflowRecoveryAction {
149
149
  * Permission/policy extensions can use it to distinguish resolved content from
150
150
  * raw workflow scripts. This audit projection never grants execution authority.
151
151
  */
152
- export interface WorkflowResourceProvenanceV1 {
152
+ export interface WorkflowResourceProvenance {
153
153
  kind: "workflow";
154
154
  name: string;
155
155
  version: number;
@@ -174,7 +174,20 @@ export interface WorkflowLaneMetadata {
174
174
  outputPaths?: string[];
175
175
  }
176
176
 
177
- export interface WorkflowChildSummaryV1 {
177
+ /** Bounded foreground activity; excludes tool arguments and transcript content. */
178
+ export interface WorkflowChildActivity {
179
+ currentTool?: string;
180
+ currentToolStartedAt?: number;
181
+ lastActivityAt?: number;
182
+ durationMs?: number;
183
+ toolCount?: number;
184
+ turnCount?: number;
185
+ tokens?: number;
186
+ inputTokens?: number;
187
+ outputTokens?: number;
188
+ }
189
+
190
+ export interface WorkflowChildSummary {
178
191
  version: 1;
179
192
  parentToolCallId: string;
180
193
  workflowRunId: string;
@@ -188,6 +201,8 @@ export interface WorkflowChildSummaryV1 {
188
201
  sessionName?: string;
189
202
  model?: string;
190
203
  thinking?: string;
204
+ /** Present only while a synchronous foreground child is running. */
205
+ activity?: WorkflowChildActivity;
191
206
  state: "pending" | "running" | "completed" | "failed" | "paused" | "stopped" | "rejected" | "detached";
192
207
  }>;
193
208
  }
@@ -215,9 +230,9 @@ export interface WorkflowReceipt {
215
230
  state: WorkflowReceiptState;
216
231
  createdAt: number;
217
232
  entries: Record<string, WorkflowReceiptEntry>;
218
- resource?: WorkflowResourceProvenanceV1;
219
- hostSteps?: HostStepNodeV1[];
220
- workflowChildren?: WorkflowChildSummaryV1;
233
+ resource?: WorkflowResourceProvenance;
234
+ hostSteps?: HostStepNode[];
235
+ workflowChildren?: WorkflowChildSummary;
221
236
  workflowResolution?: WorkflowTerminalResolution;
222
237
  terminalOutcome?: WorkflowTerminalOutcome;
223
238
  recovery?: WorkflowRecoveryAction[];
@@ -625,7 +640,7 @@ export type ProcessTerminalReason =
625
640
  | "proof-write-failed"
626
641
  | "stale-repair";
627
642
 
628
- export interface RunnerProcessInstanceExitV1 {
643
+ export interface RunnerProcessInstanceExit {
629
644
  processInstanceId: string;
630
645
  kind: "runner";
631
646
  closeObservedAt: number;
@@ -633,7 +648,7 @@ export interface RunnerProcessInstanceExitV1 {
633
648
  signal: string | null;
634
649
  }
635
650
 
636
- export type ProcessTreeTerminalV1 =
651
+ export type ProcessTreeTerminal =
637
652
  | {
638
653
  state: "observed";
639
654
  mechanism: "posix-process-group";
@@ -646,26 +661,26 @@ export type ProcessTreeTerminalV1 =
646
661
  diagnostic?: string;
647
662
  };
648
663
 
649
- export interface PiWriterProcessInstanceExitV1 {
664
+ export interface PiWriterProcessInstanceExit {
650
665
  processInstanceId: string;
651
666
  kind: "pi-writer";
652
667
  attempt: number;
653
668
  closeObservedAt: number;
654
669
  exitCode: number | null;
655
670
  signal: string | null;
656
- processTree: ProcessTreeTerminalV1;
671
+ processTree: ProcessTreeTerminal;
657
672
  }
658
673
 
659
- export type ProcessInstanceExitV1 = RunnerProcessInstanceExitV1 | PiWriterProcessInstanceExitV1;
674
+ export type ProcessInstanceExit = RunnerProcessInstanceExit | PiWriterProcessInstanceExit;
660
675
 
661
- export interface CanonicalSessionTerminalV1 {
676
+ export interface CanonicalSessionTerminal {
662
677
  canonicalSessionId: string;
663
678
  leaseDisposition: "released" | "not-held";
664
679
  freeAtObservation: true;
665
680
  canonicalSessionLeaseReleased?: true;
666
681
  }
667
682
 
668
- interface ProcessTerminalBaseV1 {
683
+ interface ProcessTerminalBase {
669
684
  version: 1;
670
685
  runId: string;
671
686
  childIndex?: number;
@@ -675,15 +690,15 @@ interface ProcessTerminalBaseV1 {
675
690
  resumeDisposition?: "resumable" | "non-resumable" | "unavailable";
676
691
  }
677
692
 
678
- export type ProcessTerminalV1 =
679
- | (ProcessTerminalBaseV1 & { state: "pending" | "not-started" })
680
- | (ProcessTerminalBaseV1 & {
693
+ export type ProcessTerminal =
694
+ | (ProcessTerminalBase & { state: "pending" | "not-started" })
695
+ | (ProcessTerminalBase & {
681
696
  state: "observed";
682
697
  observedAt: number;
683
- instances: ProcessInstanceExitV1[];
684
- canonicalSession?: CanonicalSessionTerminalV1;
698
+ instances: ProcessInstanceExit[];
699
+ canonicalSession?: CanonicalSessionTerminal;
685
700
  })
686
- | (ProcessTerminalBaseV1 & {
701
+ | (ProcessTerminalBase & {
687
702
  state: "unknown";
688
703
  reason: ProcessTerminalReason;
689
704
  diagnostic?: string;
@@ -779,6 +794,8 @@ export interface RunFanoutRejection extends RunFanoutBudgetSnapshot {
779
794
  }
780
795
 
781
796
  export interface SteeringRecoveryDescriptor {
797
+ /** Captured response identity authority; absence means no declared aliases on revival. */
798
+ modelResponseAliases?: Record<string, string[]>;
782
799
  version: 1;
783
800
  launchContractDigest?: string;
784
801
  extensionBindings?: ExtensionBindings;
@@ -830,7 +847,7 @@ export interface SteeringRecoveryDescriptor {
830
847
  maxSubagentDepth: number;
831
848
  maxOutput?: MaxOutputConfig;
832
849
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
833
- launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
850
+ launchResolvedExtensions?: LaunchResolvedChildExtensions;
834
851
  share: boolean;
835
852
  sessionDir?: string;
836
853
  artifactsDir?: string;
@@ -1162,7 +1179,7 @@ export interface AcceptanceLedger {
1162
1179
  };
1163
1180
  }
1164
1181
 
1165
- export interface LaunchResolvedChildExtensionsV1 {
1182
+ export interface LaunchResolvedChildExtensions {
1166
1183
  version: 1;
1167
1184
  /** This is parent-resolved launch intent, not child-runtime acknowledgement that extensions loaded. */
1168
1185
  source: "launch-resolved";
@@ -1177,7 +1194,7 @@ export interface LaunchResolvedChildExtensionsV1 {
1177
1194
  };
1178
1195
  }
1179
1196
 
1180
- export interface RuntimeAcknowledgedChildExtensionsV1 {
1197
+ export interface RuntimeAcknowledgedChildExtensions {
1181
1198
  version: 1;
1182
1199
  /** Best-effort child-runtime registration acknowledgement, not extension health. */
1183
1200
  source: "child-runtime";
@@ -1280,8 +1297,8 @@ export interface SingleResult {
1280
1297
  acceptance?: AcceptanceLedger;
1281
1298
  agentContract?: AgentContract;
1282
1299
  launchContractDigest?: string;
1283
- launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
1284
- runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
1300
+ launchResolvedExtensions?: LaunchResolvedChildExtensions;
1301
+ runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
1285
1302
  execution?: ExecutionProjection;
1286
1303
  review?: ReviewProjection;
1287
1304
  effects?: EffectsProjection;
@@ -1339,6 +1356,7 @@ export interface WaitCompletionChild {
1339
1356
  */
1340
1357
  export interface WaitCompletion {
1341
1358
  runId: string;
1359
+ workflowReceiptPath?: string;
1342
1360
  agent?: string;
1343
1361
  mode?: string;
1344
1362
  state?: string;
@@ -1346,7 +1364,7 @@ export interface WaitCompletion {
1346
1364
  /** Versioned bounded output archive retained with the durable completion replay. */
1347
1365
  archivePath?: string;
1348
1366
  results?: WaitCompletionChild[];
1349
- workflowChildren?: WorkflowChildSummaryV1;
1367
+ workflowChildren?: WorkflowChildSummary;
1350
1368
  }
1351
1369
 
1352
1370
  export interface AgentCapabilitiesSnapshot {
@@ -1372,13 +1390,14 @@ export interface AgentCapabilityRow {
1372
1390
 
1373
1391
  export interface Details {
1374
1392
  mode: SubagentResultMode | "management";
1393
+ workflowReceiptPath?: string;
1375
1394
  runId?: string;
1376
1395
  /** Host tool-call id retained when it differs from the internal run id. */
1377
1396
  toolCallId?: string;
1378
1397
  /** Run-level context summary. "mixed" when children resolved to different modes. */
1379
1398
  context?: "fresh" | "fork" | "mixed";
1380
1399
  results: SingleResult[];
1381
- workflowChildren?: WorkflowChildSummaryV1;
1400
+ workflowChildren?: WorkflowChildSummary;
1382
1401
  /**
1383
1402
  * Terminal completion payloads for runs this bg_wait call observed
1384
1403
  * finishing. Async completions travel as result files that are consumed and
@@ -1421,7 +1440,7 @@ export interface Details {
1421
1440
  currentStepIndex?: number; // 0-indexed current step (for running chains)
1422
1441
  workflowGraph?: WorkflowGraphSnapshot;
1423
1442
  /** Validated, display-only fanout plan supplied with a workflow launch. */
1424
- preflight?: WorkflowPreflightV1;
1443
+ preflight?: WorkflowPreflight;
1425
1444
  preflightWarnings?: string[];
1426
1445
  outputs?: ChainOutputMap;
1427
1446
  // Aggregated child usage across all agents in the run
@@ -1437,11 +1456,11 @@ export interface Details {
1437
1456
  capabilityAudit?: SubagentCapabilityAudit;
1438
1457
  parallelHandoff?: ParallelHandoffReference;
1439
1458
  lifecycleStatus?: {
1440
- processTerminal?: ProcessTerminalV1;
1459
+ processTerminal?: ProcessTerminal;
1441
1460
  };
1442
1461
  launchContractDigest?: string;
1443
- launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
1444
- runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
1462
+ launchResolvedExtensions?: LaunchResolvedChildExtensions;
1463
+ runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
1445
1464
  /** Original launch contract whose persisted session is being revived. */
1446
1465
  sourceLaunchContractDigest?: string;
1447
1466
  /** Durable mission attached to this run, when mission mode was explicitly used. */
@@ -1452,7 +1471,7 @@ export interface Details {
1452
1471
  mission?: MissionRecord;
1453
1472
  workflow?: {
1454
1473
  value?: unknown;
1455
- resource?: WorkflowResourceProvenanceV1;
1474
+ resource?: WorkflowResourceProvenance;
1456
1475
  preflightWarnings?: string[];
1457
1476
  trace: Array<{
1458
1477
  operation: "run" | "status" | "steer" | "host";
@@ -1565,9 +1584,9 @@ export interface NestedStepSummary {
1565
1584
  wrapUpRequested?: boolean;
1566
1585
  toolBudget?: ToolBudgetState;
1567
1586
  toolBudgetBlocked?: boolean;
1568
- processTerminal?: ProcessTerminalV1;
1569
- launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
1570
- runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
1587
+ processTerminal?: ProcessTerminal;
1588
+ launchResolvedExtensions?: LaunchResolvedChildExtensions;
1589
+ runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
1571
1590
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
1572
1591
  capabilityAudit?: SubagentCapabilityAudit;
1573
1592
  children?: NestedRunSummary[];
@@ -1585,9 +1604,9 @@ export interface NestedRunSummary extends NestedRunAddress {
1585
1604
  controlInbox?: string;
1586
1605
  capabilityToken?: string;
1587
1606
  mode?: SubagentRunMode;
1588
- processTerminal?: ProcessTerminalV1;
1589
- launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
1590
- runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
1607
+ processTerminal?: ProcessTerminal;
1608
+ launchResolvedExtensions?: LaunchResolvedChildExtensions;
1609
+ runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
1591
1610
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
1592
1611
  capabilityAudit?: SubagentCapabilityAudit;
1593
1612
  state: NestedRunState;
@@ -1655,10 +1674,10 @@ export interface AsyncStartedEvent {
1655
1674
  chainStepCount?: number;
1656
1675
  parallelGroups?: AsyncParallelGroupStatus[];
1657
1676
  workflowGraph?: WorkflowGraphSnapshot;
1658
- preflight?: WorkflowPreflightV1;
1677
+ preflight?: WorkflowPreflight;
1659
1678
  launchContractDigest?: string;
1660
- launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
1661
- runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
1679
+ launchResolvedExtensions?: LaunchResolvedChildExtensions;
1680
+ runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
1662
1681
  usageBudget?: UsageBudgetState;
1663
1682
  timeoutMs?: number;
1664
1683
  deadlineAt?: number;
@@ -1779,6 +1798,8 @@ export interface ExternalProcessStatus {
1779
1798
  }
1780
1799
 
1781
1800
  export interface AsyncStatus {
1801
+ /** Exact reference returned by successful current workflow receipt publication. */
1802
+ workflowReceiptPath?: string;
1782
1803
  lifecycleArtifactVersion?: SubagentLifecycleArtifactVersion;
1783
1804
  runId: string;
1784
1805
  /** Parent Pi process/window that owns local completion delivery. */
@@ -1823,17 +1844,17 @@ export interface AsyncStatus {
1823
1844
  pendingAppends?: number;
1824
1845
  parallelGroups?: AsyncParallelGroupStatus[];
1825
1846
  workflowGraph?: WorkflowGraphSnapshot;
1826
- preflight?: WorkflowPreflightV1;
1827
- processTerminal?: ProcessTerminalV1;
1847
+ preflight?: WorkflowPreflight;
1848
+ processTerminal?: ProcessTerminal;
1828
1849
  runFanoutBudget?: RunFanoutBudgetSnapshot;
1829
1850
  runFanoutBudgetDescriptor?: RunFanoutBudgetDescriptor;
1830
1851
  launchContractDigest?: string;
1831
- launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
1832
- runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
1852
+ launchResolvedExtensions?: LaunchResolvedChildExtensions;
1853
+ runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
1833
1854
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
1834
1855
  capabilityAudit?: SubagentCapabilityAudit;
1835
1856
  workflow?: Details["workflow"];
1836
- workflowChildren?: WorkflowChildSummaryV1;
1857
+ workflowChildren?: WorkflowChildSummary;
1837
1858
  parentWorkflowRunId?: string;
1838
1859
  workflowKey?: string;
1839
1860
  lane?: WorkflowLaneMetadata;
@@ -1919,13 +1940,13 @@ export interface AsyncStatus {
1919
1940
  acceptance?: AcceptanceLedger;
1920
1941
  agentContract?: AgentContract;
1921
1942
  launchContractDigest?: string;
1922
- launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
1923
- runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
1943
+ launchResolvedExtensions?: LaunchResolvedChildExtensions;
1944
+ runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
1924
1945
  execution?: ExecutionProjection;
1925
1946
  review?: ReviewProjection;
1926
1947
  effects?: EffectsProjection;
1927
1948
  watchdog?: ChildWatchdogProgress;
1928
- processTerminal?: ProcessTerminalV1;
1949
+ processTerminal?: ProcessTerminal;
1929
1950
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
1930
1951
  capabilityAudit?: SubagentCapabilityAudit;
1931
1952
  }>;
@@ -1975,11 +1996,11 @@ export interface AsyncJobState {
1975
1996
  chainStepCount?: number;
1976
1997
  parallelGroups?: AsyncParallelGroupStatus[];
1977
1998
  /** Bounded host-owned CI/gate nodes loaded from the workflow status graph. */
1978
- hostSteps?: HostStepNodeV1[];
1999
+ hostSteps?: HostStepNode[];
1979
2000
  /** Full bounded workflow plan, including not-yet-materialized stages. */
1980
2001
  workflowGraph?: WorkflowGraphSnapshot;
1981
2002
  steps?: AsyncJobStep[];
1982
- preflight?: WorkflowPreflightV1;
2003
+ preflight?: WorkflowPreflight;
1983
2004
  stepsTotal?: number;
1984
2005
  runningSteps?: number;
1985
2006
  completedSteps?: number;
@@ -2008,7 +2029,7 @@ export interface AsyncJobState {
2008
2029
  parentWorkflowRunId?: string;
2009
2030
  workflowKey?: string;
2010
2031
  workflow?: Details["workflow"];
2011
- workflowChildren?: WorkflowChildSummaryV1;
2032
+ workflowChildren?: WorkflowChildSummary;
2012
2033
  lane?: WorkflowLaneMetadata;
2013
2034
  }
2014
2035
 
@@ -2047,6 +2068,7 @@ export interface ForegroundResumeChild {
2047
2068
  agentContract?: AgentContract;
2048
2069
  /** Private bounded launch fields needed to preserve the child contract on resume. */
2049
2070
  resumeContract?: {
2071
+ modelResponseAliases?: Record<string, string[]>;
2050
2072
  outputSchema?: JsonSchemaObject;
2051
2073
  agentContract?: AgentContract;
2052
2074
  acceptance?: AcceptanceInput;
@@ -2056,8 +2078,8 @@ export interface ForegroundResumeChild {
2056
2078
  launchContractDigest?: string;
2057
2079
  /** Private retained launch authority. Never project into status or result output. */
2058
2080
  extensionBindings?: ExtensionBindings;
2059
- launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
2060
- runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
2081
+ launchResolvedExtensions?: LaunchResolvedChildExtensions;
2082
+ runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
2061
2083
  execution?: ExecutionProjection;
2062
2084
  review?: ReviewProjection;
2063
2085
  effects?: EffectsProjection;
@@ -2184,6 +2206,8 @@ export interface ActiveAsyncCapacitySnapshot {
2184
2206
  export interface SubagentState {
2185
2207
  baseCwd: string;
2186
2208
  currentSessionId: string | null;
2209
+ /** Exact SDK runtime session ID for supervisor ownership; never a session file path. */
2210
+ supervisorOwnerSessionId?: string | null;
2187
2211
  /** Session for which active status projections were restored successfully. */
2188
2212
  statusProjectionSessionId?: string | null;
2189
2213
  /** Reload-stable identity for this parent Pi process/window. */
@@ -2201,6 +2225,8 @@ export interface SubagentState {
2201
2225
  trustedSessionFileRoot?: string;
2202
2226
  /** Live async session roots created by this parent executor, keyed by run id. */
2203
2227
  liveAsyncSessionRoots?: Map<string, string>;
2228
+ /** Foreground nested routes retained after their direct parent settles, keyed by root run id. */
2229
+ retainedForegroundNestedRoutes?: Map<string, NestedRouteInfo>;
2204
2230
  /** Last valid parent session model observed for this session; used when continuation contexts omit ctx.model. */
2205
2231
  lastParentModel?: { provider: string; id: string };
2206
2232
  subagentInProgress?: boolean;
@@ -2368,7 +2394,7 @@ export interface RunSyncOptions {
2368
2394
  /** Internal foreground receipt proposal; returns true only when the outer waiter accepted it. */
2369
2395
  onDetachReceipt?: (result: SingleResult) => boolean;
2370
2396
  /** Authoritative terminal result, emitted only after the full detached run finalizes. */
2371
- onDetachedExit?: (result: SingleResult) => void;
2397
+ onDetachedExit?: (result: SingleResult) => void | Promise<void>;
2372
2398
  controlConfig?: ResolvedControlConfig;
2373
2399
  intercomSessionName?: string;
2374
2400
  orchestratorIntercomTarget?: string;
@@ -2409,6 +2435,7 @@ export interface RunSyncOptions {
2409
2435
  workflowChildPermitLaunch?: WorkflowChildPermitContext;
2410
2436
  /** Registry models available for heuristic bare-model resolution */
2411
2437
  availableModels?: Array<{ provider: string; id: string; fullId: string; contextWindow?: number }>;
2438
+ modelResponseAliases?: Record<string, string[]>;
2412
2439
  /** Current parent-session provider to prefer for ambiguous bare model ids */
2413
2440
  preferredModelProvider?: string;
2414
2441
  /** Parent Pi event host used to snapshot runtime-registered MCP servers before child launch. */
@@ -2544,6 +2571,8 @@ export interface ExtensionConfig {
2544
2571
  asyncWidget?: boolean;
2545
2572
  /** Configure the process-wide TTL policy for persisted model exclusions. */
2546
2573
  modelExclusions?: ModelExclusionsConfig;
2574
+ /** Exact provider/model candidates mapped to operator-declared equivalent response IDs. Empty arrays add no accepted IDs. */
2575
+ modelResponseAliases?: Record<string, string[]>;
2547
2576
  /** Tool description variant registered for the parent-facing subagent tool. Defaults to split metadata. */
2548
2577
  toolDescriptionMode?: ToolDescriptionMode;
2549
2578
  /** Inline chat rendering for the subagent tool. Defaults to rich. */
@@ -135,6 +135,9 @@ function isNotFoundError(error: unknown): boolean {
135
135
  * Read async job status from disk (with mtime-based caching)
136
136
  */
137
137
  export function readStatus(asyncDir: string): AsyncStatus | null {
138
+ if (Buffer.byteLength(path.basename(asyncDir), "utf-8") > 255) {
139
+ return null;
140
+ }
138
141
  const statusPath = path.join(asyncDir, "status.json");
139
142
 
140
143
  let stat: fs.Stats;
@@ -454,8 +457,13 @@ export function hasEmptyTerminalAssistantResponse(messages: Message[]): boolean
454
457
  const lastAssistant = messages.findLast((message) => message.role === "assistant");
455
458
  return lastAssistant?.role === "assistant"
456
459
  && Array.isArray(lastAssistant.content)
457
- && lastAssistant.content.length === 0
458
- && lastAssistant.usage.output === 0;
460
+ && ((lastAssistant.content.length === 0 && lastAssistant.usage.output === 0)
461
+ || (messages.at(-1) === lastAssistant
462
+ && lastAssistant.stopReason === "stop"
463
+ && !lastAssistant.errorMessage
464
+ && lastAssistant.content.length > 0
465
+ // Token accounting can be nonzero even when no response text was emitted.
466
+ && lastAssistant.content.every((part) => part.type === "text" && part.text === "")));
459
467
  }
460
468
 
461
469
  export function formatEmptyTerminalAssistantResponseError(messages: Message[]): string {
@@ -2,9 +2,11 @@ export type FileWatchPurpose =
2
2
  | "result-delivery"
3
3
  | "supervisor-channel"
4
4
  | "async-job-tracker"
5
+ | "retained-nested-route-tracker"
5
6
  | "runner-control-inbox"
6
7
  | "child-steering-inbox";
7
8
 
8
9
  export function shouldUseNativeFsWatch(_purpose: FileWatchPurpose, platform: NodeJS.Platform = process.platform): boolean {
10
+ if (_purpose === "retained-nested-route-tracker" && platform === "win32") return false;
9
11
  return platform !== "darwin";
10
12
  }
@@ -1,5 +1,5 @@
1
1
  import { stableJsonDigest } from "./launch-contract.ts";
2
- import type { WorkflowResourceProvenanceV1 } from "./types.ts";
2
+ import type { WorkflowResourceProvenance } from "./types.ts";
3
3
 
4
4
  export interface WorkflowChildPermitInput {
5
5
  issuerPackage: string;
@@ -117,12 +117,12 @@ export function workflowChildPermitConsumed(permit: WorkflowChildPermit): boolea
117
117
  }
118
118
 
119
119
  export interface WorkflowResourceHostAuthority {
120
- keys: readonly string[];
121
- commands: readonly string[];
120
+ key: string;
121
+ command: string;
122
122
  }
123
123
 
124
124
  export interface WorkflowResourceAuthority {
125
- host?: WorkflowResourceHostAuthority;
125
+ host?: readonly WorkflowResourceHostAuthority[];
126
126
  }
127
127
 
128
128
  export interface WorkflowResourcePermitInput {
@@ -143,7 +143,7 @@ interface WorkflowResourcePermitRecord {
143
143
  resourceId: string;
144
144
  scriptDigest: string;
145
145
  authority: WorkflowResourceAuthority;
146
- provenance: WorkflowResourceProvenanceV1;
146
+ provenance: WorkflowResourceProvenance;
147
147
  state: "available" | "consumed";
148
148
  }
149
149
 
@@ -152,11 +152,17 @@ const resourceRecords = new WeakMap<object, WorkflowResourcePermitRecord>();
152
152
  function cloneWorkflowResourceAuthority(authority: WorkflowResourceAuthority): WorkflowResourceAuthority {
153
153
  if (!authority || typeof authority !== "object" || Array.isArray(authority)) throw new Error("Workflow resource authority must be an object.");
154
154
  if (authority.host === undefined) return Object.freeze({});
155
- if (!authority.host || typeof authority.host !== "object" || Array.isArray(authority.host)) throw new Error("Workflow resource host authority must be an object.");
156
- const { keys, commands } = authority.host;
157
- if (!Array.isArray(keys) || keys.some((key) => typeof key !== "string" || !key.trim())) throw new Error("Workflow resource host authority keys must be non-empty strings.");
158
- if (!Array.isArray(commands) || commands.some((command) => typeof command !== "string" || !command.trim())) throw new Error("Workflow resource host authority commands must be non-empty strings.");
159
- return Object.freeze({ host: Object.freeze({ keys: Object.freeze([...keys]), commands: Object.freeze([...commands]) }) });
155
+ if (!Array.isArray(authority.host) || authority.host.length > 32) throw new Error("Workflow resource host authority must be an array of at most 32 grants.");
156
+ const keys = new Set<string>();
157
+ const host = Array.from(authority.host, (grant) => {
158
+ if (!grant || typeof grant !== "object" || Object.keys(grant).some((field) => field !== "key" && field !== "command")) throw new Error("Workflow resource host grant must contain only key and command.");
159
+ const { key, command } = grant;
160
+ if (typeof key !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(key) || keys.has(key)) throw new Error("Workflow resource host grant requires a unique safe key.");
161
+ if (typeof command !== "string" || !command.trim() || Buffer.byteLength(command.trim(), "utf8") > 16 * 1024 || command.includes("\0")) throw new Error("Workflow resource host grant requires a non-empty command of at most 16384 bytes without NUL.");
162
+ keys.add(key);
163
+ return Object.freeze({ key, command: command.trim() });
164
+ });
165
+ return Object.freeze({ host: Object.freeze(host) });
160
166
  }
161
167
 
162
168
  /** Package-internal permit for a workflow resource resolved by the extension. */
@@ -186,7 +192,7 @@ export function createWorkflowResourcePermit(input: WorkflowResourcePermitInput)
186
192
  return permit;
187
193
  }
188
194
 
189
- export function consumeWorkflowResourcePermit(permit: WorkflowResourcePermit, script: string): { provenance: WorkflowResourceProvenanceV1; authority: WorkflowResourceAuthority } | string {
195
+ export function consumeWorkflowResourcePermit(permit: WorkflowResourcePermit, script: string): { provenance: WorkflowResourceProvenance; authority: WorkflowResourceAuthority } | string {
190
196
  const record = resourceRecords.get(permit as object);
191
197
  if (!record) return "Workflow resource permit is invalid.";
192
198
  if (record.state !== "available") return "Workflow resource permit is already consumed.";
@@ -201,7 +207,6 @@ export function authorizeWorkflowResourceHost(permit: WorkflowResourcePermit, ke
201
207
  if (!record || record.state !== "consumed") return "Workflow resource authority is unavailable.";
202
208
  const host = record.authority.host;
203
209
  if (!host) return "runs.host is not allowed for this workflow resource.";
204
- if (!host.keys.includes(key)) return `runs.host('${key}') is not allowed for workflow resource '${record.resourceName}'.`;
205
- if (!host.commands.includes(command.trim())) return `The command for runs.host('${key}') is not allowed for workflow resource '${record.resourceName}'.`;
210
+ if (!host.some((grant) => grant.key === key && grant.command === command.trim())) return `The command for runs.host('${key}') is not allowed for workflow resource '${record.resourceName}'.`;
206
211
  return undefined;
207
212
  }
@@ -666,7 +666,7 @@ export class SubagentFleetStatus {
666
666
  const capacity = this.state.activeAsyncCapacity;
667
667
  const hasNativeRows = workEntries.some((entry) => !entry.external);
668
668
  const showNativeSummary = hasNativeRows || Boolean(capacity?.used);
669
- const asyncRuns = capacity && showNativeSummary ? `Async runs ${capacity.used}/${capacity.limit || "∞"}` : "";
669
+ const asyncRuns = capacity && showNativeSummary && (capacity.used > 0 || capacity.limit > 0) ? `Async runs ${capacity.used}/${capacity.limit || "∞"}` : "";
670
670
  const activeEntries = activeLeafAgentCount(workEntries);
671
671
  const noun = workEntries.some((entry) => entry.external) ? "job" : "agent";
672
672
  const agents = activeEntries > 0 ? `${activeEntries} active ${noun}${activeEntries === 1 ? "" : "s"}` : "";
package/src/tui/fleet.ts CHANGED
@@ -179,10 +179,16 @@ function asyncItems(run: AsyncRunSummary, description?: string): FleetItem[] {
179
179
  }));
180
180
  }
181
181
 
182
- function orderFleetAsyncRuns(runs: AsyncRunSummary[], terminalLimit: number): AsyncRunSummary[] {
182
+ function orderFleetAsyncRuns(runs: AsyncRunSummary[], terminalLimit: number, trackedJobs: Map<string, AsyncJobState>): AsyncRunSummary[] {
183
183
  const updatedAt = (run: AsyncRunSummary) => run.lastUpdate ?? run.endedAt ?? run.startedAt;
184
184
  const byNewest = (left: AsyncRunSummary, right: AsyncRunSummary) => updatedAt(right) - updatedAt(left);
185
- const active = runs.filter((run) => run.state === "queued" || run.state === "running").sort(byNewest);
185
+ // Missing tracked starts must not inherit the summary's heartbeat-based display fallback.
186
+ const startedAt = (run: AsyncRunSummary) => {
187
+ const job = trackedJobs.get(run.id);
188
+ return job ? job.startedAt ?? 0 : run.startedAt;
189
+ };
190
+ const byStartedAt = (left: AsyncRunSummary, right: AsyncRunSummary) => startedAt(left) - startedAt(right) || left.id.localeCompare(right.id);
191
+ const active = runs.filter((run) => run.state === "queued" || run.state === "running").sort(byStartedAt);
186
192
  const terminal = runs.filter((run) => run.state !== "queued" && run.state !== "running").sort(byNewest);
187
193
  return [...active, ...terminal.slice(0, terminalLimit)];
188
194
  }
@@ -208,7 +214,7 @@ export function collectFleetSnapshot(
208
214
  liveWorkflowForegroundControls.add(control);
209
215
  workflowForegroundChildCounts.set(control.parentWorkflowRunId, (workflowForegroundChildCounts.get(control.parentWorkflowRunId) ?? 0) + activeChildCount);
210
216
  }
211
- for (const control of [...state.foregroundControls.values()].sort((left, right) => right.updatedAt - left.updatedAt)) {
217
+ for (const control of [...state.foregroundControls.values()].sort((left, right) => left.startedAt - right.startedAt || left.runId.localeCompare(right.runId))) {
212
218
  activeForegroundIds.add(control.runId);
213
219
  if (control.parentWorkflowRunId && workflowParentIds.has(control.parentWorkflowRunId)
214
220
  && ((workflowForegroundChildCounts.get(control.parentWorkflowRunId) ?? 0) <= 1 || !liveWorkflowForegroundControls.has(control))) continue;
@@ -249,7 +255,7 @@ export function collectFleetSnapshot(
249
255
  const tracked = [...trackedJobs.values()]
250
256
  .filter((job) => belongsToCurrentSession(job.sessionId, state.currentSessionId));
251
257
  const byUpdate = (left: AsyncJobState, right: AsyncJobState) => (right.updatedAt ?? right.startedAt ?? 0) - (left.updatedAt ?? left.startedAt ?? 0);
252
- const active = tracked.filter((job) => job.status === "queued" || job.status === "running").sort(byUpdate);
258
+ const active = tracked.filter((job) => job.status === "queued" || job.status === "running");
253
259
  const recent = tracked.filter((job) => job.status !== "queued" && job.status !== "running").sort(byUpdate).slice(0, options.limit ?? MAX_RECENT_ASYNC_RUNS);
254
260
  const trackedRuns: AsyncRunSummary[] = [];
255
261
  for (const job of [...active, ...recent]) {
@@ -272,7 +278,7 @@ export function collectFleetSnapshot(
272
278
  } else {
273
279
  runs = trackedRuns;
274
280
  }
275
- for (const run of orderFleetAsyncRuns(runs, options.limit ?? MAX_RECENT_ASYNC_RUNS)) {
281
+ for (const run of orderFleetAsyncRuns(runs, options.limit ?? MAX_RECENT_ASYNC_RUNS, trackedJobs)) {
276
282
  items.push(...asyncItems(run, descriptions.get(run.id)));
277
283
  }
278
284
  } catch (cause) {