pi-task-dispatcher 3.1.2 → 3.2.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/README.md +11 -10
- package/extensions/task-dispatcher.ts +665 -35
- package/package.json +1 -1
- package/tests/fake-rpc-check.mjs +313 -127
- package/tests/fake-rpc-worker.mjs +287 -148
- package/tests/gate-probe.mjs +185 -162
- package/tests/static-check.mjs +26 -3
package/tests/gate-probe.mjs
CHANGED
|
@@ -1,188 +1,211 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Verify supervised codemode loading and a reusable worker-side boundary after one completed tool batch.
|
|
3
|
-
*/
|
|
1
|
+
/** Exercise the release extension against a live Pi child, native codemode, and a manager-proxied tool. */
|
|
4
2
|
|
|
5
|
-
import
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
6
5
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
7
|
import { homedir, tmpdir } from "node:os";
|
|
8
8
|
import path from "node:path";
|
|
9
|
-
import {
|
|
9
|
+
import { promisify } from "node:util";
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
const executeFile = promisify(execFile);
|
|
12
|
+
const projectRoot = path.resolve(import.meta.dirname, "..");
|
|
13
|
+
const temporaryDirectory = await mkdtemp(path.join(tmpdir(), "pi-task-dispatcher-gate-probe-"));
|
|
14
|
+
const configPath = path.join(temporaryDirectory, "task-dispatcher.json");
|
|
15
|
+
const readProbePath = path.join(temporaryDirectory, "codemode-read-probe.txt");
|
|
16
|
+
const marker = "pi-task-dispatcher-manager-proxy-ready";
|
|
17
|
+
const inheritedWorkerRole = process.env.PI_TASK_DISPATCHER_WORKER;
|
|
18
|
+
let handlers = new Map();
|
|
19
|
+
|
|
20
|
+
/** Resolve the managed Pi package and CLI without embedding a machine-specific installation path. */
|
|
21
|
+
async function resolvePiRuntime() {
|
|
13
22
|
const installRoot = process.env.PI_MANAGED_INSTALL_ROOT ?? path.join(homedir(), ".pi", "agent", "install");
|
|
14
23
|
const version = (await readFile(path.join(installRoot, "current-version"), "utf8")).trim();
|
|
15
24
|
const packageRoot = path.join(installRoot, "releases", version, "node_modules", "@earendil-works", "pi-coding-agent");
|
|
16
25
|
const packageJson = JSON.parse(await readFile(path.join(packageRoot, "package.json"), "utf8"));
|
|
17
26
|
const declaredBin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.pi;
|
|
18
27
|
if (!declaredBin) throw new Error("The managed Pi package does not declare its CLI entry point.");
|
|
19
|
-
return path.resolve(packageRoot, declaredBin);
|
|
28
|
+
return { packageRoot, cliPath: path.resolve(packageRoot, declaredBin) };
|
|
20
29
|
}
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
let stdoutRecords;
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
// Abort the active worker run only after its complete tool batch has been persisted.
|
|
32
|
-
await writeFile(
|
|
33
|
-
gatePath,
|
|
34
|
-
`import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
35
|
-
export default function (pi: ExtensionAPI): void {
|
|
36
|
-
pi.on("turn_end", async (event, ctx) => {
|
|
37
|
-
if (event.toolResults.length === 0) return;
|
|
38
|
-
if (typeof process.send === "function") process.send({
|
|
39
|
-
type: "task_dispatcher_boundary",
|
|
40
|
-
turnIndex: event.turnIndex,
|
|
41
|
-
toolResults: event.toolResults.length,
|
|
42
|
-
});
|
|
43
|
-
ctx.abort();
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
`,
|
|
47
|
-
"utf8",
|
|
48
|
-
);
|
|
49
|
-
// Use Pi's public factory so only codemode is model-visible while selected direct tools remain script-callable.
|
|
50
|
-
await writeFile(
|
|
51
|
-
codemodeOnlyPath,
|
|
52
|
-
`import { createCodemodeExtension, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
53
|
-
const codemodeOnly = createCodemodeExtension({ mode: "only" });
|
|
54
|
-
export default function codemodeOnlyExtension(pi: ExtensionAPI): void {
|
|
55
|
-
codemodeOnly(pi);
|
|
31
|
+
/** Parse Pi's human-readable model sizes for the dispatcher's formula-only budget metadata. */
|
|
32
|
+
function parseSize(value) {
|
|
33
|
+
const match = /^(\d+(?:\.\d+)?)([KMG])?$/i.exec(value ?? "");
|
|
34
|
+
if (!match) return undefined;
|
|
35
|
+
const scale = { K: 1_000, M: 1_000_000, G: 1_000_000_000 }[match[2]?.toUpperCase()] ?? 1;
|
|
36
|
+
return Math.round(Number(match[1]) * scale);
|
|
56
37
|
}
|
|
57
|
-
`,
|
|
58
|
-
"utf8",
|
|
59
|
-
);
|
|
60
|
-
await writeFile(readProbePath, "native-codemode-only-read-ready\n", "utf8");
|
|
61
38
|
|
|
62
|
-
|
|
63
|
-
|
|
39
|
+
/** Select the explicit/active model or the first credential-backed model listed by Pi. */
|
|
40
|
+
async function resolveModel(cliPath) {
|
|
41
|
+
const { stdout } = await executeFile(process.execPath, [cliPath, "--list-models"], { maxBuffer: 8 * 1024 * 1024 });
|
|
42
|
+
const rows = stdout.split(/\r?\n/).slice(1).map((line) => line.trim().split(/\s+/)).filter((columns) => columns.length >= 4);
|
|
43
|
+
const requested = process.env.PI_TASK_DISPATCHER_GATE_MODEL
|
|
64
44
|
?? (process.env.PI_PROVIDER && process.env.PI_MODEL ? `${process.env.PI_PROVIDER}/${process.env.PI_MODEL}` : undefined);
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
];
|
|
79
|
-
if (activeModel) workerArgs.push("--model", activeModel);
|
|
80
|
-
if (activeThinking) workerArgs.push("--thinking", activeThinking);
|
|
81
|
-
|
|
82
|
-
child = spawn(
|
|
83
|
-
process.execPath,
|
|
84
|
-
workerArgs,
|
|
85
|
-
{
|
|
86
|
-
cwd: path.resolve(import.meta.dirname, ".."),
|
|
87
|
-
stdio: ["pipe", "pipe", "pipe", "ipc"],
|
|
88
|
-
env: { ...process.env, PI_TASK_DISPATCHER_WORKER: "probe" },
|
|
89
|
-
},
|
|
90
|
-
);
|
|
45
|
+
const selected = requested
|
|
46
|
+
? rows.find(([provider, id]) => `${provider}/${id}` === requested)
|
|
47
|
+
: rows[0];
|
|
48
|
+
if (!selected && !requested) throw new Error("Pi did not list an available model for the live gate probe.");
|
|
49
|
+
const [provider, id] = requested ? requested.split(/\/(.+)/) : selected;
|
|
50
|
+
if (!provider || !id) throw new Error(`Invalid gate model: ${requested}`);
|
|
51
|
+
return {
|
|
52
|
+
provider,
|
|
53
|
+
id,
|
|
54
|
+
contextWindow: parseSize(selected?.[2]) ?? 272_000,
|
|
55
|
+
maxTokens: parseSize(selected?.[3]) ?? 128_000,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
91
58
|
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
settledResolve = resolve;
|
|
98
|
-
});
|
|
59
|
+
try {
|
|
60
|
+
const { packageRoot: piRoot, cliPath } = await resolvePiRuntime();
|
|
61
|
+
const model = await resolveModel(cliPath);
|
|
62
|
+
await writeFile(configPath, "{}\n", "utf8");
|
|
63
|
+
await writeFile(readProbePath, `${marker}\n`, "utf8");
|
|
99
64
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
65
|
+
// Load the release source while directing only its child-process lookup and configuration to the probe.
|
|
66
|
+
delete process.env.PI_TASK_DISPATCHER_WORKER;
|
|
67
|
+
process.env.PI_TASK_DISPATCHER_TEST_MODE = "1";
|
|
68
|
+
process.env.PI_TASK_DISPATCHER_CONFIG_PATH = configPath;
|
|
69
|
+
process.env.PI_TASK_DISPATCHER_CLI_OVERRIDE = cliPath;
|
|
70
|
+
const requireFromPi = createRequire(path.join(piRoot, "package.json"));
|
|
71
|
+
const { createJiti } = requireFromPi("jiti");
|
|
72
|
+
const jiti = createJiti(import.meta.url, {
|
|
73
|
+
interopDefault: true,
|
|
74
|
+
alias: {
|
|
75
|
+
"@earendil-works/pi-coding-agent": path.join(piRoot, "dist", "index.js"),
|
|
76
|
+
"@earendil-works/pi-tui": requireFromPi.resolve("@earendil-works/pi-tui"),
|
|
77
|
+
typebox: requireFromPi.resolve("typebox"),
|
|
78
|
+
},
|
|
114
79
|
});
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
80
|
+
const loaded = await jiti.import(path.join(projectRoot, "extensions", "task-dispatcher.ts"));
|
|
81
|
+
const register = loaded.default ?? loaded;
|
|
82
|
+
const registeredTools = new Map();
|
|
83
|
+
handlers = new Map();
|
|
84
|
+
const readTool = {
|
|
85
|
+
name: "read",
|
|
86
|
+
label: "Read",
|
|
87
|
+
description: "Read one UTF-8 text file through the live gate manager.",
|
|
88
|
+
parameters: {
|
|
89
|
+
type: "object",
|
|
90
|
+
additionalProperties: false,
|
|
91
|
+
required: ["path"],
|
|
92
|
+
properties: { path: { type: "string" } },
|
|
93
|
+
},
|
|
94
|
+
executionMode: "parallel",
|
|
95
|
+
};
|
|
96
|
+
const codemodeTool = {
|
|
97
|
+
name: "codemode",
|
|
98
|
+
label: "Codemode",
|
|
99
|
+
description: "Run child-local codemode.",
|
|
100
|
+
parameters: { type: "object", properties: { code: { type: "string" } } },
|
|
101
|
+
executionMode: "sequential",
|
|
102
|
+
};
|
|
103
|
+
const managerTools = [readTool, codemodeTool];
|
|
104
|
+
const pi = {
|
|
105
|
+
registerTool(tool) { registeredTools.set(tool.name, tool); },
|
|
106
|
+
getActiveTools() { return managerTools.map((tool) => tool.name); },
|
|
107
|
+
getAllTools() {
|
|
108
|
+
return managerTools.map((tool) => ({
|
|
109
|
+
name: tool.name,
|
|
110
|
+
description: tool.description,
|
|
111
|
+
parameters: tool.parameters,
|
|
112
|
+
exposure: "direct",
|
|
113
|
+
annotations: { readOnlyHint: tool.name === "read" },
|
|
114
|
+
promptGuidelines: [],
|
|
115
|
+
sourceInfo: { path: `<gate:${tool.name}>`, source: "gate", scope: "temporary", origin: "top-level", baseDir: undefined },
|
|
116
|
+
}));
|
|
117
|
+
},
|
|
118
|
+
on(event, handler) {
|
|
119
|
+
const entries = handlers.get(event) ?? [];
|
|
120
|
+
entries.push(handler);
|
|
121
|
+
handlers.set(event, entries);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
register(pi);
|
|
128
125
|
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
126
|
+
const managerCalls = [];
|
|
127
|
+
const context = {
|
|
128
|
+
cwd: projectRoot,
|
|
129
|
+
hasUI: false,
|
|
130
|
+
model,
|
|
131
|
+
thinkingLevel: process.env.PI_TASK_DISPATCHER_GATE_THINKING ?? process.env.PI_REASONING_LEVEL ?? "medium",
|
|
132
|
+
get tools() { return managerTools.filter((tool) => tool.name !== "codemode"); },
|
|
133
|
+
ui: { theme: { fg(_color, text) { return text; } }, setWidget() {}, setStatus() {} },
|
|
134
|
+
modelRegistry: {
|
|
135
|
+
find(provider, id) { return provider === model.provider && id === model.id ? model : undefined; },
|
|
136
|
+
hasConfiguredAuth() { return true; },
|
|
137
|
+
},
|
|
138
|
+
async executeTool(name, args, options) {
|
|
139
|
+
managerCalls.push({ name, args });
|
|
140
|
+
if (name !== "read") {
|
|
141
|
+
return { toolCall: { id: "gate-denied", name, arguments: args }, isError: true, result: { content: [{ type: "text", text: `Unexpected manager tool: ${name}` }], details: undefined } };
|
|
142
|
+
}
|
|
143
|
+
options?.onUpdate?.({ content: [{ type: "text", text: "manager read in progress" }], details: undefined });
|
|
144
|
+
try {
|
|
145
|
+
const text = await readFile(args.path, "utf8");
|
|
146
|
+
return { toolCall: { id: "gate-read", name, arguments: args }, isError: false, result: { content: [{ type: "text", text }], details: undefined } };
|
|
147
|
+
} catch (error) {
|
|
148
|
+
return { toolCall: { id: "gate-read", name, arguments: args }, isError: true, result: { content: [{ type: "text", text: String(error) }], details: undefined } };
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
const start = registeredTools.get("delegate_engineering");
|
|
153
|
+
const control = registeredTools.get("delegate_control");
|
|
154
|
+
assert(start && control, "The release extension did not register its engineering/control tools.");
|
|
155
|
+
const workload = { investigationUnits: 1, changeUnits: 0, verificationUnits: 1, expectedLongRunningSeconds: 0 };
|
|
156
|
+
const startAbort = new AbortController();
|
|
157
|
+
const startTimer = setTimeout(() => startAbort.abort(), 180_000);
|
|
158
|
+
let result;
|
|
159
|
+
try {
|
|
160
|
+
result = await start.execute(
|
|
161
|
+
"gate-start",
|
|
162
|
+
{
|
|
163
|
+
task: `Call codemode exactly once. In its JavaScript source, call await tools.read({ path: ${JSON.stringify(readProbePath)} }); verify the returned text contains ${marker}, then call text(${JSON.stringify(marker)}). Do not call tools directly or make any other tool calls. After that action batch, report only the marker.`,
|
|
164
|
+
tools: ["read", "codemode"],
|
|
165
|
+
workload,
|
|
166
|
+
},
|
|
167
|
+
startAbort.signal,
|
|
168
|
+
undefined,
|
|
169
|
+
context,
|
|
170
|
+
);
|
|
171
|
+
} finally {
|
|
172
|
+
clearTimeout(startTimer);
|
|
143
173
|
}
|
|
174
|
+
assert.equal(result.isError, false, result.content?.[0]?.text);
|
|
175
|
+
assert.equal(result.details.status, "paused", result.content?.[0]?.text);
|
|
176
|
+
assert.deepEqual(managerCalls, [{ name: "read", args: { path: readProbePath } }], "The child did not use the exact manager-proxied read grant once.");
|
|
177
|
+
assert(result.details.actionLedger.some((action) => action.toolName === "codemode"), "The live child did not execute native codemode.");
|
|
178
|
+
assert(result.details.actionLedger.some((action) => action.toolName === "read" && action.parentToolCallId), "Codemode did not execute read through the child proxy.");
|
|
144
179
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
settled = new Promise((resolve) => {
|
|
148
|
-
settledResolve = resolve;
|
|
149
|
-
});
|
|
150
|
-
child.stdin.write(`${JSON.stringify({
|
|
151
|
-
id: "probe-2",
|
|
152
|
-
type: "prompt",
|
|
153
|
-
message: "Continue from the completed native codemode result. Do not call tools. Report only the exact marker produced by the script.",
|
|
154
|
-
})}\n`);
|
|
155
|
-
await Promise.race([
|
|
156
|
-
settled,
|
|
157
|
-
stdoutFramingFailure,
|
|
158
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error("Continuation run did not settle.")), 120_000)),
|
|
159
|
-
]);
|
|
160
|
-
|
|
161
|
-
const finalText = events
|
|
162
|
-
.filter((event) => event.type === "message_end" && event.message?.role === "assistant")
|
|
163
|
-
.flatMap((event) => event.message.content ?? [])
|
|
164
|
-
.filter((part) => part.type === "text")
|
|
165
|
-
.map((part) => part.text)
|
|
166
|
-
.join("\n")
|
|
167
|
-
.trim();
|
|
168
|
-
if (!finalText.includes("pi-task-dispatcher-codemode-ready")) throw new Error(`Unexpected continuation output: ${finalText}`);
|
|
169
|
-
|
|
170
|
-
console.log(JSON.stringify({ firstBoundaryCount, firstAssistantMessages: firstAssistantMessages.length, finalText, nativeCodemodeOnly: true }, null, 2));
|
|
171
|
-
if (stderr.trim()) console.error(stderr.trim());
|
|
172
|
-
} finally {
|
|
180
|
+
const continueAbort = new AbortController();
|
|
181
|
+
const continueTimer = setTimeout(() => continueAbort.abort(), 180_000);
|
|
173
182
|
try {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
|
|
182
|
-
}
|
|
183
|
-
if (stdoutRecords) await stdoutRecords;
|
|
183
|
+
result = await control.execute(
|
|
184
|
+
"gate-control",
|
|
185
|
+
{ jobId: result.details.jobId, action: "revise", instructions: `Do not call tools. Report only ${marker}.`, actionBatches: 1 },
|
|
186
|
+
continueAbort.signal,
|
|
187
|
+
undefined,
|
|
188
|
+
context,
|
|
189
|
+
);
|
|
184
190
|
} finally {
|
|
185
|
-
|
|
186
|
-
await rm(temporaryDirectory, { recursive: true, force: true });
|
|
191
|
+
clearTimeout(continueTimer);
|
|
187
192
|
}
|
|
193
|
+
assert.equal(result.isError, false, result.content?.[0]?.text);
|
|
194
|
+
assert.equal(result.details.status, "completed", result.content?.[0]?.text);
|
|
195
|
+
assert.match(result.details.workerText, new RegExp(marker));
|
|
196
|
+
console.log(JSON.stringify({
|
|
197
|
+
model: `${model.provider}/${model.id}`,
|
|
198
|
+
status: result.details.status,
|
|
199
|
+
managerProxyCalls: managerCalls.length,
|
|
200
|
+
nativeCodemode: true,
|
|
201
|
+
finalText: result.details.workerText,
|
|
202
|
+
}, null, 2));
|
|
203
|
+
} finally {
|
|
204
|
+
for (const handler of handlers.get("session_shutdown") ?? []) await handler();
|
|
205
|
+
delete process.env.PI_TASK_DISPATCHER_TEST_MODE;
|
|
206
|
+
delete process.env.PI_TASK_DISPATCHER_CONFIG_PATH;
|
|
207
|
+
delete process.env.PI_TASK_DISPATCHER_CLI_OVERRIDE;
|
|
208
|
+
if (inheritedWorkerRole === undefined) delete process.env.PI_TASK_DISPATCHER_WORKER;
|
|
209
|
+
else process.env.PI_TASK_DISPATCHER_WORKER = inheritedWorkerRole;
|
|
210
|
+
await rm(temporaryDirectory, { recursive: true, force: true });
|
|
188
211
|
}
|
package/tests/static-check.mjs
CHANGED
|
@@ -48,17 +48,40 @@ for (const required of [
|
|
|
48
48
|
"tool_execution_update",
|
|
49
49
|
"PRESENTATION_UPDATE_THROTTLE_MS",
|
|
50
50
|
"context.lastComponent",
|
|
51
|
+
"task_dispatcher_proxy_request",
|
|
52
|
+
"task_dispatcher_proxy_cancel",
|
|
53
|
+
"task_dispatcher_proxy_update",
|
|
54
|
+
"task_dispatcher_proxy_result",
|
|
55
|
+
"buildDelegatedToolManifests",
|
|
56
|
+
"ctx.executeTool",
|
|
57
|
+
"revalidateManifest",
|
|
58
|
+
"openExecutorLease",
|
|
59
|
+
"authorizeNestedToolCall",
|
|
60
|
+
"PROXY_PAYLOAD_LIMIT_BYTES",
|
|
61
|
+
"PROXY_MAX_IN_FLIGHT_REQUESTS",
|
|
62
|
+
"PROXY_MAX_REQUESTS_PER_JOB",
|
|
63
|
+
"REVOKED_EXECUTOR_ROOT_LIMIT",
|
|
64
|
+
"revokedExecutorGuardSaturated",
|
|
65
|
+
"randomBytes",
|
|
66
|
+
"Type.Unsafe",
|
|
51
67
|
]) {
|
|
52
68
|
if (!source.includes(required)) throw new Error(`Missing supervised-protocol marker: ${required}`);
|
|
53
69
|
}
|
|
54
70
|
|
|
55
|
-
if (packageJson.version !== "3.
|
|
71
|
+
if (packageJson.version !== "3.2.0") throw new Error(`Expected release version 3.2.0, found ${packageJson.version}`);
|
|
56
72
|
// Keep test-side RPC framing independent from readline's broader line-separator behavior.
|
|
57
73
|
for (const [name, reader] of [["fake RPC worker", fakeRpcWorker], ["gate probe", gateProbe]]) {
|
|
58
74
|
if (reader.includes("node:readline")) throw new Error(`${name} still imports node:readline.`);
|
|
59
75
|
}
|
|
60
76
|
if (source.includes("builtin:codemode")) throw new Error("The dispatcher must not load codemode through a built-in extension selector.");
|
|
61
|
-
|
|
62
|
-
throw new Error(
|
|
77
|
+
for (const forbidden of ["ISOLATED_WORKER_TOOLS", "MUTATING_TOOLS", "Isolated workers do not support these tools"]) {
|
|
78
|
+
if (source.includes(forbidden)) throw new Error(`Legacy tool-name classification remains in source: ${forbidden}`);
|
|
79
|
+
}
|
|
80
|
+
if (!source.includes("ctx.tools")) throw new Error("Delegated tool grants must be built from manager-callable ctx.tools.");
|
|
81
|
+
if (!fakeRpcWorker.includes("security-cross-job") || !fakeRpcWorker.includes("security-paused") || !fakeRpcWorker.includes("proxy-rich") || !fakeRpcWorker.includes("transitive")) {
|
|
82
|
+
throw new Error("The deterministic worker no longer covers proxy security and rich-result transport.");
|
|
83
|
+
}
|
|
84
|
+
for (const required of ['"task-dispatcher.ts"', "managerCalls", 'tools: ["read", "codemode"]', "parentToolCallId"]) {
|
|
85
|
+
if (!gateProbe.includes(required)) throw new Error(`The live gate probe no longer exercises the release proxy/codemode path: ${required}`);
|
|
63
86
|
}
|
|
64
87
|
console.log(`Static supervised-protocol checks passed for ${packageJson.name}@${packageJson.version}.`);
|