Files
OmniRoute/tests/unit/skills-interception-server-owned.test.ts
Bob.Hou d6f315018a fix(chat): continue after a server-owned tool on Chat Completions (#12867)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437, ESLint 0 erros nos 152 arquivos alterados, e a suíte vitest:ui completa (2149) verde.

Sobre esta PR especificamente: rodei os **23 arquivos de teste** que ela toca sobre o tip final, depois do merge da base — **392/392**. A migration `174_server_tool_executions.sql` não colide (o tip está em 173, e você já a renumerou em `c35f0fd7`).

O dono foi consultado antes do merge, porque o loop está atrás da flag `SERVER_OWNED_TOOL_LOOP_ENABLED` mas o primeiro send não-streaming mudou de dono sem flag, e a verificação manual em combo com Memory continuava desmarcada. A condição dele foi: entra se os testes focados passarem aqui. Passaram.

O lock de passthrough (`fetchCalls.length === 1`) é a parte que mais me convenceu — o double-dispatch que um `if (stream)` em volta do send existente causaria é exatamente o tipo de regressão que não aparece em teste de comportamento, só em contagem de chamada.

**Três ajustes meus na sua branch:**

1. `tests/unit/chatcore-stream-error-result.test.ts` procurava `"const legResult = await runNonStreamingProviderLeg"`, mas o seu commit final `6077b9dd` passou a reatribuir `legResult` e trocou para `let`. O guard falhava na sua própria branch (confirmei que o arquivo e o `chatCore.ts` eram byte-idênticos ao head da PR, então não era efeito da leva). Passou a aceitar `const|let` — a intenção do guard é o try/catch em volta da chamada, não a palavra-chave.

2. `tests/integration/skills-pipeline.test.ts` foi de 1156 para 1338 linhas e estourou o `testCap` de 1200. Segui o mesmo caminho que você já tinha tomado em `a1d2d20d` para os testes unitários: extraí os três casos do server-owned tool loop para `tests/integration/server-owned-tool-loop-pipeline.test.ts` (259 linhas), com instância própria do harness. O glob `tests/integration/*.test.ts` pega o arquivo novo sem registro adicional. 3/3 verdes isolados.

3. O arquivo novo herdou cinco `any` do original — que só passavam por estarem congelados no `eslint-suppressions.json` sob o nome antigo. Tipei como `Record<string, unknown>`. E `tests/unit/non-streaming-finalization.test.ts` tinha dois argumentos não usados em `trackPendingRequest`, agora prefixados com `_`.

Nada disso toca produção nem enfraquece asserção.
2026-09-07 09:15:00 -03:00

796 lines
27 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
// Local shape for formatter return values — avoids explicit any in assertions.
type FormattedResponse = {
choices?: Array<{
message: {
content: string | null;
tool_calls?: Array<{ id: string; function: { name: string; arguments: string } }>;
};
finish_reason: string;
}>;
content?: Array<{ type: string; id?: string; text?: string }>;
stop_reason?: string;
stop_sequence?: string | null;
output?: Array<{ type: string; call_id?: string; name?: string; arguments?: string }>;
response?: {
output?: Array<{ type: string; call_id?: string; name?: string; arguments?: string }>;
};
};
const TEST_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-skills-interception-owned-"));
const TEST_DATA_DIR = path.join(TEST_ROOT, "data");
const TEST_PLUGINS_DIR = path.join(TEST_ROOT, "plugins");
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
const ORIGINAL_PLUGINS_DIR = process.env.OMNIROUTE_PLUGINS_DIR;
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
fs.mkdirSync(TEST_PLUGINS_DIR, { recursive: true });
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.OMNIROUTE_PLUGINS_DIR = TEST_PLUGINS_DIR;
const coreDb = await import("../../src/lib/db/core.ts");
const { skillRegistry } = await import("../../src/lib/skills/registry.ts");
const { skillExecutor } = await import("../../src/lib/skills/executor.ts");
const {
classifyServerOwnedCalls,
formatEscapeHatchResponse,
executeServerOwned,
ServerOwnedExecutionError,
} = await import("../../src/lib/skills/interception.ts");
function resetRuntime() {
skillRegistry["registeredSkills"].clear();
skillRegistry["versionCache"].clear();
skillExecutor["handlers"].clear();
skillExecutor.setTimeout(50);
}
async function resetStorage() {
resetRuntime();
coreDb.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
async function registerRuntimeSkills() {
await skillRegistry.register({
name: "lookup",
version: "1.0.0",
description: "lookup records",
schema: { input: { id: "string" }, output: { record: "string" } },
handler: "lookup-handler",
enabled: true,
apiKeyId: "key-a",
});
await skillRegistry.register({
name: "broken",
version: "1.0.0",
description: "always fails",
schema: { input: {}, output: {} },
handler: "broken-handler",
enabled: true,
apiKeyId: "key-a",
});
skillExecutor.registerHandler("lookup-handler", async (input) => ({
record: `resolved:${input.id}`,
}));
skillExecutor.registerHandler("broken-handler", async () => {
throw new Error("skill failure");
});
}
test.beforeEach(async () => {
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.OMNIROUTE_PLUGINS_DIR = TEST_PLUGINS_DIR;
await resetStorage();
await registerRuntimeSkills();
});
test.after(() => {
resetRuntime();
coreDb.resetDbInstance();
if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR;
else process.env.DATA_DIR = ORIGINAL_DATA_DIR;
if (ORIGINAL_PLUGINS_DIR === undefined) delete process.env.OMNIROUTE_PLUGINS_DIR;
else process.env.OMNIROUTE_PLUGINS_DIR = ORIGINAL_PLUGINS_DIR;
fs.rmSync(TEST_ROOT, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
});
// ─── Task 3: classifyServerOwnedCalls + formatEscapeHatchResponse RED tests ──
test("classifyServerOwnedCalls: owner set builtin/custom → serverOwned; registry-registered but not in owner set → clientNative", async () => {
const calls = [
{ id: "c1", name: "http_request", arguments: {} },
{ id: "c2", name: "lookup@1.0.0", arguments: {} },
{ id: "c3", name: "Bash", arguments: {} },
];
const result = await classifyServerOwnedCalls(calls, {
apiKeyId: "key-a",
sessionId: "s1",
requestId: "r1",
builtinToolNames: ["http_request"],
injectedCustomSkillNames: ["lookup@1.0.0"],
customSkillExecutionEnabled: true,
});
assert.equal(result.serverOwned.length, 2);
assert.equal(result.serverOwned[0].id, "c1");
assert.equal(result.serverOwned[1].id, "c2");
assert.equal(result.clientNative.length, 1);
assert.equal(result.clientNative[0].id, "c3");
});
test("classifyServerOwnedCalls: client same-name memory_search → not server-owned", async () => {
const calls = [
{ id: "c1", name: "memory_search", arguments: {} },
{ id: "c2", name: "http_request", arguments: {} },
];
const result = await classifyServerOwnedCalls(calls, {
apiKeyId: "key-a",
sessionId: "s1",
requestId: "r1",
builtinToolNames: ["http_request"],
// memory_search NOT in builtinToolNames (client owns it)
injectedCustomSkillNames: [],
customSkillExecutionEnabled: true,
});
// memory_search is client-native because it's not in any owner set.
assert.equal(result.clientNative.length, 1);
assert.equal(result.clientNative[0].id, "c1");
assert.equal(result.serverOwned.length, 1);
assert.equal(result.serverOwned[0].id, "c2");
});
test("classifyServerOwnedCalls: registered skill with client same-name → client-native (not server-owned)", async () => {
// Register a skill that the client also declares with the same encoded name.
await skillRegistry.register({
name: "collision-check",
version: "1.0.0",
description: "collision test",
schema: { input: {}, output: {} },
handler: "collision-handler",
enabled: true,
apiKeyId: "key-a",
mode: "on",
});
const encodedName = (await import("../../src/lib/skills/injection.ts")).encodeSkillToolName(
"collision-check",
"1.0.0"
);
// Client declares a tool with the same encoded name.
const calls = [
{ id: "c1", name: encodedName, arguments: {} },
{ id: "c2", name: "http_request", arguments: {} },
];
const result = await classifyServerOwnedCalls(calls, {
apiKeyId: "key-a",
sessionId: "s1",
requestId: "r1",
builtinToolNames: ["http_request"],
injectedCustomSkillNames: [], // empty: client collision prevented injection
customSkillExecutionEnabled: true,
});
// The registered skill with client same-name must be client-native.
assert.equal(result.clientNative.length, 1);
assert.equal(result.clientNative[0].id, "c1");
assert.equal(result.serverOwned.length, 1);
assert.equal(result.serverOwned[0].id, "c2");
skillRegistry["registeredSkills"].clear();
skillRegistry["versionCache"].clear();
});
test("formatEscapeHatchResponse: mixed OpenAI — strip server calls, append results to content, keep client calls, finish_reason:tool_calls", async () => {
const response = {
choices: [
{
message: {
content: null,
tool_calls: [
{ id: "srv1", function: { name: "http_request", arguments: "{}" } },
{ id: "cli1", function: { name: "Bash", arguments: '{"cmd":"ls"}' } },
],
},
finish_reason: "tool_calls",
},
],
};
const serverCalls = [{ id: "srv1", name: "http_request", arguments: {} }];
const clientCalls = [{ id: "cli1", name: "Bash", arguments: { cmd: "ls" } }];
const results = [{ id: "srv1", name: "http_request", result: { status: 200 }, replayed: false }];
const formatted = formatEscapeHatchResponse(
response,
serverCalls,
results,
clientCalls,
"openai"
);
const choice = (formatted as FormattedResponse).choices[0];
// Server call stripped from tool_calls, client call kept.
assert.equal(choice.message.tool_calls.length, 1);
assert.equal(choice.message.tool_calls[0].id, "cli1");
// Server result appended to content.
assert.ok(typeof choice.message.content === "string");
assert.ok(choice.message.content.includes("200"));
// finish_reason stays tool_calls (mixed).
assert.equal(choice.finish_reason, "tool_calls");
});
test("formatEscapeHatchResponse: all-server OpenAI — strip tool calls, append results, finish_reason:stop", async () => {
const response = {
choices: [
{
message: {
content: null,
tool_calls: [{ id: "srv1", function: { name: "http_request", arguments: "{}" } }],
},
finish_reason: "tool_calls",
},
],
};
const serverCalls = [{ id: "srv1", name: "http_request", arguments: {} }];
const results = [{ id: "srv1", name: "http_request", result: { ok: true }, replayed: false }];
const formatted = formatEscapeHatchResponse(response, serverCalls, results, [], "openai");
const choice = (formatted as FormattedResponse).choices[0];
assert.ok(
!choice.message.tool_calls || choice.message.tool_calls.length === 0,
"all-server must have no remaining tool_calls"
);
assert.ok(typeof choice.message.content === "string");
assert.ok(choice.message.content.includes("ok"));
assert.equal(choice.finish_reason, "stop");
});
test("formatEscapeHatchResponse: Claude mixed — strip server tool_use, keep client tool_use, end_turn stays", async () => {
const response = {
content: [
{ type: "tool_use", id: "srv1", name: "http_request", input: {} },
{ type: "tool_use", id: "cli1", name: "Bash", input: { cmd: "ls" } },
],
stop_reason: "tool_use",
};
const serverCalls = [{ id: "srv1", name: "http_request", arguments: {} }];
const clientCalls = [{ id: "cli1", name: "Bash", arguments: { cmd: "ls" } }];
const results = [{ id: "srv1", name: "http_request", result: { ok: true }, replayed: false }];
const formatted = formatEscapeHatchResponse(
response,
serverCalls,
results,
clientCalls,
"claude"
);
const content = (formatted as FormattedResponse).content as Array<{ type: string; id?: string }>;
// Server tool_use removed, client tool_use kept.
const toolUses = content.filter((b) => b.type === "tool_use");
assert.equal(toolUses.length, 1);
assert.equal(toolUses[0].id, "cli1");
// stop_reason stays tool_use (mixed).
assert.equal((formatted as FormattedResponse).stop_reason, "tool_use");
});
test("formatEscapeHatchResponse: Claude all-server — strip tool_use, append text, end_turn", async () => {
const response = {
content: [{ type: "tool_use", id: "srv1", name: "http_request", input: {} }],
stop_reason: "tool_use",
};
const serverCalls = [{ id: "srv1", name: "http_request", arguments: {} }];
const results = [{ id: "srv1", name: "http_request", result: { ok: true }, replayed: false }];
const formatted = formatEscapeHatchResponse(response, serverCalls, results, [], "claude");
const content = (formatted as FormattedResponse).content as Array<{ type: string }>;
const toolUses = content.filter((b) => b.type === "tool_use");
assert.equal(toolUses.length, 0);
const textBlocks = content.filter((b) => b.type === "text");
assert.ok(textBlocks.length > 0);
assert.equal((formatted as FormattedResponse).stop_reason, "end_turn");
});
test("formatEscapeHatchResponse: formatter does not call interceptToolCalls or any handler (purity)", async () => {
// Purity proof: the formatter is synchronous and its source must not contain
// calls to interceptToolCalls, skillExecutor, or handler invocations.
const fnSource = formatEscapeHatchResponse.toString();
assert.ok(
!fnSource.includes("interceptToolCalls"),
"formatter source must not reference interceptToolCalls"
);
assert.ok(
!fnSource.includes("skillExecutor"),
"formatter source must not reference skillExecutor"
);
assert.ok(!fnSource.includes("await"), "formatter must be synchronous (no await)");
// Also verify it returns immediately without side effects.
const response = {
choices: [
{
message: {
content: null,
tool_calls: [{ id: "srv1", function: { name: "http_request", arguments: "{}" } }],
},
finish_reason: "tool_calls",
},
],
};
const result = formatEscapeHatchResponse(
response,
[{ id: "srv1", name: "http_request", arguments: {} }],
[{ id: "srv1", name: "http_request", result: { ok: true }, replayed: false }],
[],
"openai"
);
assert.ok(result, "formatter returns a result");
assert.ok(result.choices[0].message.content, "formatter populates content");
});
test("formatEscapeHatchResponse: Responses wrapper is byte-identical for function_call_output", async () => {
const response = {
object: "response",
output: [{ type: "function_call", call_id: "call1", name: "lookup@1.0.0", arguments: "{}" }],
};
const serverCalls = [{ id: "call1", name: "lookup@1.0.0", arguments: {} }];
const results = [
{ id: "call1", name: "lookup@1.0.0", result: { record: "42" }, replayed: false },
];
const formatted = formatEscapeHatchResponse(response, serverCalls, results, [], "openai");
// Responses format: original output + function_call_output appended.
const output = (formatted as FormattedResponse).output;
assert.equal(output.length, 2);
assert.equal(output[0].type, "function_call");
assert.equal(output[1].type, "function_call_output");
assert.equal(output[1].call_id, "call1");
});
// ─── F3: nested Responses output formatter ──────────────────────────────────
test("formatEscapeHatchResponse: nested {response:{output}} appends function_call_output to nested output, not top-level", async () => {
const nestedResponse = {
object: "response",
response: {
output: [{ type: "function_call", call_id: "nc1", name: "lookup@1.0.0", arguments: "{}" }],
},
};
const serverCalls = [{ id: "nc1", name: "lookup@1.0.0", arguments: {} }];
const results = [
{ id: "nc1", name: "lookup@1.0.0", result: { record: "nested-42" }, replayed: false },
];
const formatted = formatEscapeHatchResponse(nestedResponse, serverCalls, results, [], "openai");
// Must append to nested response.output, not top-level output.
const nestedOutput = (formatted as { response?: { output?: unknown[] } }).response?.output;
assert.ok(Array.isArray(nestedOutput), "nested response.output must be an array");
assert.equal(nestedOutput.length, 2, "nested output must have original + appended");
assert.equal(nestedOutput[0].type, "function_call");
assert.equal(nestedOutput[1].type, "function_call_output");
assert.equal((nestedOutput[1] as { call_id: string }).call_id, "nc1");
// Top-level must NOT have an output array.
assert.equal(
Array.isArray((formatted as { output?: unknown[] }).output),
false,
"top-level output must not exist"
);
});
// ─── F1: executeServerOwned RED tests ───────────────────────────────────────
test("executeServerOwned: requires requestIdentity when executionFenceEnabled", async () => {
const calls = [{ id: "c1", name: "http_request", arguments: { url: "https://example.com" } }];
const context = {
apiKeyId: "key-fence",
sessionId: "s1",
requestId: "r1",
builtinToolNames: ["http_request"],
executionFenceEnabled: true,
// requestIdentity is deliberately missing
};
await assert.rejects(
() => executeServerOwned(calls, context),
/requestIdentity/,
"must require requestIdentity when executionFenceEnabled"
);
});
test("executeServerOwned: dispatches memory builtin and returns ExecutedToolResult", async () => {
const calls = [{ id: "c1", name: "memory_search", arguments: { query: "test" } }];
const context = {
apiKeyId: "key-mem",
sessionId: "s1",
requestId: "r1",
builtinToolNames: ["memory_search"],
executionFenceEnabled: false,
};
const results = await executeServerOwned(calls, context);
assert.equal(results.length, 1);
assert.equal(results[0].id, "c1");
assert.equal(results[0].name, "memory_search");
assert.equal(results[0].replayed, false);
assert.ok(results[0].result !== undefined, "result must be present");
});
test("executeServerOwned: dispatches ordinary builtin (http_request)", async () => {
const calls = [{ id: "c1", name: "http_request", arguments: { url: "https://example.com" } }];
const context = {
apiKeyId: "key-builtin",
sessionId: "s1",
requestId: "r1",
builtinToolNames: ["http_request"],
executionFenceEnabled: false,
};
const results = await executeServerOwned(calls, context);
assert.equal(results.length, 1);
assert.equal(results[0].id, "c1");
assert.equal(results[0].name, "http_request");
assert.equal(results[0].replayed, false);
});
test("executeServerOwned: surfaces identity_conflict as typed error, never feeds to model", async () => {
// Custom skill call with no matching handler — should surface as error
const calls = [{ id: "c1", name: "missing-skill@1.0.0", arguments: {} }];
const context = {
apiKeyId: "key-err",
sessionId: "s1",
requestId: "r1",
injectedCustomSkillNames: ["missing-skill@1.0.0"],
customSkillExecutionEnabled: true,
executionFenceEnabled: false,
};
const results = await executeServerOwned(calls, context);
assert.equal(results.length, 1);
assert.equal(results[0].id, "c1");
assert.equal(results[0].replayed, false);
// Result must contain an error indicator
const resultRecord = results[0].result as Record<string, unknown>;
assert.ok(
resultRecord && (resultRecord.error || resultRecord.status),
"result must contain error indicator"
);
});
// ─── Fix Round 2: Defect 1 — typed errors for fence control-flow states ────
test("ServerOwnedExecutionError is an exported class with code and httpStatus", () => {
const err = new ServerOwnedExecutionError("test", "TEST_CODE", 409);
assert.ok(err instanceof Error);
assert.equal(err.code, "TEST_CODE");
assert.equal(err.httpStatus, 409);
assert.equal(err.message, "test");
});
test("executeServerOwned: in_progress fence state → throws ServerOwnedExecutionError with TOOL_IN_PROGRESS (409)", async () => {
const { setFenceFnForTesting } = await import("../../src/lib/skills/interception.ts");
const mockFence = async () => ({ kind: "in_progress" as const });
setFenceFnForTesting(mockFence);
try {
const calls = [{ id: "c1", name: "http_request", arguments: {} }];
const context = {
apiKeyId: "key-fence",
sessionId: "s1",
requestId: "r1",
builtinToolNames: ["http_request"],
executionFenceEnabled: true,
requestIdentity: "identity-1",
};
try {
await executeServerOwned(calls, context);
assert.fail("must throw");
} catch (e: unknown) {
assert.ok(e instanceof ServerOwnedExecutionError);
assert.equal(e.code, "TOOL_IN_PROGRESS");
assert.equal(e.httpStatus, 409);
}
} finally {
setFenceFnForTesting(null);
}
});
test("executeServerOwned: unknown fence state → throws ServerOwnedExecutionError with TOOL_STATE_UNKNOWN (500)", async () => {
const { setFenceFnForTesting } = await import("../../src/lib/skills/interception.ts");
const mockFence = async () => ({ kind: "unknown" as const });
setFenceFnForTesting(mockFence);
try {
const calls = [{ id: "c1", name: "http_request", arguments: {} }];
const context = {
apiKeyId: "key-fence",
sessionId: "s1",
requestId: "r1",
builtinToolNames: ["http_request"],
executionFenceEnabled: true,
requestIdentity: "identity-1",
};
try {
await executeServerOwned(calls, context);
assert.fail("must throw");
} catch (e: unknown) {
assert.ok(e instanceof ServerOwnedExecutionError);
assert.equal(e.code, "TOOL_STATE_UNKNOWN");
assert.equal(e.httpStatus, 500);
}
} finally {
setFenceFnForTesting(null);
}
});
test("executeServerOwned: identity_conflict fence state → throws ServerOwnedExecutionError with IDENTITY_CONFLICT (409)", async () => {
const { setFenceFnForTesting } = await import("../../src/lib/skills/interception.ts");
const mockFence = async () => ({ kind: "identity_conflict" as const });
setFenceFnForTesting(mockFence);
try {
const calls = [{ id: "c1", name: "http_request", arguments: {} }];
const context = {
apiKeyId: "key-fence",
sessionId: "s1",
requestId: "r1",
builtinToolNames: ["http_request"],
executionFenceEnabled: true,
requestIdentity: "identity-1",
};
try {
await executeServerOwned(calls, context);
assert.fail("must throw");
} catch (e: unknown) {
assert.ok(e instanceof ServerOwnedExecutionError);
assert.equal(e.code, "IDENTITY_CONFLICT");
assert.equal(e.httpStatus, 409);
}
} finally {
setFenceFnForTesting(null);
}
});
// ─── Fix Round 3: Defect 1 — replay error/timeout detection ───────────────
test("executeServerOwned: error replay → throws ServerOwnedExecutionError with TOOL_EXECUTION_ERROR (500)", async () => {
const { setFenceFnForTesting } = await import("../../src/lib/skills/interception.ts");
const mockFence = async () => ({
kind: "replayed" as const,
value: null,
status: "error" as const,
errorMessage: "handler crashed",
});
setFenceFnForTesting(mockFence);
try {
const calls = [{ id: "c1", name: "http_request", arguments: {} }];
const context = {
apiKeyId: "key-fence",
sessionId: "s1",
requestId: "r1",
builtinToolNames: ["http_request"],
executionFenceEnabled: true,
requestIdentity: "identity-1",
};
try {
await executeServerOwned(calls, context);
assert.fail("must throw");
} catch (e: unknown) {
assert.ok(e instanceof ServerOwnedExecutionError);
assert.equal(e.code, "TOOL_EXECUTION_ERROR");
assert.equal(e.httpStatus, 500);
assert.equal(e.message, "handler crashed");
}
} finally {
setFenceFnForTesting(null);
}
});
test("executeServerOwned: timeout replay → throws ServerOwnedExecutionError with TOOL_EXECUTION_TIMEOUT (504)", async () => {
const { setFenceFnForTesting } = await import("../../src/lib/skills/interception.ts");
const mockFence = async () => ({
kind: "replayed" as const,
value: null,
status: "timeout" as const,
errorMessage: "execution exceeded deadline",
});
setFenceFnForTesting(mockFence);
try {
const calls = [{ id: "c1", name: "http_request", arguments: {} }];
const context = {
apiKeyId: "key-fence",
sessionId: "s1",
requestId: "r1",
builtinToolNames: ["http_request"],
executionFenceEnabled: true,
requestIdentity: "identity-1",
};
try {
await executeServerOwned(calls, context);
assert.fail("must throw");
} catch (e: unknown) {
assert.ok(e instanceof ServerOwnedExecutionError);
assert.equal(e.code, "TOOL_EXECUTION_TIMEOUT");
assert.equal(e.httpStatus, 504);
assert.equal(e.message, "execution exceeded deadline");
}
} finally {
setFenceFnForTesting(null);
}
});
test("executeServerOwned: success replay → returns ExecutedToolResult with replayed:true", async () => {
const { setFenceFnForTesting } = await import("../../src/lib/skills/interception.ts");
const mockFence = async () => ({
kind: "replayed" as const,
value: { cached: true },
status: "success" as const,
errorMessage: null,
});
setFenceFnForTesting(mockFence);
try {
const calls = [{ id: "c1", name: "http_request", arguments: {} }];
const context = {
apiKeyId: "key-fence",
sessionId: "s1",
requestId: "r1",
builtinToolNames: ["http_request"],
executionFenceEnabled: true,
requestIdentity: "identity-1",
};
const results = await executeServerOwned(calls, context);
assert.equal(results.length, 1);
assert.equal(results[0].id, "c1");
assert.equal(results[0].replayed, true);
assert.deepEqual(results[0].result, { cached: true });
} finally {
setFenceFnForTesting(null);
}
});
test("executeServerOwned: error replay with null errorMessage → uses default message", async () => {
const { setFenceFnForTesting } = await import("../../src/lib/skills/interception.ts");
const mockFence = async () => ({
kind: "replayed" as const,
value: null,
status: "error" as const,
errorMessage: null,
});
setFenceFnForTesting(mockFence);
try {
const calls = [{ id: "c1", name: "http_request", arguments: {} }];
const context = {
apiKeyId: "key-fence",
sessionId: "s1",
requestId: "r1",
builtinToolNames: ["http_request"],
executionFenceEnabled: true,
requestIdentity: "identity-1",
};
try {
await executeServerOwned(calls, context);
assert.fail("must throw");
} catch (e: unknown) {
assert.ok(e instanceof ServerOwnedExecutionError);
assert.equal(e.code, "TOOL_EXECUTION_ERROR");
assert.equal(e.message, "Tool execution failed");
}
} finally {
setFenceFnForTesting(null);
}
});
// ─── Fix Round 2: Defect 2 — executeClaimed non-success throws ──────────────
test("executeClaimed: custom skill handler returns non-success status → executeClaimed throws safe error", async () => {
// Register a custom skill whose handler returns a failure output
await skillRegistry.register({
name: "fail-skill",
version: "1.0.0",
description: "always returns failure status",
schema: { input: {}, output: {} },
handler: "fail-handler",
enabled: true,
apiKeyId: "key-a",
});
skillExecutor.registerHandler("fail-handler", async () => ({
status: "failed",
message: "something went wrong",
}));
await assert.rejects(
() =>
skillExecutor.executeClaimed(
"fail-skill",
{},
{ apiKeyId: "key-a", sessionId: "s1" },
"exec-fail"
),
/Skill execution failed/,
"executeClaimed must throw when handler returns non-success status"
);
skillRegistry["registeredSkills"].clear();
skillRegistry["versionCache"].clear();
});
// ─── Fix Round 2: Defect 3 — LEASE_DURATION_MS = 120000 ─────────────────────
test("LEASE_DURATION_MS is 120000 to match loop wall-clock upper bound", async () => {
const fs = await import("node:fs");
const sourceCode = fs.readFileSync(
new URL("../../src/lib/skills/interception.ts", import.meta.url),
"utf8"
);
const match = sourceCode.match(/const\s+LEASE_DURATION_MS\s*=\s*([\d_]+)/);
assert.ok(match, "LEASE_DURATION_MS must be defined in interception.ts");
const value = Number(match[1].replace(/_/g, ""));
assert.equal(value, 120_000, "LEASE_DURATION_MS must be 120000 (not 30000)");
});
// ─── Fix Round 2: Defect 4 — classifyServerOwnedCalls no DB load ─────────────
test("classifyServerOwnedCalls does not call skillRegistry.loadFromDatabase (ownership from owner sets only)", async () => {
let loadFromDatabaseCalled = false;
const origLoad = skillRegistry.loadFromDatabase.bind(skillRegistry);
skillRegistry.loadFromDatabase = async (..._args: unknown[]) => {
loadFromDatabaseCalled = true;
return origLoad(...(_args as [string]));
};
try {
const calls = [{ id: "c1", name: "http_request", arguments: {} }];
await classifyServerOwnedCalls(calls, {
apiKeyId: "key-a",
sessionId: "s1",
requestId: "r1",
builtinToolNames: ["http_request"],
injectedCustomSkillNames: [],
customSkillExecutionEnabled: false,
});
assert.equal(
loadFromDatabaseCalled,
false,
"classifyServerOwnedCalls must NOT call loadFromDatabase — owner sets are sufficient"
);
} finally {
skillRegistry.loadFromDatabase = origLoad;
}
});