pi-subagents 0.41.0 → 0.42.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.
@@ -3,41 +3,99 @@ import * as fs from "node:fs";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { encodeNestedPathEnv, parseNestedPathEnv, type NestedPathEntry } from "./nested-path.ts";
7
- import { resolveMcpDirectToolSelections, type ResolvedMcpDirectToolSelection } from "./mcp-direct-tool-allowlist.ts";
6
+ import {
7
+ encodeNestedPathEnv,
8
+ parseNestedPathEnv,
9
+ type NestedPathEntry,
10
+ } from "./nested-path.ts";
11
+ import {
12
+ resolveMcpDirectToolSelections,
13
+ type ResolvedMcpDirectToolSelection,
14
+ } from "./mcp-direct-tool-allowlist.ts";
8
15
  import { resolvePiPackageRoot } from "./pi-spawn.ts";
9
16
  import { RUNTIME_EXTENSION_ACK_PATH_ENV } from "./runtime-acknowledged-extensions.ts";
10
- import { STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV } from "./structured-output.ts";
11
- import { TEMP_ROOT_DIR, type JsonSchemaObject, type LaunchResolvedChildExtensionsV1, type ResolvedToolBudget } from "../../shared/types.ts";
17
+ import {
18
+ STRUCTURED_OUTPUT_CAPTURE_ENV,
19
+ STRUCTURED_OUTPUT_SCHEMA_ENV,
20
+ } from "./structured-output.ts";
21
+ import {
22
+ TEMP_ROOT_DIR,
23
+ type JsonSchemaObject,
24
+ type LaunchResolvedChildExtensionsV1,
25
+ type ResolvedToolBudget,
26
+ } from "../../shared/types.ts";
12
27
  import { THINKING_LEVELS } from "../../shared/model-info.ts";
13
- import { TOOL_BUDGET_ENV, TOOL_BUDGET_ZERO_AUTH_ENV, encodeToolBudgetEnv } from "./tool-budget.ts";
14
- import { CHILD_TOOL_DIAGNOSTIC_PATH_ENV, MCP_DIRECT_CHILD_TOOLS_ENV, REQUIRED_CHILD_TOOLS_ENV } from "./tool-availability.ts";
15
- import { CHILD_WATCHDOG_CONFIG_ENV, encodeChildWatchdogConfig, type ChildWatchdogConfig } from "../../watchdog/child-status.ts";
28
+ import {
29
+ TOOL_BUDGET_ENV,
30
+ TOOL_BUDGET_ZERO_AUTH_ENV,
31
+ encodeToolBudgetEnv,
32
+ } from "./tool-budget.ts";
33
+ import {
34
+ CHILD_TOOL_DIAGNOSTIC_PATH_ENV,
35
+ MCP_DIRECT_CHILD_TOOLS_ENV,
36
+ REQUIRED_CHILD_TOOLS_ENV,
37
+ } from "./tool-availability.ts";
38
+ import {
39
+ CHILD_WATCHDOG_CONFIG_ENV,
40
+ encodeChildWatchdogConfig,
41
+ type ChildWatchdogConfig,
42
+ } from "../../watchdog/child-status.ts";
16
43
  import { WAIT_TOOL_ENABLED_ENV } from "../background/wait-config.ts";
17
- import { PI_CODING_AGENT_PACKAGE_ROOT_ENV } from "../../shared/utils.ts";
18
- import { encodePermissionRules, PERMISSION_AUDIT_PATH_ENV, PERMISSION_POLICY_ENV, type PermissionRules } from "./permissions.ts";
19
- import { SUBAGENT_CAPABILITY_CEILING_ENV, capabilityCeilingAgentRestrictionSources, decodeSubagentCapabilityCeiling, encodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, isAgentAllowedByCapabilityCeiling, type ResolvedSubagentCapabilityCeiling, type SubagentCapabilityAudit } from "./capability-ceiling.ts";
44
+ import {
45
+ PI_CODING_AGENT_PACKAGE_ROOT_ENV,
46
+ getAgentDir,
47
+ } from "../../shared/utils.ts";
48
+ import {
49
+ encodePermissionRules,
50
+ PERMISSION_AUDIT_PATH_ENV,
51
+ PERMISSION_POLICY_ENV,
52
+ type PermissionRules,
53
+ } from "./permissions.ts";
54
+ import {
55
+ SUBAGENT_CAPABILITY_CEILING_ENV,
56
+ capabilityCeilingAgentRestrictionSources,
57
+ decodeSubagentCapabilityCeiling,
58
+ encodeSubagentCapabilityCeiling,
59
+ intersectSubagentCapabilityCeilings,
60
+ isAgentAllowedByCapabilityCeiling,
61
+ type ResolvedSubagentCapabilityCeiling,
62
+ type SubagentCapabilityAudit,
63
+ } from "./capability-ceiling.ts";
20
64
 
21
65
  const TASK_ARG_LIMIT = 8000;
22
66
  const MAX_LAUNCH_RESOLVED_EXTENSION_IDS = 32;
23
- const PROMPT_RUNTIME_EXTENSION_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-prompt-runtime.ts");
24
- const FANOUT_CHILD_EXTENSION_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "extension", "fanout-child.ts");
67
+ const PROMPT_RUNTIME_EXTENSION_PATH = path.join(
68
+ path.dirname(fileURLToPath(import.meta.url)),
69
+ "subagent-prompt-runtime.ts",
70
+ );
71
+ const FANOUT_CHILD_EXTENSION_PATH = path.join(
72
+ path.dirname(fileURLToPath(import.meta.url)),
73
+ "..",
74
+ "..",
75
+ "extension",
76
+ "fanout-child.ts",
77
+ );
25
78
  export const SUBAGENT_CHILD_ENV = "PI_SUBAGENT_CHILD";
