pi-subagents 0.24.4 → 0.25.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.
@@ -0,0 +1,52 @@
1
+ import * as path from "node:path";
2
+
3
+ const MAX_NESTED_ID_LENGTH = 128;
4
+ export const MAX_NESTED_PATH_ENTRIES = 4;
5
+
6
+ export type NestedPathEntry = { runId: string; stepIndex?: number; agent?: string };
7
+
8
+ export function isSafeNestedPathId(value: unknown): value is string {
9
+ return typeof value === "string"
10
+ && value.length > 0
11
+ && value.length <= MAX_NESTED_ID_LENGTH
12
+ && !path.isAbsolute(value)
13
+ && !value.includes("/")
14
+ && !value.includes("\\")
15
+ && !value.includes("..");
16
+ }
17
+
18
+ function finiteNumber(value: unknown): number | undefined {
19
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
20
+ }
21
+
22
+ function nonEmptyString(value: unknown, max: number): string | undefined {
23
+ return typeof value === "string" && value.length > 0 ? value.slice(0, max) : undefined;
24
+ }
25
+
26
+ export function sanitizeNestedPath(value: unknown): NestedPathEntry[] {
27
+ if (!Array.isArray(value)) return [];
28
+ return value.map((part) => {
29
+ if (!part || typeof part !== "object") return undefined;
30
+ const record = part as Record<string, unknown>;
31
+ if (!isSafeNestedPathId(record.runId)) return undefined;
32
+ return {
33
+ runId: record.runId,
34
+ ...(finiteNumber(record.stepIndex) !== undefined ? { stepIndex: finiteNumber(record.stepIndex) } : {}),
35
+ ...(nonEmptyString(record.agent, 128) ? { agent: nonEmptyString(record.agent, 128) } : {}),
36
+ };
37
+ }).filter((part): part is NestedPathEntry => Boolean(part)).slice(0, MAX_NESTED_PATH_ENTRIES);
38
+ }
39
+
40
+ export function parseNestedPathEnv(value: string | undefined): NestedPathEntry[] {
41
+ if (!value) return [];
42
+ try {
43
+ return sanitizeNestedPath(JSON.parse(value) as unknown);
44
+ } catch {
45
+ return [];
46
+ }
47
+ }
48
+
49
+ export function encodeNestedPathEnv(value: NestedPathEntry[]): string {
50
+ const sanitized = sanitizeNestedPath(value);
51
+ return sanitized.length ? JSON.stringify(sanitized) : "";
52
+ }
@@ -0,0 +1,115 @@
1
+ import { formatDuration, formatTokens, shortenPath } from "../../shared/formatters.ts";
2
+ import { formatActivityLabel } from "../../shared/status-format.ts";
3
+ import type { ActivityState, NestedRunSummary, NestedStepSummary } from "../../shared/types.ts";
4
+
5
+ export interface NestedRunCounts {
6
+ total: number;
7
+ running: number;
8
+ paused: number;
9
+ complete: number;
10
+ failed: number;
11
+ queued: number;
12
+ }
13
+
14
+ export function countNestedRuns(children: NestedRunSummary[] | undefined): NestedRunCounts {
15
+ const counts: NestedRunCounts = { total: 0, running: 0, paused: 0, complete: 0, failed: 0, queued: 0 };
16
+ for (const child of children ?? []) {
17
+ counts.total++;
18
+ counts[child.state]++;
19
+ const nested = countNestedRuns([...(child.children ?? []), ...(child.steps?.flatMap((step) => step.children ?? []) ?? [])]);
20
+ counts.total += nested.total;
21
+ counts.running += nested.running;
22
+ counts.paused += nested.paused;
23
+ counts.complete += nested.complete;
24
+ counts.failed += nested.failed;
25
+ counts.queued += nested.queued;
26
+ }
27
+ return counts;
28
+ }
29
+
30
+ export function formatNestedAggregate(children: NestedRunSummary[] | undefined): string | undefined {
31
+ const counts = countNestedRuns(children);
32
+ if (counts.total === 0) return undefined;
33
+ const parts = [
34
+ counts.running > 0 ? `${counts.running} running` : "",
35
+ counts.paused > 0 ? `${counts.paused} paused` : "",
36
+ counts.failed > 0 ? `${counts.failed} failed` : "",
37
+ counts.complete > 0 ? `${counts.complete} complete` : "",
38
+ counts.queued > 0 ? `${counts.queued} queued` : "",
39
+ ].filter(Boolean);
40
+ return `+${counts.total} nested run${counts.total === 1 ? "" : "s"}${parts.length ? ` (${parts.join(", ")})` : ""}`;
41
+ }
42
+
43
+ function nestedRunLabel(run: NestedRunSummary): string {
44
+ if (run.agent) return run.agent;
45
+ if (run.agents?.length) return run.agents.length === 1 ? run.agents[0]! : `${run.agents.slice(0, 2).join(", ")}${run.agents.length > 2 ? ` +${run.agents.length - 2}` : ""}`;
46
+ return run.id;
47
+ }
48
+
49
+ function formatNestedActivity(input: {
50
+ activityState?: ActivityState;
51
+ lastActivityAt?: number;
52
+ currentTool?: string;
53
+ currentToolStartedAt?: number;
54
+ currentPath?: string;
55
+ turnCount?: number;
56
+ toolCount?: number;
57
+ totalTokens?: NestedRunSummary["totalTokens"];
58
+ }): string | undefined {
59
+ const facts: string[] = [];
60
+ if (input.currentTool && input.currentToolStartedAt !== undefined) facts.push(`tool ${input.currentTool} ${formatDuration(Math.max(0, Date.now() - input.currentToolStartedAt))}`);
61
+ else if (input.currentTool) facts.push(`tool ${input.currentTool}`);
62
+ if (input.currentPath) facts.push(shortenPath(input.currentPath));
63
+ if (input.turnCount !== undefined) facts.push(`${input.turnCount} turns`);
64
+ if (input.toolCount !== undefined) facts.push(`${input.toolCount} tools`);
65
+ if (input.totalTokens) facts.push(`${formatTokens(input.totalTokens.total)} tok`);
66
+ const activity = formatActivityLabel(input.lastActivityAt, input.activityState as ActivityState | undefined);
67
+ return activity || facts.length ? [activity, ...facts].filter(Boolean).join(" | ") : undefined;
68
+ }
69
+
70
+ function formatNestedRunLines(children: NestedRunSummary[] | undefined, options: { indent: string; maxDepth: number; maxLines: number; commandHints?: boolean }): string[] {
71
+ const lines: string[] = [];
72
+ const append = (items: NestedRunSummary[] | undefined, depth: number, indent: string): void => {
73
+ if (!items?.length || lines.length >= options.maxLines) return;
74
+ if (depth > options.maxDepth) {
75
+ const aggregate = formatNestedAggregate(items);
76
+ if (aggregate && lines.length < options.maxLines) lines.push(`${indent}↳ ${aggregate}`);
77
+ return;
78
+ }
79
+ for (let index = 0; index < items.length; index++) {
80
+ const child = items[index]!;
81
+ if (lines.length >= options.maxLines) {
82
+ const aggregate = formatNestedAggregate(items.slice(index));
83
+ if (aggregate) lines[lines.length - 1] = `${indent}↳ ${aggregate}`;
84
+ return;
85
+ }
86
+ const activity = child.state === "running" ? formatNestedActivity(child) : undefined;
87
+ const error = child.error ? ` | error: ${child.error}` : "";
88
+ lines.push(`${indent}↳ ${nestedRunLabel(child)} [${child.id}] ${child.state}${activity ? ` | ${activity}` : ""}${error}`);
89
+ if (options.commandHints && lines.length < options.maxLines) lines.push(`${indent} Status: subagent({ action: "status", id: "${child.id}" })`);
90
+ if (depth === options.maxDepth) {
91
+ const aggregate = formatNestedAggregate([...(child.steps?.flatMap((step) => step.children ?? []) ?? []), ...(child.children ?? [])]);
92
+ if (aggregate && lines.length < options.maxLines) lines.push(`${indent} ↳ ${aggregate}`);
93
+ continue;
94
+ }
95
+ for (const [stepIndex, step] of (child.steps ?? []).entries()) {
96
+ if (lines.length >= options.maxLines) return;
97
+ const stepActivity = step.status === "running" ? formatNestedActivity(step) : undefined;
98
+ lines.push(`${indent} ${stepIndex + 1}. ${step.agent} ${step.status}${stepActivity ? ` | ${stepActivity}` : ""}${step.error ? ` | error: ${step.error}` : ""}`);
99
+ append(step.children, depth + 1, `${indent} `);
100
+ }
101
+ append(child.children, depth + 1, `${indent} `);
102
+ }
103
+ };
104
+ append(children, 0, options.indent);
105
+ return lines;
106
+ }
107
+
108
+ export function formatNestedRunStatusLines(children: NestedRunSummary[] | undefined, options: { indent?: string; maxDepth?: number; maxLines?: number; commandHints?: boolean } = {}): string[] {
109
+ return formatNestedRunLines(children, {
110
+ indent: options.indent ?? " ",
111
+ maxDepth: options.maxDepth ?? 2,
112
+ maxLines: options.maxLines ?? 40,
113
+ commandHints: options.commandHints ?? false,
114
+ });
115
+ }
@@ -2,16 +2,27 @@ import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { encodeNestedPathEnv, parseNestedPathEnv, type NestedPathEntry } from "./nested-path.ts";
5
6
  import { resolveMcpDirectToolNames } from "./mcp-direct-tool-allowlist.ts";
