pi-subagents 0.36.0 → 0.37.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.
- package/CHANGELOG.md +17 -0
- package/README.md +93 -8
- package/agents/delegate.md +2 -0
- package/agents/worker.md +2 -0
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +20 -4
- package/src/agents/agent-management.ts +7 -1
- package/src/agents/agents.ts +99 -12
- package/src/api/capability-ceiling.ts +17 -0
- package/src/api/delegation.ts +3 -1
- package/src/api/preflight.ts +399 -0
- package/src/extension/index.ts +2 -0
- package/src/extension/rpc.ts +4 -0
- package/src/extension/schemas.ts +8 -2
- package/src/extension/tool-description.ts +2 -0
- package/src/runs/background/async-execution.ts +125 -14
- package/src/runs/background/async-resume.ts +24 -7
- package/src/runs/background/async-status.ts +18 -0
- package/src/runs/background/process-terminal.ts +280 -0
- package/src/runs/background/run-status.ts +7 -1
- package/src/runs/background/scheduled-runs.ts +6 -1
- package/src/runs/background/stale-run-reconciler.ts +6 -0
- package/src/runs/background/subagent-runner.ts +182 -12
- package/src/runs/foreground/chain-execution.ts +5 -0
- package/src/runs/foreground/execution.ts +59 -13
- package/src/runs/foreground/subagent-executor.ts +32 -2
- package/src/runs/shared/acceptance.ts +41 -30
- package/src/runs/shared/capability-ceiling.ts +177 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
- package/src/runs/shared/nested-events.ts +8 -1
- package/src/runs/shared/parallel-utils.ts +5 -0
- package/src/runs/shared/pi-args.ts +141 -58
- package/src/runs/shared/session-lease.ts +25 -5
- package/src/runs/shared/subagent-prompt-runtime.ts +14 -1
- package/src/runs/shared/tool-availability.ts +18 -2
- package/src/shared/launch-contract.ts +123 -0
- package/src/shared/types.ts +104 -7
- package/src/shared/utils.ts +17 -42
- package/src/slash/delegation-adapters.ts +1 -1
- package/src/slash/slash-commands.ts +1 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
|
|
3
|
+
export const SUBAGENT_CAPABILITY_CEILING_VERSION = 1 as const;
|
|
4
|
+
export const SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY = "pi-subagents.capability-ceiling.v1";
|
|
5
|
+
export const SUBAGENT_CAPABILITY_CEILING_ENV = "PI_SUBAGENT_CAPABILITY_CEILING_V1";
|
|
6
|
+
|
|
7
|
+
export type SubagentCapabilityCeiling =
|
|
8
|
+
| { allowedTools: readonly string[]; denyExtensions?: boolean }
|
|
9
|
+
| { allowedTools?: readonly string[]; denyExtensions: boolean };
|
|
10
|
+
|
|
11
|
+
export interface ResolvedSubagentCapabilityCeiling {
|
|
12
|
+
version: typeof SUBAGENT_CAPABILITY_CEILING_VERSION;
|
|
13
|
+
allowedTools?: string[];
|
|
14
|
+
denyExtensions: boolean;
|
|
15
|
+
sources: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SubagentCapabilityAudit {
|
|
19
|
+
ceiling: ResolvedSubagentCapabilityCeiling;
|
|
20
|
+
requestedTools?: string[];
|
|
21
|
+
effectiveTools: string[];
|
|
22
|
+
removedTools: string[];
|
|
23
|
+
internalTools: string[];
|
|
24
|
+
extensionsDenied: boolean;
|
|
25
|
+
removedExtensionCount: number;
|
|
26
|
+
requestedMcpToolCount: number;
|
|
27
|
+
effectiveMcpTools: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RegisterSubagentCapabilityCeilingOptions {
|
|
31
|
+
sessionId: string;
|
|
32
|
+
source: string;
|
|
33
|
+
ceiling: SubagentCapabilityCeiling;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SubagentCapabilityCeilingHandle {
|
|
37
|
+
update(ceiling: SubagentCapabilityCeiling): void;
|
|
38
|
+
dispose(): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type Registration = { source: string; ceiling: ResolvedSubagentCapabilityCeiling };
|
|
42
|
+
type Registry = Map<string, Map<symbol, Registration>>;
|
|
43
|
+
|
|
44
|
+
function registry(): Registry {
|
|
45
|
+
const key = Symbol.for(SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY);
|
|
46
|
+
const store = globalThis as typeof globalThis & { [key: symbol]: unknown };
|
|
47
|
+
const existing = store[key];
|
|
48
|
+
if (existing instanceof Map) return existing as Registry;
|
|
49
|
+
const created: Registry = new Map();
|
|
50
|
+
store[key] = created;
|
|
51
|
+
return created;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function validateText(value: unknown, field: string): string {
|
|
55
|
+
if (typeof value !== "string" || !value.trim() || /[\u0000-\u001f\u007f]/u.test(value) || Buffer.byteLength(value.trim(), "utf8") > 256) {
|
|
56
|
+
throw new Error(`Invalid capability ceiling ${field}; expected a non-empty string without control characters (max 256 UTF-8 bytes).`);
|
|
57
|
+
}
|
|
58
|
+
return value.trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeCeiling(ceiling: SubagentCapabilityCeiling): ResolvedSubagentCapabilityCeiling {
|
|
62
|
+
if (!ceiling || typeof ceiling !== "object" || Array.isArray(ceiling)) throw new Error("Invalid capability ceiling; expected an object.");
|
|
63
|
+
const hasAllowedTools = Object.hasOwn(ceiling, "allowedTools");
|
|
64
|
+
const hasDenyExtensions = Object.hasOwn(ceiling, "denyExtensions");
|
|
65
|
+
if (!hasAllowedTools && !hasDenyExtensions) throw new Error("Invalid capability ceiling; expected allowedTools or denyExtensions.");
|
|
66
|
+
if (hasDenyExtensions && typeof ceiling.denyExtensions !== "boolean") throw new Error("Invalid capability ceiling denyExtensions; expected a boolean.");
|
|
67
|
+
let allowedTools: string[] | undefined;
|
|
68
|
+
if (hasAllowedTools) {
|
|
69
|
+
if (!Array.isArray(ceiling.allowedTools)) throw new Error("Invalid capability ceiling allowedTools; expected an array.");
|
|
70
|
+
if (ceiling.allowedTools.length > 256) throw new Error("Invalid capability ceiling allowedTools; expected at most 256 names.");
|
|
71
|
+
allowedTools = [...new Set(ceiling.allowedTools.map((tool) => {
|
|
72
|
+
const name = validateText(tool, "allowedTools entry");
|
|
73
|
+
if (!/^[A-Za-z0-9_.:-]+$/u.test(name)) throw new Error(`Invalid capability ceiling allowedTools entry '${name}'.`);
|
|
74
|
+
if (Buffer.byteLength(name, "utf8") > 128) throw new Error(`Invalid capability ceiling allowedTools entry '${name}'; max 128 UTF-8 bytes.`);
|
|
75
|
+
return name;
|
|
76
|
+
}))].sort();
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
version: SUBAGENT_CAPABILITY_CEILING_VERSION,
|
|
80
|
+
...(allowedTools ? { allowedTools } : {}),
|
|
81
|
+
denyExtensions: ceiling.denyExtensions === true,
|
|
82
|
+
sources: [],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function parseSubagentCapabilityCeiling(value: unknown, field = "capability ceiling"): ResolvedSubagentCapabilityCeiling {
|
|
87
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`Invalid ${field}; expected an object.`);
|
|
88
|
+
const record = value as Record<string, unknown>;
|
|
89
|
+
if (record.version !== SUBAGENT_CAPABILITY_CEILING_VERSION) throw new Error(`Invalid ${field} version.`);
|
|
90
|
+
const normalized = normalizeCeiling(record as SubagentCapabilityCeiling);
|
|
91
|
+
const sources = record.sources;
|
|
92
|
+
if (!Array.isArray(sources) || sources.some((source) => typeof source !== "string")) throw new Error(`Invalid ${field} sources; expected an array of strings.`);
|
|
93
|
+
normalized.sources = [...new Set(sources.map((source) => validateText(source, `${field} source`)))].sort();
|
|
94
|
+
return normalized;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function registerSubagentCapabilityCeiling(options: RegisterSubagentCapabilityCeilingOptions): SubagentCapabilityCeilingHandle {
|
|
98
|
+
const sessionId = validateText(options.sessionId, "sessionId");
|
|
99
|
+
const source = validateText(options.source, "source");
|
|
100
|
+
let normalized = normalizeCeiling(options.ceiling);
|
|
101
|
+
const token = Symbol(source);
|
|
102
|
+
const store = registry();
|
|
103
|
+
let session = store.get(sessionId);
|
|
104
|
+
if (!session) {
|
|
105
|
+
session = new Map();
|
|
106
|
+
store.set(sessionId, session);
|
|
107
|
+
}
|
|
108
|
+
const setRegistration = () => {
|
|
109
|
+
normalized = normalizeCeiling(normalized);
|
|
110
|
+
normalized.sources = [source];
|
|
111
|
+
session!.set(token, { source, ceiling: normalized });
|
|
112
|
+
};
|
|
113
|
+
setRegistration();
|
|
114
|
+
let disposed = false;
|
|
115
|
+
return {
|
|
116
|
+
update(ceiling) {
|
|
117
|
+
if (disposed) throw new Error("Cannot update a disposed capability ceiling handle.");
|
|
118
|
+
normalized = normalizeCeiling(ceiling);
|
|
119
|
+
normalized.sources = [source];
|
|
120
|
+
session!.set(token, { source, ceiling: normalized });
|
|
121
|
+
},
|
|
122
|
+
dispose() {
|
|
123
|
+
if (disposed) return;
|
|
124
|
+
disposed = true;
|
|
125
|
+
session!.delete(token);
|
|
126
|
+
if (session!.size === 0) store.delete(sessionId);
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function intersectSubagentCapabilityCeilings(...ceilings: Array<ResolvedSubagentCapabilityCeiling | undefined>): ResolvedSubagentCapabilityCeiling | undefined {
|
|
132
|
+
const active = ceilings.filter((ceiling): ceiling is ResolvedSubagentCapabilityCeiling => ceiling !== undefined);
|
|
133
|
+
if (active.length === 0) return undefined;
|
|
134
|
+
const definedLists = active.filter((ceiling) => ceiling.allowedTools !== undefined).map((ceiling) => new Set(ceiling.allowedTools));
|
|
135
|
+
let allowedTools: string[] | undefined;
|
|
136
|
+
if (definedLists.length > 0) {
|
|
137
|
+
allowedTools = [...definedLists[0]!].filter((tool) => definedLists.every((list) => list.has(tool))).sort();
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
version: SUBAGENT_CAPABILITY_CEILING_VERSION,
|
|
141
|
+
...(allowedTools ? { allowedTools } : {}),
|
|
142
|
+
denyExtensions: active.some((ceiling) => ceiling.denyExtensions),
|
|
143
|
+
sources: [...new Set(active.flatMap((ceiling) => ceiling.sources))].sort(),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function resolveSubagentCapabilityCeiling(sessionId: string | undefined, inherited?: ResolvedSubagentCapabilityCeiling): ResolvedSubagentCapabilityCeiling | undefined {
|
|
148
|
+
const active: ResolvedSubagentCapabilityCeiling[] = [];
|
|
149
|
+
if (sessionId) {
|
|
150
|
+
const registrations = registry().get(sessionId);
|
|
151
|
+
if (registrations) active.push(...Array.from(registrations.values(), ({ ceiling }) => ceiling));
|
|
152
|
+
}
|
|
153
|
+
return intersectSubagentCapabilityCeilings(inherited, ...active);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function resolveCurrentSubagentCapabilityCeiling(sessionId: string | undefined): ResolvedSubagentCapabilityCeiling | undefined {
|
|
157
|
+
return resolveSubagentCapabilityCeiling(sessionId, decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function encodeSubagentCapabilityCeiling(ceiling: ResolvedSubagentCapabilityCeiling | undefined): string | undefined {
|
|
161
|
+
if (!ceiling) return undefined;
|
|
162
|
+
return Buffer.from(JSON.stringify(ceiling), "utf8").toString("base64url");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function decodeSubagentCapabilityCeiling(value: string | undefined): ResolvedSubagentCapabilityCeiling | undefined {
|
|
166
|
+
if (value === undefined || value === "") return undefined;
|
|
167
|
+
let parsed: unknown;
|
|
168
|
+
try {
|
|
169
|
+
parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
170
|
+
} catch (error) {
|
|
171
|
+
throw new Error(`Invalid inherited capability ceiling: ${error instanceof Error ? error.message : String(error)}`);
|
|
172
|
+
}
|
|
173
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || (parsed as { version?: unknown }).version !== SUBAGENT_CAPABILITY_CEILING_VERSION) {
|
|
174
|
+
throw new Error("Invalid inherited capability ceiling version.");
|
|
175
|
+
}
|
|
176
|
+
return parseSubagentCapabilityCeiling(parsed, "inherited capability ceiling");
|
|
177
|
+
}
|
|
@@ -50,7 +50,7 @@ const DYNAMIC_PARALLEL_KEYS = new Set(["agent", "task", "phase", "label", "outpu
|
|
|
50
50
|
const RUNNER_DYNAMIC_PARALLEL_KEYS = new Set([
|
|
51
51
|
...DYNAMIC_PARALLEL_KEYS,
|
|
52
52
|
"outputName", "structured", "inheritProjectContext", "inheritSkills", "skills", "outputPath", "namespaceOutputPath", "maxSubagentDepth", "waitToolEnabled",
|
|
53
|
-
"structuredOutput", "structuredOutputSchema", "tools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "completionGuard", "systemPrompt",
|
|
53
|
+
"structuredOutput", "structuredOutputSchema", "tools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "capabilityCeiling", "completionGuard", "systemPrompt",
|
|
54
54
|
"systemPromptMode", "thinking", "modelCandidates", "sessionFile", "effectiveAcceptance", "acceptanceInput", "acceptanceRole", "parentSessionId",
|
|
55
55
|
]);
|
|
56
56
|
const DYNAMIC_COLLECT_KEYS = new Set(["as", "outputSchema"]);
|
|
@@ -69,14 +69,16 @@ interface MetadataCache {
|
|
|
69
69
|
servers: Record<string, ServerCacheEntry>;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
export
|
|
72
|
+
export interface ResolvedMcpDirectToolSelection { name: string; selector: string }
|
|
73
|
+
|
|
74
|
+
export function resolveMcpDirectToolSelections(mcpDirectTools: string[] | undefined, cwd = process.cwd()): ResolvedMcpDirectToolSelection[] {
|
|
73
75
|
if (!mcpDirectTools?.length) return [];
|
|
74
76
|
|
|
75
77
|
try {
|
|
76
78
|
const config = loadMcpConfig(cwd);
|
|
77
79
|
const cache = loadMetadataCache();
|
|
78
80
|
if (!cache) return [];
|
|
79
|
-
return
|
|
81
|
+
return resolveDirectToolSelections(config, cache, getToolPrefix(config.settings?.toolPrefix), mcpDirectTools);
|
|
80
82
|
} catch {
|
|
81
83
|
return [];
|
|
82
84
|
}
|
|
@@ -195,8 +197,8 @@ function extractServers(config: unknown, kind: ImportKind): Record<string, Serve
|
|
|
195
197
|
return servers && typeof servers === "object" && !Array.isArray(servers) ? servers as Record<string, ServerEntry> : {};
|
|
196
198
|
}
|
|
197
199
|
|
|
198
|
-
function
|
|
199
|
-
const names:
|
|
200
|
+
function resolveDirectToolSelections(config: McpConfig, cache: MetadataCache, prefix: ToolPrefix, envOverride: string[]): ResolvedMcpDirectToolSelection[] {
|
|
201
|
+
const names: ResolvedMcpDirectToolSelection[] = [];
|
|
200
202
|
const seenNames = new Set<string>();
|
|
201
203
|
const { servers: selectedServers, tools: selectedTools } = parseSelections(envOverride);
|
|
202
204
|
|
|
@@ -216,7 +218,7 @@ function resolveDirectToolNames(config: McpConfig, cache: MetadataCache, prefix:
|
|
|
216
218
|
const prefixedName = formatToolName(tool.name, serverName, prefix);
|
|
217
219
|
if (BUILTIN_TOOL_NAMES.has(prefixedName) || seenNames.has(prefixedName)) continue;
|
|
218
220
|
seenNames.add(prefixedName);
|
|
219
|
-
names.push(prefixedName);
|
|
221
|
+
names.push({ name: prefixedName, selector: `${serverName}/${tool.name}` });
|
|
220
222
|
}
|
|
221
223
|
|
|
222
224
|
if (definition.exposeResources === false) continue;
|
|
@@ -228,13 +230,17 @@ function resolveDirectToolNames(config: McpConfig, cache: MetadataCache, prefix:
|
|
|
228
230
|
const prefixedName = formatToolName(baseName, serverName, prefix);
|
|
229
231
|
if (BUILTIN_TOOL_NAMES.has(prefixedName) || seenNames.has(prefixedName)) continue;
|
|
230
232
|
seenNames.add(prefixedName);
|
|
231
|
-
names.push(prefixedName);
|
|
233
|
+
names.push({ name: prefixedName, selector: `${serverName}/${baseName}` });
|
|
232
234
|
}
|
|
233
235
|
}
|
|
234
236
|
|
|
235
237
|
return names;
|
|
236
238
|
}
|
|
237
239
|
|
|
240
|
+
export function resolveMcpDirectToolNames(mcpDirectTools: string[] | undefined, cwd = process.cwd()): string[] {
|
|
241
|
+
return resolveMcpDirectToolSelections(mcpDirectTools, cwd).map((selection) => selection.name);
|
|
242
|
+
}
|
|
243
|
+
|
|
238
244
|
function parseSelections(selections: string[]): { servers: Set<string>; tools: Map<string, Set<string>> } {
|
|
239
245
|
const servers = new Set<string>();
|
|
240
246
|
const tools = new Map<string, Set<string>>();
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
SUBAGENT_PARENT_RUN_ID_ENV,
|
|
28
28
|
} from "./pi-args.ts";
|
|
29
29
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
30
|
+
import { sanitizeProcessTerminal } from "../background/process-terminal.ts";
|
|
30
31
|
|
|
31
32
|
export const NESTED_EVENTS_DIR = path.join(TEMP_ROOT_DIR, "nested-subagent-events");
|
|
32
33
|
const ROUTE_FILE = "route.json";
|
|
@@ -873,6 +874,9 @@ export function nestedSummaryFromAsyncStatus(status: AsyncStatus, asyncDir: stri
|
|
|
873
874
|
...(status.pid ? { pid: status.pid } : {}),
|
|
874
875
|
...(status.sessionId ? { sessionId: status.sessionId } : {}),
|
|
875
876
|
mode: status.mode ?? fallback.mode,
|
|
877
|
+
...(status.processTerminal ? { processTerminal: sanitizeProcessTerminal(status.processTerminal, { runId: status.runId || fallback.id, runnerProcessInstanceId: status.processTerminal.runnerProcessInstanceId }, `${asyncDir}/status.json`) } : {}),
|
|
878
|
+
...(status.capabilityCeiling ? { capabilityCeiling: status.capabilityCeiling } : {}),
|
|
879
|
+
...(status.capabilityAudit ? { capabilityAudit: status.capabilityAudit } : {}),
|
|
876
880
|
state: status.state,
|
|
877
881
|
...(status.currentStep !== undefined ? { currentStep: status.currentStep } : {}),
|
|
878
882
|
...(status.chainStepCount !== undefined ? { chainStepCount: status.chainStepCount } : {}),
|
|
@@ -896,7 +900,7 @@ export function nestedSummaryFromAsyncStatus(status: AsyncStatus, asyncDir: stri
|
|
|
896
900
|
...(status.endedAt !== undefined ? { endedAt: status.endedAt } : {}),
|
|
897
901
|
lastUpdate: status.lastUpdate ?? fallback.ts,
|
|
898
902
|
...(status.sessionFile ? { sessionFile: status.sessionFile } : {}),
|
|
899
|
-
...(status.steps?.length ? { steps: status.steps.map((step) => ({
|
|
903
|
+
...(status.steps?.length ? { steps: status.steps.map((step, index) => ({
|
|
900
904
|
agent: step.agent,
|
|
901
905
|
status: step.status,
|
|
902
906
|
...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
|
|
@@ -915,6 +919,9 @@ export function nestedSummaryFromAsyncStatus(status: AsyncStatus, asyncDir: stri
|
|
|
915
919
|
...(step.turnBudget ? { turnBudget: step.turnBudget } : {}),
|
|
916
920
|
...(step.turnBudgetExceeded !== undefined ? { turnBudgetExceeded: step.turnBudgetExceeded } : {}),
|
|
917
921
|
...(step.wrapUpRequested !== undefined ? { wrapUpRequested: step.wrapUpRequested } : {}),
|
|
922
|
+
...(step.processTerminal ? { processTerminal: sanitizeProcessTerminal(step.processTerminal, { runId: status.runId || fallback.id, runnerProcessInstanceId: step.processTerminal.runnerProcessInstanceId }, `${asyncDir}/status.json step ${index}`) } : {}),
|
|
923
|
+
...(step.capabilityCeiling ? { capabilityCeiling: step.capabilityCeiling } : {}),
|
|
924
|
+
...(step.capabilityAudit ? { capabilityAudit: step.capabilityAudit } : {}),
|
|
918
925
|
})).slice(0, MAX_STEPS) } : {}),
|
|
919
926
|
};
|
|
920
927
|
}
|
|
@@ -43,11 +43,16 @@ export interface RunnerSubagentStep {
|
|
|
43
43
|
};
|
|
44
44
|
structuredOutputSchema?: import("../../shared/types.ts").JsonSchemaObject;
|
|
45
45
|
agentContract?: import("../../shared/types.ts").AgentContract;
|
|
46
|
+
definitionDigest?: string;
|
|
47
|
+
launchBindingTask?: string;
|
|
48
|
+
launchContractDigest?: string;
|
|
46
49
|
effectiveAcceptance?: import("../../shared/types.ts").ResolvedAcceptanceConfig;
|
|
47
50
|
acceptanceInput?: import("../../shared/types.ts").AcceptanceInput;
|
|
48
51
|
acceptanceRole?: import("../../shared/types.ts").AcceptanceRole;
|
|
49
52
|
gateOn?: import("../../shared/types.ts").ChainGateLayer;
|
|
50
53
|
toolBudget?: import("../../shared/types.ts").ResolvedToolBudget;
|
|
54
|
+
capabilityCeiling?: import("./capability-ceiling.ts").ResolvedSubagentCapabilityCeiling;
|
|
55
|
+
capabilityAudit?: import("./capability-ceiling.ts").SubagentCapabilityAudit;
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
export interface ParallelStepGroup {
|
|
@@ -3,16 +3,17 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { encodeNestedPathEnv, parseNestedPathEnv, type NestedPathEntry } from "./nested-path.ts";
|
|
6
|
-
import {
|
|
6
|
+
import { resolveMcpDirectToolSelections, type ResolvedMcpDirectToolSelection } from "./mcp-direct-tool-allowlist.ts";
|
|
7
7
|
import { resolvePiPackageRoot } from "./pi-spawn.ts";
|
|
8
8
|
import { STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV } from "./structured-output.ts";
|
|
9
9
|
import { TEMP_ROOT_DIR, type JsonSchemaObject, type ResolvedToolBudget } from "../../shared/types.ts";
|
|
10
10
|
import { THINKING_LEVELS } from "../../shared/model-info.ts";
|
|
11
11
|
import { TOOL_BUDGET_ENV, TOOL_BUDGET_ZERO_AUTH_ENV, encodeToolBudgetEnv } from "./tool-budget.ts";
|
|
12
|
-
import { CHILD_TOOL_DIAGNOSTIC_PATH_ENV, REQUIRED_CHILD_TOOLS_ENV } from "./tool-availability.ts";
|
|
12
|
+
import { CHILD_TOOL_DIAGNOSTIC_PATH_ENV, MCP_DIRECT_CHILD_TOOLS_ENV, REQUIRED_CHILD_TOOLS_ENV } from "./tool-availability.ts";
|
|
13
13
|
import { CHILD_WATCHDOG_CONFIG_ENV, encodeChildWatchdogConfig, type ChildWatchdogConfig } from "../../watchdog/child-status.ts";
|
|
14
14
|
import { WAIT_TOOL_ENABLED_ENV } from "../background/wait-config.ts";
|
|
15
15
|
import { PI_CODING_AGENT_PACKAGE_ROOT_ENV } from "../../shared/utils.ts";
|
|
16
|
+
import { SUBAGENT_CAPABILITY_CEILING_ENV, decodeSubagentCapabilityCeiling, encodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, type ResolvedSubagentCapabilityCeiling, type SubagentCapabilityAudit } from "./capability-ceiling.ts";
|
|
16
17
|
|
|
17
18
|
const TASK_ARG_LIMIT = 8000;
|
|
18
19
|
const PROMPT_RUNTIME_EXTENSION_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-prompt-runtime.ts");
|
|
@@ -38,7 +39,7 @@ export const SUBAGENT_STEER_INBOX_ENV = "PI_SUBAGENT_STEER_INBOX";
|
|
|
38
39
|
export const SUBAGENT_STEER_CAPABILITY_ENV = "PI_SUBAGENT_STEER_CAPABILITY";
|
|
39
40
|
export const SUBAGENT_STEER_ACK_DIR_ENV = "PI_SUBAGENT_STEER_ACK_DIR";
|
|
40
41
|
|
|
41
|
-
interface BuildPiArgsInput {
|
|
42
|
+
export interface BuildPiArgsInput {
|
|
42
43
|
parentSessionId?: string;
|
|
43
44
|
baseArgs: string[];
|
|
44
45
|
task: string;
|
|
@@ -83,13 +84,15 @@ interface BuildPiArgsInput {
|
|
|
83
84
|
allowZeroToolBudget?: boolean;
|
|
84
85
|
childWatchdog?: ChildWatchdogConfig;
|
|
85
86
|
waitToolEnabled?: boolean;
|
|
87
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
interface BuildPiArgsResult {
|
|
90
|
+
export interface BuildPiArgsResult {
|
|
89
91
|
args: string[];
|
|
90
92
|
env: Record<string, string | undefined>;
|
|
91
93
|
tempDir?: string;
|
|
92
94
|
toolDiagnosticPath?: string;
|
|
95
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
function sanitizeSupervisorChannelSegment(value: string): string {
|
|
@@ -109,6 +112,106 @@ export function applyThinkingSuffix(model: string | undefined, thinking: string
|
|
|
109
112
|
return `${model}:${thinking}`;
|
|
110
113
|
}
|
|
111
114
|
|
|
115
|
+
export interface ResolvePiLaunchToolPlanInput {
|
|
116
|
+
tools?: string[];
|
|
117
|
+
extensions?: string[];
|
|
118
|
+
subagentOnlyExtensions?: string[];
|
|
119
|
+
mcpDirectTools?: string[];
|
|
120
|
+
cwd?: string;
|
|
121
|
+
requireReadTool?: boolean;
|
|
122
|
+
structuredOutput?: boolean;
|
|
123
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
124
|
+
inheritedCapabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface PiLaunchToolPlan {
|
|
128
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
129
|
+
requestedBuiltinTools: string[];
|
|
130
|
+
declaredBuiltinTools: string[];
|
|
131
|
+
toolExtensionPaths: string[];
|
|
132
|
+
resolvedMcpSelections: ResolvedMcpDirectToolSelection[];
|
|
133
|
+
effectiveMcpSelections: ResolvedMcpDirectToolSelection[];
|
|
134
|
+
effectiveMcpTools: string[];
|
|
135
|
+
explicitToolAllowlist: boolean;
|
|
136
|
+
internalTools: string[];
|
|
137
|
+
effectiveToolAllowlist: string[];
|
|
138
|
+
requiredChildTools: string[];
|
|
139
|
+
fanoutAuthorized: boolean;
|
|
140
|
+
runtimeExtensions: string[];
|
|
141
|
+
configuredExtensions: string[];
|
|
142
|
+
extensionArgs: string[];
|
|
143
|
+
disableAmbientExtensions: boolean;
|
|
144
|
+
capabilityAudit?: SubagentCapabilityAudit;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function resolvePiLaunchToolPlan(input: ResolvePiLaunchToolPlanInput): PiLaunchToolPlan {
|
|
148
|
+
const capabilityCeiling = intersectSubagentCapabilityCeilings(input.capabilityCeiling, input.inheritedCapabilityCeiling);
|
|
149
|
+
const allowedToolSet = capabilityCeiling?.allowedTools === undefined ? undefined : new Set(capabilityCeiling.allowedTools);
|
|
150
|
+
const requestedBuiltinTools = input.tools?.filter((tool) => !(tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js"))) ?? [];
|
|
151
|
+
if (input.requireReadTool && allowedToolSet && !allowedToolSet.has("read")) {
|
|
152
|
+
throw new Error(`Capability ceiling from ${capabilityCeiling?.sources.join(", ") || "unknown source"} excludes required tool 'read' for lazy skill loading.`);
|
|
153
|
+
}
|
|
154
|
+
const declaredBuiltinTools = input.tools === undefined
|
|
155
|
+
? (allowedToolSet ? [...allowedToolSet] : [])
|
|
156
|
+
: (input.requireReadTool && requestedBuiltinTools.length > 0 && !requestedBuiltinTools.includes("read") && !allowedToolSet
|
|
157
|
+
? ["read", ...requestedBuiltinTools]
|
|
158
|
+
: requestedBuiltinTools).filter((tool) => !allowedToolSet || allowedToolSet.has(tool));
|
|
159
|
+
const fanoutAuthorized = declaredBuiltinTools.includes("subagent");
|
|
160
|
+
const toolExtensionPaths: string[] = capabilityCeiling?.denyExtensions ? [] : (input.tools ?? []).filter((tool) => !requestedBuiltinTools.includes(tool) && (tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js")));
|
|
161
|
+
const resolvedMcpSelections = capabilityCeiling?.denyExtensions ? [] : resolveMcpDirectToolSelections(input.mcpDirectTools, input.cwd);
|
|
162
|
+
const effectiveMcpSelections = resolvedMcpSelections.filter((selection) => !allowedToolSet || allowedToolSet.has(selection.name));
|
|
163
|
+
const effectiveMcpTools = effectiveMcpSelections.map((selection) => selection.name);
|
|
164
|
+
const explicitToolAllowlist = input.tools !== undefined || (input.mcpDirectTools?.length ?? 0) > 0 || allowedToolSet !== undefined;
|
|
165
|
+
const internalTools = input.structuredOutput ? ["structured_output"] : [];
|
|
166
|
+
const effectiveToolAllowlist = [...new Set([...declaredBuiltinTools, ...effectiveMcpTools, ...internalTools])];
|
|
167
|
+
const requiredChildTools = explicitToolAllowlist ? [...new Set([
|
|
168
|
+
...(input.tools !== undefined ? declaredBuiltinTools : []),
|
|
169
|
+
...(input.mcpDirectTools?.length ? effectiveMcpTools : []),
|
|
170
|
+
...internalTools,
|
|
171
|
+
])] : [];
|
|
172
|
+
const runtimeExtensions = fanoutAuthorized
|
|
173
|
+
? [PROMPT_RUNTIME_EXTENSION_PATH, FANOUT_CHILD_EXTENSION_PATH]
|
|
174
|
+
: [PROMPT_RUNTIME_EXTENSION_PATH];
|
|
175
|
+
const disableAmbientExtensions = capabilityCeiling?.denyExtensions === true || input.extensions !== undefined;
|
|
176
|
+
const configuredExtensions = capabilityCeiling?.denyExtensions ? [] : [...toolExtensionPaths, ...(input.extensions ?? []), ...(input.subagentOnlyExtensions ?? [])];
|
|
177
|
+
const extensionArgs = disableAmbientExtensions
|
|
178
|
+
? [...new Set([...runtimeExtensions, ...configuredExtensions])]
|
|
179
|
+
: [...new Set([...runtimeExtensions, ...toolExtensionPaths, ...(input.subagentOnlyExtensions ?? [])])];
|
|
180
|
+
const requestedToolNames = input.tools !== undefined
|
|
181
|
+
? [...new Set([...requestedBuiltinTools, ...resolvedMcpSelections.map((selection) => selection.name)])]
|
|
182
|
+
: undefined;
|
|
183
|
+
const capabilityAudit = capabilityCeiling ? {
|
|
184
|
+
ceiling: capabilityCeiling,
|
|
185
|
+
...(requestedToolNames ? { requestedTools: requestedToolNames } : {}),
|
|
186
|
+
effectiveTools: effectiveToolAllowlist,
|
|
187
|
+
removedTools: requestedToolNames?.filter((tool) => !effectiveToolAllowlist.includes(tool)) ?? [],
|
|
188
|
+
internalTools,
|
|
189
|
+
extensionsDenied: capabilityCeiling.denyExtensions,
|
|
190
|
+
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,
|
|
191
|
+
requestedMcpToolCount: input.mcpDirectTools?.length ?? 0,
|
|
192
|
+
effectiveMcpTools,
|
|
193
|
+
} satisfies SubagentCapabilityAudit : undefined;
|
|
194
|
+
return {
|
|
195
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
196
|
+
requestedBuiltinTools,
|
|
197
|
+
declaredBuiltinTools,
|
|
198
|
+
toolExtensionPaths,
|
|
199
|
+
resolvedMcpSelections,
|
|
200
|
+
effectiveMcpSelections,
|
|
201
|
+
effectiveMcpTools,
|
|
202
|
+
explicitToolAllowlist,
|
|
203
|
+
internalTools,
|
|
204
|
+
effectiveToolAllowlist,
|
|
205
|
+
requiredChildTools,
|
|
206
|
+
fanoutAuthorized,
|
|
207
|
+
runtimeExtensions,
|
|
208
|
+
configuredExtensions,
|
|
209
|
+
extensionArgs,
|
|
210
|
+
disableAmbientExtensions,
|
|
211
|
+
...(capabilityAudit ? { capabilityAudit } : {}),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
112
215
|
export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
113
216
|
const args = [...input.baseArgs];
|
|
114
217
|
|
|
@@ -130,46 +233,25 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
130
233
|
args.push("--model", modelArg);
|
|
131
234
|
}
|
|
132
235
|
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
236
|
+
const toolPlan = resolvePiLaunchToolPlan({
|
|
237
|
+
tools: input.tools,
|
|
238
|
+
extensions: input.extensions,
|
|
239
|
+
subagentOnlyExtensions: input.subagentOnlyExtensions,
|
|
240
|
+
mcpDirectTools: input.mcpDirectTools,
|
|
241
|
+
cwd: input.cwd,
|
|
242
|
+
requireReadTool: input.requireReadTool,
|
|
243
|
+
structuredOutput: input.structuredOutput,
|
|
244
|
+
capabilityCeiling: input.capabilityCeiling,
|
|
245
|
+
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
246
|
+
});
|
|
247
|
+
if (toolPlan.explicitToolAllowlist) {
|
|
248
|
+
args.push(toolPlan.effectiveToolAllowlist.length > 0 ? "--tools" : "--no-tools");
|
|
249
|
+
if (toolPlan.effectiveToolAllowlist.length > 0) args.push(toolPlan.effectiveToolAllowlist.join(","));
|
|
145
250
|
}
|
|
146
|
-
if (
|
|
147
|
-
const allowedTools = [
|
|
148
|
-
...declaredBuiltinTools,
|
|
149
|
-
...resolveMcpDirectToolNames(input.mcpDirectTools, input.cwd),
|
|
150
|
-
...(input.structuredOutput ? ["structured_output"] : []),
|
|
151
|
-
];
|
|
152
|
-
requiredChildTools = [...new Set(allowedTools)];
|
|
153
|
-
if (requiredChildTools.length > 0) {
|
|
154
|
-
args.push("--tools", requiredChildTools.join(","));
|
|
155
|
-
} else {
|
|
156
|
-
args.push("--no-tools");
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const runtimeExtensions = fanoutAuthorized
|
|
161
|
-
? [PROMPT_RUNTIME_EXTENSION_PATH, FANOUT_CHILD_EXTENSION_PATH]
|
|
162
|
-
: [PROMPT_RUNTIME_EXTENSION_PATH];
|
|
163
|
-
if (input.extensions !== undefined) {
|
|
251
|
+
if (toolPlan.disableAmbientExtensions) {
|
|
164
252
|
args.push("--no-extensions");
|
|
165
|
-
for (const extPath of [...new Set([...runtimeExtensions, ...toolExtensionPaths, ...input.extensions, ...(input.subagentOnlyExtensions ?? [])])]) {
|
|
166
|
-
args.push("--extension", extPath);
|
|
167
|
-
}
|
|
168
|
-
} else {
|
|
169
|
-
for (const extPath of [...new Set([...runtimeExtensions, ...toolExtensionPaths, ...(input.subagentOnlyExtensions ?? [])])]) {
|
|
170
|
-
args.push("--extension", extPath);
|
|
171
|
-
}
|
|
172
253
|
}
|
|
254
|
+
for (const extPath of toolPlan.extensionArgs) args.push("--extension", extPath);
|
|
173
255
|
|
|
174
256
|
if (!input.inheritSkills) {
|
|
175
257
|
args.push("--no-skills");
|
|
@@ -199,14 +281,15 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
199
281
|
const piPackageRoot = process.env[PI_CODING_AGENT_PACKAGE_ROOT_ENV] ?? resolvePiPackageRoot();
|
|
200
282
|
if (piPackageRoot) env[PI_CODING_AGENT_PACKAGE_ROOT_ENV] = piPackageRoot;
|
|
201
283
|
let toolDiagnosticPath: string | undefined;
|
|
202
|
-
if (requiredChildTools.length > 0) {
|
|
284
|
+
if (toolPlan.requiredChildTools.length > 0) {
|
|
203
285
|
if (!tempDir) tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
204
286
|
toolDiagnosticPath = path.join(tempDir, "tool-diagnostic.json");
|
|
205
|
-
env[REQUIRED_CHILD_TOOLS_ENV] = JSON.stringify(requiredChildTools);
|
|
287
|
+
env[REQUIRED_CHILD_TOOLS_ENV] = JSON.stringify(toolPlan.requiredChildTools);
|
|
206
288
|
env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = toolDiagnosticPath;
|
|
207
289
|
}
|
|
290
|
+
env[MCP_DIRECT_CHILD_TOOLS_ENV] = toolPlan.effectiveMcpTools.length > 0 ? JSON.stringify(toolPlan.effectiveMcpTools) : undefined;
|
|
208
291
|
env[SUBAGENT_CHILD_ENV] = "1";
|
|
209
|
-
env[SUBAGENT_FANOUT_CHILD_ENV] = fanoutAuthorized ? "1" : "0";
|
|
292
|
+
env[SUBAGENT_FANOUT_CHILD_ENV] = toolPlan.fanoutAuthorized ? "1" : "0";
|
|
210
293
|
if (input.waitToolEnabled !== undefined) {
|
|
211
294
|
env[WAIT_TOOL_ENABLED_ENV] = input.waitToolEnabled ? "true" : "false";
|
|
212
295
|
}
|
|
@@ -227,20 +310,20 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
227
310
|
...(input.childAgentName ? { agent: input.childAgentName } : {}),
|
|
228
311
|
}] : []),
|
|
229
312
|
];
|
|
230
|
-
env[SUBAGENT_PARENT_EVENT_SINK_ENV] = fanoutAuthorized
|
|
313
|
+
env[SUBAGENT_PARENT_EVENT_SINK_ENV] = toolPlan.fanoutAuthorized
|
|
231
314
|
? input.parentEventSink ?? process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] ?? ""
|
|
232
315
|
: "";
|
|
233
|
-
env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] = fanoutAuthorized
|
|
316
|
+
env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] = toolPlan.fanoutAuthorized
|
|
234
317
|
? input.parentControlInbox ?? process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] ?? ""
|
|
235
318
|
: "";
|
|
236
|
-
env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] = fanoutAuthorized
|
|
319
|
+
env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] = toolPlan.fanoutAuthorized
|
|
237
320
|
? input.parentRootRunId ?? process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] ?? input.runId ?? ""
|
|
238
321
|
: "";
|
|
239
|
-
env[SUBAGENT_PARENT_RUN_ID_ENV] = fanoutAuthorized ? parentRunId : "";
|
|
240
|
-
env[SUBAGENT_PARENT_CHILD_INDEX_ENV] = fanoutAuthorized ? parentChildIndex : "";
|
|
241
|
-
env[SUBAGENT_PARENT_DEPTH_ENV] = fanoutAuthorized ? String(parentDepth) : "";
|
|
242
|
-
env[SUBAGENT_PARENT_PATH_ENV] = fanoutAuthorized ? encodeNestedPathEnv(parentPath) : "";
|
|
243
|
-
env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] = fanoutAuthorized
|
|
322
|
+
env[SUBAGENT_PARENT_RUN_ID_ENV] = toolPlan.fanoutAuthorized ? parentRunId : "";
|
|
323
|
+
env[SUBAGENT_PARENT_CHILD_INDEX_ENV] = toolPlan.fanoutAuthorized ? parentChildIndex : "";
|
|
324
|
+
env[SUBAGENT_PARENT_DEPTH_ENV] = toolPlan.fanoutAuthorized ? String(parentDepth) : "";
|
|
325
|
+
env[SUBAGENT_PARENT_PATH_ENV] = toolPlan.fanoutAuthorized ? encodeNestedPathEnv(parentPath) : "";
|
|
326
|
+
env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] = toolPlan.fanoutAuthorized
|
|
244
327
|
? input.parentCapabilityToken ?? process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] ?? ""
|
|
245
328
|
: "";
|
|
246
329
|
env.PI_SUBAGENT_INHERIT_PROJECT_CONTEXT = input.inheritProjectContext ? "1" : "0";
|
|
@@ -270,11 +353,11 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
270
353
|
if (input.childIndex !== undefined) {
|
|
271
354
|
env[SUBAGENT_CHILD_INDEX_ENV] = String(input.childIndex);
|
|
272
355
|
}
|
|
273
|
-
if (input.mcpDirectTools?.length)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
356
|
+
if (!toolPlan.capabilityCeiling && input.mcpDirectTools?.length) env.MCP_DIRECT_TOOLS = input.mcpDirectTools.join(",");
|
|
357
|
+
else if (toolPlan.capabilityCeiling && toolPlan.effectiveMcpSelections.length && !toolPlan.capabilityCeiling.denyExtensions) env.MCP_DIRECT_TOOLS = toolPlan.effectiveMcpSelections.map((selection) => selection.selector).join(",");
|
|
358
|
+
else env.MCP_DIRECT_TOOLS = "__none__";
|
|
359
|
+
const encodedCapabilityCeiling = encodeSubagentCapabilityCeiling(toolPlan.capabilityCeiling);
|
|
360
|
+
if (encodedCapabilityCeiling) env[SUBAGENT_CAPABILITY_CEILING_ENV] = encodedCapabilityCeiling;
|
|
278
361
|
if (input.structuredOutput) {
|
|
279
362
|
env[STRUCTURED_OUTPUT_CAPTURE_ENV] = input.structuredOutput.outputPath;
|
|
280
363
|
env[STRUCTURED_OUTPUT_SCHEMA_ENV] = input.structuredOutput.schemaPath;
|
|
@@ -292,7 +375,7 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
292
375
|
|
|
293
376
|
env[SUBAGENT_PARENT_SESSION_ENV] = input.parentSessionId ?? process.env[SUBAGENT_PARENT_SESSION_ENV] ?? "";
|
|
294
377
|
|
|
295
|
-
return { args, env, tempDir, toolDiagnosticPath };
|
|
378
|
+
return { args, env, tempDir, toolDiagnosticPath, capabilityAudit: toolPlan.capabilityAudit };
|
|
296
379
|
}
|
|
297
380
|
|
|
298
381
|
export const parseParentPathEnv = parseNestedPathEnv;
|
|
@@ -37,9 +37,14 @@ export interface SessionLeaseHandle {
|
|
|
37
37
|
leaseDir: string;
|
|
38
38
|
owner: SessionLeaseOwner;
|
|
39
39
|
updateWriter(writer: { state: "none" | "spawning" } | { state: "running"; pid: number }): void;
|
|
40
|
-
release():
|
|
40
|
+
release(): boolean;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export type SessionLeaseState =
|
|
44
|
+
| { state: "free"; canonicalSessionFile: string; canonicalSessionId: string }
|
|
45
|
+
| { state: "owned"; canonicalSessionFile: string; canonicalSessionId: string; owner: SessionLeaseOwner }
|
|
46
|
+
| { state: "unreadable"; canonicalSessionFile: string; canonicalSessionId: string };
|
|
47
|
+
|
|
43
48
|
interface SessionLeaseOptions {
|
|
44
49
|
rootDir?: string;
|
|
45
50
|
now?: () => number;
|
|
@@ -98,11 +103,25 @@ export function canonicalSessionFilePath(sessionFile: string): string {
|
|
|
98
103
|
return fs.realpathSync.native(path.resolve(sessionFile));
|
|
99
104
|
}
|
|
100
105
|
|
|
101
|
-
export function
|
|
106
|
+
export function canonicalSessionId(sessionFile: string): string {
|
|
102
107
|
const canonical = canonicalSessionFilePath(sessionFile);
|
|
103
108
|
const key = process.platform === "win32" ? canonical.toLowerCase() : canonical;
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
return createHash("sha256").update(key).digest("hex");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function sessionLeaseDir(sessionFile: string, rootDir = SESSION_LEASES_DIR): string {
|
|
113
|
+
return path.join(rootDir, canonicalSessionId(sessionFile));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function inspectSessionLease(sessionFile: string, rootDir = SESSION_LEASES_DIR): SessionLeaseState {
|
|
117
|
+
const canonicalSessionFile = canonicalSessionFilePath(sessionFile);
|
|
118
|
+
const canonicalSessionIdValue = canonicalSessionId(canonicalSessionFile);
|
|
119
|
+
const leaseDir = path.join(rootDir, canonicalSessionIdValue);
|
|
120
|
+
if (!fs.existsSync(leaseDir)) return { state: "free", canonicalSessionFile, canonicalSessionId: canonicalSessionIdValue };
|
|
121
|
+
const owner = readLeaseOwner(leaseDir);
|
|
122
|
+
return owner
|
|
123
|
+
? { state: "owned", canonicalSessionFile, canonicalSessionId: canonicalSessionIdValue, owner }
|
|
124
|
+
: { state: "unreadable", canonicalSessionFile, canonicalSessionId: canonicalSessionIdValue };
|
|
106
125
|
}
|
|
107
126
|
|
|
108
127
|
function parseOwner(value: unknown): SessionLeaseOwner | undefined {
|
|
@@ -251,8 +270,9 @@ export function acquireSessionLease(request: SessionLeaseRequest, options: Sessi
|
|
|
251
270
|
},
|
|
252
271
|
release() {
|
|
253
272
|
const currentOwner = readLeaseOwner(leaseDir);
|
|
254
|
-
if (!currentOwner || currentOwner.token !== owner.token) return;
|
|
273
|
+
if (!currentOwner || currentOwner.token !== owner.token) return false;
|
|
255
274
|
fs.rmSync(leaseDir, { recursive: true, force: true });
|
|
275
|
+
return !fs.existsSync(leaseDir);
|
|
256
276
|
},
|
|
257
277
|
};
|
|
258
278
|
}
|