26
- export const SUBAGENT_ORCHESTRATOR_TARGET_ENV = "PI_SUBAGENT_ORCHESTRATOR_TARGET";
27
- export const SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV = "PI_SUBAGENT_ORCHESTRATOR_SESSION_ID";
28
- export const SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV = "PI_SUBAGENT_SUPERVISOR_CHANNEL_DIR";
79
+ export const SUBAGENT_ORCHESTRATOR_TARGET_ENV =
80
+ "PI_SUBAGENT_ORCHESTRATOR_TARGET";
81
+ export const SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV =
82
+ "PI_SUBAGENT_ORCHESTRATOR_SESSION_ID";
83
+ export const SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV =
84
+ "PI_SUBAGENT_SUPERVISOR_CHANNEL_DIR";
29
85
  export const SUBAGENT_RUN_ID_ENV = "PI_SUBAGENT_RUN_ID";
30
86
  export const SUBAGENT_CHILD_AGENT_ENV = "PI_SUBAGENT_CHILD_AGENT";
31
87
  export const SUBAGENT_CHILD_INDEX_ENV = "PI_SUBAGENT_CHILD_INDEX";
32
88
  export const SUBAGENT_FANOUT_CHILD_ENV = "PI_SUBAGENT_FANOUT_CHILD";
33
89
  export const SUBAGENT_PARENT_EVENT_SINK_ENV = "PI_SUBAGENT_PARENT_EVENT_SINK";
34
- export const SUBAGENT_PARENT_CONTROL_INBOX_ENV = "PI_SUBAGENT_PARENT_CONTROL_INBOX";
90
+ export const SUBAGENT_PARENT_CONTROL_INBOX_ENV =
91
+ "PI_SUBAGENT_PARENT_CONTROL_INBOX";
35
92
  export const SUBAGENT_PARENT_ROOT_RUN_ID_ENV = "PI_SUBAGENT_PARENT_ROOT_RUN_ID";
36
93
  export const SUBAGENT_PARENT_RUN_ID_ENV = "PI_SUBAGENT_PARENT_RUN_ID";
37
94
  export const SUBAGENT_PARENT_CHILD_INDEX_ENV = "PI_SUBAGENT_PARENT_CHILD_INDEX";
38
95
  export const SUBAGENT_PARENT_DEPTH_ENV = "PI_SUBAGENT_PARENT_DEPTH";
39
96
  export const SUBAGENT_PARENT_PATH_ENV = "PI_SUBAGENT_PARENT_PATH";
40
- export const SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV = "PI_SUBAGENT_PARENT_CAPABILITY_TOKEN";
97
+ export const SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV =
98
+ "PI_SUBAGENT_PARENT_CAPABILITY_TOKEN";
41
99
  export const SUBAGENT_PARENT_SESSION_ENV = "PI_SUBAGENT_PARENT_SESSION";
42
100
  export const SUBAGENT_STEER_INBOX_ENV = "PI_SUBAGENT_STEER_INBOX";
43
101
  export const SUBAGENT_STEER_CAPABILITY_ENV = "PI_SUBAGENT_STEER_CAPABILITY";
@@ -105,17 +163,37 @@ export interface BuildPiArgsResult {
105
163
  }
106
164
 
107
165
  function sanitizeSupervisorChannelSegment(value: string): string {
108
- return value.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
166
+ return (
167
+ value
168
+ .trim()
169
+ .replace(/[^A-Za-z0-9._-]+/g, "-")
170
+ .replace(/^-+|-+$/g, "") || "unknown"
171
+ );
109
172
  }
110
173
 