6
7
 
7
8
  const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"];
8
9
  const TASK_ARG_LIMIT = 8000;
9
10
  const PROMPT_RUNTIME_EXTENSION_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-prompt-runtime.ts");
11
+ const FANOUT_CHILD_EXTENSION_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "extension", "fanout-child.ts");
10
12
  export const SUBAGENT_CHILD_ENV = "PI_SUBAGENT_CHILD";
11
13
  export const SUBAGENT_ORCHESTRATOR_TARGET_ENV = "PI_SUBAGENT_ORCHESTRATOR_TARGET";
12
14
  export const SUBAGENT_RUN_ID_ENV = "PI_SUBAGENT_RUN_ID";
13
15
  export const SUBAGENT_CHILD_AGENT_ENV = "PI_SUBAGENT_CHILD_AGENT";
14
16
  export const SUBAGENT_CHILD_INDEX_ENV = "PI_SUBAGENT_CHILD_INDEX";
17
+ export const SUBAGENT_FANOUT_CHILD_ENV = "PI_SUBAGENT_FANOUT_CHILD";
18
+ export const SUBAGENT_PARENT_EVENT_SINK_ENV = "PI_SUBAGENT_PARENT_EVENT_SINK";
19
+ export const SUBAGENT_PARENT_CONTROL_INBOX_ENV = "PI_SUBAGENT_PARENT_CONTROL_INBOX";
20
+ export const SUBAGENT_PARENT_ROOT_RUN_ID_ENV = "PI_SUBAGENT_PARENT_ROOT_RUN_ID";
21
+ export const SUBAGENT_PARENT_RUN_ID_ENV = "PI_SUBAGENT_PARENT_RUN_ID";
22
+ export const SUBAGENT_PARENT_CHILD_INDEX_ENV = "PI_SUBAGENT_PARENT_CHILD_INDEX";
23
+ export const SUBAGENT_PARENT_DEPTH_ENV = "PI_SUBAGENT_PARENT_DEPTH";
24
+ export const SUBAGENT_PARENT_PATH_ENV = "PI_SUBAGENT_PARENT_PATH";
25
+ export const SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV = "PI_SUBAGENT_PARENT_CAPABILITY_TOKEN";
15
26
 
