pi-subagents 0.63.0 → 0.65.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 (107) hide show
  1. package/CHANGELOG.md +57 -1
  2. package/README.md +2 -2
  3. package/agents/reviewer.md +1 -1
  4. package/agents/scout.md +1 -1
  5. package/docs/agents.md +18 -16
  6. package/docs/configuration.md +7 -15
  7. package/docs/extension-api.md +15 -6
  8. package/docs/missions.md +2 -0
  9. package/docs/observability.md +10 -12
  10. package/docs/tool-reference.md +7 -6
  11. package/docs/watchdog.md +92 -114
  12. package/docs/workflows.md +5 -3
  13. package/package.json +3 -4
  14. package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  15. package/skills/pi-subagents/references/execution-controls.md +6 -5
  16. package/src/agents/agent-management.ts +47 -15
  17. package/src/api/capability-ceiling.ts +0 -1
  18. package/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
  19. package/src/api/preflight.ts +2 -3
  20. package/src/extension/doctor.ts +2 -10
  21. package/src/extension/fanout-child.ts +9 -11
  22. package/src/extension/index.ts +27 -5
  23. package/src/extension/public-execution.ts +14 -0
  24. package/src/extension/rpc.ts +3 -2
  25. package/src/extension/schemas.ts +2 -1
  26. package/src/extension/tool-description.ts +9 -8
  27. package/src/intercom/native-supervisor-channel.ts +138 -60
  28. package/src/intercom/supervisor-ui.ts +243 -0
  29. package/src/runs/background/async-execution.ts +46 -24
  30. package/src/runs/background/async-job-tracker.ts +11 -0
  31. package/src/runs/background/async-resume.ts +11 -2
  32. package/src/runs/background/control-channel.ts +2 -204
  33. package/src/runs/background/notify.ts +41 -2
  34. package/src/runs/background/process-terminal.ts +1 -1
  35. package/src/runs/background/run-child-session.ts +613 -0
  36. package/src/runs/background/run-status.ts +0 -1
  37. package/src/runs/background/runner-aliases.ts +125 -0
  38. package/src/runs/background/runner-child-sessions.ts +31 -0
  39. package/src/runs/background/scheduled-runs.ts +18 -4
  40. package/src/runs/background/subagent-runner.ts +203 -898
  41. package/src/runs/foreground/async-steering-action.ts +1 -17
  42. package/src/runs/foreground/execution.ts +196 -378
  43. package/src/runs/foreground/foreground-control.ts +4 -0
  44. package/src/runs/foreground/subagent-executor.ts +121 -58
  45. package/src/runs/foreground/workflow-foreground-steering.ts +24 -98
  46. package/src/runs/shared/abort-recovery.ts +3 -3
  47. package/src/runs/shared/acceptance.ts +10 -0
  48. package/src/runs/shared/async-status-projection.ts +11 -43
  49. package/src/runs/shared/capability-ceiling.ts +1 -2
  50. package/src/runs/shared/child-hooks.ts +25 -0
  51. package/src/runs/shared/child-identity.ts +13 -2
  52. package/src/runs/shared/child-launch.ts +314 -0
  53. package/src/runs/shared/child-lifecycle.ts +25 -0
  54. package/src/runs/shared/child-runtime-config.ts +126 -0
  55. package/src/runs/shared/child-session.ts +342 -0
  56. package/src/runs/shared/child-tool-plan.ts +530 -0
  57. package/src/runs/shared/claude-code-adapter.ts +5 -1
  58. package/src/runs/shared/completion-guard.ts +1 -1
  59. package/src/runs/shared/external-cli-preflight.ts +16 -0
  60. package/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
  61. package/src/runs/shared/model-exclusions.ts +82 -14
  62. package/src/runs/shared/model-fallback.ts +47 -4
  63. package/src/runs/shared/nested-events.ts +29 -45
  64. package/src/runs/shared/nested-path.ts +0 -14
  65. package/src/runs/shared/orca-progress-tabs.ts +12 -7
  66. package/src/runs/shared/parallel-utils.ts +0 -2
  67. package/src/runs/shared/permissions.ts +0 -13
  68. package/src/runs/shared/process-signal.ts +4 -1
  69. package/src/runs/shared/run-fanout-budget.ts +0 -13
  70. package/src/runs/shared/runtime-acknowledged-extensions.ts +0 -27
  71. package/src/runs/shared/structured-output.ts +17 -4
  72. package/src/runs/shared/subagent-control.ts +6 -2
  73. package/src/runs/shared/subagent-prompt-runtime.ts +87 -384
  74. package/src/runs/shared/tool-availability.ts +18 -62
  75. package/src/runs/shared/tool-budget.ts +0 -14
  76. package/src/runs/shared/worktree-cleanup-plan.ts +25 -6
  77. package/src/runs/shared/worktree.ts +117 -30
  78. package/src/shared/child-session-name.ts +1 -1
  79. package/src/shared/jsonl-writer.ts +11 -0
  80. package/src/shared/thinking-ceiling.ts +0 -6
  81. package/src/shared/types.ts +64 -30
  82. package/src/shared/utils.ts +3 -4
  83. package/src/slash/slash-commands.ts +0 -6
  84. package/src/tui/fleet.ts +0 -1
  85. package/src/tui/render.ts +238 -31
  86. package/src/watchdog/child-status.ts +54 -34
  87. package/src/watchdog/diff-tool.ts +77 -0
  88. package/src/watchdog/emission-guard.ts +5 -3
  89. package/src/watchdog/guidance.ts +20 -0
  90. package/src/watchdog/register-child.ts +28 -25
  91. package/src/watchdog/register-main.ts +10 -9
  92. package/src/watchdog/render.ts +4 -5
  93. package/src/watchdog/review.ts +15 -4
  94. package/src/watchdog/rules.ts +70 -0
  95. package/src/watchdog/runtime.ts +75 -92
  96. package/src/watchdog/scope.ts +0 -11
  97. package/src/watchdog/settings.ts +48 -104
  98. package/src/watchdog/types.ts +18 -32
  99. package/src/watchdog/warning-format.ts +0 -1
  100. package/src/workflows/chat-progress.ts +3 -2
  101. package/src/workflows/scripted-workflow.ts +48 -1
  102. package/src/workflows/workflow-checklist.ts +10 -12
  103. package/src/workflows/workflow-preflight.ts +28 -1
  104. package/src/runs/shared/child-protocol.ts +0 -415
  105. package/src/runs/shared/pi-args.ts +0 -1062
  106. package/src/runs/shared/subagent-startup-retry.ts +0 -116
  107. package/src/shared/post-exit-stdio-guard.ts +0 -85
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Builds the in-process launch for one child: the tool plan, the typed child
3
+ * runtime config the hooks read, and the session launch input. Foreground
4
+ * children are built in the parent process; background children are built in
5
+ * the detached runner from the step it received in its config.
6
+ */
7
+ import * as fs from "node:fs";
8
+ import * as path from "node:path";
9
+ import type { ChildWatchdogConfig, ChildWatchdogStatusEvent } from "../../watchdog/child-status.ts";
10
+ import type { ThinkingLevel } from "../../shared/model-info.ts";
11
+ import { intersectThinkingCeilings } from "../../shared/thinking-ceiling.ts";
12
+ import {
13
+ resolveChildDepth,
14
+ type LaunchResolvedChildExtensionsV1,
15
+ type ResolvedToolBudget,
16
+ type RunFanoutBudgetDescriptor,
17
+ } from "../../shared/types.ts";
18
+ import type { NestedPathEntry } from "./nested-path.ts";
19
+ import type { McpRuntimeSnapshotHost } from "./mcp-direct-tool-allowlist.ts";
20
+ import type { PermissionRules } from "./permissions.ts";
21
+ import type { StructuredOutputRuntime } from "./structured-output.ts";
22
+ import type { ChildToolDiagnostic } from "./tool-availability.ts";
23
+ import type { RuntimeAcknowledgedChildExtensionsV1 } from "../../shared/types.ts";
24
+ import { projectRuntimeAcknowledgedExtensions } from "./runtime-acknowledged-extensions.ts";
25
+ import { encodeExtensionBindings, PI_SUBAGENT_EXTENSION_BINDINGS_ENV, type ExtensionBindings } from "./extension-bindings.ts";
26
+ import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "./capability-ceiling.ts";
27
+ import {
28
+ isSubagentRuntimeExtensionPath,
29
+ projectLaunchResolvedChildExtensions,
30
+ resolvePiLaunchToolPlan,
31
+ supervisorChannelDir,
32
+ type PiLaunchToolPlan,
33
+ } from "./child-tool-plan.ts";
34
+ import type { ChildRuntimeConfig } from "./child-runtime-config.ts";
35
+ import { createChildHooks } from "./child-hooks.ts";
36
+ import type { ChildSessionLaunch, ChildSessionStorage } from "./child-session.ts";
37
+
38
+ /** Environment variable pi-mcp-adapter reads for the tools a child may expose. */
39
+ export const MCP_DIRECT_TOOLS_ENV = "MCP_DIRECT_TOOLS";
40
+
41
+ /**
42
+ * The parts of the launching executor's own child runtime that a child it
43
+ * launches inherits. Serialized into the background runner config; the
44
+ * foreground path passes the executor's full `ChildRuntimeConfig`.
45
+ */
46
+ export type InheritedChildRuntime = Pick<ChildRuntimeConfig, "depth" | "maxDepth" | "nestedRoute" | "nestedParent" | "capabilityCeiling" | "thinkingCeiling" | "runFanoutBudget">;
47
+
48
+ export function inheritedChildRuntime(config: ChildRuntimeConfig | undefined): InheritedChildRuntime | undefined {
49
+ if (!config) return undefined;
50
+ return {
51
+ depth: config.depth,
52
+ ...(config.maxDepth !== undefined ? { maxDepth: config.maxDepth } : {}),
53
+ ...(config.nestedRoute ? { nestedRoute: config.nestedRoute } : {}),
54
+ ...(config.nestedParent ? { nestedParent: config.nestedParent } : {}),
55
+ ...(config.capabilityCeiling ? { capabilityCeiling: config.capabilityCeiling } : {}),
56
+ ...(config.thinkingCeiling ? { thinkingCeiling: config.thinkingCeiling } : {}),
57
+ ...(config.runFanoutBudget ? { runFanoutBudget: config.runFanoutBudget } : {}),
58
+ };
59
+ }
60
+
61
+ export interface BuildInProcessChildLaunchInput {
62
+ parentSessionId?: string;
63
+ forkCacheKey?: string;
64
+ sessionEnabled: boolean;
65
+ sessionDir?: string;
66
+ sessionFile?: string;
67
+ /** Model reference with the thinking suffix already applied. */
68
+ model?: string;
69
+ systemPromptMode?: "append" | "replace";
70
+ inheritProjectContext: boolean;
71
+ inheritGlobalContext: boolean;
72
+ inheritSkills: boolean;
73
+ requireReadTool?: boolean;
74
+ tools?: string[];
75
+ excludeTools?: string[];
76
+ extensions?: string[];
77
+ subagentOnlyExtensions?: string[];
78
+ systemPrompt?: string | null;
79
+ mcpDirectTools?: string[];
80
+ extensionBindings?: ExtensionBindings;
81
+ cwd: string;
82
+ intercomSessionName?: string;
83
+ sessionName?: string;
84
+ orchestratorIntercomTarget?: string;
85
+ runId?: string;
86
+ childAgentName: string;
87
+ childIndex: number;
88
+ nestedRoute?: { rootRunId: string; eventSink: string; controlInbox: string; capabilityToken: string };
89
+ runFanoutBudget?: RunFanoutBudgetDescriptor;
90
+ structuredOutput?: StructuredOutputRuntime;
91
+ fast?: boolean;
92
+ modelCandidates?: readonly string[];
93
+ toolBudget?: ResolvedToolBudget;
94
+ permissionRules?: PermissionRules;
95
+ permissionAuditPath?: string;
96
+ childWatchdog?: ChildWatchdogConfig;
97
+ watchdogStatus?: (event: ChildWatchdogStatusEvent) => void;
98
+ waitToolEnabled?: boolean;
99
+ waitToolDefaultTimeoutMs?: number;
100
+ allowNestedSubagents?: boolean;
101
+ capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
102
+ thinkingCeiling?: ThinkingLevel;
103
+ maxSubagentDepth?: number;
104
+ runtimeSnapshotHost?: McpRuntimeSnapshotHost;
105
+ /** The launching executor's own child runtime when it is itself an in-process child. */
106
+ inherited?: InheritedChildRuntime;
107
+ /**
108
+ * Which process hosts the session. The parent never loads ambient extensions
109
+ * or writes child environment values (it shares its process with the parent
110
+ * session); the runner loads ambient extensions when the tool plan allows
111
+ * them and exposes the child environment external extensions read.
112
+ */
113
+ host: "parent" | "runner";
114
+ }
115
+
116
+ export interface InProcessChildCapture {
117
+ structuredOutput(): { called: boolean; value?: unknown; acceptanceReport?: unknown; acceptanceReportProvided: boolean };
118
+ toolDiagnostic(): ChildToolDiagnostic | undefined;
119
+ runtimeAcknowledgedExtensions(): RuntimeAcknowledgedChildExtensionsV1 | undefined;
120
+ }
121
+
122
+ export interface InProcessChildLaunch {
123
+ toolPlan: PiLaunchToolPlan;
124
+ config: ChildRuntimeConfig;
125
+ session: Omit<ChildSessionLaunch, "onExtensionError">;
126
+ capture: InProcessChildCapture;
127
+ launchResolvedExtensions: LaunchResolvedChildExtensionsV1;
128
+ warnings: string[];
129
+ capabilityAudit?: SubagentCapabilityAudit;
130
+ }
131
+
132
+ /** Escape XML-significant characters in a string for safe attribute interpolation. */
133
+ function escapeXmlAttr(value: string): string {
134
+ return value
135
+ .replace(/&/g, "&amp;")
136
+ .replace(/"/g, "&quot;")
137
+ .replace(/</g, "&lt;")
138
+ .replace(/>/g, "&gt;");
139
+ }
140
+
141
+ function inheritedCapabilityCeiling(inherited: InheritedChildRuntime | undefined): ResolvedSubagentCapabilityCeiling | undefined {
142
+ return inherited?.capabilityCeiling;
143
+ }
144
+
145
+ /** Environment values external child extensions read; only the runner applies them. */
146
+ function childProcessEnv(input: BuildInProcessChildLaunchInput, toolPlan: PiLaunchToolPlan): Record<string, string | undefined> {
147
+ const env: Record<string, string | undefined> = {};
148
+ env[PI_SUBAGENT_EXTENSION_BINDINGS_ENV] = encodeExtensionBindings(input.extensionBindings);
149
+ if (!toolPlan.capabilityCeiling && input.mcpDirectTools?.length) env[MCP_DIRECT_TOOLS_ENV] = input.mcpDirectTools.join(",");
150
+ else if (toolPlan.capabilityCeiling && toolPlan.effectiveMcpSelections.length && !toolPlan.capabilityCeiling.denyExtensions) {
151
+ env[MCP_DIRECT_TOOLS_ENV] = toolPlan.effectiveMcpSelections.map((selection) => selection.selector).join(",");
152
+ } else env[MCP_DIRECT_TOOLS_ENV] = "__none__";
153
+ return env;
154
+ }
155
+
156
+ function childStorage(input: BuildInProcessChildLaunchInput): ChildSessionStorage {
157
+ if (input.sessionFile) {
158
+ fs.mkdirSync(path.dirname(input.sessionFile), { recursive: true });
159
+ return { kind: "file", sessionFile: input.sessionFile };
160
+ }
161
+ if (!input.sessionEnabled) return { kind: "memory" };
162
+ if (input.sessionDir) {
163
+ fs.mkdirSync(input.sessionDir, { recursive: true });
164
+ return { kind: "dir", sessionDir: input.sessionDir };
165
+ }
166
+ return { kind: "default" };
167
+ }
168
+
169
+ export function buildInProcessChildLaunch(input: BuildInProcessChildLaunchInput): InProcessChildLaunch {
170
+ const toolPlan = resolvePiLaunchToolPlan({
171
+ tools: input.tools,
172
+ excludeTools: input.excludeTools,
173
+ allowNestedSubagents: input.allowNestedSubagents,
174
+ extensions: input.extensions,
175
+ subagentOnlyExtensions: input.subagentOnlyExtensions,
176
+ mcpDirectTools: input.mcpDirectTools,
177
+ cwd: input.cwd,
178
+ requireReadTool: input.requireReadTool,
179
+ structuredOutput: Boolean(input.structuredOutput),
180
+ fast: input.fast,
181
+ model: input.model,
182
+ modelCandidates: input.modelCandidates,
183
+ capabilityCeiling: input.capabilityCeiling,
184
+ inheritedCapabilityCeiling: inheritedCapabilityCeiling(input.inherited),
185
+ agentName: input.childAgentName,
186
+ permissionRules: input.permissionRules,
187
+ runtimeSnapshotHost: input.runtimeSnapshotHost,
188
+ });
189
+
190
+ const inherited = input.inherited;
191
+ const fanout = toolPlan.fanoutAuthorized;
192
+ const inheritedRoute = inherited?.nestedRoute;
193
+ const parentRunId = input.runId ?? inherited?.nestedParent?.parentRunId ?? "";
194
+ const parentChildIndex = input.childIndex;
195
+ const parentDepth = inheritedRoute && inherited?.nestedParent ? inherited.nestedParent.depth + 1 : 1;
196
+ const parentPath: NestedPathEntry[] = [
197
+ ...(inherited?.nestedParent?.path ?? []),
198
+ ...(parentRunId ? [{ runId: parentRunId, stepIndex: parentChildIndex, agent: input.childAgentName }] : []),
199
+ ];
200
+ const nestedRoute = fanout ? (input.nestedRoute ?? inheritedRoute) : undefined;
201
+ const childDepth = resolveChildDepth(input.maxSubagentDepth, inherited);
202
+ const permissions = input.permissionRules && Object.keys(input.permissionRules).length > 0
203
+ ? { rules: input.permissionRules, ...(input.permissionAuditPath ? { auditPath: input.permissionAuditPath } : {}) }
204
+ : undefined;
205
+ let supervisorDir: string | undefined;
206
+ if (input.orchestratorIntercomTarget && input.parentSessionId && input.runId) {
207
+ supervisorDir = supervisorChannelDir(input.runId, input.childAgentName, input.childIndex);
208
+ fs.mkdirSync(path.join(supervisorDir, "requests"), { recursive: true });
209
+ fs.mkdirSync(path.join(supervisorDir, "replies"), { recursive: true });
210
+ }
211
+ const thinkingCeiling = intersectThinkingCeilings(input.thinkingCeiling, inherited?.thinkingCeiling);
212
+
213
+ let structuredValue: unknown;
214
+ let structuredAcceptanceReport: unknown;
215
+ let structuredCalled = false;
216
+ let structuredAcceptanceProvided = false;
217
+ let toolDiagnostic: ChildToolDiagnostic | undefined;
218
+ let acknowledgedIds: string[] | undefined;
219
+
220
+ const config: ChildRuntimeConfig = {
221
+ ...(input.runId ? { runId: input.runId } : {}),
222
+ agent: input.childAgentName,
223
+ childIndex: input.childIndex,
224
+ fanoutChild: fanout,
225
+ ...(input.sessionName?.trim() ? { sessionName: input.sessionName.trim() } : {}),
226
+ ...(input.intercomSessionName ? { intercomSessionName: input.intercomSessionName } : {}),
227
+ ...(input.orchestratorIntercomTarget ? { orchestratorTarget: input.orchestratorIntercomTarget } : {}),
228
+ ...(input.parentSessionId ? { orchestratorSessionId: input.parentSessionId, parentSessionId: input.parentSessionId } : {}),
229
+ ...(supervisorDir ? { supervisorChannelDir: supervisorDir } : {}),
230
+ ...(nestedRoute ? { nestedRoute } : {}),
231
+ ...(fanout && parentRunId ? { nestedParent: { parentRunId, parentChildIndex, depth: parentDepth, path: parentPath } } : {}),
232
+ ...(fanout && (input.runFanoutBudget ?? inherited?.runFanoutBudget) ? { runFanoutBudget: input.runFanoutBudget ?? inherited?.runFanoutBudget } : {}),
233
+ depth: childDepth.depth,
234
+ maxDepth: childDepth.maxDepth,
235
+ ...(toolPlan.capabilityCeiling ? { capabilityCeiling: toolPlan.capabilityCeiling } : {}),
236
+ ...(thinkingCeiling ? { thinkingCeiling } : {}),
237
+ inheritProjectContext: input.inheritProjectContext,
238
+ inheritGlobalContext: input.inheritGlobalContext,
239
+ inheritSkills: input.inheritSkills,
240
+ ...(input.forkCacheKey?.trim() ? { forkCacheKey: input.forkCacheKey.trim() } : {}),
241
+ ...(permissions ? { permissions } : {}),
242
+ ...(input.toolBudget ? { toolBudget: input.toolBudget } : {}),
243
+ ...(input.childWatchdog ? { childWatchdog: input.childWatchdog } : {}),
244
+ ...(input.watchdogStatus ? { watchdogStatus: input.watchdogStatus } : {}),
245
+ waitTool: {
246
+ enabled: input.waitToolEnabled ?? true,
247
+ ...(input.waitToolDefaultTimeoutMs !== undefined ? { defaultTimeoutMs: input.waitToolDefaultTimeoutMs } : {}),
248
+ },
249
+ ...(input.structuredOutput
250
+ ? {
251
+ structuredOutput: {
252
+ schema: input.structuredOutput.schema,
253
+ ...(input.structuredOutput.acceptanceReportPath
254
+ ? { acceptanceReport: input.structuredOutput.acceptanceReportRequired ? "required" as const : "optional" as const }
255
+ : {}),
256
+ capture: (value: unknown, acceptanceReport: unknown) => {
257
+ structuredCalled = true;
258
+ structuredValue = value;
259
+ structuredAcceptanceProvided = acceptanceReport !== undefined;
260
+ structuredAcceptanceReport = acceptanceReport;
261
+ },
262
+ },
263
+ }
264
+ : {}),
265
+ ...(toolPlan.requiredChildTools.length > 0 ? { requiredTools: toolPlan.requiredChildTools } : {}),
266
+ ...(toolPlan.effectiveMcpTools.length > 0 ? { mcpDirectTools: toolPlan.effectiveMcpTools } : {}),
267
+ toolDiagnostic: (diagnostic) => { toolDiagnostic = diagnostic; },
268
+ runtimeAcknowledgements: (ids) => { acknowledgedIds = ids; },
269
+ fast: input.fast === true,
270
+ };
271
+
272
+ const extensionPaths = toolPlan.extensionArgs.filter((extensionPath) => !isSubagentRuntimeExtensionPath(extensionPath));
273
+ const ambientExtensions = input.host === "runner" && !toolPlan.disableAmbientExtensions;
274
+ const launchResolvedExtensions = projectLaunchResolvedChildExtensions({
275
+ runtimeExtensions: toolPlan.runtimeExtensions,
276
+ configuredExtensions: toolPlan.configuredExtensions,
277
+ extensionArgs: toolPlan.extensionArgs,
278
+ disableAmbientExtensions: !ambientExtensions,
279
+ });
280
+ const taggedPrompt = input.systemPrompt !== undefined && input.systemPrompt !== null
281
+ ? `<active_agent name="${escapeXmlAttr(input.childAgentName)}"/>\n\n${input.systemPrompt}`
282
+ : undefined;
283
+ const session: Omit<ChildSessionLaunch, "onExtensionError"> = {
284
+ cwd: input.cwd,
285
+ storage: childStorage(input),
286
+ ...(input.model ? { model: input.model } : {}),
287
+ ...(toolPlan.explicitToolAllowlist ? { tools: toolPlan.effectiveToolAllowlist } : {}),
288
+ ...(!toolPlan.explicitToolAllowlist && toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
289
+ extensionPaths,
290
+ ambientExtensions,
291
+ hooks: createChildHooks(config),
292
+ ...(input.host === "runner" ? { processEnv: childProcessEnv(input, toolPlan) } : {}),
293
+ runtime: config,
294
+ noSkills: !input.inheritSkills,
295
+ noContextFiles: !input.inheritProjectContext,
296
+ ...(taggedPrompt !== undefined
297
+ ? input.systemPromptMode === "replace" ? { systemPrompt: taggedPrompt } : { appendSystemPrompt: taggedPrompt }
298
+ : {}),
299
+ };
300
+
301
+ return {
302
+ toolPlan,
303
+ config,
304
+ session,
305
+ capture: {
306
+ structuredOutput: () => ({ called: structuredCalled, value: structuredValue, acceptanceReport: structuredAcceptanceReport, acceptanceReportProvided: structuredAcceptanceProvided }),
307
+ toolDiagnostic: () => toolDiagnostic,
308
+ runtimeAcknowledgedExtensions: () => (acknowledgedIds ? projectRuntimeAcknowledgedExtensions(acknowledgedIds) : undefined),
309
+ },
310
+ launchResolvedExtensions,
311
+ warnings: toolPlan.warnings,
312
+ ...(toolPlan.capabilityAudit ? { capabilityAudit: toolPlan.capabilityAudit } : {}),
313
+ };
314
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Child lifecycle projection: when the observed child session events mean
3
+ * the run is settling and the observer may start (or must cancel) its final
4
+ * drain window.
5
+ */
6
+ export type ChildLifecycleAction = "start-drain" | "cancel-drain" | "none";
7
+
8
+ export interface ChildLifecycleState {
9
+ compactionRetryActive: boolean;
10
+ }
11
+
12
+ export function projectChildLifecycle(event: { type?: string; willRetry?: unknown }, terminalAssistantStop = false, state?: ChildLifecycleState): ChildLifecycleAction {
13
+ if (event.type === "compaction_end") {
14
+ if (state) state.compactionRetryActive = event.willRetry === true;
15
+ return event.willRetry === true ? "cancel-drain" : "none";
16
+ }
17
+ if (event.type === "agent_start" || event.type === "auto_retry_start") {
18
+ if (state) state.compactionRetryActive = false;
19
+ }
20
+ if (event.type === "agent_end" && event.willRetry === true) return "cancel-drain";
21
+ if (event.type === "agent_end" && state) state.compactionRetryActive = false;
22
+ if (event.type === "agent_settled") return state?.compactionRetryActive ? "none" : "start-drain";
23
+ if (terminalAssistantStop) return "start-drain";
24
+ return "none";
25
+ }
@@ -0,0 +1,126 @@
1
+ import type { JsonSchemaObject, ResolvedToolBudget, RunFanoutBudgetDescriptor } from "../../shared/types.ts";
2
+ import type { ThinkingLevel } from "../../shared/model-info.ts";
3
+ import type { NestedPathEntry } from "./nested-path.ts";
4
+ import type { PermissionRules } from "./permissions.ts";
5
+ import type { ChildWatchdogConfig, ChildWatchdogStatusEvent } from "../../watchdog/child-status.ts";
6
+ import type { ResolvedWaitToolConfig } from "../background/wait-config.ts";
7
+ import type { ChildToolDiagnostic } from "./tool-availability.ts";
8
+ import type { ResolvedSubagentCapabilityCeiling } from "./capability-ceiling.ts";
9
+
10
+ /**
11
+ * Set in processes that host child sessions (the async runner). The extension
12
+ * entry point registers nothing when it sees it, so an ambient copy of
13
+ * pi-subagents loaded into a child session stays inert.
14
+ */
15
+ export const SUBAGENT_CHILD_ENV = "PI_SUBAGENT_CHILD";
16
+ /** Root parent session id the parent publishes for pi-permission-system ask forwarding. */
17
+ export const SUBAGENT_PARENT_SESSION_ENV = "PI_SUBAGENT_PARENT_SESSION";
18
+
19
+ export interface ChildNestedRoute {
20
+ rootRunId: string;
21
+ eventSink: string;
22
+ controlInbox: string;
23
+ capabilityToken: string;
24
+ }
25
+
26
+ export interface ChildNestedParent {
27
+ parentRunId: string;
28
+ parentChildIndex?: number;
29
+ depth: number;
30
+ path: NestedPathEntry[];
31
+ }
32
+
33
+ export interface ChildPermissions {
34
+ rules: PermissionRules;
35
+ auditPath?: string;
36
+ }
37
+
38
+ export interface ChildStructuredOutput {
39
+ schema: JsonSchemaObject;
40
+ acceptanceReport?: "optional" | "required";
41
+ /** Receives the validated value; `acceptanceReport` is undefined when the child omitted it. */
42
+ capture: (value: unknown, acceptanceReport: unknown | undefined) => void;
43
+ }
44
+
45
+ export interface ChildSupervisorMetadata {
46
+ channelDir: string;
47
+ runId: string;
48
+ agent: string;
49
+ childIndex: number;
50
+ orchestratorTarget?: string;
51
+ orchestratorSessionId: string;
52
+ childTarget?: string;
53
+ }
54
+
55
+ /**
56
+ * Everything the child-side hooks need to know about the launch. The process
57
+ * that hosts the child session builds it and passes it to the hooks directly.
58
+ */
59
+ export interface ChildRuntimeConfig {
60
+ runId?: string;
61
+ agent?: string;
62
+ childIndex?: number;
63
+ fanoutChild: boolean;
64
+ sessionName?: string;
65
+ intercomSessionName?: string;
66
+ orchestratorTarget?: string;
67
+ orchestratorSessionId?: string;
68
+ parentSessionId?: string;
69
+ supervisorChannelDir?: string;
70
+ /** Route the child reports nested runs on; set only for fanout-authorized children. */
71
+ nestedRoute?: ChildNestedRoute;
72
+ nestedParent?: ChildNestedParent;
73
+ runFanoutBudget?: RunFanoutBudgetDescriptor;
74
+ /** Nesting depth of this child (1 for a top-level parent's child). */
75
+ depth: number;
76
+ maxDepth?: number;
77
+ capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
78
+ thinkingCeiling?: ThinkingLevel;
79
+ inheritProjectContext?: boolean;
80
+ inheritGlobalContext?: boolean;
81
+ inheritSkills?: boolean;
82
+ forkCacheKey?: string;
83
+ permissions?: ChildPermissions;
84
+ toolBudget?: ResolvedToolBudget;
85
+ childWatchdog?: ChildWatchdogConfig;
86
+ /** Receives child watchdog status events. */
87
+ watchdogStatus?: (event: ChildWatchdogStatusEvent) => void;
88
+ waitTool: ResolvedWaitToolConfig;
89
+ structuredOutput?: ChildStructuredOutput;
90
+ requiredTools?: string[];
91
+ mcpDirectTools?: string[];
92
+ /** Receives the tool-availability diagnostic at every agent start; undefined when every required tool is present. */
93
+ toolDiagnostic?: (diagnostic: ChildToolDiagnostic | undefined) => void;
94
+ /** Receives the runtime-acknowledged extension ids when the child run ends. */
95
+ runtimeAcknowledgements?: (ids: string[]) => void;
96
+ fast: boolean;
97
+ }
98
+
99
+ export function childSupervisorMetadata(config: ChildRuntimeConfig): ChildSupervisorMetadata | undefined {
100
+ if (!config.supervisorChannelDir || !config.runId || !config.agent || !config.orchestratorSessionId || config.childIndex === undefined) return undefined;
101
+ return {
102
+ channelDir: config.supervisorChannelDir,
103
+ runId: config.runId,
104
+ agent: config.agent,
105
+ childIndex: config.childIndex,
106
+ ...(config.orchestratorTarget ? { orchestratorTarget: config.orchestratorTarget } : {}),
107
+ orchestratorSessionId: config.orchestratorSessionId,
108
+ ...(config.intercomSessionName ? { childTarget: config.intercomSessionName } : {}),
109
+ };
110
+ }
111
+
112
+ /** Compute the child tool-availability diagnostic; undefined when every required tool is present. */
113
+ export function evaluateChildToolDiagnostic(config: Pick<ChildRuntimeConfig, "agent" | "requiredTools" | "mcpDirectTools">, availableTools: string[]): ChildToolDiagnostic | undefined {
114
+ if (!config.requiredTools) return undefined;
115
+ const available = new Set(availableTools);
116
+ const missing = config.requiredTools.filter((name) => !available.has(name));
117
+ if (missing.length === 0) return undefined;
118
+ const missingMcpDirectTools = config.mcpDirectTools?.length ? missing.filter((name) => config.mcpDirectTools!.includes(name)) : [];
119
+ return {
120
+ ...(config.agent ? { agent: config.agent } : {}),
121
+ required: config.requiredTools,
122
+ available: availableTools,
123
+ missing,
124
+ ...(missingMcpDirectTools.length > 0 ? { missingMcpDirectTools } : {}),
125
+ };
126
+ }