111
- function supervisorChannelDir(runId: string, agent: string, childIndex: number): string {
112
- return path.join(TEMP_ROOT_DIR, "supervisor-channels", `${sanitizeSupervisorChannelSegment(runId)}-${sanitizeSupervisorChannelSegment(agent)}-${childIndex}`);
174
+ function supervisorChannelDir(
175
+ runId: string,
176
+ agent: string,
177
+ childIndex: number,
178
+ ): string {
179
+ return path.join(
180
+ TEMP_ROOT_DIR,
181
+ "supervisor-channels",
182
+ `${sanitizeSupervisorChannelSegment(runId)}-${sanitizeSupervisorChannelSegment(agent)}-${childIndex}`,
183
+ );
113
184
  }
114
185
 
115
- export function applyThinkingSuffix(model: string | undefined, thinking: string | false | undefined, replaceExisting = false): string | undefined {
186
+ export function applyThinkingSuffix(
187
+ model: string | undefined,
188
+ thinking: string | false | undefined,
189
+ replaceExisting = false,
190
+ ): string | undefined {
116
191
  if (!model || !thinking) return model;
117
192
  const colonIdx = model.lastIndexOf(":");
118
- if (colonIdx !== -1 && THINKING_LEVELS.some((level) => level === model.substring(colonIdx + 1))) {
193
+ if (
194
+ colonIdx !== -1 &&
195
+ THINKING_LEVELS.some((level) => level === model.substring(colonIdx + 1))
196
+ ) {
119
197
  return replaceExisting ? `${model.slice(0, colonIdx)}:${thinking}` : model;
120
198
  }
121
199
  return `${model}:${thinking}`;
@@ -128,11 +206,13 @@ export interface ResolvePiLaunchToolPlanInput {
128
206
  mcpDirectTools?: string[];
129
207
  cwd?: string;
130
208
  requireReadTool?: boolean;
131
- structuredOutput?: boolean | {
132
- schema: JsonSchemaObject;
133
- schemaPath: string;
134
- outputPath: string;
135
- };
209
+ structuredOutput?:
210
+ | boolean
211
+ | {
212
+ schema: JsonSchemaObject;
213
+ schemaPath: string;
214
+ outputPath: string;
215
+ };
136
216
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
137
217
  inheritedCapabilityCeiling?: ResolvedSubagentCapabilityCeiling;
138
218
  agentName?: string;
@@ -162,7 +242,10 @@ function extensionIdentifier(value: string): string {
162
242
  return `sha256:${createHash("sha256").update(path.normalize(value.trim())).digest("hex").slice(0, 16)}`;
163
243
  }
164
244
 
165
- function boundedExtensionIdentifiers(values: string[]): { ids: string[]; omitted: number } {
245
+ function boundedExtensionIdentifiers(values: string[]): {
246
+ ids: string[];
247
+ omitted: number;
248
+ } {
166
249
  const ids = [...new Set(values.map(extensionIdentifier))];
167
250
  return {
168
251
  ids: ids.slice(0, MAX_LAUNCH_RESOLVED_EXTENSION_IDS),
@@ -170,7 +253,15 @@ function boundedExtensionIdentifiers(values: string[]): { ids: string[]; omitted
170
253
  };
171
254
  }
172
255
 
173
- export function projectLaunchResolvedChildExtensions(toolPlan: Pick<PiLaunchToolPlan, "runtimeExtensions" | "configuredExtensions" | "extensionArgs" | "disableAmbientExtensions">): LaunchResolvedChildExtensionsV1 {
256
+ export function projectLaunchResolvedChildExtensions(
257
+ toolPlan: Pick<
258
+ PiLaunchToolPlan,
259
+ | "runtimeExtensions"
260
+ | "configuredExtensions"
261
+ | "extensionArgs"
262
+ | "disableAmbientExtensions"
263
+ >,
264
+ ): LaunchResolvedChildExtensionsV1 {
174
265
  const runtime = boundedExtensionIdentifiers(toolPlan.runtimeExtensions);
175
266
  const configured = boundedExtensionIdentifiers(toolPlan.configuredExtensions);
176
267
  const effective = boundedExtensionIdentifiers(toolPlan.extensionArgs);
@@ -189,55 +280,207 @@ export function projectLaunchResolvedChildExtensions(toolPlan: Pick<PiLaunchTool
189
280
  };
190
281
  }
191
282
 
192
- export function resolvePiLaunchToolPlan(input: ResolvePiLaunchToolPlanInput): PiLaunchToolPlan {
193
- const capabilityCeiling = intersectSubagentCapabilityCeilings(input.capabilityCeiling, input.inheritedCapabilityCeiling);
194
- const allowedToolSet = capabilityCeiling?.allowedTools === undefined ? undefined : new Set(capabilityCeiling.allowedTools);
195
- const requestedBuiltinTools = input.tools?.filter((tool) => !(tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js"))) ?? [];
283
+ /**
284
+ * Resolve the permission-system extension entry point when installed.
285
+ * Returns the absolute path to the extension's main module, or undefined
286
+ * when the package is not installed. Callers can check `autoInject` config
287
+ * to decide whether to include it in child processes.
288
+ */
289
+ export function resolvePermissionSystemExtension(): string | undefined {
290
+ const agentDir = getAgentDir();
291
+ const candidates = [
292
+ // npm-scoped package (most common)
293
+ path.join(
294
+ agentDir,
295
+ "npm",
296
+ "node_modules",
297
+ "@gotgenes",
298
+ "pi-permission-system",
299
+ ),
300
+ // direct extension directory (some layouts)
301
+ path.join(agentDir, "extensions", "pi-permission-system"),
302
+ ];
303
+ const errors: Error[] = [];
304
+ for (const extDir of candidates) {
305
+ if (!fs.existsSync(extDir)) continue;
306
+ const pkgPath = path.join(extDir, "package.json");
307
+ if (!fs.existsSync(pkgPath)) {
308
+ errors.push(new Error(`Permission-system package manifest is missing at ${pkgPath}.`));
309
+ continue;
310
+ }
311
+ try {
312
+ let pkg: { pi?: { extensions?: string[] } };
313
+ const parsed: unknown = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
314
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
315
+ throw new Error("manifest root must be an object");
316
+ }
317
+ pkg = parsed as typeof pkg;
318
+ const extensions = pkg.pi?.extensions;
319
+ const entry = Array.isArray(extensions) ? extensions[0] : undefined;
320
+ if (typeof entry !== "string" || !entry.trim()) {
321
+ throw new Error(
322
+ `Permission-system package manifest at ${pkgPath} must declare pi.extensions[0] as a non-empty string.`,
323
+ );
324
+ }
325
+ const resolved = path.resolve(extDir, entry);
326
+ if (fs.existsSync(resolved)) return resolved;
327
+ throw new Error(
328
+ `Permission-system extension entry ${JSON.stringify(entry)} in ${pkgPath} does not exist at ${resolved}.`,
329
+ );
330
+ } catch (error) {
331
+ const message = error instanceof Error ? error.message : String(error);
332
+ errors.push(message.startsWith("Permission-system") ? new Error(message) : new Error(`Cannot read permission-system package manifest at ${pkgPath}: ${message}`));
333
+ }
334
+ }
335
+ if (errors.length > 0) throw errors[0]!;
336
+ return undefined;
337
+ }
338
+
339
+ export function resolvePiLaunchToolPlan(
340
+ input: ResolvePiLaunchToolPlanInput,
341
+ ): PiLaunchToolPlan {
342
+ const capabilityCeiling = intersectSubagentCapabilityCeilings(
343
+ input.capabilityCeiling,
344
+ input.inheritedCapabilityCeiling,
345
+ );
346
+ const allowedToolSet =
347
+ capabilityCeiling?.allowedTools === undefined
348
+ ? undefined
349
+ : new Set(capabilityCeiling.allowedTools);
350
+ const requestedBuiltinTools =
351
+ input.tools?.filter(
352
+ (tool) =>
353
+ !(tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js")),
354
+ ) ?? [];
196
355
  if (input.requireReadTool && allowedToolSet && !allowedToolSet.has("read")) {
197
- throw new Error(`Capability ceiling from ${capabilityCeiling?.sources.join(", ") || "unknown source"} excludes required tool 'read' for lazy skill loading.`);
356
+ throw new Error(
357
+ `Capability ceiling from ${capabilityCeiling?.sources.join(", ") || "unknown source"} excludes required tool 'read' for lazy skill loading.`,
358
+ );
198
359
  }
199
- const declaredBuiltinTools = input.tools === undefined
200
- ? (allowedToolSet ? [...allowedToolSet] : [])
201
- : (input.requireReadTool && requestedBuiltinTools.length > 0 && !requestedBuiltinTools.includes("read") && !allowedToolSet
202
- ? ["read", ...requestedBuiltinTools]
203
- : requestedBuiltinTools).filter((tool) => !allowedToolSet || allowedToolSet.has(tool));
360
+ const declaredBuiltinTools =
361
+ input.tools === undefined
362
+ ? allowedToolSet
363
+ ? [...allowedToolSet]
364
+ : []
365
+ : (input.requireReadTool &&
366
+ requestedBuiltinTools.length > 0 &&
367
+ !requestedBuiltinTools.includes("read") &&
368
+ !allowedToolSet
369
+ ? ["read", ...requestedBuiltinTools]
370
+ : requestedBuiltinTools
371
+ ).filter((tool) => !allowedToolSet || allowedToolSet.has(tool));
204
372
  const fanoutAuthorized = declaredBuiltinTools.includes("subagent");
205
- const toolExtensionPaths: string[] = capabilityCeiling?.denyExtensions ? [] : (input.tools ?? []).filter((tool) => !requestedBuiltinTools.includes(tool) && (tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js")));
206
- const resolvedMcpSelections = capabilityCeiling?.denyExtensions ? [] : resolveMcpDirectToolSelections(input.mcpDirectTools, input.cwd);
207
- const effectiveMcpSelections = resolvedMcpSelections.filter((selection) => !allowedToolSet || allowedToolSet.has(selection.name));
208
- const effectiveMcpTools = effectiveMcpSelections.map((selection) => selection.name);
209
- const explicitToolAllowlist = input.tools !== undefined || (input.mcpDirectTools?.length ?? 0) > 0 || allowedToolSet !== undefined;
373
+ const toolExtensionPaths: string[] = capabilityCeiling?.denyExtensions
374
+ ? []
375
+ : (input.tools ?? []).filter(
376
+ (tool) =>
377
+ !requestedBuiltinTools.includes(tool) &&
378
+ (tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js")),
379
+ );
380
+ const resolvedMcpSelections = capabilityCeiling?.denyExtensions
381
+ ? []
382
+ : resolveMcpDirectToolSelections(input.mcpDirectTools, input.cwd);
383
+ const effectiveMcpSelections = resolvedMcpSelections.filter(
384
+ (selection) => !allowedToolSet || allowedToolSet.has(selection.name),
385
+ );
386
+ const effectiveMcpTools = effectiveMcpSelections.map(
387
+ (selection) => selection.name,
388
+ );
389
+ const explicitToolAllowlist =
390
+ input.tools !== undefined ||
391
+ (input.mcpDirectTools?.length ?? 0) > 0 ||
392
+ allowedToolSet !== undefined;
210
393
  const internalTools = input.structuredOutput ? ["structured_output"] : [];
211
- const effectiveToolAllowlist = [...new Set([...declaredBuiltinTools, ...effectiveMcpTools, ...internalTools])];
212
- const requiredChildTools = explicitToolAllowlist ? [...new Set([
213
- ...(input.tools !== undefined ? declaredBuiltinTools : []),
214
- ...(input.mcpDirectTools?.length ? effectiveMcpTools : []),
215
- ...internalTools,
216
- ])] : [];
217
- const runtimeExtensions = fanoutAuthorized
218
- ? [PROMPT_RUNTIME_EXTENSION_PATH, FANOUT_CHILD_EXTENSION_PATH]
219
- : [PROMPT_RUNTIME_EXTENSION_PATH];
220
- const disableAmbientExtensions = capabilityCeiling?.denyExtensions === true || input.extensions !== undefined;
221
- const configuredExtensions = capabilityCeiling?.denyExtensions ? [] : [...toolExtensionPaths, ...(input.extensions ?? []), ...(input.subagentOnlyExtensions ?? [])];
394
+ const effectiveToolAllowlist = [
395
+ ...new Set([
396
+ ...declaredBuiltinTools,
397
+ ...effectiveMcpTools,
398
+ ...internalTools,
399
+ ]),
400
+ ];
401
+ const requiredChildTools = explicitToolAllowlist
402
+ ? [
403
+ ...new Set([
404
+ ...(input.tools !== undefined ? declaredBuiltinTools : []),
405
+ ...(input.mcpDirectTools?.length ? effectiveMcpTools : []),
406
+ ...internalTools,
407
+ ]),
408
+ ]
409
+ : [];
410
+ const permSystemExt = capabilityCeiling?.denyExtensions
411
+ ? undefined
412
+ : resolvePermissionSystemExtension();
413
+ const runtimeExtensions = [
414
+ PROMPT_RUNTIME_EXTENSION_PATH,
415
+ ...(fanoutAuthorized ? [FANOUT_CHILD_EXTENSION_PATH] : []),
416
+ ...(permSystemExt ? [permSystemExt] : []),
417
+ ];
418
+ const disableAmbientExtensions =
419
+ capabilityCeiling?.denyExtensions === true ||
420
+ input.extensions !== undefined;
421
+ const configuredExtensions = capabilityCeiling?.denyExtensions
422
+ ? []
423
+ : [
424
+ ...toolExtensionPaths,
425
+ ...(input.extensions ?? []),
426
+ ...(input.subagentOnlyExtensions ?? []),
427
+ ];
222
428
  const extensionArgs = disableAmbientExtensions
223
429
  ? [...new Set([...runtimeExtensions, ...configuredExtensions])]
224
- : [...new Set([...runtimeExtensions, ...toolExtensionPaths, ...(input.subagentOnlyExtensions ?? [])])];
225
- const requestedToolNames = input.tools !== undefined
226
- ? [...new Set([...requestedBuiltinTools, ...resolvedMcpSelections.map((selection) => selection.name)])]
430
+ : [
431
+ ...new Set([
432
+ ...runtimeExtensions,
433
+ ...toolExtensionPaths,
434
+ ...(input.subagentOnlyExtensions ?? []),
435
+ ]),
436
+ ];
437
+ const requestedToolNames =
438
+ input.tools !== undefined
439
+ ? [
440
+ ...new Set([
441
+ ...requestedBuiltinTools,
442
+ ...resolvedMcpSelections.map((selection) => selection.name),
443
+ ]),
444
+ ]
445
+ : undefined;
446
+ const capabilityAudit = capabilityCeiling
447
+ ? ({
448
+ ceiling: capabilityCeiling,
449
+ ...(requestedToolNames ? { requestedTools: requestedToolNames } : {}),
450
+ effectiveTools: effectiveToolAllowlist,
451
+ removedTools:
452
+ requestedToolNames?.filter(
453
+ (tool) => !effectiveToolAllowlist.includes(tool),
454
+ ) ?? [],
455
+ internalTools,
456
+ extensionsDenied: capabilityCeiling.denyExtensions,
457
+ removedExtensionCount: capabilityCeiling.denyExtensions
458
+ ? (input.extensions?.length ?? 0) +
459
+ (input.subagentOnlyExtensions?.length ?? 0) +
460
+ (input.tools ?? []).filter(
461
+ (tool) =>
462
+ tool.includes("/") ||
463
+ tool.endsWith(".ts") ||
464
+ tool.endsWith(".js"),
465
+ ).length
466
+ : 0,
467
+ requestedMcpToolCount: input.mcpDirectTools?.length ?? 0,
468
+ effectiveMcpTools,
469
+ agentAllowed:
470
+ input.agentName === undefined
471
+ ? true
472
+ : isAgentAllowedByCapabilityCeiling(
473
+ input.agentName,
474
+ capabilityCeiling,
475
+ ),
476
+ ...(capabilityCeilingAgentRestrictionSources(capabilityCeiling)
477
+ ? {
478
+ agentRestrictionSources:
479
+ capabilityCeilingAgentRestrictionSources(capabilityCeiling),
480
+ }
481
+ : {}),
482
+ } satisfies SubagentCapabilityAudit)
227
483
  : undefined;
228
- const capabilityAudit = capabilityCeiling ? {
229
- ceiling: capabilityCeiling,
230
- ...(requestedToolNames ? { requestedTools: requestedToolNames } : {}),
231
- effectiveTools: effectiveToolAllowlist,
232
- removedTools: requestedToolNames?.filter((tool) => !effectiveToolAllowlist.includes(tool)) ?? [],
233
- internalTools,
234
- extensionsDenied: capabilityCeiling.denyExtensions,
235
- removedExtensionCount: capabilityCeiling.denyExtensions ? (input.extensions?.length ?? 0) + (input.subagentOnlyExtensions?.length ?? 0) + ((input.tools ?? []).filter((tool) => tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js")).length) : 0,
236
- requestedMcpToolCount: input.mcpDirectTools?.length ?? 0,
237
- effectiveMcpTools,
238
- agentAllowed: input.agentName === undefined ? true : isAgentAllowedByCapabilityCeiling(input.agentName, capabilityCeiling),
239
- ...(capabilityCeilingAgentRestrictionSources(capabilityCeiling) ? { agentRestrictionSources: capabilityCeilingAgentRestrictionSources(capabilityCeiling) } : {}),
240
- } satisfies SubagentCapabilityAudit : undefined;
241
484
  return {
242
485
  ...(capabilityCeiling ? { capabilityCeiling } : {}),
243
486
  requestedBuiltinTools,
@@ -259,6 +502,15 @@ export function resolvePiLaunchToolPlan(input: ResolvePiLaunchToolPlanInput): Pi
259
502
  };
260
503
  }
261
504
 
505
+ /** Escape XML-significant characters in a string for safe attribute interpolation. */
506
+ function escapeXmlAttr(value: string): string {
507
+ return value
508
+ .replace(/&/g, "&amp;")
509
+ .replace(/"/g, "&quot;")
510
+ .replace(/</g, "&lt;")
511
+ .replace(/>/g, "&gt;");
512
+ }
513
+
262
514
  export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
263
515
  const args = [...input.baseArgs];
264
516
 
@@ -289,17 +541,23 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
289
541
  requireReadTool: input.requireReadTool,
290
542
  structuredOutput: input.structuredOutput,
291
543
  capabilityCeiling: input.capabilityCeiling,
292
- inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
544
+ inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(
545
+ process.env[SUBAGENT_CAPABILITY_CEILING_ENV],
546
+ ),
293
547
  agentName: input.childAgentName,
294
548
  });
295
549
  if (toolPlan.explicitToolAllowlist) {
296
- args.push(toolPlan.effectiveToolAllowlist.length > 0 ? "--tools" : "--no-tools");
297
- if (toolPlan.effectiveToolAllowlist.length > 0) args.push(toolPlan.effectiveToolAllowlist.join(","));
550
+ args.push(
551
+ toolPlan.effectiveToolAllowlist.length > 0 ? "--tools" : "--no-tools",
552
+ );
553
+ if (toolPlan.effectiveToolAllowlist.length > 0)
554
+ args.push(toolPlan.effectiveToolAllowlist.join(","));
298
555
  }
299
556
  if (toolPlan.disableAmbientExtensions) {
300
557
  args.push("--no-extensions");
301
558
  }
302
- for (const extPath of toolPlan.extensionArgs) args.push("--extension", extPath);
559
+ for (const extPath of toolPlan.extensionArgs)
560
+ args.push("--extension", extPath);
303
561
 
304
562
  if (!input.inheritProjectContext) {
305
563
  args.push("--no-context-files");
@@ -313,8 +571,18 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
313
571
  tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
314
572
  const stem = (input.promptFileStem ?? "prompt").replace(/[^\w.-]/g, "_");
315
573
  const promptPath = path.join(tempDir, `${stem}.md`);
316
- fs.writeFileSync(promptPath, input.systemPrompt, { mode: 0o600 });
317
- args.push(input.systemPromptMode === "replace" ? "--system-prompt" : "--append-system-prompt", promptPath);
574
+ // Inject <active_agent> tag so @gotgenes/pi-permission-system can
575
+ // resolve per-agent policy inside the child session.
576
+ const taggedPrompt = input.childAgentName
577
+ ? `<active_agent name="${escapeXmlAttr(input.childAgentName)}"/>\n\n${input.systemPrompt}`
578
+ : input.systemPrompt;
579
+ fs.writeFileSync(promptPath, taggedPrompt, { mode: 0o600 });
580
+ args.push(
581
+ input.systemPromptMode === "replace"
582
+ ? "--system-prompt"
583
+ : "--append-system-prompt",
584
+ promptPath,
585
+ );
318
586
  }
319
587
 
320
588
  if (input.task.length > TASK_ARG_LIMIT) {
@@ -329,58 +597,106 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
329
597
  }
330
598
 
331
599
  const env: Record<string, string | undefined> = {};
332
- const piPackageRoot = process.env[PI_CODING_AGENT_PACKAGE_ROOT_ENV] ?? resolvePiPackageRoot();
600
+ const piPackageRoot =
601
+ process.env[PI_CODING_AGENT_PACKAGE_ROOT_ENV] ?? resolvePiPackageRoot();
333
602
  if (piPackageRoot) env[PI_CODING_AGENT_PACKAGE_ROOT_ENV] = piPackageRoot;
334
- if (!tempDir) tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
335
- const runtimeAcknowledgedExtensionsPath = path.join(tempDir, "runtime-acknowledged-extensions.json");
603
+ if (!tempDir)
604
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
605
+ const runtimeAcknowledgedExtensionsPath = path.join(
606
+ tempDir,
607
+ "runtime-acknowledged-extensions.json",
608
+ );
336
609
  env[RUNTIME_EXTENSION_ACK_PATH_ENV] = runtimeAcknowledgedExtensionsPath;
337
610
  let toolDiagnosticPath: string | undefined;
338
611
  if (toolPlan.requiredChildTools.length > 0) {
339
- if (!tempDir) tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
612
+ if (!tempDir)
613
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
340
614
  toolDiagnosticPath = path.join(tempDir, "tool-diagnostic.json");
341
615
  env[REQUIRED_CHILD_TOOLS_ENV] = JSON.stringify(toolPlan.requiredChildTools);
342
616
  env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = toolDiagnosticPath;
343
617
  }
344
- env[MCP_DIRECT_CHILD_TOOLS_ENV] = toolPlan.effectiveMcpTools.length > 0 ? JSON.stringify(toolPlan.effectiveMcpTools) : undefined;
618
+ env[MCP_DIRECT_CHILD_TOOLS_ENV] =
619
+ toolPlan.effectiveMcpTools.length > 0
620
+ ? JSON.stringify(toolPlan.effectiveMcpTools)
621
+ : undefined;
345
622
  env[SUBAGENT_CHILD_ENV] = "1";
346
623
  env[SUBAGENT_FANOUT_CHILD_ENV] = toolPlan.fanoutAuthorized ? "1" : "0";
347
624
  if (input.waitToolEnabled !== undefined) {
348
625
  env[WAIT_TOOL_ENABLED_ENV] = input.waitToolEnabled ? "true" : "false";
349
626
  }
350
- 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]);
351
- const parentRunId = input.parentRunId ?? input.runId ?? (inheritedNestedRoute ? process.env[SUBAGENT_RUN_ID_ENV] : undefined) ?? process.env[SUBAGENT_PARENT_RUN_ID_ENV] ?? "";
352
- const parentChildIndex = input.parentChildIndex !== undefined
353
- ? String(input.parentChildIndex)
354
- : input.childIndex !== undefined
355
- ? String(input.childIndex)
356
- : process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV] ?? "";
627
+ const inheritedNestedRoute = Boolean(
628
+ process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] &&
629
+ process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] &&
630
+ process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV],
631
+ );
632
+ const parentRunId =
633
+ input.parentRunId ??
634
+ input.runId ??
635
+ (inheritedNestedRoute ? process.env[SUBAGENT_RUN_ID_ENV] : undefined) ??
636
+ process.env[SUBAGENT_PARENT_RUN_ID_ENV] ??
637
+ "";
638
+ const parentChildIndex =
639
+ input.parentChildIndex !== undefined
640
+ ? String(input.parentChildIndex)
641
+ : input.childIndex !== undefined
642
+ ? String(input.childIndex)
643
+ : (process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV] ?? "");
357
644
  const inheritedDepth = Number(process.env[SUBAGENT_PARENT_DEPTH_ENV]);
358
- const parentDepth = input.parentDepth ?? (inheritedNestedRoute && Number.isFinite(inheritedDepth) ? inheritedDepth + 1 : 1);
645
+ const parentDepth =
646
+ input.parentDepth ??
647
+ (inheritedNestedRoute && Number.isFinite(inheritedDepth)
648
+ ? inheritedDepth + 1
649
+ : 1);
359
650
  const parentPath = input.parentPath ?? [
360
651
  ...parseNestedPathEnv(process.env[SUBAGENT_PARENT_PATH_ENV]),
361
- ...(parentRunId ? [{
362
- runId: parentRunId,
363
- ...(parentChildIndex && /^\d+$/.test(parentChildIndex) ? { stepIndex: Number(parentChildIndex) } : {}),
364
- ...(input.childAgentName ? { agent: input.childAgentName } : {}),
365
- }] : []),
652
+ ...(parentRunId
653
+ ? [
654
+ {
655
+ runId: parentRunId,
656
+ ...(parentChildIndex && /^\d+$/.test(parentChildIndex)
657
+ ? { stepIndex: Number(parentChildIndex) }
658
+ : {}),
659
+ ...(input.childAgentName ? { agent: input.childAgentName } : {}),
660
+ },
661
+ ]
662
+ : []),
366
663
  ];
367
664
  env[SUBAGENT_PARENT_EVENT_SINK_ENV] = toolPlan.fanoutAuthorized
368
- ? input.parentEventSink ?? process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] ?? ""
665
+ ? (input.parentEventSink ??
666
+ process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] ??
667
+ "")
369
668
  : "";
370
669
  env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] = toolPlan.fanoutAuthorized
371
- ? input.parentControlInbox ?? process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] ?? ""
670
+ ? (input.parentControlInbox ??
671
+ process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] ??
672
+ "")
372
673
  : "";
373
674
  env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] = toolPlan.fanoutAuthorized
374
- ? input.parentRootRunId ?? process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] ?? input.runId ?? ""
675
+ ? (input.parentRootRunId ??
676
+ process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] ??
677
+ input.runId ??
678
+ "")
679
+ : "";
680
+ env[SUBAGENT_PARENT_RUN_ID_ENV] = toolPlan.fanoutAuthorized
681
+ ? parentRunId
682
+ : "";
683
+ env[SUBAGENT_PARENT_CHILD_INDEX_ENV] = toolPlan.fanoutAuthorized
684
+ ? parentChildIndex
685
+ : "";
686
+ env[SUBAGENT_PARENT_DEPTH_ENV] = toolPlan.fanoutAuthorized
687
+ ? String(parentDepth)
688
+ : "";
689
+ env[SUBAGENT_PARENT_PATH_ENV] = toolPlan.fanoutAuthorized
690
+ ? encodeNestedPathEnv(parentPath)
375
691
  : "";