16
27
  interface BuildPiArgsInput {
17
28
  baseArgs: string[];
@@ -35,6 +46,14 @@ interface BuildPiArgsInput {
35
46
  runId?: string;
36
47
  childAgentName?: string;
37
48
  childIndex?: number;
49
+ parentEventSink?: string;
50
+ parentControlInbox?: string;
51
+ parentRootRunId?: string;
52
+ parentRunId?: string;
53
+ parentChildIndex?: number;
54
+ parentDepth?: number;
55
+ parentPath?: NestedPathEntry[];
56
+ parentCapabilityToken?: string;
38
57
  }
39
58
 
40
59
  interface BuildPiArgsResult {
@@ -71,14 +90,14 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
71
90
  args.push("--model", modelArg);
72
91
  }
73
92
 
93
+ const declaredBuiltinTools = input.tools?.filter((tool) => !(tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js"))) ?? [];
94
+ const fanoutAuthorized = declaredBuiltinTools.includes("subagent");
74
95
  const toolExtensionPaths: string[] = [];
75
96
  if (input.tools?.length) {
76
- const builtinTools: string[] = [];
97
+ const builtinTools = [...declaredBuiltinTools];
77
98
  for (const tool of input.tools) {
78
- if (tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js")) {
99
+ if (!declaredBuiltinTools.includes(tool) && (tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js"))) {
79
100
  toolExtensionPaths.push(tool);
80
- } else {
81
- builtinTools.push(tool);
82
101
  }
83
102
  }
84
103
  if (builtinTools.length > 0) {
@@ -89,7 +108,9 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
89
108
  }
90
109
  }
91
110
 
92
- const runtimeExtensions = [PROMPT_RUNTIME_EXTENSION_PATH];
111
+ const runtimeExtensions = fanoutAuthorized
112
+ ? [PROMPT_RUNTIME_EXTENSION_PATH, FANOUT_CHILD_EXTENSION_PATH]
113
+ : [PROMPT_RUNTIME_EXTENSION_PATH];
93
114
  if (input.extensions !== undefined) {
94
115
  args.push("--no-extensions");
95
116
  for (const extPath of [...new Set([...runtimeExtensions, ...toolExtensionPaths, ...input.extensions])]) {
@@ -127,6 +148,40 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
127
148
 
128
149
  const env: Record<string, string | undefined> = {};
129
150
  env[SUBAGENT_CHILD_ENV] = "1";
151
+ env[SUBAGENT_FANOUT_CHILD_ENV] = fanoutAuthorized ? "1" : "0";
152
+ const inheritedNestedRoute = Boolean(process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] && process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] && process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV]);
153
+ const parentRunId = input.parentRunId ?? input.runId ?? (inheritedNestedRoute ? process.env[SUBAGENT_RUN_ID_ENV] : undefined) ?? process.env[SUBAGENT_PARENT_RUN_ID_ENV] ?? "";
154
+ const parentChildIndex = input.parentChildIndex !== undefined
155
+ ? String(input.parentChildIndex)
156
+ : input.childIndex !== undefined
157
+ ? String(input.childIndex)
158
+ : process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV] ?? "";
159
+ const inheritedDepth = Number(process.env[SUBAGENT_PARENT_DEPTH_ENV]);
160
+ const parentDepth = input.parentDepth ?? (inheritedNestedRoute && Number.isFinite(inheritedDepth) ? inheritedDepth + 1 : 1);
161
+ const parentPath = input.parentPath ?? [
162
+ ...parseNestedPathEnv(process.env[SUBAGENT_PARENT_PATH_ENV]),
163
+ ...(parentRunId ? [{
164
+ runId: parentRunId,
165
+ ...(parentChildIndex && /^\d+$/.test(parentChildIndex) ? { stepIndex: Number(parentChildIndex) } : {}),
166
+ ...(input.childAgentName ? { agent: input.childAgentName } : {}),
167
+ }] : []),
168
+ ];
169
+ env[SUBAGENT_PARENT_EVENT_SINK_ENV] = fanoutAuthorized
170
+ ? input.parentEventSink ?? process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] ?? ""
171
+ : "";
172
+ env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] = fanoutAuthorized
173
+ ? input.parentControlInbox ?? process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] ?? ""
174
+ : "";
175
+ env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] = fanoutAuthorized
176
+ ? input.parentRootRunId ?? process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] ?? input.runId ?? ""
177
+ : "";
178
+ env[SUBAGENT_PARENT_RUN_ID_ENV] = fanoutAuthorized ? parentRunId : "";
179
+ env[SUBAGENT_PARENT_CHILD_INDEX_ENV] = fanoutAuthorized ? parentChildIndex : "";
180
+ env[SUBAGENT_PARENT_DEPTH_ENV] = fanoutAuthorized ? String(parentDepth) : "";
181
+ env[SUBAGENT_PARENT_PATH_ENV] = fanoutAuthorized ? encodeNestedPathEnv(parentPath) : "";
182
+ env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] = fanoutAuthorized
183
+ ? input.parentCapabilityToken ?? process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] ?? ""
184
+ : "";
130
185
  env.PI_SUBAGENT_INHERIT_PROJECT_CONTEXT = input.inheritProjectContext ? "1" : "0";
