pi-subagents 0.33.1 → 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 +84 -4
  2. package/README.md +220 -31
  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 +94 -52
  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 +66 -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 +184 -27
  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 -91
  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 +36 -9
  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 +8 -5
  72. package/src/shared/status-format.ts +7 -1
  73. package/src/shared/types.ts +210 -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 -353
@@ -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
  };
@@ -30,7 +33,7 @@ export function toModelInfo(model: RegistryModelLike): ModelInfo {
30
33
  /** Resolve the effective thinking level from a model string (which may contain a known suffix like `:high`)
31
34
  * and an explicit thinking config value. Returns `undefined` when no thinking is applicable
32
35
  * (e.g. no model was specified, or the model has no suffix and no config was provided). */
33
- export function resolveEffectiveThinking(model: string | undefined, configThinking: string | undefined): string | undefined {
36
+ export function resolveEffectiveThinking(model: string | undefined, configThinking: string | false | undefined): string | undefined {
34
37
  if (!model) return undefined;
35
38
  const { thinkingSuffix } = splitKnownThinkingSuffix(model);
36
39
  if (thinkingSuffix) return thinkingSuffix.slice(1);
@@ -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
 
@@ -181,6 +184,12 @@ export interface CompletionBatchConfig {
181
184
  stragglerWindowMs?: number;
182
185
  }
183
186
 
187
+ export interface WaitToolConfigObject {
188
+ enabled?: boolean;
189
+ }
190
+
191
+ export type WaitToolConfig = boolean | WaitToolConfigObject;
192
+
184
193
  export interface ControlEvent {
185
194
  type: ControlEventType;
186
195
  from?: ActivityState;
@@ -203,14 +212,113 @@ export interface ControlEvent {
203
212
  recentFailureSummary?: string;
204
213
  }
205
214
 
206
- export type SubagentResultStatus = "completed" | "failed" | "paused" | "detached";
215
+ export type SubagentResultStatus = "completed" | "failed" | "paused" | "stopped" | "detached";
207
216
  export type SubagentRunMode = "single" | "parallel" | "chain";
208
- export const SUBAGENT_LIFECYCLE_ARTIFACT_VERSION = 1;
217
+ export const SUBAGENT_LIFECYCLE_ARTIFACT_VERSION = 2;
209
218
  export type SubagentLifecycleArtifactVersion = typeof SUBAGENT_LIFECYCLE_ARTIFACT_VERSION;
210
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
+
211
319
  export type PublicNestedStepSummary = Pick<
212
320
  NestedStepSummary,
213
- "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"
214
322
  > & {
215
323
  children?: PublicNestedRunSummary[];
216
324
  };
@@ -223,7 +331,7 @@ export type CostSummary = {
223
331
 
224
332
  export type PublicNestedRunSummary = Pick<
225
333
  NestedRunSummary,
226
- "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"
227
335
  > & {
228
336
  steps?: PublicNestedStepSummary[];
229
337
  children?: PublicNestedRunSummary[];
@@ -263,6 +371,15 @@ export interface SubagentResultIntercomPayload {
263
371
  // Progress Tracking
264
372
  // ============================================================================
265
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
+
266
383
  export interface AgentProgress {
267
384
  index: number;
268
385
  agent: string;
@@ -283,6 +400,7 @@ export interface AgentProgress {
283
400
  durationMs: number;
284
401
  error?: string;
285
402
  failedTool?: string;
403
+ watchdog?: ChildWatchdogProgress;
286
404
  }
287
405
 
288
406
  export interface ToolCallSummary {
@@ -353,7 +471,8 @@ export interface AcceptanceConfig {
353
471
  reason?: string;
354
472
  }
355
473
 
356
- 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;
357
476
 
358
477
  export interface ResolvedAcceptanceGate extends AcceptanceGate {
359
478
  id: string;
@@ -426,6 +545,7 @@ export interface AcceptanceReviewResult {
426
545
  }
427
546
 
428
547
  export type AcceptanceLedgerStatus =
548
+ | "pending"
429
549
  | "not-required"
430
550
  | "claimed"
431
551
  | "attested"
@@ -453,6 +573,15 @@ export interface AcceptanceLedger {
453
573
  };
454
574
  }
455
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
+
456
585
  export interface SingleResult {
457
586
  agent: string;
458
587
  task: string;
@@ -461,6 +590,7 @@ export interface SingleResult {
461
590
  detachedReason?: string;
462
591
  interrupted?: boolean;
463
592
  timedOut?: boolean;
593
+ stopped?: boolean;
464
594
  turnBudget?: TurnBudgetState;
465
595
  turnBudgetExceeded?: boolean;
466
596
  wrapUpRequested?: boolean;
@@ -473,6 +603,7 @@ export interface SingleResult {
473
603
  modelAttempts?: ModelAttempt[];
474
604
  controlEvents?: ControlEvent[];
475
605
  error?: string;
606
+ protocolError?: ProtocolOutputLimit;
476
607
  sessionFile?: string;
477
608
  skills?: string[];
478
609
  skillsWarning?: string;
@@ -493,6 +624,25 @@ export interface SingleResult {
493
624
  transcriptPath?: string;
494
625
  transcriptError?: string;
495
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[];
496
646
  }
497
647
 
498
648
  export interface Details {
@@ -501,11 +651,13 @@ export interface Details {
501
651
  context?: "fresh" | "fork";
502
652
  results: SingleResult[];
503
653
  controlEvents?: ControlEvent[];
654
+ steering?: SteerActionResult;
504
655
  asyncId?: string;
505
656
  asyncDir?: string;
506
657
  timeoutMs?: number;
507
658
  deadlineAt?: number;
508
659
  timedOut?: boolean;
660
+ stopped?: boolean;
509
661
  turnBudget?: ResolvedTurnBudget;
510
662
  toolBudget?: ResolvedToolBudget;
511
663
  progress?: AgentProgress[];
@@ -530,6 +682,7 @@ export interface Details {
530
682
  totalChildUsage?: Usage;
531
683
  // Aggregated cost across all agents in the run
532
684
  totalCost?: CostSummary;
685
+ spawnBudget?: SpawnBudgetSnapshot;
533
686
  }
534
687
 
535
688
  // ============================================================================
@@ -564,7 +717,7 @@ export interface AsyncParallelGroupStatus {
564
717
  stepIndex: number;
565
718
  }
566
719
 
567
- export type NestedRunState = "queued" | "running" | "complete" | "failed" | "paused";
720
+ export type NestedRunState = "queued" | "running" | "complete" | "failed" | "paused" | "stopped";
568
721
  export type NestedOwnerState = "live" | "gone" | "unknown";
569
722
 
570
723
  export interface NestedRunAddress {
@@ -578,7 +731,7 @@ export interface NestedRunAddress {
578
731
 
579
732
  export interface NestedStepSummary {
580
733
  agent: string;
581
- status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
734
+ status: "pending" | "running" | "complete" | "completed" | "failed" | "paused" | "stopped";
582
735
  sessionFile?: string;
583
736
  transcriptPath?: string;
584
737
  transcriptError?: string;
@@ -592,7 +745,9 @@ export interface NestedStepSummary {
592
745
  startedAt?: number;
593
746
  endedAt?: number;
594
747
  error?: string;
748
+ watchdog?: ChildWatchdogProgress;
595
749
  timedOut?: boolean;
750
+ stopped?: boolean;
596
751
  turnBudget?: TurnBudgetState;
597
752
  turnBudgetExceeded?: boolean;
598
753
  wrapUpRequested?: boolean;
@@ -636,6 +791,7 @@ export interface NestedRunSummary extends NestedRunAddress {
636
791
  timeoutMs?: number;
637
792
  deadlineAt?: number;
638
793
  timedOut?: boolean;
794
+ stopped?: boolean;
639
795
  turnBudget?: TurnBudgetState;
640
796
  turnBudgetExceeded?: boolean;
641
797
  wrapUpRequested?: boolean;
@@ -660,6 +816,10 @@ export interface AsyncStartedEvent {
660
816
  mode?: SubagentRunMode;
661
817
  agent?: string;
662
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;
663
823
  chain?: string[];
664
824
  chainStepCount?: number;
665
825
  parallelGroups?: AsyncParallelGroupStatus[];
@@ -675,7 +835,8 @@ export interface AsyncStatus {
675
835
  runId: string;
676
836
  sessionId?: string;
677
837
  mode: SubagentRunMode;
678
- state: "queued" | "running" | "complete" | "failed" | "paused";
838
+ isNested?: boolean;
839
+ state: "queued" | "running" | "complete" | "failed" | "paused" | "stopped";
679
840
  error?: string;
680
841
  activityState?: ActivityState;
681
842
  lastActivityAt?: number;
@@ -684,14 +845,14 @@ export interface AsyncStatus {
684
845
  currentPath?: string;
685
846
  turnCount?: number;
686
847
  toolCount?: number;
687
- steerCount?: number;
688
- lastSteerAt?: number;
848
+ steering?: SteeringStatus;
689
849
  startedAt: number;
690
850
  endedAt?: number;
691
851
  lastUpdate?: number;
692
852
  timeoutMs?: number;
693
853
  deadlineAt?: number;
694
854
  timedOut?: boolean;
855
+ stopped?: boolean;
695
856
  turnBudget?: TurnBudgetState;
696
857
  turnBudgetExceeded?: boolean;
697
858
  wrapUpRequested?: boolean;
@@ -710,7 +871,7 @@ export interface AsyncStatus {
710
871
  label?: string;
711
872
  outputName?: string;
712
873
  structured?: boolean;
713
- status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
874
+ status: "pending" | "running" | "complete" | "completed" | "failed" | "paused" | "stopped";
714
875
  children?: NestedRunSummary[];
715
876
  sessionFile?: string;
716
877
  transcriptPath?: string;
@@ -730,6 +891,7 @@ export interface AsyncStatus {
730
891
  durationMs?: number;
731
892
  exitCode?: number | null;
732
893
  timedOut?: boolean;
894
+ stopped?: boolean;
733
895
  turnBudget?: TurnBudgetState;
734
896
  turnBudgetExceeded?: boolean;
735
897
  wrapUpRequested?: boolean;
@@ -742,13 +904,13 @@ export interface AsyncStatus {
742
904
  attemptedModels?: string[];
743
905
  modelAttempts?: ModelAttempt[];
744
906
  totalCost?: CostSummary;
745
- steerCount?: number;
746
- lastSteerAt?: number;
907
+ steering?: SteeringStatus;
747
908
  error?: string;
748
909
  structuredOutput?: unknown;
749
910
  structuredOutputPath?: string;
750
911
  structuredOutputSchemaPath?: string;
751
912
  acceptance?: AcceptanceLedger;
913
+ watchdog?: ChildWatchdogProgress;
752
914
  }>;
753
915
  sessionDir?: string;
754
916
  outputFile?: string;
@@ -765,7 +927,7 @@ export type AsyncJobStep = NonNullable<AsyncStatus["steps"]>[number] & {
765
927
  export interface AsyncJobState {
766
928
  asyncId: string;
767
929
  asyncDir: string;
768
- status: "queued" | "running" | "complete" | "failed" | "paused";
930
+ status: "queued" | "running" | "complete" | "failed" | "paused" | "stopped";
769
931
  pid?: number;
770
932
  sessionId?: string;
771
933
  activityState?: ActivityState;
@@ -775,8 +937,7 @@ export interface AsyncJobState {
775
937
  currentPath?: string;
776
938
  turnCount?: number;
777
939
  toolCount?: number;
778
- steerCount?: number;
779
- lastSteerAt?: number;
940
+ steering?: SteeringStatus;
780
941
  mode?: SubagentRunMode;
781
942
  agents?: string[];
782
943
  currentStep?: number;
@@ -793,6 +954,7 @@ export interface AsyncJobState {
793
954
  timeoutMs?: number;
794
955
  deadlineAt?: number;
795
956
  timedOut?: boolean;
957
+ stopped?: boolean;
796
958
  turnBudget?: TurnBudgetState;
797
959
  turnBudgetExceeded?: boolean;
798
960
  wrapUpRequested?: boolean;
@@ -813,11 +975,16 @@ export interface ForegroundResumeChild {
813
975
  sessionFile?: string;
814
976
  status: SubagentResultStatus;
815
977
  exitCode?: number;
978
+ error?: string;
816
979
  finalOutput?: string;
980
+ outputMode?: OutputMode;
981
+ savedOutputPath?: string;
982
+ outputSaveError?: string;
817
983
  artifactPaths?: ArtifactPaths;
818
984
  transcriptPath?: string;
819
985
  transcriptError?: string;
820
986
  detachedReason?: string;
987
+ acceptance?: AcceptanceLedger;
821
988
  updatedAt?: number;
822
989
  }
823
990
 
@@ -825,6 +992,8 @@ export interface ForegroundResumeRun {
825
992
  runId: string;
826
993
  mode: SubagentRunMode;
827
994
  cwd: string;
995
+ /** Originating parent session. Detached exits can outlive the active session. */
996
+ sessionId?: string;
828
997
  updatedAt: number;
829
998
  children: ForegroundResumeChild[];
830
999
  }
@@ -833,8 +1002,16 @@ export interface SubagentState {
833
1002
  baseCwd: string;
834
1003
  currentSessionId: string | null;
835
1004
  subagentInProgress?: boolean;
836
- 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
+ };
837
1012
  asyncJobs: Map<string, AsyncJobState>;
1013
+ /** Current-session active and recent async runs for the native fleet inspector. */
1014
+ fleetJobs?: Map<string, AsyncJobState>;
838
1015
  foregroundRuns?: Map<string, ForegroundResumeRun>;
839
1016
  foregroundControls: Map<string, {
840
1017
  runId: string;
@@ -897,8 +1074,10 @@ export const INTERCOM_DETACH_REQUEST_EVENT = "pi-intercom:detach-request";
897
1074
  export const INTERCOM_DETACH_RESPONSE_EVENT = "pi-intercom:detach-response";
898
1075
  export const SUBAGENT_ASYNC_STARTED_EVENT = "subagent:async-started";
899
1076
  export const SUBAGENT_ASYNC_COMPLETE_EVENT = "subagent:async-complete";
1077
+ export const SUBAGENT_FOREGROUND_COMPLETE_EVENT = "subagent:foreground-complete";
900
1078
  export const SUBAGENT_CONTROL_EVENT = "subagent:control-event";
901
1079
  export const SUBAGENT_CONTROL_INTERCOM_EVENT = "subagent:control-intercom";
1080
+ export const SUBAGENT_STEERING_NOTICE_EVENT = "subagent:steering-notice";
902
1081
  export const SUBAGENT_RESULT_INTERCOM_EVENT = "subagent:result-intercom";
903
1082
  export const SUBAGENT_RESULT_INTERCOM_DELIVERY_EVENT = "subagent:result-intercom-delivery";
904
1083
 
@@ -935,6 +1114,8 @@ export interface RunSyncOptions {
935
1114
  outputPath?: string;
936
1115
  outputMode?: OutputMode;
937
1116
  maxSubagentDepth?: number;
1117
+ /** Effective parent wait-tool setting propagated to the child runtime. */
1118
+ waitToolEnabled?: boolean;
938
1119
  nestedRoute?: NestedRouteInfo;
939
1120
  /** Override the agent's default model (format: "provider/id" or just "id") */
940
1121
  modelOverride?: string;
@@ -997,12 +1178,16 @@ export interface ScheduledRunsConfig {
997
1178
 
998
1179
  export interface ExtensionConfig {
999
1180
  asyncByDefault?: boolean;
1181
+ /** Show the above-editor async runs widget. Defaults to true. */
1182
+ asyncWidget?: boolean;
1000
1183
  /** Tool description variant registered for the parent-facing subagent tool. Defaults to full. */
1001
1184
  toolDescriptionMode?: ToolDescriptionMode;
1002
1185
  forceTopLevelAsync?: boolean;
1186
+ waitTool?: WaitToolConfig;
1003
1187
  defaultSessionDir?: string;
1004
1188
  singleRunOutputBaseDir?: string;
1005
1189
  maxSubagentDepth?: number;
1190
+ /** Optional cumulative session cap. Unset or 0 means unlimited. */
1006
1191
  maxSubagentSpawnsPerSession?: number;
1007
1192
  /** Global cap on simultaneously-running subagent tasks within a single run. Defaults to 20. */
1008
1193
  globalConcurrencyLimit?: number;
@@ -1110,8 +1295,7 @@ export const SLASH_SUBAGENT_CANCEL_EVENT = "subagent:slash:cancel";
1110
1295
  export const POLL_INTERVAL_MS = 250;
1111
1296
  export const MAX_WIDGET_JOBS = 4;
1112
1297
  export const DEFAULT_SUBAGENT_MAX_DEPTH = 2;
1113
- export const DEFAULT_MAX_SUBAGENT_SPAWNS_PER_SESSION = 40;
1114
- 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;
1115
1299
 
1116
1300
  export const DEFAULT_FORK_PREAMBLE =
1117
1301
  "You are a delegated subagent running from a fork of the parent session. " +
@@ -1196,10 +1380,11 @@ export function normalizeMaxSubagentSpawnsPerSession(value: unknown): number | u
1196
1380
  return normalizeNonNegativeInteger(value);
1197
1381
  }
1198
1382
 
1199
- export function resolveMaxSubagentSpawnsPerSession(configMaxSpawns?: number): number {
1200
- return normalizeMaxSubagentSpawnsPerSession(process.env.PI_SUBAGENT_MAX_SPAWNS_PER_SESSION)
1201
- ?? normalizeMaxSubagentSpawnsPerSession(configMaxSpawns)
1202
- ?? 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;
1203
1388
  }
1204
1389
 
1205
1390
  // ============================================================================