376
- env[SUBAGENT_PARENT_RUN_ID_ENV] = toolPlan.fanoutAuthorized ? parentRunId : "";
377
- env[SUBAGENT_PARENT_CHILD_INDEX_ENV] = toolPlan.fanoutAuthorized ? parentChildIndex : "";
378
- env[SUBAGENT_PARENT_DEPTH_ENV] = toolPlan.fanoutAuthorized ? String(parentDepth) : "";
379
- env[SUBAGENT_PARENT_PATH_ENV] = toolPlan.fanoutAuthorized ? encodeNestedPathEnv(parentPath) : "";
380
692
  env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] = toolPlan.fanoutAuthorized
381
- ? input.parentCapabilityToken ?? process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] ?? ""
693
+ ? (input.parentCapabilityToken ??
694
+ process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] ??
695
+ "")
382
696
  : "";
383
- env.PI_SUBAGENT_INHERIT_PROJECT_CONTEXT = input.inheritProjectContext ? "1" : "0";
697
+ env.PI_SUBAGENT_INHERIT_PROJECT_CONTEXT = input.inheritProjectContext
698
+ ? "1"
699
+ : "0";
384
700
  env.PI_SUBAGENT_INHERIT_SKILLS = input.inheritSkills ? "1" : "0";
385
701
  env[PI_INTERCOM_STABLE_ID_ENV] = input.intercomSessionName || undefined;
