@henryqw/pi-subagent 23.0.2 → 24.0.1
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/CONTEXT.md +1 -1
- package/README.md +2 -2
- package/dist/index.d.ts +5 -6
- package/dist/index.js +37 -59
- package/dist/launch-runtime.d.ts +0 -2
- package/dist/launch-runtime.js +2 -9
- package/dist/mcp-role.d.ts +0 -1
- package/dist/mcp-role.js +0 -11
- package/dist/runner.js +9 -16
- package/dist/schema.d.ts +39 -8
- package/dist/schema.js +26 -2
- package/dist/worktree.d.ts +0 -2
- package/dist/worktree.js +0 -4
- package/extensions/admission.ts +23 -31
- package/extensions/config.ts +2 -8
- package/extensions/isolated.ts +4 -5
- package/extensions/result-transport.ts +15 -32
- package/extensions/role-mcp.ts +3 -7
- package/extensions/role-tools.ts +2 -8
- package/extensions/subagent-command.ts +5 -5
- package/extensions/subagent.ts +16 -27
- package/extensions/workflow.ts +14 -1
- package/package.json +1 -1
- package/dist/working-evidence.d.ts +0 -25
- package/dist/working-evidence.js +0 -261
package/CONTEXT.md
CHANGED
|
@@ -76,7 +76,7 @@ Own Role-configured Pi delegation through one `delegate_task` surface:
|
|
|
76
76
|
- Recognized Pi JSON events renew idle timeout; raw bytes do not. Child maximum runtime always terminates ephemeral children.
|
|
77
77
|
- Output, stderr, protocol events, callback draining, and inherited descendant streams are bounded.
|
|
78
78
|
- Low-level worktree finalization returns explicit `pruned`, `retained`, or `recovery` evidence and never force-deletes uncertain work.
|
|
79
|
-
- Exact review
|
|
79
|
+
- Exact review helpers produce bounded private evidence; callers must preserve identity guards and verdict validation.
|
|
80
80
|
|
|
81
81
|
## Owned storage
|
|
82
82
|
|
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ pi install npm:pi-mcp-adapter
|
|
|
30
30
|
|
|
31
31
|
Ask Main to delegate a bounded task, such as “Have a scout trace sign-in without editing files.” Main uses `delegate_task`; you receive progress and a result or an actionable failure while Main remains available.
|
|
32
32
|
|
|
33
|
-
Commands are for you; tools and the packaged skill are for Main. You do not need to call agent tools or manage candidate identities yourself.
|
|
33
|
+
Commands are for you; tools and the packaged skill are for Main. You do not need to call agent tools or manage candidate identities yourself. Delegation and continuation tools use plain-object parameter schemas so providers that omit root-union tools can expose them; each mode and action still requires its exact arguments.
|
|
34
34
|
|
|
35
35
|
| Surface | Type | Purpose |
|
|
36
36
|
| --- | --- | --- |
|
|
@@ -199,7 +199,7 @@ Children disable ambient extension and Skill discovery. Only declared resources
|
|
|
199
199
|
| `prepareRoleLaunch`, `finalizeRoleLaunch` | functions | Prepare Role prompts, arguments, and tool policy. |
|
|
200
200
|
| `createEphemeralSubagentExecutor` | function | Run bounded no-session Pi children through a FIFO pool. |
|
|
201
201
|
| `createChildWorktree`, `finalizeChildWorktree` | functions | Create and conservatively finalize child worktrees. |
|
|
202
|
-
| `prepareExactReviewEvidence
|
|
202
|
+
| `prepareExactReviewEvidence` | function | Prepare private exact base-to-tip review evidence. |
|
|
203
203
|
| `ExecuteRequestSchema`, `StageRequestSchema`, `IntegrationActionSchema` | schemas | Validate checked request and integration inputs. |
|
|
204
204
|
| `IsolatedRunner`, `FileRunStore` | classes | Run checked work and persist durable request state. |
|
|
205
205
|
| `RoleLaunchRuntime`, `HerdrHostRuntime`, `CheckedGitRuntime` | classes | Supply launch, host, and Git behavior to the runner. |
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { type AvailableModel, type ModelTask, type ProfileName, type ResolvedTaskRoute, type ThinkingLevel } from "@henryqw/pi-task-models";
|
|
3
3
|
export { DISPLAY_TEXT_CONTRACT, hasDisplayControlCharacters } from "./display-text.ts";
|
|
4
|
-
export { parseRoleMcpAllowlist,
|
|
4
|
+
export { parseRoleMcpAllowlist, selectRoleMcpConfig, type RoleMcpConfig } from "./mcp-role.ts";
|
|
5
5
|
export { addUsage, capEphemeralSubagentOutput, createEphemeralSubagentExecutor, DEFAULT_MAX_TURNS, EphemeralSubagentError, EXECUTION_BUDGET_ENV, formatDuration, type EphemeralSubagentActivityEvent, type EphemeralSubagentErrorCode, type EphemeralSubagentExecutionBudget, type EphemeralSubagentExecutor, type EphemeralSubagentExecutorOptions, type EphemeralSubagentResult, type EphemeralSubagentRunInput, type EphemeralSubagentTimeout, } from "./ephemeral.ts";
|
|
6
|
-
export { createChildWorktree, finalizeChildWorktree, inspectIndexFlags, inspectWorktreeDirty, WorktreeSetupError,
|
|
6
|
+
export { createChildWorktree, finalizeChildWorktree, inspectIndexFlags, inspectWorktreeDirty, WorktreeSetupError, type WorktreeDirtyInspection, type WorktreeInfo, type WorktreePayload, } from "./worktree.ts";
|
|
7
7
|
export { prepareExactReviewEvidence, REVIEW_MAX_PATCH_BYTES, REVIEW_MAX_PATHS, type PreparedReviewEvidence, type PrepareExactReviewEvidenceInput, } from "./review-evidence.ts";
|
|
8
|
-
export { captureWorkingCheckoutBaseline, captureWorkingCheckoutState, prepareWorkingChangeEvidence, sameWorkingCheckoutState, sameWorkingSnapshot, type PreparedWorkingChangeEvidence, type WorkingCheckoutBaseline, type WorkingCheckoutState, type WorkingSnapshotIdentity, } from "./working-evidence.ts";
|
|
9
8
|
export declare const PI_SUBAGENT_PROCESS_LEASE = "PI_SUBAGENT_PROCESS_LEASE";
|
|
10
9
|
export declare const ROLE_MCP_POLICY_FLAG = "pi-subagent-role-mcps";
|
|
10
|
+
/** Read child MCP policy before Pi binds registered extension flag values. */
|
|
11
|
+
export declare function roleMcpAllowlistFromArgv(args: readonly string[]): string[];
|
|
11
12
|
export declare const ROLE_TOOL_POLICY_FLAG = "pi-subagent-role-tools";
|
|
12
|
-
export declare const EXECUTION_BUDGET_FLAG = "pi-subagent-execution-budget";
|
|
13
13
|
export declare const CHILD_EXCLUDED_TOOL_NAMES: readonly ["delegate_task", "ask_question", "subagent_status", "subagent_resume", "subagent_stage", "subagent_abort"];
|
|
14
|
-
export declare const CHILD_EXCLUDED_TOOLS: string;
|
|
15
14
|
export declare const DELEGATE_TASK: {
|
|
16
15
|
readonly id: "pi-subagent/delegateTask";
|
|
17
16
|
readonly label: "Subagent delegation";
|
|
@@ -91,7 +90,7 @@ export declare function prepareRoleLaunch(pi: Pick<ExtensionAPI, "getCommands">,
|
|
|
91
90
|
export declare function prepareRoleLaunch(pi: Pick<ExtensionAPI, "getCommands">, ctx: ExtensionContext, input: CreateRoleLaunchInput): PreparedRoleLaunch;
|
|
92
91
|
/** Resolve and prepare a configured Role with its package-owned resources. */
|
|
93
92
|
export declare function resolveConfiguredRoleLaunch(pi: Pick<ExtensionAPI, "getCommands">, ctx: ExtensionContext, input: ResolveConfiguredRoleLaunchInput): Promise<PreparedRoleLaunch>;
|
|
94
|
-
/**
|
|
93
|
+
/** Insert the system prompt pair after a caller has prepared its launch argv. */
|
|
95
94
|
export declare function finalizeRoleLaunch(prepared: PreparedRoleLaunch): ResolvedRoleLaunch;
|
|
96
95
|
export * from "./composition.ts";
|
|
97
96
|
export * from "./git-runtime.ts";
|
package/dist/index.js
CHANGED
|
@@ -4,22 +4,28 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import { DefaultPackageManager, getAgentDir, parseFrontmatter, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { extensionConfigDir } from "@henryqw/pi-config-store";
|
|
6
6
|
import { hasDisplayControlCharacters } from "./display-text.js";
|
|
7
|
-
import { selectRoleMcpConfig } from "./mcp-role.js";
|
|
7
|
+
import { parseRoleMcpAllowlist, selectRoleMcpConfig } from "./mcp-role.js";
|
|
8
8
|
import { loadTaskModelsConfig, modelReference, orderedProfileRoutes, PROFILE_NAMES, resolveConfiguredTaskRoute, resolveTaskModelRoute, } from "@henryqw/pi-task-models";
|
|
9
9
|
export { DISPLAY_TEXT_CONTRACT, hasDisplayControlCharacters } from "./display-text.js";
|
|
10
|
-
export { parseRoleMcpAllowlist,
|
|
10
|
+
export { parseRoleMcpAllowlist, selectRoleMcpConfig } from "./mcp-role.js";
|
|
11
11
|
export { addUsage, capEphemeralSubagentOutput, createEphemeralSubagentExecutor, DEFAULT_MAX_TURNS, EphemeralSubagentError, EXECUTION_BUDGET_ENV, formatDuration, } from "./ephemeral.js";
|
|
12
|
-
export { createChildWorktree, finalizeChildWorktree, inspectIndexFlags, inspectWorktreeDirty, WorktreeSetupError,
|
|
12
|
+
export { createChildWorktree, finalizeChildWorktree, inspectIndexFlags, inspectWorktreeDirty, WorktreeSetupError, } from "./worktree.js";
|
|
13
13
|
export { prepareExactReviewEvidence, REVIEW_MAX_PATCH_BYTES, REVIEW_MAX_PATHS, } from "./review-evidence.js";
|
|
14
|
-
export { captureWorkingCheckoutBaseline, captureWorkingCheckoutState, prepareWorkingChangeEvidence, sameWorkingCheckoutState, sameWorkingSnapshot, } from "./working-evidence.js";
|
|
15
14
|
const CODEX_ALIAS = /^openai-codex-(?:[2-9]|[1-9]\d+)$/;
|
|
16
15
|
const MULTI_CODEX_EXTENSION = fileURLToPath(import.meta.resolve("@henryqw/pi-multi-codex/extensions/multi-codex.ts"));
|
|
17
16
|
const ROLE_MCP_EXTENSION = fileURLToPath(new URL("../extensions/role-mcp.ts", import.meta.url));
|
|
18
17
|
const ROLE_TOOLS_EXTENSION = fileURLToPath(new URL("../extensions/role-tools.ts", import.meta.url));
|
|
19
18
|
export const PI_SUBAGENT_PROCESS_LEASE = "PI_SUBAGENT_PROCESS_LEASE";
|
|
20
19
|
export const ROLE_MCP_POLICY_FLAG = "pi-subagent-role-mcps";
|
|
20
|
+
/** Read child MCP policy before Pi binds registered extension flag values. */
|
|
21
|
+
export function roleMcpAllowlistFromArgv(args) {
|
|
22
|
+
const flag = `--${ROLE_MCP_POLICY_FLAG}`;
|
|
23
|
+
const indexes = args.flatMap((arg, index) => arg === flag ? [index] : []);
|
|
24
|
+
if (indexes.length !== 1)
|
|
25
|
+
throw new Error(`${flag} must appear exactly once.`);
|
|
26
|
+
return parseRoleMcpAllowlist(args[indexes[0] + 1]);
|
|
27
|
+
}
|
|
21
28
|
export const ROLE_TOOL_POLICY_FLAG = "pi-subagent-role-tools";
|
|
22
|
-
export const EXECUTION_BUDGET_FLAG = "pi-subagent-execution-budget";
|
|
23
29
|
export const CHILD_EXCLUDED_TOOL_NAMES = [
|
|
24
30
|
"delegate_task",
|
|
25
31
|
"ask_question",
|
|
@@ -28,7 +34,6 @@ export const CHILD_EXCLUDED_TOOL_NAMES = [
|
|
|
28
34
|
"subagent_stage",
|
|
29
35
|
"subagent_abort",
|
|
30
36
|
];
|
|
31
|
-
export const CHILD_EXCLUDED_TOOLS = CHILD_EXCLUDED_TOOL_NAMES.join(",");
|
|
32
37
|
const SYSTEM_PROMPT_FLAG = "--append-system-prompt";
|
|
33
38
|
const CHILD_IDENTITY_POLICY = "You are a delegated Pi Subagent, not Main. Execute the assigned Role and task directly. Main-only delegation rules do not apply. Recursive delegation is unavailable; do not seek or invoke delegation tools.";
|
|
34
39
|
export const DELEGATE_TASK = {
|
|
@@ -238,9 +243,10 @@ export async function resolveRolePackageResources(role, ctx) {
|
|
|
238
243
|
};
|
|
239
244
|
}
|
|
240
245
|
export function createRoleLaunch(pi, ctx, input) {
|
|
241
|
-
return
|
|
246
|
+
return finalizeRoleLaunch(prepareRoleLaunchFromSkills(ctx, input, resolveRoleSkills(pi, input.role)));
|
|
242
247
|
}
|
|
243
|
-
|
|
248
|
+
/** Build the prompt-free argv and tool policy; the Role prompt stays separate until `finalizeRoleLaunch`. */
|
|
249
|
+
function prepareRoleLaunchFromSkills(ctx, input, skills) {
|
|
244
250
|
const role = input.role;
|
|
245
251
|
const mcps = mcpList(role.mcps, `Role ${role.name}`);
|
|
246
252
|
const tools = roleToolPolicy(role, input.tools);
|
|
@@ -262,7 +268,7 @@ function createRoleLaunchFromSkills(ctx, input, skills) {
|
|
|
262
268
|
throw new Error(`Invalid launch environment value: ${key}`);
|
|
263
269
|
return [key, value];
|
|
264
270
|
}));
|
|
265
|
-
const args = ["--no-session", "--no-extensions", "--no-skills", "--exclude-tools",
|
|
271
|
+
const args = ["--no-session", "--no-extensions", "--no-skills", "--exclude-tools", CHILD_EXCLUDED_TOOL_NAMES.join(",")];
|
|
266
272
|
if (mcps.length)
|
|
267
273
|
args.push(`--${ROLE_MCP_POLICY_FLAG}`, JSON.stringify(mcps));
|
|
268
274
|
for (const extension of new Set(extensions))
|
|
@@ -274,46 +280,30 @@ function createRoleLaunchFromSkills(ctx, input, skills) {
|
|
|
274
280
|
if (input.route.thinkingLevel)
|
|
275
281
|
args.push("--thinking", input.route.thinkingLevel);
|
|
276
282
|
args.push(ctx.isProjectTrusted() ? "--approve" : "--no-approve");
|
|
277
|
-
args.push(SYSTEM_PROMPT_FLAG, `${CHILD_IDENTITY_POLICY}\n\n${cleanText(role.systemPrompt, "system prompt", `Role ${role.name}`)}`);
|
|
278
283
|
return {
|
|
279
284
|
env,
|
|
280
285
|
args,
|
|
281
286
|
model: input.route.model,
|
|
282
287
|
thinkingLevel: input.route.thinkingLevel,
|
|
283
288
|
missingSkills: skills.missing,
|
|
289
|
+
role: role.name,
|
|
290
|
+
tools: Object.freeze(tools),
|
|
291
|
+
systemPrompt: `${CHILD_IDENTITY_POLICY}\n\n${cleanText(role.systemPrompt, "system prompt", `Role ${role.name}`)}`,
|
|
292
|
+
promptArgIndex: args.length,
|
|
284
293
|
};
|
|
285
294
|
}
|
|
286
|
-
|
|
295
|
+
function prepareResolvedRoleLaunch(pi, ctx, input) {
|
|
287
296
|
const { task, modelClass, agentDir, ...launchInput } = input;
|
|
288
297
|
const selectedClass = modelClass ?? input.role.modelClass;
|
|
289
|
-
return
|
|
298
|
+
return prepareRoleLaunchFromSkills(ctx, {
|
|
290
299
|
...launchInput,
|
|
291
300
|
route: selectedClass === undefined
|
|
292
301
|
? resolveConfiguredTaskRoute(ctx, task, agentDir)
|
|
293
302
|
: resolveTaskRoute(ctx, selectedClass, agentDir),
|
|
294
|
-
});
|
|
303
|
+
}, resolveRoleSkills(pi, input.role));
|
|
295
304
|
}
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
if (indexes.length !== 1)
|
|
299
|
-
throw new Error(`Role launch must contain exactly one ${SYSTEM_PROMPT_FLAG} pair.`);
|
|
300
|
-
const promptArgIndex = indexes[0];
|
|
301
|
-
const systemPrompt = rawArgs[promptArgIndex + 1];
|
|
302
|
-
if (typeof systemPrompt !== "string" || !systemPrompt.includes("\n") || !systemPrompt.trim() || systemPrompt.includes("\0")) {
|
|
303
|
-
throw new Error(`Role ${SYSTEM_PROMPT_FLAG} value must be the exact multiline Role prompt.`);
|
|
304
|
-
}
|
|
305
|
-
const args = [...rawArgs.slice(0, promptArgIndex), ...rawArgs.slice(promptArgIndex + 2)];
|
|
306
|
-
if (args.includes(SYSTEM_PROMPT_FLAG) || args.includes(systemPrompt)) {
|
|
307
|
-
throw new Error("Sanitized Role argv must contain no prompt flag or raw Role prompt.");
|
|
308
|
-
}
|
|
309
|
-
return { args, systemPrompt, promptArgIndex };
|
|
310
|
-
}
|
|
311
|
-
function prepareResolvedRoleLaunch(roleDefinition, launch, additionalTools = []) {
|
|
312
|
-
const role = parseRoleName(roleDefinition.name);
|
|
313
|
-
rejectRetiredRoleIsolation(roleDefinition.isolation, `Role ${role}`);
|
|
314
|
-
const tools = Object.freeze(roleToolPolicy(roleDefinition, additionalTools));
|
|
315
|
-
const { args, systemPrompt, promptArgIndex } = stripRoleSystemPrompt(launch.args);
|
|
316
|
-
return { ...launch, args, role, tools, systemPrompt, promptArgIndex };
|
|
305
|
+
export function resolveRoleLaunch(pi, ctx, input) {
|
|
306
|
+
return finalizeRoleLaunch(prepareResolvedRoleLaunch(pi, ctx, input));
|
|
317
307
|
}
|
|
318
308
|
function assertNoMissingRoleSkills(role, launch) {
|
|
319
309
|
if (!launch.missingSkills.length)
|
|
@@ -321,11 +311,13 @@ function assertNoMissingRoleSkills(role, launch) {
|
|
|
321
311
|
throw new Error(`Role ${parseRoleName(role.name)} requires missing Skills: ${launch.missingSkills.join(", ")}.`);
|
|
322
312
|
}
|
|
323
313
|
export function prepareRoleLaunch(pi, ctx, input) {
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
314
|
+
const role = parseRoleName(input.role.name);
|
|
315
|
+
rejectRetiredRoleIsolation(input.role.isolation, `Role ${role}`);
|
|
316
|
+
const prepared = "route" in input
|
|
317
|
+
? prepareRoleLaunchFromSkills(ctx, input, resolveRoleSkills(pi, input.role))
|
|
318
|
+
: prepareResolvedRoleLaunch(pi, ctx, input);
|
|
319
|
+
assertNoMissingRoleSkills(input.role, prepared);
|
|
320
|
+
return { ...prepared, role };
|
|
329
321
|
}
|
|
330
322
|
/** Resolve and prepare a configured Role with its package-owned resources. */
|
|
331
323
|
export async function resolveConfiguredRoleLaunch(pi, ctx, input) {
|
|
@@ -347,36 +339,22 @@ export async function resolveConfiguredRoleLaunch(pi, ctx, input) {
|
|
|
347
339
|
...namedSkills,
|
|
348
340
|
paths: [...new Set([...namedSkills.paths, ...resources.skills])],
|
|
349
341
|
};
|
|
350
|
-
const
|
|
342
|
+
const prepared = prepareRoleLaunchFromSkills(ctx, {
|
|
351
343
|
role: effectiveRole,
|
|
352
344
|
route: resolveTaskRoute(ctx, input.modelClass),
|
|
353
345
|
}, skills);
|
|
354
|
-
assertNoMissingRoleSkills(effectiveRole,
|
|
355
|
-
const
|
|
346
|
+
assertNoMissingRoleSkills(effectiveRole, prepared);
|
|
347
|
+
const args = [
|
|
348
|
+
...prepared.args,
|
|
356
349
|
"--no-prompt-templates",
|
|
357
350
|
"--no-themes",
|
|
358
351
|
...resources.prompts.flatMap((path) => ["--prompt-template", path]),
|
|
359
352
|
...resources.themes.flatMap((path) => ["--theme", path]),
|
|
360
353
|
];
|
|
361
|
-
|
|
362
|
-
if (promptArgIndex < 0)
|
|
363
|
-
throw new Error(`Resolved Role launch has no ${SYSTEM_PROMPT_FLAG}.`);
|
|
364
|
-
const args = [...launch.args];
|
|
365
|
-
args.splice(promptArgIndex, 0, ...additions);
|
|
366
|
-
return prepareResolvedRoleLaunch(effectiveRole, { ...launch, args });
|
|
354
|
+
return { ...prepared, args, promptArgIndex: args.length };
|
|
367
355
|
}
|
|
368
|
-
/**
|
|
356
|
+
/** Insert the system prompt pair after a caller has prepared its launch argv. */
|
|
369
357
|
export function finalizeRoleLaunch(prepared) {
|
|
370
|
-
if (prepared.args.includes(SYSTEM_PROMPT_FLAG)) {
|
|
371
|
-
throw new Error(`Prepared Role launch already contains ${SYSTEM_PROMPT_FLAG}.`);
|
|
372
|
-
}
|
|
373
|
-
if (!Number.isSafeInteger(prepared.promptArgIndex)
|
|
374
|
-
|| prepared.promptArgIndex < 0 || prepared.promptArgIndex > prepared.args.length) {
|
|
375
|
-
throw new Error("Prepared Role launch has an invalid system prompt insertion index.");
|
|
376
|
-
}
|
|
377
|
-
if (typeof prepared.systemPrompt !== "string" || !prepared.systemPrompt.trim() || prepared.systemPrompt.includes("\0")) {
|
|
378
|
-
throw new Error("Prepared Role launch has an invalid system prompt.");
|
|
379
|
-
}
|
|
380
358
|
const args = [...prepared.args];
|
|
381
359
|
args.splice(prepared.promptArgIndex, 0, SYSTEM_PROMPT_FLAG, prepared.systemPrompt);
|
|
382
360
|
return {
|
package/dist/launch-runtime.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { EphemeralSubagentExecutionBudget } from "./ephemeral.ts";
|
|
3
2
|
import type { CoordinatorRuntime, OperationContext, TransientLaunchHandle, VerifiedLaunch } from "./runner.ts";
|
|
4
3
|
import { type ExecuteRequest, type ModelClass, type WorkspaceIdentity } from "./schema.ts";
|
|
5
4
|
export declare const ISOLATED_MODEL_TASK: {
|
|
@@ -21,7 +20,6 @@ export interface LaunchRuntimeOptions {
|
|
|
21
20
|
inspectMainBase(input: {
|
|
22
21
|
root: string;
|
|
23
22
|
}, context: OperationContext): Promise<WorkspaceIdentity>;
|
|
24
|
-
executionBudget?: () => Omit<EphemeralSubagentExecutionBudget, "startedAt">;
|
|
25
23
|
now?: () => number;
|
|
26
24
|
randomToken?: () => string;
|
|
27
25
|
}
|
package/dist/launch-runtime.js
CHANGED
|
@@ -3,7 +3,7 @@ import { constants } from "node:fs";
|
|
|
3
3
|
import { chmod, lstat, mkdtemp, open, realpath, rmdir, unlink } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { isAbsolute, join, normalize, relative, sep } from "node:path";
|
|
6
|
-
import {
|
|
6
|
+
import { resolveConfiguredRoleLaunch } from "./index.js";
|
|
7
7
|
import { registerModelTask } from "@henryqw/pi-task-models";
|
|
8
8
|
const PROMPT_FLAG = "--append-system-prompt";
|
|
9
9
|
const DIRECTORY_MODE = 0o700;
|
|
@@ -130,20 +130,13 @@ export class RoleLaunchRuntime {
|
|
|
130
130
|
if (prepared.missingSkills.length) {
|
|
131
131
|
throw new Error(`Role ${role} requires missing Skills: ${prepared.missingSkills.join(", ")}.`);
|
|
132
132
|
}
|
|
133
|
-
const executionBudget = this.options.executionBudget?.();
|
|
134
133
|
return Object.freeze({
|
|
135
134
|
launch: Object.freeze({
|
|
136
135
|
role: prepared.role,
|
|
137
136
|
modelClass,
|
|
138
137
|
model: `${prepared.model.provider}/${prepared.model.id}`,
|
|
139
138
|
thinkingLevel: prepared.thinkingLevel,
|
|
140
|
-
args: Object.freeze([
|
|
141
|
-
...prepared.args,
|
|
142
|
-
...(executionBudget === undefined ? [] : [
|
|
143
|
-
`--${EXECUTION_BUDGET_FLAG}`,
|
|
144
|
-
JSON.stringify({ ...executionBudget, startedAt: this.now() }),
|
|
145
|
-
]),
|
|
146
|
-
]),
|
|
139
|
+
args: Object.freeze([...prepared.args]),
|
|
147
140
|
env: Object.freeze({ ...prepared.env }),
|
|
148
141
|
tools: Object.freeze([...prepared.tools]),
|
|
149
142
|
}),
|
package/dist/mcp-role.d.ts
CHANGED
|
@@ -3,5 +3,4 @@ export interface RoleMcpConfig {
|
|
|
3
3
|
settings?: Record<string, unknown>;
|
|
4
4
|
}
|
|
5
5
|
export declare function parseRoleMcpAllowlist(value: unknown): string[];
|
|
6
|
-
export declare function roleMcpFlagValue(args: readonly string[], flag: string): string | undefined;
|
|
7
6
|
export declare function selectRoleMcpConfig(config: RoleMcpConfig, allowlist: readonly string[]): RoleMcpConfig;
|
package/dist/mcp-role.js
CHANGED
|
@@ -17,17 +17,6 @@ export function parseRoleMcpAllowlist(value) {
|
|
|
17
17
|
throw new Error("The Role MCP policy flag contains duplicate MCP server names.");
|
|
18
18
|
return names;
|
|
19
19
|
}
|
|
20
|
-
export function roleMcpFlagValue(args, flag) {
|
|
21
|
-
const indexes = args.flatMap((arg, index) => arg === flag ? [index] : []);
|
|
22
|
-
if (indexes.length > 1)
|
|
23
|
-
throw new Error(`${flag} must appear at most once.`);
|
|
24
|
-
if (!indexes.length)
|
|
25
|
-
return;
|
|
26
|
-
const value = args[indexes[0] + 1];
|
|
27
|
-
if (value === undefined)
|
|
28
|
-
throw new Error(`${flag} requires a value.`);
|
|
29
|
-
return value;
|
|
30
|
-
}
|
|
31
20
|
export function selectRoleMcpConfig(config, allowlist) {
|
|
32
21
|
const missing = allowlist.filter((name) => !Object.hasOwn(config.mcpServers, name));
|
|
33
22
|
if (missing.length)
|
package/dist/runner.js
CHANGED
|
@@ -69,7 +69,7 @@ class DurableRunStopped extends Error {
|
|
|
69
69
|
}
|
|
70
70
|
class ProductiveScope {
|
|
71
71
|
signal;
|
|
72
|
-
constructor(
|
|
72
|
+
constructor(outerSignal) {
|
|
73
73
|
this.signal = outerSignal ?? new AbortController().signal;
|
|
74
74
|
}
|
|
75
75
|
async call(operation) {
|
|
@@ -150,10 +150,6 @@ class FollowupControl {
|
|
|
150
150
|
this.sealed = true;
|
|
151
151
|
return undefined;
|
|
152
152
|
}
|
|
153
|
-
invalidate() {
|
|
154
|
-
this.sealed = true;
|
|
155
|
-
this.queue.length = 0;
|
|
156
|
-
}
|
|
157
153
|
close() {
|
|
158
154
|
this.sealed = true;
|
|
159
155
|
this.queue.length = 0;
|
|
@@ -662,7 +658,7 @@ export class IsolatedRunner {
|
|
|
662
658
|
async execute(value, cwd, outerSignal) {
|
|
663
659
|
const request = parseExecuteRequest(value);
|
|
664
660
|
const policy = Object.freeze({ ...this.currentPolicy() });
|
|
665
|
-
const scope = new ProductiveScope(
|
|
661
|
+
const scope = new ProductiveScope(outerSignal);
|
|
666
662
|
const canonicalCwd = realpathSync.native(cwd);
|
|
667
663
|
const prepared = await scope.call(async (context) => await this.coordinatorRuntime.preflight({ request, cwd: canonicalCwd }, context));
|
|
668
664
|
const root = realpathSync.native(prepared.root);
|
|
@@ -748,8 +744,7 @@ export class IsolatedRunner {
|
|
|
748
744
|
return loaded;
|
|
749
745
|
}, { productiveRunLease: lifecycle.lease });
|
|
750
746
|
const state = handle.state;
|
|
751
|
-
const
|
|
752
|
-
const scope = new ProductiveScope(Math.min(state.policy.childMaxMs, current.childMaxMs), () => this.coordinatorRuntime.now(), outerSignal);
|
|
747
|
+
const scope = new ProductiveScope(outerSignal);
|
|
753
748
|
try {
|
|
754
749
|
if (request.action === "finalize")
|
|
755
750
|
return await this.finalize(handle, scope);
|
|
@@ -782,8 +777,6 @@ export class IsolatedRunner {
|
|
|
782
777
|
throw error;
|
|
783
778
|
}
|
|
784
779
|
finally {
|
|
785
|
-
if (scope instanceof DeadlineScope)
|
|
786
|
-
scope.close();
|
|
787
780
|
this.closeRequestControls(root, request.id);
|
|
788
781
|
if (!lifecycle.stopped) {
|
|
789
782
|
delete state.recovery;
|
|
@@ -820,7 +813,7 @@ export class IsolatedRunner {
|
|
|
820
813
|
? generation.combinedTip ?? generation.stages.at(-1)?.tip ?? generation.integrationBase : state.main;
|
|
821
814
|
if (!sameIdentity(action.expectedTip, expected))
|
|
822
815
|
throw new Error("Rejection recovery has a stale integration tip.");
|
|
823
|
-
const scope = new ProductiveScope(
|
|
816
|
+
const scope = new ProductiveScope(outerSignal);
|
|
824
817
|
const termination = attempt.termination;
|
|
825
818
|
const result = await scope.call((context) => this.hostRuntime.reconcileWorkerTermination({
|
|
826
819
|
task: changesetTaskRequest(state, task.taskId), attempt, workerId: termination.workerId,
|
|
@@ -841,7 +834,7 @@ export class IsolatedRunner {
|
|
|
841
834
|
|| !sameIdentity(attempt.readiness.candidate, action.candidate)) {
|
|
842
835
|
throw new Error("Stage action refers to a stale or unowned candidate.");
|
|
843
836
|
}
|
|
844
|
-
const scope = new ProductiveScope(
|
|
837
|
+
const scope = new ProductiveScope(outerSignal);
|
|
845
838
|
const generations = state.integration.generations;
|
|
846
839
|
let generation = generations.at(-1);
|
|
847
840
|
const nextNumber = generation?.status === "superseded" ? generation.number + 1 : generation?.number ?? 1;
|
|
@@ -1100,7 +1093,7 @@ export class IsolatedRunner {
|
|
|
1100
1093
|
const request = parseExecuteRequest(state.request);
|
|
1101
1094
|
const generation = state.integration.generations.at(-1);
|
|
1102
1095
|
if (action.action === "release") {
|
|
1103
|
-
const scope = new ProductiveScope(
|
|
1096
|
+
const scope = new ProductiveScope(outerSignal);
|
|
1104
1097
|
if (action.taskId !== undefined || action.attempt !== undefined) {
|
|
1105
1098
|
if (!action.taskId || !action.attempt || action.generation !== (generation?.number ?? 1)) {
|
|
1106
1099
|
throw new Error("Candidate release requires the latest generation and exact task attempt.");
|
|
@@ -1149,7 +1142,7 @@ export class IsolatedRunner {
|
|
|
1149
1142
|
if (state.integration.generations.filter((item) => item.worktree && !item.cleanup?.every((step) => step.status === "completed")).length >= MAX_RETAINED_INTEGRATION_GENERATIONS) {
|
|
1150
1143
|
throw new Error("Retained integration worktree limit is exhausted.");
|
|
1151
1144
|
}
|
|
1152
|
-
const scope = new ProductiveScope(
|
|
1145
|
+
const scope = new ProductiveScope(outerSignal);
|
|
1153
1146
|
const actualMain = await scope.call((context) => this.gitRuntime.inspectMain({ root }, context));
|
|
1154
1147
|
if (!isCleanCommitted(action.newMain) || !sameIdentity(actualMain, action.newMain)) {
|
|
1155
1148
|
throw new Error("Refresh requires the exact new clean Main identity.");
|
|
@@ -1233,7 +1226,7 @@ export class IsolatedRunner {
|
|
|
1233
1226
|
|| (terminal(state) && !(state.status === "completed" && action.action === "cleanup"))
|
|
1234
1227
|
|| (state.status !== "needs_attention" && !(state.status === "completed" && action.action === "cleanup")))
|
|
1235
1228
|
throw new Error("Integration action has a stale generation or combined tip.");
|
|
1236
|
-
const scope = new ProductiveScope(
|
|
1229
|
+
const scope = new ProductiveScope(outerSignal);
|
|
1237
1230
|
const receipts = this.stageReceipts(generation);
|
|
1238
1231
|
if (action.action === "advance") {
|
|
1239
1232
|
if (generation.status !== "staging" || generation.stages.some((stage) => stage.status !== "staged")
|
|
@@ -1603,7 +1596,7 @@ export class IsolatedRunner {
|
|
|
1603
1596
|
}
|
|
1604
1597
|
const activeControls = this.activeControls(root, id);
|
|
1605
1598
|
for (const { control } of activeControls)
|
|
1606
|
-
control.
|
|
1599
|
+
control.close();
|
|
1607
1600
|
if (!activeControls.length
|
|
1608
1601
|
&& !lifecycle.productiveRunLeaseActive
|
|
1609
1602
|
&& this.recoverInterrupted(state)) {
|
package/dist/schema.d.ts
CHANGED
|
@@ -129,6 +129,11 @@ export declare const ResumeRequestSchema: Type.TUnion<[Type.TObject<{
|
|
|
129
129
|
id: Type.TString;
|
|
130
130
|
action: Type.TLiteral<"finalize">;
|
|
131
131
|
}>]>;
|
|
132
|
+
export declare const ResumeRequestParameters: Type.TObject<{
|
|
133
|
+
id: Type.TString;
|
|
134
|
+
action: Type.TUnion<[Type.TLiteral<"retry">, Type.TLiteral<"verify">, Type.TLiteral<"finalize">]>;
|
|
135
|
+
taskId: Type.TOptional<Type.TString>;
|
|
136
|
+
}>;
|
|
132
137
|
export type IdOnly = Static<typeof IdOnlySchema>;
|
|
133
138
|
export type CheckCommand = Static<typeof CheckCommandSchema>;
|
|
134
139
|
export type Judgment = Static<typeof JudgmentSchema>;
|
|
@@ -139,14 +144,7 @@ type ExecuteRequestInput = Static<typeof ExecuteRequestSchema>;
|
|
|
139
144
|
export type ExecuteRequest = Omit<ExecuteRequestInput, "finalChecks"> & {
|
|
140
145
|
finalChecks: CheckCommand[];
|
|
141
146
|
};
|
|
142
|
-
export
|
|
143
|
-
maxSubagents: number;
|
|
144
|
-
maxTurns: number;
|
|
145
|
-
maxTokens?: number;
|
|
146
|
-
childIdleMs: number;
|
|
147
|
-
childMaxMs: number;
|
|
148
|
-
maxCorrections: number;
|
|
149
|
-
}
|
|
147
|
+
export type ExecutionPolicySnapshot = Static<typeof ExecutionPolicySnapshotSchema>;
|
|
150
148
|
export type ResumeRequest = Static<typeof ResumeRequestSchema>;
|
|
151
149
|
export type WorkspaceIdentity = Static<typeof WorkspaceSchema>;
|
|
152
150
|
declare const WorktreeRecordSchema: Type.TObject<{
|
|
@@ -376,6 +374,31 @@ export declare const IntegrationActionSchema: Type.TUnion<[Type.TObject<{
|
|
|
376
374
|
taskId: Type.TOptional<Type.TString>;
|
|
377
375
|
attempt: Type.TOptional<Type.TInteger>;
|
|
378
376
|
}>]>;
|
|
377
|
+
export declare const IntegrationActionParameters: Type.TObject<{
|
|
378
|
+
id: Type.TString;
|
|
379
|
+
generation: Type.TInteger;
|
|
380
|
+
action: Type.TUnion<[Type.TLiteral<"refresh">, Type.TUnion<[Type.TLiteral<"advance">, Type.TLiteral<"validate">, Type.TLiteral<"correct">, Type.TLiteral<"promote">, Type.TLiteral<"reconcile">, Type.TLiteral<"cleanup">]>, Type.TLiteral<"release">]>;
|
|
381
|
+
expectedTip: Type.TObject<{
|
|
382
|
+
branch: Type.TString;
|
|
383
|
+
head: Type.TString;
|
|
384
|
+
index: Type.TString;
|
|
385
|
+
tree: Type.TString;
|
|
386
|
+
}>;
|
|
387
|
+
expectedMain: Type.TOptional<Type.TObject<{
|
|
388
|
+
branch: Type.TString;
|
|
389
|
+
head: Type.TString;
|
|
390
|
+
index: Type.TString;
|
|
391
|
+
tree: Type.TString;
|
|
392
|
+
}>>;
|
|
393
|
+
newMain: Type.TOptional<Type.TObject<{
|
|
394
|
+
branch: Type.TString;
|
|
395
|
+
head: Type.TString;
|
|
396
|
+
index: Type.TString;
|
|
397
|
+
tree: Type.TString;
|
|
398
|
+
}>>;
|
|
399
|
+
taskId: Type.TOptional<Type.TString>;
|
|
400
|
+
attempt: Type.TOptional<Type.TInteger>;
|
|
401
|
+
}>;
|
|
379
402
|
export type IntegrationAction = Static<typeof IntegrationActionSchema>;
|
|
380
403
|
export declare function parseIntegrationAction(value: unknown): IntegrationAction;
|
|
381
404
|
export declare function parseStageRequest(value: unknown): StageRequest;
|
|
@@ -1083,6 +1106,14 @@ declare const WaveStateSchema: Type.TObject<{
|
|
|
1083
1106
|
taskIds: Type.TArray<Type.TString>;
|
|
1084
1107
|
status: Type.TUnion<[Type.TLiteral<"dispatching">, Type.TLiteral<"integrating">, Type.TLiteral<"completed">, Type.TLiteral<"needs_attention">]>;
|
|
1085
1108
|
}>;
|
|
1109
|
+
declare const ExecutionPolicySnapshotSchema: Type.TObject<{
|
|
1110
|
+
maxSubagents: Type.TInteger;
|
|
1111
|
+
maxTurns: Type.TInteger;
|
|
1112
|
+
maxTokens: Type.TOptional<Type.TInteger>;
|
|
1113
|
+
childIdleMs: Type.TNumber;
|
|
1114
|
+
childMaxMs: Type.TNumber;
|
|
1115
|
+
maxCorrections: Type.TInteger;
|
|
1116
|
+
}>;
|
|
1086
1117
|
declare const IntegrationCandidateSchema: Type.TObject<{
|
|
1087
1118
|
taskId: Type.TString;
|
|
1088
1119
|
attempt: Type.TInteger;
|
package/dist/schema.js
CHANGED
|
@@ -65,7 +65,17 @@ export const ResumeRequestSchema = Type.Union([
|
|
|
65
65
|
Type.Object({ id: IdSchema, action: Type.Literal("retry"), taskId: IdSchema }, { additionalProperties: false }),
|
|
66
66
|
Type.Object({ id: IdSchema, action: Type.Literal("verify"), taskId: IdSchema }, { additionalProperties: false }),
|
|
67
67
|
Type.Object({ id: IdSchema, action: Type.Literal("finalize") }, { additionalProperties: false }),
|
|
68
|
-
]
|
|
68
|
+
]);
|
|
69
|
+
// Tool parameters need a plain object root; the action union remains the validation boundary.
|
|
70
|
+
export const ResumeRequestParameters = Type.Object({
|
|
71
|
+
id: IdSchema,
|
|
72
|
+
action: Type.Union([
|
|
73
|
+
ResumeRequestSchema.anyOf[0].properties.action,
|
|
74
|
+
ResumeRequestSchema.anyOf[1].properties.action,
|
|
75
|
+
ResumeRequestSchema.anyOf[2].properties.action,
|
|
76
|
+
]),
|
|
77
|
+
taskId: Type.Optional(IdSchema),
|
|
78
|
+
}, { additionalProperties: false });
|
|
69
79
|
const AllocationLifecycleFields = {
|
|
70
80
|
generation: Type.Integer({ minimum: 1, maximum: 2 }),
|
|
71
81
|
token: Type.String({ pattern: TOKEN_PATTERN }),
|
|
@@ -167,7 +177,21 @@ export const IntegrationActionSchema = Type.Union([
|
|
|
167
177
|
taskId: Type.Optional(IdSchema),
|
|
168
178
|
attempt: Type.Optional(Type.Integer({ minimum: 1, maximum: 2 })),
|
|
169
179
|
}, { additionalProperties: false }),
|
|
170
|
-
]
|
|
180
|
+
]);
|
|
181
|
+
export const IntegrationActionParameters = Type.Object({
|
|
182
|
+
id: IntegrationActionSchema.anyOf[0].properties.id,
|
|
183
|
+
generation: IntegrationActionSchema.anyOf[0].properties.generation,
|
|
184
|
+
action: Type.Union([
|
|
185
|
+
IntegrationActionSchema.anyOf[0].properties.action,
|
|
186
|
+
IntegrationActionSchema.anyOf[1].properties.action,
|
|
187
|
+
IntegrationActionSchema.anyOf[2].properties.action,
|
|
188
|
+
]),
|
|
189
|
+
expectedTip: IntegrationActionSchema.anyOf[0].properties.expectedTip,
|
|
190
|
+
expectedMain: Type.Optional(IntegrationActionSchema.anyOf[0].properties.expectedMain),
|
|
191
|
+
newMain: Type.Optional(IntegrationActionSchema.anyOf[0].properties.newMain),
|
|
192
|
+
taskId: IntegrationActionSchema.anyOf[2].properties.taskId,
|
|
193
|
+
attempt: IntegrationActionSchema.anyOf[2].properties.attempt,
|
|
194
|
+
}, { additionalProperties: false });
|
|
171
195
|
export function parseIntegrationAction(value) {
|
|
172
196
|
if (!Check(IntegrationActionSchema, value))
|
|
173
197
|
throw schemaValidationError("subagent_integrate requires an exact generation and tip", Errors(IntegrationActionSchema, value));
|
package/dist/worktree.d.ts
CHANGED
|
@@ -57,5 +57,3 @@ export declare function inspectWorktreeDirty(cwd: string, run?: GitRunner, inclu
|
|
|
57
57
|
* recorded; any uncertainty preserves work and omits unknown measurements.
|
|
58
58
|
*/
|
|
59
59
|
export declare function finalizeChildWorktree(info: WorktreeInfo, run?: GitRunner): Promise<WorktreePayload>;
|
|
60
|
-
/** Context block telling the child to work inside its isolated worktree. */
|
|
61
|
-
export declare function worktreeContextNote(info: WorktreeInfo): string;
|
package/dist/worktree.js
CHANGED
|
@@ -269,7 +269,3 @@ export async function finalizeChildWorktree(info, run = runGit) {
|
|
|
269
269
|
}
|
|
270
270
|
return { outcome: "pruned", path: info.path, branch: info.branch };
|
|
271
271
|
}
|
|
272
|
-
/** Context block telling the child to work inside its isolated worktree. */
|
|
273
|
-
export function worktreeContextNote(info) {
|
|
274
|
-
return `\n\n[WORKTREE ISOLATION] Work only in ${info.path} on ${info.branch}; do not use main checkout. Commit changes to this branch for parent review.`;
|
|
275
|
-
}
|
package/extensions/admission.ts
CHANGED
|
@@ -14,10 +14,6 @@ export function roleIsReadOnlyScout(role: Role): boolean {
|
|
|
14
14
|
return !roleCanWrite(role) && role.extensions.length === 0 && !role.mcps?.length;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export interface CheckoutAdmission {
|
|
18
|
-
register(pi: ExtensionAPI, directCanWrite: (input: unknown) => boolean): void;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
17
|
async function checkoutKey(pi: ExtensionAPI, ctx: ExtensionContext): Promise<string> {
|
|
22
18
|
const cwd = await realpath(ctx.cwd);
|
|
23
19
|
const result = await pi.exec("git", ["rev-parse", "--show-toplevel"], { cwd, timeout: 5_000 });
|
|
@@ -32,7 +28,7 @@ async function checkoutKey(pi: ExtensionAPI, ctx: ExtensionContext): Promise<str
|
|
|
32
28
|
* not an OS sandbox: external processes and trusted extension lifecycle code
|
|
33
29
|
* remain outside interception.
|
|
34
30
|
*/
|
|
35
|
-
export function
|
|
31
|
+
export function registerCheckoutAdmission(pi: ExtensionAPI, directCanWrite: (input: unknown) => boolean): void {
|
|
36
32
|
const ownerByCheckout = new Map<string, string>();
|
|
37
33
|
const checkoutByCall = new Map<string, string>();
|
|
38
34
|
const release = (toolCallId: string) => {
|
|
@@ -41,30 +37,26 @@ export function createCheckoutAdmission(): CheckoutAdmission {
|
|
|
41
37
|
checkoutByCall.delete(toolCallId);
|
|
42
38
|
if (ownerByCheckout.get(key) === toolCallId) ownerByCheckout.delete(key);
|
|
43
39
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
checkoutByCall.clear();
|
|
67
|
-
});
|
|
68
|
-
},
|
|
69
|
-
};
|
|
40
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
41
|
+
const potentiallyWriting = event.toolName === "delegate_task"
|
|
42
|
+
? directCanWrite(event.input)
|
|
43
|
+
: !READ_ONLY_TOOLS.has(event.toolName);
|
|
44
|
+
if (!potentiallyWriting) return;
|
|
45
|
+
const key = await checkoutKey(pi, ctx);
|
|
46
|
+
const owner = ownerByCheckout.get(key);
|
|
47
|
+
if (owner && owner !== event.toolCallId) {
|
|
48
|
+
return {
|
|
49
|
+
block: true,
|
|
50
|
+
reason: `Checkout ${key} already has an admitted Pi writer (${owner}); retry after it settles.`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
ownerByCheckout.set(key, event.toolCallId);
|
|
54
|
+
checkoutByCall.set(event.toolCallId, key);
|
|
55
|
+
});
|
|
56
|
+
pi.on("tool_result", (event) => release(event.toolCallId));
|
|
57
|
+
pi.on("tool_execution_end", (event) => release(event.toolCallId));
|
|
58
|
+
pi.on("session_shutdown", () => {
|
|
59
|
+
ownerByCheckout.clear();
|
|
60
|
+
checkoutByCall.clear();
|
|
61
|
+
});
|
|
70
62
|
}
|