131
186
  env.PI_SUBAGENT_INHERIT_SKILLS = input.inheritSkills ? "1" : "0";
132
187
  if (input.intercomSessionName) {
@@ -153,6 +208,8 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
153
208
  return { args, env, tempDir };
154
209
  }
155
210
 
211
+ export const parseParentPathEnv = parseNestedPathEnv;
212
+
156
213
  export function cleanupTempDir(tempDir: string | null | undefined): void {
157
214
  if (!tempDir) return;
158
215
  try {
@@ -1,4 +1,5 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { SUBAGENT_FANOUT_CHILD_ENV } from "./pi-args.ts";
2
3
 
3
4
  const SUBAGENT_INHERIT_PROJECT_CONTEXT_ENV = "PI_SUBAGENT_INHERIT_PROJECT_CONTEXT";
4
5
  const SUBAGENT_INHERIT_SKILLS_ENV = "PI_SUBAGENT_INHERIT_SKILLS";
@@ -12,6 +13,15 @@ export const CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS = [
12
13
  "If you need to edit files, call the actual edit/write tools. Do not print tool-call syntax, patches, or pseudo-tool calls as text.",
13
14
  ].join("\n");
14
15
 
16
+ export const CHILD_FANOUT_BOUNDARY_INSTRUCTIONS = [
17
+ "You are a child subagent with explicit fanout responsibility for this assigned task.",
18
+ "The parent session owns final orchestration, acceptance, and follow-up implementation launches.",
19
+ "You may use the `subagent` tool only for the fanout work explicitly requested in this task.",
20
+ "Do not broaden yourself into general parent orchestration. Do not launch follow-up workers unless the task explicitly asks for that.",
21
+ "The maxSubagentDepth cap still applies and may block further fanout.",
22
+ "If you need to edit files, call the actual edit/write tools. Do not print tool-call syntax, patches, or pseudo-tool calls as text.",
23
+ ].join("\n");
24
+
15
25
  const PARENT_ONLY_CUSTOM_MESSAGE_TYPES = new Set([
16
26
  "subagent-orchestration-instructions",
17
27
  "subagent-slash-result",
@@ -62,9 +72,17 @@ export function stripSubagentOrchestrationSkill(prompt: string): string {
62
72
  .replace(/[ \t]*<skill>\s*[\s\S]*?<\/skill>\s*/g, (block) => SUBAGENT_ORCHESTRATION_SKILL_NAME_PATTERN.test(block) ? "" : block);
63
73
  }
64
74
 
75
+ function stripChildBoundaryInstructions(prompt: string): string {
76
+ let rewritten = prompt;
77
+ for (const boundary of [CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS, CHILD_FANOUT_BOUNDARY_INSTRUCTIONS]) {
78
+ rewritten = rewritten.split(boundary).join("");
79
+ }
80
+ return rewritten.replace(/^(?:[ \t]*\r?\n)+/, "");
81
+ }
82
+
65
83
  export function rewriteSubagentPrompt(
66
84
  prompt: string,
67
- options: { inheritProjectContext: boolean; inheritSkills: boolean },
85
+ options: { inheritProjectContext: boolean; inheritSkills: boolean; fanoutChild?: boolean },
68
86
  ): string {
69
87
  let rewritten = prompt;
70
88
  if (!options.inheritProjectContext) {
@@ -74,9 +92,9 @@ export function rewriteSubagentPrompt(
74
92
  rewritten = stripInheritedSkills(rewritten);
75
93
  }
76
94
  rewritten = stripSubagentOrchestrationSkill(rewritten);
77
- return rewritten.includes(CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS)
78
- ? rewritten
79
- : `${CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS}\n\n${rewritten}`;
95
+ rewritten = stripChildBoundaryInstructions(rewritten);
96
+ const boundary = options.fanoutChild ? CHILD_FANOUT_BOUNDARY_INSTRUCTIONS : CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS;
97
+ return `${boundary}\n\n${rewritten}`;
80
98
  }
81
99
 
82
100
  function isParentOnlySubagentMessage(message: unknown): boolean {
@@ -139,10 +157,12 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
139
157
 
140
158
  const inheritProjectContext = readBooleanEnv(SUBAGENT_INHERIT_PROJECT_CONTEXT_ENV);
141
159
  const inheritSkills = readBooleanEnv(SUBAGENT_INHERIT_SKILLS_ENV);
142
- if (inheritProjectContext === undefined && inheritSkills === undefined) return;
160
+ const fanoutChild = readBooleanEnv(SUBAGENT_FANOUT_CHILD_ENV);
161
+ if (inheritProjectContext === undefined && inheritSkills === undefined && fanoutChild === undefined) return;
143
162
  const rewritten = rewriteSubagentPrompt(event.systemPrompt, {
144
163
  inheritProjectContext: inheritProjectContext ?? true,
145
164
  inheritSkills: inheritSkills ?? true,
165
+ fanoutChild: fanoutChild === true,
146
166
  });
147
167
  if (rewritten === event.systemPrompt) return;
148
168
  return { systemPrompt: rewritten };
@@ -83,6 +83,8 @@ export interface ControlEvent {
83
83
  agent: string;
84
84
  index?: number;
85
85
  runId: string;
86
+ nestedRunId?: string;
87
+ nestingPath?: NestedRunAddress["path"];
86
88
  message: string;
87
89
  reason?: "idle" | "completion_guard" | "active_long_running" | "tool_failures" | "time_threshold" | "turn_threshold" | "token_threshold";
88
90
  turns?: number;
@@ -98,6 +100,21 @@ export interface ControlEvent {
98
100
  export type SubagentResultStatus = "completed" | "failed" | "paused" | "detached";
99
101
  export type SubagentRunMode = "single" | "parallel" | "chain";
100
102
 
103
+ export type PublicNestedStepSummary = Pick<
104
+ NestedStepSummary,
105
+ "agent" | "status" | "sessionFile" | "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount" | "startedAt" | "endedAt" | "error"
106
+ > & {
107
+ children?: PublicNestedRunSummary[];
108
+ };
109
+
110
+ export type PublicNestedRunSummary = Pick<
111
+ NestedRunSummary,
112
+ "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" | "totalTokens" | "startedAt" | "endedAt" | "lastUpdate" | "error"
113
+ > & {
114
+ steps?: PublicNestedStepSummary[];
115
+ children?: PublicNestedRunSummary[];
116
+ };
117
+
101
118
  export interface SubagentResultIntercomChild {
102
119
  agent: string;
103
120
  status: SubagentResultStatus;
@@ -106,6 +123,7 @@ export interface SubagentResultIntercomChild {
106
123
  artifactPath?: string;
107
124
  sessionPath?: string;
108
125
  intercomTarget?: string;
126
+ children?: PublicNestedRunSummary[];
109
127
  }
110
128
 
111
129
  export interface SubagentResultIntercomPayload {
@@ -261,6 +279,76 @@ export interface AsyncParallelGroupStatus {
261
279
  stepIndex: number;
262
280
  }
263
281
 
282
+ export type NestedRunState = "queued" | "running" | "complete" | "failed" | "paused";
283
+ export type NestedOwnerState = "live" | "gone" | "unknown";
284
+
285
+ export interface NestedRunAddress {
286
+ id: string;
287
+ parentRunId: string;
288
+ parentStepIndex?: number;
289
+ parentAgent?: string;
290
+ depth: number;
291
+ path: Array<{ runId: string; stepIndex?: number; agent?: string }>;
292
+ }
293
+
294
+ export interface NestedStepSummary {
295
+ agent: string;
296
+ status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
297
+ sessionFile?: string;
298
+ activityState?: ActivityState;
299
+ lastActivityAt?: number;
300
+ currentTool?: string;
301
+ currentToolStartedAt?: number;
302
+ currentPath?: string;
303
+ turnCount?: number;
304
+ toolCount?: number;
305
+ startedAt?: number;
306
+ endedAt?: number;
307
+ error?: string;
308
+ children?: NestedRunSummary[];
309
+ }
310
+
311
+ export interface NestedRunSummary extends NestedRunAddress {
312
+ asyncDir?: string;
313
+ pid?: number;
314
+ sessionId?: string;
315
+ sessionFile?: string;
316
+ intercomTarget?: string;
317
+ ownerIntercomTarget?: string;
318
+ leafIntercomTarget?: string;
319
+ ownerState?: NestedOwnerState;
320
+ controlInbox?: string;
321
+ capabilityToken?: string;
322
+ mode?: SubagentRunMode;
323
+ state: NestedRunState;
324
+ agent?: string;
325
+ agents?: string[];
326
+ currentStep?: number;
327
+ chainStepCount?: number;
328
+ parallelGroups?: AsyncParallelGroupStatus[];
329
+ steps?: NestedStepSummary[];
330
+ children?: NestedRunSummary[];
331
+ activityState?: ActivityState;
332
+ lastActivityAt?: number;
333
+ currentTool?: string;
334
+ currentToolStartedAt?: number;
335
+ currentPath?: string;
336
+ turnCount?: number;
337
+ toolCount?: number;
338
+ totalTokens?: TokenUsage;
339
+ startedAt?: number;
340
+ endedAt?: number;
341
+ lastUpdate?: number;
342
+ error?: string;
343
+ }
344
+
345
+ export interface NestedRouteInfo {
346
+ rootRunId: string;
347
+ eventSink: string;
348
+ controlInbox: string;
349
+ capabilityToken: string;
350
+ }
351
+
264
352
  export interface AsyncStartedEvent {
265
353
  id?: string;
266
354
  asyncDir?: string;
@@ -272,6 +360,7 @@ export interface AsyncStartedEvent {
272
360
  chain?: string[];
273
361
  chainStepCount?: number;
274
362
  parallelGroups?: AsyncParallelGroupStatus[];
363
+ nestedRoute?: NestedRouteInfo;
275
364
  }
276
365
 
277
366
  export interface AsyncStatus {
@@ -297,6 +386,7 @@ export interface AsyncStatus {
297
386
  steps?: Array<{
298
387
  agent: string;
299
388
  status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
389
+ children?: NestedRunSummary[];
300
390
  sessionFile?: string;
301
391
  activityState?: ActivityState;
302
392
  lastActivityAt?: number;
@@ -361,6 +451,8 @@ export interface AsyncJobState {
361
451
  totalTokens?: TokenUsage;
362
452
  sessionFile?: string;
363
453
  controlEventCursor?: number;
454
+ nestedRoute?: NestedRouteInfo;
455
+ nestedChildren?: NestedRunSummary[];
364
456
  }
365
457
 
366
458
  export interface ForegroundResumeChild {
@@ -398,6 +490,8 @@ export interface SubagentState {
398
490
  turnCount?: number;
399
491
  tokens?: number;
400
492
  toolCount?: number;
493
+ nestedRoute?: NestedRouteInfo;
494
+ nestedChildren?: NestedRunSummary[];
401
495
  interrupt?: () => boolean;
402
496
  }>;
403
497
  lastForegroundControlId: string | null;
@@ -473,6 +567,7 @@ export interface RunSyncOptions {
473
567
  outputPath?: string;
474
568
  outputMode?: OutputMode;
475
569
  maxSubagentDepth?: number;
570
+ nestedRoute?: NestedRouteInfo;
476
571
  /** Override the agent's default model (format: "provider/id" or just "id") */
477
572
  modelOverride?: string;
478
573
  /** Registry models available for heuristic bare-model resolution */