386
702
  env[PI_INTERCOM_SESSION_ID_ENV] = undefined;
@@ -394,14 +710,25 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
394
710
  env[SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV] = input.parentSessionId;
395
711
  }
396
712
  const encodedPermissionRules = encodePermissionRules(input.permissionRules);
397
- if (input.orchestratorIntercomTarget && input.parentSessionId && input.runId && input.childAgentName) {
713
+ if (
714
+ input.orchestratorIntercomTarget &&
715
+ input.parentSessionId &&
716
+ input.runId &&
717
+ input.childAgentName
718
+ ) {
398
719
  const childIndex = input.childIndex ?? 0;
399
- const channelDir = supervisorChannelDir(input.runId, input.childAgentName, childIndex);
720
+ const channelDir = supervisorChannelDir(
721
+ input.runId,
722
+ input.childAgentName,
723
+ childIndex,
724
+ );
400
725
  fs.mkdirSync(path.join(channelDir, "requests"), { recursive: true });
401
726
  fs.mkdirSync(path.join(channelDir, "replies"), { recursive: true });
402
727
  env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV] = channelDir;
403
728
  }
404
- if (encodedPermissionRules) env[PERMISSION_AUDIT_PATH_ENV] = input.permissionAuditPath ?? path.join(tempDir, "permission-audit.jsonl");
729
+ if (encodedPermissionRules)
730
+ env[PERMISSION_AUDIT_PATH_ENV] =
731
+ input.permissionAuditPath ?? path.join(tempDir, "permission-audit.jsonl");
405
732
  if (input.runId) {
406
733
  env[SUBAGENT_RUN_ID_ENV] = input.runId;
407
734
  }
