pi-subagents 0.34.0 → 0.35.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 (98) hide show
  1. package/CHANGELOG.md +78 -9
  2. package/README.md +213 -32
  3. package/index.ts +1 -0
  4. package/install.mjs +1 -1
  5. package/package.json +23 -8
  6. package/prompts/review-loop.md +3 -1
  7. package/skills/pi-subagents/SKILL.md +87 -25
  8. package/src/agents/agent-management.ts +82 -15
  9. package/src/agents/agent-serializer.ts +19 -0
  10. package/src/agents/agents.ts +91 -49
  11. package/src/agents/frontmatter.ts +67 -13
  12. package/src/agents/skills.ts +25 -12
  13. package/src/api/background-work.ts +197 -0
  14. package/src/api/delegation.ts +158 -0
  15. package/src/extension/chain-validation.ts +165 -0
  16. package/src/extension/doctor.ts +15 -0
  17. package/src/extension/fanout-child.ts +3 -1
  18. package/src/extension/index.ts +65 -124
  19. package/src/extension/rpc.ts +10 -2
  20. package/src/extension/schemas.ts +18 -14
  21. package/src/extension/steering-notices.ts +35 -0
  22. package/src/extension/tool-description.ts +20 -9
  23. package/src/intercom/intercom-bridge.ts +3 -2
  24. package/src/intercom/native-supervisor-channel.ts +9 -1
  25. package/src/intercom/result-intercom.ts +4 -0
  26. package/src/runs/background/async-execution.ts +293 -44
  27. package/src/runs/background/async-job-tracker.ts +56 -9
  28. package/src/runs/background/async-resume.ts +159 -52
  29. package/src/runs/background/async-status.ts +25 -18
  30. package/src/runs/background/auto-drain.ts +67 -0
  31. package/src/runs/background/chain-root-attachment.ts +16 -8
  32. package/src/runs/background/control-channel.ts +260 -13
  33. package/src/runs/background/fleet-view.ts +23 -2
  34. package/src/runs/background/notify.ts +79 -10
  35. package/src/runs/background/result-watcher.ts +12 -9
  36. package/src/runs/background/run-id-resolver.ts +14 -2
  37. package/src/runs/background/run-status.ts +23 -15
  38. package/src/runs/background/scheduled-runs.ts +3 -0
  39. package/src/runs/background/stale-run-reconciler.ts +32 -10
  40. package/src/runs/background/steering.ts +237 -0
  41. package/src/runs/background/subagent-runner.ts +898 -236
  42. package/src/runs/background/subagent-wait.ts +484 -0
  43. package/src/runs/background/top-level-async.ts +2 -1
  44. package/src/runs/background/wait-config.ts +36 -0
  45. package/src/runs/background/wait-tool.ts +26 -0
  46. package/src/runs/foreground/async-steering-action.ts +230 -0
  47. package/src/runs/foreground/chain-clarify.ts +22 -6
  48. package/src/runs/foreground/chain-execution.ts +50 -32
  49. package/src/runs/foreground/execution.ts +308 -94
  50. package/src/runs/foreground/subagent-executor.ts +592 -268
  51. package/src/runs/shared/acceptance.ts +355 -97
  52. package/src/runs/shared/child-protocol.ts +121 -0
  53. package/src/runs/shared/completion-guard.ts +8 -127
  54. package/src/runs/shared/dynamic-fanout.ts +6 -4
  55. package/src/runs/shared/model-fallback.ts +36 -0
  56. package/src/runs/shared/nested-events.ts +9 -4
  57. package/src/runs/shared/nested-render.ts +4 -1
  58. package/src/runs/shared/parallel-utils.ts +7 -0
  59. package/src/runs/shared/pi-args.ts +34 -7
  60. package/src/runs/shared/pi-spawn.ts +18 -12
  61. package/src/runs/shared/session-lease.ts +279 -0
  62. package/src/runs/shared/single-output.ts +61 -6
  63. package/src/runs/shared/spawn-budget.ts +128 -0
  64. package/src/runs/shared/subagent-control.ts +10 -6
  65. package/src/runs/shared/subagent-prompt-runtime.ts +127 -26
  66. package/src/runs/shared/task-intent.ts +176 -0
  67. package/src/runs/shared/tool-availability.ts +65 -0
  68. package/src/runs/shared/turn-budget.ts +49 -4
  69. package/src/shared/atomic-json.ts +4 -1
  70. package/src/shared/fork-context.ts +28 -3
  71. package/src/shared/model-info.ts +7 -4
  72. package/src/shared/status-format.ts +7 -1
  73. package/src/shared/types.ts +203 -25
  74. package/src/shared/utils.ts +35 -7
  75. package/src/slash/delegation-adapters.ts +457 -0
  76. package/src/slash/delegation-request.ts +103 -0
  77. package/src/slash/prompt-template-bridge.ts +167 -344
  78. package/src/slash/slash-commands.ts +239 -6
  79. package/src/slash/subagents-admin.ts +428 -0
  80. package/src/slash/subagents-editor.ts +86 -0
  81. package/src/tui/fleet.ts +405 -0
  82. package/src/tui/render.ts +90 -16
  83. package/src/watchdog/change-signature.ts +127 -0
  84. package/src/watchdog/child-status.ts +205 -0
  85. package/src/watchdog/emission-guard.ts +123 -0
  86. package/src/watchdog/lsp-diagnostics.ts +532 -0
  87. package/src/watchdog/model-selection.ts +167 -0
  88. package/src/watchdog/register-child.ts +117 -0
  89. package/src/watchdog/register-main.ts +433 -0
  90. package/src/watchdog/render.ts +54 -0
  91. package/src/watchdog/review.ts +293 -0
  92. package/src/watchdog/runtime.ts +712 -0
  93. package/src/watchdog/settings.ts +528 -0
  94. package/src/watchdog/tool-actions.ts +155 -0
  95. package/src/watchdog/turn-delta.ts +161 -0
  96. package/src/watchdog/types.ts +188 -0
  97. package/src/watchdog/warning-format.ts +73 -0
  98. package/src/runs/background/wait.ts +0 -394
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { SessionManager } from "@earendil-works/pi-coding-agent";
5
+ import { findModelInfo, type ModelInfo } from "./model-info.ts";
5
6
 