@@ -411,12 +738,24 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
411
738
  if (input.childIndex !== undefined) {
412
739
  env[SUBAGENT_CHILD_INDEX_ENV] = String(input.childIndex);
413
740
  }
414
- if (!toolPlan.capabilityCeiling && input.mcpDirectTools?.length) env.MCP_DIRECT_TOOLS = input.mcpDirectTools.join(",");
415
- else if (toolPlan.capabilityCeiling && toolPlan.effectiveMcpSelections.length && !toolPlan.capabilityCeiling.denyExtensions) env.MCP_DIRECT_TOOLS = toolPlan.effectiveMcpSelections.map((selection) => selection.selector).join(",");
741
+ if (!toolPlan.capabilityCeiling && input.mcpDirectTools?.length)
742
+ env.MCP_DIRECT_TOOLS = input.mcpDirectTools.join(",");
743
+ else if (
744
+ toolPlan.capabilityCeiling &&
745
+ toolPlan.effectiveMcpSelections.length &&
746
+ !toolPlan.capabilityCeiling.denyExtensions
747
+ )
748
+ env.MCP_DIRECT_TOOLS = toolPlan.effectiveMcpSelections
749
+ .map((selection) => selection.selector)
750
+ .join(",");
416
751
  else env.MCP_DIRECT_TOOLS = "__none__";
417
- const encodedCapabilityCeiling = encodeSubagentCapabilityCeiling(toolPlan.capabilityCeiling);
418
- if (encodedCapabilityCeiling) env[SUBAGENT_CAPABILITY_CEILING_ENV] = encodedCapabilityCeiling;
419
- if (encodedPermissionRules) env[PERMISSION_POLICY_ENV] = encodedPermissionRules;
752
+ const encodedCapabilityCeiling = encodeSubagentCapabilityCeiling(
753
+ toolPlan.capabilityCeiling,
754
+ );
755
+ if (encodedCapabilityCeiling)
756
+ env[SUBAGENT_CAPABILITY_CEILING_ENV] = encodedCapabilityCeiling;
757
+ if (encodedPermissionRules)
758
+ env[PERMISSION_POLICY_ENV] = encodedPermissionRules;
420
759
  if (input.structuredOutput) {
421
760
  env[STRUCTURED_OUTPUT_CAPTURE_ENV] = input.structuredOutput.outputPath;
422
761
  env[STRUCTURED_OUTPUT_SCHEMA_ENV] = input.structuredOutput.schemaPath;
@@ -424,17 +763,27 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
424
763
  if (input.steerInboxDir) {
425
764
  env[SUBAGENT_STEER_INBOX_ENV] = input.steerInboxDir;
426
765
  }
427
- if (input.steerCapabilityPath) env[SUBAGENT_STEER_CAPABILITY_ENV] = input.steerCapabilityPath;
766
+ if (input.steerCapabilityPath)
767
+ env[SUBAGENT_STEER_CAPABILITY_ENV] = input.steerCapabilityPath;
428
768
  if (input.steerAckDir) env[SUBAGENT_STEER_ACK_DIR_ENV] = input.steerAckDir;
429
769
  const encodedToolBudget = encodeToolBudgetEnv(input.toolBudget);
430
770
  if (encodedToolBudget) env[TOOL_BUDGET_ENV] = encodedToolBudget;
431
771
  env[TOOL_BUDGET_ZERO_AUTH_ENV] = input.allowZeroToolBudget ? "1" : undefined;
432
772
  const encodedChildWatchdog = encodeChildWatchdogConfig(input.childWatchdog);
433
- if (encodedChildWatchdog) env[CHILD_WATCHDOG_CONFIG_ENV] = encodedChildWatchdog;
773
+ if (encodedChildWatchdog)
774
+ env[CHILD_WATCHDOG_CONFIG_ENV] = encodedChildWatchdog;
434
775
 
435
- env[SUBAGENT_PARENT_SESSION_ENV] = input.parentSessionId ?? process.env[SUBAGENT_PARENT_SESSION_ENV] ?? "";
776
+ env[SUBAGENT_PARENT_SESSION_ENV] =
777
+ input.parentSessionId ?? process.env[SUBAGENT_PARENT_SESSION_ENV] ?? "";
436
778
 
437
- return { args, env, tempDir, toolDiagnosticPath, runtimeAcknowledgedExtensionsPath, capabilityAudit: toolPlan.capabilityAudit };
779
+ return {
780
+ args,
781
+ env,
782
+ tempDir,
783
+ toolDiagnosticPath,
784
+ runtimeAcknowledgedExtensionsPath,
785
+ capabilityAudit: toolPlan.capabilityAudit,
786
+ };
438
787
  }
439
788
 
440
789
  export const parseParentPathEnv = parseNestedPathEnv;