6
7
  type SubagentExecutionContext = "fresh" | "fork";
7
8
 
@@ -35,6 +36,9 @@ interface ForkableSessionManager {
35
36
 
36
37
  interface ForkContextResolverOptions {
37
38
  openSession?: (path: string, sessionDir?: string) => BranchSessionManager;
39
+ /** Decide per child index whether a sanitized transcript must also disable the child's
40
+ * thinking. Defaults to true (the pre-existing conservative behavior) when omitted. */
41
+ forceThinkingOffForIndex?: (index: number) => boolean;
38
42
  }
39
43
 
40
44
  interface ForkContextResolution {
@@ -51,6 +55,20 @@ export function resolveSubagentContext(value: unknown): SubagentExecutionContext
51
55
  return value === "fork" ? "fork" : "fresh";
52
56
  }
53
57
 
58
+ /** Decide whether a resolved child model uses Anthropic's provider or message API, which
59
+ * requires the sanitized fork to disable thinking. Unknown models stay conservative. */
60
+ export function forkedChildRequiresThinkingOff(
61
+ model: string | undefined,
62
+ availableModels?: ModelInfo[],
63
+ preferredProvider?: string,
64
+ ): boolean {
65
+ if (!model) return true;
66
+ const info = findModelInfo(model, availableModels, preferredProvider);
67
+ if (!info) return true;
68
+ return info.provider.toLowerCase() === "anthropic"
69
+ || info.api?.toLowerCase() === "anthropic-messages";
70
+ }
71
+
54
72
  function isUnsafeAnthropicThinkingBlock(message: BranchSessionEntry["message"], block: unknown): boolean {
55
73
  if (!message || !block || typeof block !== "object" || !("type" in block)) return false;
56
74
  const provider = typeof message.provider === "string" ? message.provider.toLowerCase() : "";
@@ -94,7 +112,6 @@ function sanitizeUnsafeThinkingBlocks(entries: BranchSessionEntry[]): boolean {
94
112
  entry.message.content = filtered;
95
113
  sanitized = true;
96
114
  }
97
- if (sanitized) appendThinkingOffEntry(entries);
98
115
  return sanitized;
99
116
  }
100
117
 
@@ -150,6 +167,8 @@ export function createForkContextResolver(
150
167
  if (!sessionFile) {
151
168
  throw new Error("Session manager did not return a forked session file.");
152
169
  }
170
+ const forceThinkingOff = (sanitized: boolean): boolean =>
171
+ sanitized && (options.forceThinkingOffForIndex?.(index) ?? true);
153
172
  let thinkingOverride: "off" | undefined;
154
173
  if (!fs.existsSync(sessionFile)) {
155
174
  const header = sourceManager.getHeader?.();
@@ -157,13 +176,19 @@ export function createForkContextResolver(
157
176
  if (!header || !entries) {
158
177
  throw new Error(`Session manager returned a forked session file that does not exist and cannot be persisted by fallback: ${sessionFile}`);
159
178
  }
160
- if (sanitizeUnsafeThinkingBlocks(entries)) thinkingOverride = "off";
179
+ if (forceThinkingOff(sanitizeUnsafeThinkingBlocks(entries))) {
180
+ appendThinkingOffEntry(entries);
181
+ thinkingOverride = "off";
182
+ }
161
183
  fs.mkdirSync(path.dirname(sessionFile), { recursive: true });
162
184
  fs.writeFileSync(sessionFile, `${[header, ...entries].map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf-8");
163
185
  } else {
164
186
  const entries = readSessionEntries(sessionFile);
165
187
  if (sanitizeUnsafeThinkingBlocks(entries)) {
166
- thinkingOverride = "off";
188
+ if (forceThinkingOff(true)) {
189
+ appendThinkingOffEntry(entries);
190
+ thinkingOverride = "off";
191
+ }
167
192
  fs.writeFileSync(sessionFile, `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf-8");
168
193
  }
169
194
  }
@@ -1,4 +1,4 @@
1
- export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
1
+ export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
2
2
  export type ThinkingLevel = typeof THINKING_LEVELS[number];
3
3
  export type ThinkingLevelMap = Partial<Record<ThinkingLevel, string | null>>;
4
4
 
@@ -6,6 +6,7 @@ export interface ModelInfo {
6
6
  provider: string;
7
7
  id: string;
8
8
  fullId: string;
9
+ api?: string;
9
10
  reasoning?: boolean;
10
11
  thinkingLevelMap?: ThinkingLevelMap;
11
12
  }
@@ -13,6 +14,7 @@ export interface ModelInfo {
13
14
  interface RegistryModelLike {
14
15
  provider: string;
15
16
  id: string;
17
+ api?: string;
16
18
  reasoning?: boolean;
17
19
  thinkingLevelMap?: ThinkingLevelMap;
18
20
  }
@@ -22,6 +24,7 @@ export function toModelInfo(model: RegistryModelLike): ModelInfo {
22
24
  provider: model.provider,
23
25
  id: model.id,
24
26
  fullId: `${model.provider}/${model.id}`,
27
+ api: model.api,
25
28
  reasoning: model.reasoning,
26
29
  thinkingLevelMap: model.thinkingLevelMap,
27
30
  };
@@ -63,15 +66,15 @@ export function findModelInfo(model: string | undefined, availableModels: ModelI
63
66
  }
64
67
 
65
68
  export function getSupportedThinkingLevels(model: ModelInfo | undefined): ThinkingLevel[] {
66
- if (!model) return [...THINKING_LEVELS];
69
+ if (!model) return THINKING_LEVELS.filter((level) => level !== "max");
67
70
  if (model.reasoning === false) return ["off"];
68
71
 
69
- if (!model.thinkingLevelMap) return [...THINKING_LEVELS];
72
+ if (!model.thinkingLevelMap) return THINKING_LEVELS.filter((level) => level !== "max");
70
73
 
71
74
  const levels = THINKING_LEVELS.filter((level) => {
72
75
  const mapped = model.thinkingLevelMap?.[level];
73
76
  if (mapped === null) return false;
74
- if (level === "xhigh") return mapped !== undefined;
77
+ if (level === "xhigh" || level === "max") return mapped !== undefined;
75
78
  return true;
76
79
  });
77
80
  return levels;
@@ -16,7 +16,10 @@ export function formatActivityLabel(lastActivityAt: number | undefined, activity
16
16
  }
17
17
  const age = formatActivityAge(Math.max(0, now - lastActivityAt));
18
18
  if (activityState === "needs_attention") return `no activity for ${age}`;
19
- if (activityState === "active_long_running") return `active but long-running · last activity ${age} ago`;
19
+ if (activityState === "active_long_running") {
20
+ const activityAge = age === "now" ? "now" : `${age} ago`;
21
+ return `active but long-running · last activity ${activityAge}`;
22
+ }
20
23
  return age === "now" ? "active now" : `active ${age} ago`;
21
24
  }
22
25
 
@@ -27,6 +30,7 @@ function isCompletedStepStatus(status: AsyncJobStep["status"]): boolean {
27
30
  export function aggregateStepStatus(steps: StepStatusLike[]): AsyncJobStep["status"] {
28
31
  if (steps.some((step) => step.status === "running")) return "running";
29
32
  if (steps.some((step) => step.status === "failed")) return "failed";
33
+ if (steps.some((step) => step.status === "stopped")) return "stopped";
30
34
  if (steps.some((step) => step.status === "paused")) return "paused";
31
35
  if (steps.length > 0 && steps.every((step) => isCompletedStepStatus(step.status))) return "complete";
32
36
  return "pending";
@@ -40,10 +44,12 @@ export function formatParallelOutcome(steps: StepStatusLike[], total: number, op
40
44
  const running = steps.filter((step) => step.status === "running").length;
41
45
  const done = steps.filter((step) => isCompletedStepStatus(step.status)).length;
42
46
  const failed = steps.filter((step) => step.status === "failed").length;
47
+ const stopped = steps.filter((step) => step.status === "stopped").length;
43
48
  const paused = steps.filter((step) => step.status === "paused").length;
44
49
  const parts = [`${done}/${total} done`];
45
50
  if (options.showRunning !== false && running > 0) parts.unshift(formatAgentRunningLabel(running));
46
51
  if (failed > 0) parts.push(`${failed} failed`);
52
+ if (stopped > 0) parts.push(`${stopped} stopped`);
47
53
  if (paused > 0) parts.push(`${paused} paused`);
48
54
  return parts.join(" · ");
49
55
  }
@@ -20,6 +20,8 @@ export interface MaxOutputConfig {
20
20
 
21
21
  export type OutputMode = "inline" | "file-only";
22
22
 
23
+ export type AcceptanceRole = "read-only" | "writer";
24
+
23
25
  export type JsonSchemaObject = Record<string, unknown>;
24
26
 
25
27
  export interface ChainOutputMapEntry {
@@ -31,7 +33,7 @@ export interface ChainOutputMapEntry {
31
33
 
32
34
  export type ChainOutputMap = Record<string, ChainOutputMapEntry>;
33
35
 
34
- export type WorkflowNodeStatus = "pending" | "running" | "completed" | "failed" | "paused" | "detached";
36
+ export type WorkflowNodeStatus = "pending" | "running" | "completed" | "failed" | "paused" | "stopped" | "detached";
35
37
 
36
38
  export interface WorkflowGraphNode {
37
39
  id: string;
@@ -121,12 +123,13 @@ export interface ToolBudgetState extends ResolvedToolBudget {
121
123
  blockedTool?: string;
122
124
  }
123
125
 
124
- export type TurnBudgetOutcome = "within-budget" | "wrap-up-requested" | "exceeded";
126
+ export type TurnBudgetOutcome = "within-budget" | "wrap-up-requested" | "termination-deferred" | "exceeded";
125
127
 
126
128
  export interface TurnBudgetState extends ResolvedTurnBudget {
127
129
  outcome: TurnBudgetOutcome;
128
130
  turnCount: number;
129
131
  wrapUpRequestedAtTurn?: number;
132
+ terminationDeferredAtTurn?: number;
130
133
  exceededAtTurn?: number;
131
134
  }
132
135
 
@@ -209,14 +212,113 @@ export interface ControlEvent {
209
212
  recentFailureSummary?: string;
210
213
  }
211
214
 
212
- export type SubagentResultStatus = "completed" | "failed" | "paused" | "detached";
215
+ export type SubagentResultStatus = "completed" | "failed" | "paused" | "stopped" | "detached";
213
216
  export type SubagentRunMode = "single" | "parallel" | "chain";
214
- export const SUBAGENT_LIFECYCLE_ARTIFACT_VERSION = 1;
217
+ export const SUBAGENT_LIFECYCLE_ARTIFACT_VERSION = 2;
215
218
  export type SubagentLifecycleArtifactVersion = typeof SUBAGENT_LIFECYCLE_ARTIFACT_VERSION;
216
219
 
220
+ export type SteeringActionState = "delivered" | "scheduled" | "pending" | "partial" | "recovered" | "failed";
221
+ export type SteeringTargetState = "scheduled" | "routed" | "delivered" | "late" | "failed" | "recovered";
222
+
223
+ export interface SteeringTargetStatus {
224
+ index: number;
225
+ state: SteeringTargetState;
226
+ routedAt?: number;
227
+ deliveredAt?: number;
228
+ lateDeliveredAt?: number;
229
+ failedAt?: number;
230
+ recoveredAt?: number;
231
+ reason?: string;
232
+ replacementRunId?: string;
233
+ }
234
+
235
+ export interface SteeringRequestStatus {
236
+ id: string;
237
+ requestedAt: number;
238
+ source?: string;
239
+ messagePreview: string;
240
+ targets: SteeringTargetStatus[];
241
+ }
242
+
243
+ export interface SteeringStatus {
244
+ requested: number;
245
+ scheduled: number;
246
+ pending: number;
247
+ delivered: number;
248
+ failed: number;
249
+ recovered: number;
250
+ lastRequestedAt?: number;
251
+ lastDeliveredAt?: number;
252
+ recent: SteeringRequestStatus[];
253
+ }
254
+
255
+ export interface SteerActionTarget {
256
+ index: number;
257
+ state: SteeringTargetState;
258
+ deliveredAt?: number;
259
+ lateDeliveredAt?: number;
260
+ reason?: string;
261
+ replacementRunId?: string;
262
+ }
263
+
264
+ export interface SteerActionResult {
265
+ requestId: string;
266
+ state: SteeringActionState;
267
+ sourceRunId: string;
268
+ replacementRunId?: string;
269
+ targets: SteerActionTarget[];
270
+ }
271
+
272
+ export interface SteeringNotice {
273
+ type: "subagent.steering.notice";
274
+ ts: number;
275
+ runId: string;
276
+ requestId: string;
277
+ state: "failed" | "partial" | "recovered";
278
+ message: string;
279
+ currentSessionId?: string;
280
+ }
281
+
282
+ export interface SteeringRecoveryDescriptor {
283
+ version: 1;
284
+ sourceRunId: string;
285
+ agent: string;
286
+ sessionFile?: string;
287
+ cwd: string;
288
+ model?: string;
289
+ fallbackModels?: string[];
290
+ thinking?: string;
291
+ tools?: string[];
292
+ extensions?: string[];
293
+ subagentOnlyExtensions?: string[];
294
+ mcpDirectTools?: string[];
295
+ systemPrompt?: string;
296
+ systemPromptMode: "append" | "replace";
297
+ inheritProjectContext: boolean;
298
+ inheritSkills: boolean;
299
+ skills?: string[];
300
+ skillPath?: string[];
301
+ agentFilePath?: string;
302
+ completionGuard?: boolean;
303
+ memory?: { scope: "project" | "user"; path: string };
304
+ outputPath?: string;
305
+ outputMode: "inline" | "file-only";
306
+ acceptance?: AcceptanceInput;
307
+ controlConfig?: ResolvedControlConfig;
308
+ absoluteDeadlineAt?: number;
309
+ initialTurnBudget?: ResolvedTurnBudget;
310
+ initialToolBudget?: ResolvedToolBudget;
311
+ maxSubagentDepth: number;
312
+ maxOutput?: MaxOutputConfig;
313
+ share: boolean;
314
+ sessionDir?: string;
315
+ artifactsDir?: string;
316
+ artifactConfig?: ArtifactConfig;
317
+ }
318
+
217
319
  export type PublicNestedStepSummary = Pick<
218
320
  NestedStepSummary,
219
- "agent" | "status" | "sessionFile" | "transcriptPath" | "transcriptError" | "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount" | "toolBudget" | "toolBudgetBlocked" | "startedAt" | "endedAt" | "error" | "timedOut"
321
+ "agent" | "status" | "sessionFile" | "transcriptPath" | "transcriptError" | "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount" | "toolBudget" | "toolBudgetBlocked" | "startedAt" | "endedAt" | "error" | "timedOut" | "stopped"
220
322
  > & {
221
323
  children?: PublicNestedRunSummary[];
222
324
  };
@@ -229,7 +331,7 @@ export type CostSummary = {
229
331
 
230
332
  export type PublicNestedRunSummary = Pick<
231
333
  NestedRunSummary,
232
- "id" | "parentRunId" | "parentStepIndex" | "parentAgent" | "depth" | "path" | "asyncDir" | "sessionId" | "sessionFile" | "intercomTarget" | "ownerIntercomTarget" | "leafIntercomTarget" | "ownerState" | "mode" | "state" | "agent" | "agents" | "currentStep" | "chainStepCount" | "parallelGroups" | "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount" | "toolBudget" | "toolBudgetBlocked" | "totalTokens" | "totalCost" | "startedAt" | "endedAt" | "lastUpdate" | "error" | "timeoutMs" | "deadlineAt" | "timedOut" | "turnBudget" | "turnBudgetExceeded" | "wrapUpRequested"
334
+ "id" | "parentRunId" | "parentStepIndex" | "parentAgent" | "depth" | "path" | "asyncDir" | "sessionId" | "sessionFile" | "intercomTarget" | "ownerIntercomTarget" | "leafIntercomTarget" | "ownerState" | "mode" | "state" | "agent" | "agents" | "currentStep" | "chainStepCount" | "parallelGroups" | "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount" | "toolBudget" | "toolBudgetBlocked" | "totalTokens" | "totalCost" | "startedAt" | "endedAt" | "lastUpdate" | "error" | "timeoutMs" | "deadlineAt" | "timedOut" | "stopped" | "turnBudget" | "turnBudgetExceeded" | "wrapUpRequested"
233
335
  > & {
234
336
  steps?: PublicNestedStepSummary[];
235
337
  children?: PublicNestedRunSummary[];
@@ -269,6 +371,15 @@ export interface SubagentResultIntercomPayload {
269
371
  // Progress Tracking
270
372
  // ============================================================================
271
373
 
374
+ export interface ChildWatchdogProgress {
375
+ phase: "idle" | "reviewing" | "autofollow" | "settling" | "stale" | "failed";
376
+ seq: number;
377
+ lastUpdate: number;
378
+ followUpPending: boolean;
379
+ reason?: string;
380
+ timedOut?: boolean;
381
+ }
382
+
272
383
  export interface AgentProgress {
273
384
  index: number;
274
385
  agent: string;
@@ -289,6 +400,7 @@ export interface AgentProgress {
289
400
  durationMs: number;
290
401
  error?: string;
291
402
  failedTool?: string;
403
+ watchdog?: ChildWatchdogProgress;
292
404
  }
293
405
 
294
406
  export interface ToolCallSummary {
@@ -359,7 +471,8 @@ export interface AcceptanceConfig {
359
471
  reason?: string;
360
472
  }
361
473
 
362
- export type AcceptanceInput = AcceptanceLevel | false | AcceptanceConfig;
474
+ /** Bare "none" is not accepted: use { level: "none", reason: "..." }; false remains a deprecated shorthand. */
475
+ export type AcceptanceInput = Exclude<AcceptanceLevel, "none"> | false | AcceptanceConfig;
363
476
 
364
477
  export interface ResolvedAcceptanceGate extends AcceptanceGate {
365
478
  id: string;
@@ -432,6 +545,7 @@ export interface AcceptanceReviewResult {
432
545
  }
433
546
 
434
547
  export type AcceptanceLedgerStatus =
548
+ | "pending"
435
549
  | "not-required"
436
550
  | "claimed"
437
551
  | "attested"
@@ -459,6 +573,15 @@ export interface AcceptanceLedger {
459
573
  };
460
574
  }
461
575
 
576
+ export interface ProtocolOutputLimit {
577
+ code: "protocol_output_limit";
578
+ stream: "stdout" | "stderr";
579
+ limitBytes: number;
580
+ observedBytes: number;
581
+ diagnosticPrefix: string;
582
+ diagnosticTail: string;
583
+ }
584
+
462
585
  export interface SingleResult {
463
586
  agent: string;
464
587
  task: string;
@@ -467,6 +590,7 @@ export interface SingleResult {
467
590
  detachedReason?: string;
468
591
  interrupted?: boolean;
469
592
  timedOut?: boolean;
593
+ stopped?: boolean;
470
594
  turnBudget?: TurnBudgetState;
471
595
  turnBudgetExceeded?: boolean;
472
596
  wrapUpRequested?: boolean;
@@ -479,6 +603,7 @@ export interface SingleResult {
479
603
  modelAttempts?: ModelAttempt[];
480
604
  controlEvents?: ControlEvent[];
481
605
  error?: string;
606
+ protocolError?: ProtocolOutputLimit;
482
607
  sessionFile?: string;
483
608
  skills?: string[];
484
609
  skillsWarning?: string;
@@ -499,6 +624,25 @@ export interface SingleResult {
499
624
  transcriptPath?: string;
500
625
  transcriptError?: string;
501
626
  children?: NestedRunSummary[];
627
+ watchdog?: ChildWatchdogProgress;
628
+ }
629
+
630
+ export interface SpawnBudgetGrant {
631
+ sessionId: string;
632
+ amount: number;
633
+ grantedAt: number;
634
+ previousLimit: number;
635
+ limit: number;
636
+ }
637
+
638
+ export interface SpawnBudgetSnapshot {
639
+ used: number;
640
+ configuredLimit: number | null;
641
+ granted: number;
642
+ limit: number | null;
643
+ remaining: number | null;
644
+ grantRemaining: number | null;
645
+ grantHistory: SpawnBudgetGrant[];
502
646
  }
503
647
 
504
648
  export interface Details {
@@ -507,11 +651,13 @@ export interface Details {
507
651
  context?: "fresh" | "fork";
508
652
  results: SingleResult[];
509
653
  controlEvents?: ControlEvent[];
654
+ steering?: SteerActionResult;
510
655
  asyncId?: string;
511
656
  asyncDir?: string;
512
657
  timeoutMs?: number;
513
658
  deadlineAt?: number;
514
659
  timedOut?: boolean;
660
+ stopped?: boolean;
515
661
  turnBudget?: ResolvedTurnBudget;
516
662
  toolBudget?: ResolvedToolBudget;
517
663
  progress?: AgentProgress[];
@@ -536,6 +682,7 @@ export interface Details {
536
682
  totalChildUsage?: Usage;
537
683
  // Aggregated cost across all agents in the run
538
684
  totalCost?: CostSummary;
685
+ spawnBudget?: SpawnBudgetSnapshot;
539
686
  }
540
687
 
541
688
  // ============================================================================
@@ -570,7 +717,7 @@ export interface AsyncParallelGroupStatus {
570
717
  stepIndex: number;
571
718
  }
572
719
 
573
- export type NestedRunState = "queued" | "running" | "complete" | "failed" | "paused";
720
+ export type NestedRunState = "queued" | "running" | "complete" | "failed" | "paused" | "stopped";
574
721
  export type NestedOwnerState = "live" | "gone" | "unknown";
575
722
 
576
723
  export interface NestedRunAddress {
@@ -584,7 +731,7 @@ export interface NestedRunAddress {
584
731
 
585
732
  export interface NestedStepSummary {
586
733
  agent: string;
587
- status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
734
+ status: "pending" | "running" | "complete" | "completed" | "failed" | "paused" | "stopped";
588
735
  sessionFile?: string;
589
736
  transcriptPath?: string;
590
737
  transcriptError?: string;
@@ -598,7 +745,9 @@ export interface NestedStepSummary {
598
745
  startedAt?: number;
599
746
  endedAt?: number;
600
747
  error?: string;
748
+ watchdog?: ChildWatchdogProgress;
601
749
  timedOut?: boolean;
750
+ stopped?: boolean;
602
751
  turnBudget?: TurnBudgetState;
603
752
  turnBudgetExceeded?: boolean;
604
753
  wrapUpRequested?: boolean;
@@ -642,6 +791,7 @@ export interface NestedRunSummary extends NestedRunAddress {
642
791
  timeoutMs?: number;
643
792
  deadlineAt?: number;
644
793
  timedOut?: boolean;
794
+ stopped?: boolean;
645
795
  turnBudget?: TurnBudgetState;
646
796
  turnBudgetExceeded?: boolean;
647
797
  wrapUpRequested?: boolean;
@@ -666,6 +816,10 @@ export interface AsyncStartedEvent {
666
816
  mode?: SubagentRunMode;
667
817
  agent?: string;
668
818
  agents?: string[];
819
+ /** Truncated first child task retained for backwards compatibility. */
820
+ task?: string;
821
+ /** Workflow-level caller task, falling back to the first child task. */
822
+ goal?: string;
669
823
  chain?: string[];
670
824
  chainStepCount?: number;
671
825
  parallelGroups?: AsyncParallelGroupStatus[];
@@ -681,7 +835,8 @@ export interface AsyncStatus {
681
835
  runId: string;
682
836
  sessionId?: string;
683
837
  mode: SubagentRunMode;
684
- state: "queued" | "running" | "complete" | "failed" | "paused";
838
+ isNested?: boolean;
839
+ state: "queued" | "running" | "complete" | "failed" | "paused" | "stopped";
685
840
  error?: string;
686
841
  activityState?: ActivityState;
687
842
  lastActivityAt?: number;
@@ -690,14 +845,14 @@ export interface AsyncStatus {
690
845
  currentPath?: string;
691
846
  turnCount?: number;
692
847
  toolCount?: number;
693
- steerCount?: number;
694
- lastSteerAt?: number;
848
+ steering?: SteeringStatus;
695
849
  startedAt: number;
696
850
  endedAt?: number;
697
851
  lastUpdate?: number;
698
852
  timeoutMs?: number;
699
853
  deadlineAt?: number;
700
854
  timedOut?: boolean;
855
+ stopped?: boolean;
701
856
  turnBudget?: TurnBudgetState;
702
857
  turnBudgetExceeded?: boolean;
703
858
  wrapUpRequested?: boolean;
@@ -716,7 +871,7 @@ export interface AsyncStatus {
716
871
  label?: string;
717
872
  outputName?: string;
718
873
  structured?: boolean;
719
- status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
874
+ status: "pending" | "running" | "complete" | "completed" | "failed" | "paused" | "stopped";
720
875
  children?: NestedRunSummary[];
721
876
  sessionFile?: string;
722
877
  transcriptPath?: string;
@@ -736,6 +891,7 @@ export interface AsyncStatus {
736
891
  durationMs?: number;
737
892
  exitCode?: number | null;
738
893
  timedOut?: boolean;
894
+ stopped?: boolean;
739
895
  turnBudget?: TurnBudgetState;
740
896
  turnBudgetExceeded?: boolean;
741
897
  wrapUpRequested?: boolean;
@@ -748,13 +904,13 @@ export interface AsyncStatus {
748
904
  attemptedModels?: string[];
749
905
  modelAttempts?: ModelAttempt[];
750
906
  totalCost?: CostSummary;
751
- steerCount?: number;
752
- lastSteerAt?: number;
907
+ steering?: SteeringStatus;
753
908
  error?: string;
754
909
  structuredOutput?: unknown;
755
910
  structuredOutputPath?: string;
756
911
  structuredOutputSchemaPath?: string;
757
912
  acceptance?: AcceptanceLedger;
913
+ watchdog?: ChildWatchdogProgress;
758
914
  }>;
759
915
  sessionDir?: string;
760
916
  outputFile?: string;
@@ -771,7 +927,7 @@ export type AsyncJobStep = NonNullable<AsyncStatus["steps"]>[number] & {
771
927
  export interface AsyncJobState {
772
928
  asyncId: string;
773
929
  asyncDir: string;
774
- status: "queued" | "running" | "complete" | "failed" | "paused";
930
+ status: "queued" | "running" | "complete" | "failed" | "paused" | "stopped";
775
931
  pid?: number;
776
932
  sessionId?: string;
777
933
  activityState?: ActivityState;
@@ -781,8 +937,7 @@ export interface AsyncJobState {
781
937
  currentPath?: string;
782
938
  turnCount?: number;
783
939
  toolCount?: number;
784
- steerCount?: number;
785
- lastSteerAt?: number;
940
+ steering?: SteeringStatus;
786
941
  mode?: SubagentRunMode;
787
942
  agents?: string[];
788
943
  currentStep?: number;
@@ -799,6 +954,7 @@ export interface AsyncJobState {
799
954
  timeoutMs?: number;
800
955
  deadlineAt?: number;
801
956
  timedOut?: boolean;
957
+ stopped?: boolean;
802
958
  turnBudget?: TurnBudgetState;
803
959
  turnBudgetExceeded?: boolean;
804
960
  wrapUpRequested?: boolean;
@@ -819,11 +975,16 @@ export interface ForegroundResumeChild {
819
975
  sessionFile?: string;
820
976
  status: SubagentResultStatus;
821
977
  exitCode?: number;
978
+ error?: string;
822
979
  finalOutput?: string;
980
+ outputMode?: OutputMode;
981
+ savedOutputPath?: string;
982
+ outputSaveError?: string;
823
983
  artifactPaths?: ArtifactPaths;
824
984
  transcriptPath?: string;
825
985
  transcriptError?: string;
826
986
  detachedReason?: string;
987
+ acceptance?: AcceptanceLedger;
827
988
  updatedAt?: number;
828
989
  }
829
990
 
@@ -831,6 +992,8 @@ export interface ForegroundResumeRun {
831
992
  runId: string;
832
993
  mode: SubagentRunMode;
833
994
  cwd: string;
995
+ /** Originating parent session. Detached exits can outlive the active session. */
996
+ sessionId?: string;
834
997
  updatedAt: number;
835
998
  children: ForegroundResumeChild[];
836
999
  }
@@ -839,8 +1002,16 @@ export interface SubagentState {
839
1002
  baseCwd: string;
840
1003
  currentSessionId: string | null;
841
1004
  subagentInProgress?: boolean;
842
- subagentSpawns?: { sessionId: string | null; count: number };
1005
+ subagentSpawns?: {
1006
+ sessionId: string | null;
1007
+ count: number;
1008
+ configuredLimit?: number | null;
1009
+ granted?: number;
1010
+ grantHistory?: SpawnBudgetGrant[];
1011
+ };
843
1012
  asyncJobs: Map<string, AsyncJobState>;
1013
+ /** Current-session active and recent async runs for the native fleet inspector. */
1014
+ fleetJobs?: Map<string, AsyncJobState>;
844
1015
  foregroundRuns?: Map<string, ForegroundResumeRun>;
845
1016
  foregroundControls: Map<string, {
846
1017
  runId: string;
@@ -903,8 +1074,10 @@ export const INTERCOM_DETACH_REQUEST_EVENT = "pi-intercom:detach-request";
903
1074
  export const INTERCOM_DETACH_RESPONSE_EVENT = "pi-intercom:detach-response";
904
1075
  export const SUBAGENT_ASYNC_STARTED_EVENT = "subagent:async-started";
905
1076
  export const SUBAGENT_ASYNC_COMPLETE_EVENT = "subagent:async-complete";
1077
+ export const SUBAGENT_FOREGROUND_COMPLETE_EVENT = "subagent:foreground-complete";
906
1078
  export const SUBAGENT_CONTROL_EVENT = "subagent:control-event";
907
1079
  export const SUBAGENT_CONTROL_INTERCOM_EVENT = "subagent:control-intercom";
1080
+ export const SUBAGENT_STEERING_NOTICE_EVENT = "subagent:steering-notice";
908
1081
  export const SUBAGENT_RESULT_INTERCOM_EVENT = "subagent:result-intercom";
909
1082
  export const SUBAGENT_RESULT_INTERCOM_DELIVERY_EVENT = "subagent:result-intercom-delivery";
910
1083
 
@@ -941,6 +1114,8 @@ export interface RunSyncOptions {
941
1114
  outputPath?: string;
942
1115
  outputMode?: OutputMode;
943
1116
  maxSubagentDepth?: number;
1117
+ /** Effective parent wait-tool setting propagated to the child runtime. */
1118
+ waitToolEnabled?: boolean;
944
1119
  nestedRoute?: NestedRouteInfo;
945
1120
  /** Override the agent's default model (format: "provider/id" or just "id") */
946
1121
  modelOverride?: string;
@@ -1003,6 +1178,8 @@ export interface ScheduledRunsConfig {
1003
1178
 
1004
1179
  export interface ExtensionConfig {
1005
1180
  asyncByDefault?: boolean;
1181
+ /** Show the above-editor async runs widget. Defaults to true. */
1182
+ asyncWidget?: boolean;
1006
1183
  /** Tool description variant registered for the parent-facing subagent tool. Defaults to full. */
1007
1184
  toolDescriptionMode?: ToolDescriptionMode;
1008
1185
  forceTopLevelAsync?: boolean;
@@ -1010,6 +1187,7 @@ export interface ExtensionConfig {
1010
1187
  defaultSessionDir?: string;
1011
1188
  singleRunOutputBaseDir?: string;
1012
1189
  maxSubagentDepth?: number;
1190
+ /** Optional cumulative session cap. Unset or 0 means unlimited. */
1013
1191
  maxSubagentSpawnsPerSession?: number;
1014
1192
  /** Global cap on simultaneously-running subagent tasks within a single run. Defaults to 20. */
1015
1193
  globalConcurrencyLimit?: number;
@@ -1117,8 +1295,7 @@ export const SLASH_SUBAGENT_CANCEL_EVENT = "subagent:slash:cancel";
1117
1295
  export const POLL_INTERVAL_MS = 250;
1118
1296
  export const MAX_WIDGET_JOBS = 4;
1119
1297
  export const DEFAULT_SUBAGENT_MAX_DEPTH = 2;
1120
- export const DEFAULT_MAX_SUBAGENT_SPAWNS_PER_SESSION = 40;
1121
- export const SUBAGENT_ACTIONS = ["list", "get", "models", "create", "update", "delete", "eject", "disable", "enable", "reset", "status", "interrupt", "resume", "steer", "append-step", "doctor", "schedule", "schedule-list", "schedule-status", "schedule-cancel"] as const;
1298
+ export const SUBAGENT_ACTIONS = ["list", "get", "models", "create", "update", "delete", "eject", "disable", "enable", "reset", "status", "grant-spawn-budget", "interrupt", "resume", "steer", "stop", "append-step", "doctor", "watchdog.status", "watchdog.check", "watchdog.configure", "watchdog.recommend-model", "schedule", "schedule-list", "schedule-status", "schedule-cancel"] as const;
1122
1299
 
1123
1300
  export const DEFAULT_FORK_PREAMBLE =
1124
1301
  "You are a delegated subagent running from a fork of the parent session. " +
@@ -1203,10 +1380,11 @@ export function normalizeMaxSubagentSpawnsPerSession(value: unknown): number | u
1203
1380
  return normalizeNonNegativeInteger(value);
1204
1381
  }
1205
1382
 
1206
- export function resolveMaxSubagentSpawnsPerSession(configMaxSpawns?: number): number {
1207
- return normalizeMaxSubagentSpawnsPerSession(process.env.PI_SUBAGENT_MAX_SPAWNS_PER_SESSION)
1208
- ?? normalizeMaxSubagentSpawnsPerSession(configMaxSpawns)
1209
- ?? DEFAULT_MAX_SUBAGENT_SPAWNS_PER_SESSION;
1383
+ export function resolveMaxSubagentSpawnsPerSession(configMaxSpawns?: number): number | undefined {
1384
+ const envMaxSpawns = normalizeMaxSubagentSpawnsPerSession(process.env.PI_SUBAGENT_MAX_SPAWNS_PER_SESSION);
1385
+ if (envMaxSpawns !== undefined) return envMaxSpawns === 0 ? undefined : envMaxSpawns;
1386
+ const configuredMaxSpawns = normalizeMaxSubagentSpawnsPerSession(configMaxSpawns);
1387
+ return configuredMaxSpawns === 0 ? undefined : configuredMaxSpawns;
1210
1388
  }
1211
1389
 
1212
1390
  // ============================================================================