Files
openclaw/src/agents/code-mode.guest.test.ts

714 lines
25 KiB
TypeScript

/** Tests Code Mode guest execution. */
import { expectDefined } from "@openclaw/normalization-core";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { applyCodeModeCatalog, createCodeModeTools } from "./code-mode.js";
import {
resetCodeModeTestState,
pluginTool,
resultDetails,
createCodeModeHarness,
runUntilCompleted,
testing,
} from "./code-mode.test-support.js";
import { createToolSearchCatalogRef } from "./tool-search.js";
describe("Code Mode guest execution", () => {
beforeEach(() => {
vi.useRealTimers();
});
afterEach(() => {
vi.useRealTimers();
resetCodeModeTestState();
});
it("accepts command as an exec-compatible code alias", async () => {
const { config, catalogRef, tools } = createCodeModeHarness();
applyCodeModeCatalog({
tools: [...tools, pluginTool("fake_noop", "Noop")],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const result = resultDetails(
await expectDefined(tools[0], "tools[0] test invariant").execute("code-call-command-alias", {
command: "return 7;",
}),
);
expect(result.status).toBe("completed");
expect(result.value).toBe(7);
});
it("rejects divergent code and command aliases", async () => {
const { config, catalogRef, tools } = createCodeModeHarness();
applyCodeModeCatalog({
tools: [...tools, pluginTool("fake_noop", "Noop")],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
await expect(
expectDefined(tools[0], "tools[0] test invariant").execute("code-call-divergent-alias", {
code: "return 1;",
command: "return 2;",
}),
).rejects.toThrow("code and command must match when both are provided");
});
it.each([
{ code: "ls -la /workspace/" },
{ code: "ls -1" },
{ command: "ls -la /workspace/" },
{ code: "pwd", command: "pwd" },
{ command: "pwd;" },
{ command: "pwd; // inspect the workspace" },
{ code: "# inspect the workspace\npwd" },
{ code: "#!/bin/sh\npwd" },
{ code: "pwd\nls -la /workspace" },
{ command: "pwd;ls -la /workspace" },
{ command: "/bin/ls /workspace/" },
{ command: "./gradlew test" },
{ code: ".\\gradlew.bat test" },
{ command: ".\\script.ps1" },
{ code: "C:\\workspace\\run.cmd /q" },
{ code: "/workspace/run.sh --verbose" },
{ command: "sh -c 'ls /workspace/'" },
{ command: "git status" },
{ command: 'git status; const note = "git";' },
{ command: "ls -1; const metadata = { ls: true };" },
{ code: "ls -1; const note = 'function ls';" },
{ command: "ls -1; let ls = 7;" },
{ command: "npm test" },
{ command: "NODE_ENV=test npm test" },
{ code: "NODE_ENV=test\nnpm test" },
{ code: "FOO=bar ./gradlew test" },
{ command: 'GREETING="hello world" npm test' },
{ command: "whoami" },
{ code: "set -euo pipefail" },
{ command: "exit" },
{ command: "if [ -d /workspace ]; then pwd; fi" },
{ code: "while test -d /workspace; do pwd; done" },
{ command: 'for ((i=0; i<3; i++)); do echo "$i"; done' },
{ code: "function task { pwd; }" },
{ command: "source ./env" },
{ code: "command ls" },
{ command: "go test ./..." },
{ code: "cargo test" },
{ command: "sort /workspace/file" },
{ code: "wc -l file" },
{ command: "jq . file.json" },
{ code: "exec ls" },
{ command: "custom-tool --format=json" },
{ command: "ls > output" },
{ code: "ls>output" },
{ command: "ls >output" },
{ code: "ls >> output" },
{ command: "cat<input" },
{ code: "// inspect the workspace\npwd" },
{ command: "/* inspect the workspace */ pwd;" },
{ code: "rg code-mode src/agents" },
])("rejects shell source before starting a guest worker: %j", async (args) => {
const { config, catalogRef, tools } = createCodeModeHarness();
applyCodeModeCatalog({
tools: [...tools, pluginTool("fake_noop", "Noop")],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const details = resultDetails(
await expectDefined(tools[0], "tools[0] test invariant").execute(
"code-call-shell-source",
args,
),
);
expect(details.status).toBe("failed");
expect(details.code).toBe("invalid_input");
expect(details.error).toMatch(/JavaScript or TypeScript, not shell commands/);
expect(testing.activeRuns.size).toBe(0);
});
it.each([
{ code: "true;", value: null },
{ code: "false;", value: null },
{ code: "return true || false;", value: true },
{ code: "return -1;", value: -1 },
{ code: "return /foo/.test('foo');", value: true },
{ code: "Infinity -1; return 42;", value: 42 },
{ code: "eval; return typeof eval;", value: "function" },
{ code: "if (true) { return -1; }", value: -1 },
{ code: "for (let i = 0; i < 3; i++) { if (i === 2) { return i; } }", value: 2 },
{ code: "function task() { return 7; } return task();", value: 7 },
{ code: "// explain the guest program\nreturn 7;", value: 7 },
{ code: "const ls = 7; return ls;", value: 7 },
{ code: "const echo = (value) => value; return echo('hello');", value: "hello" },
{ code: "test instanceof Function; function test() {}", value: null },
{ code: "ls -1; function ls() {}", value: null },
{ code: "ls -1; function/**/ls() {}", value: null },
{ code: "ls > limit; function ls() {} var limit = 1;", value: null },
{ code: "echo `hello`; function echo(parts) { return parts[0]; }", value: null },
{ code: "pwd; var { pwd } = { pwd: 7 }; return pwd;", value: 7 },
{ code: "pwd; var [pwd] = [7]; return pwd;", value: 7 },
{ code: "pwd; for (var pwd of [7]) {} return pwd;", value: 7 },
{ code: "pwd; var other = 1, pwd = 7; return pwd;", value: 7 },
{ code: "pwd; function* pwd() { yield 7; } return pwd().next().value;", value: 7 },
{ code: "pwd; function/**/pwd() { return 7; } return pwd();", value: 7 },
{ code: "pwd; var/**/{ pwd } = { pwd: 7 }; return pwd;", value: 7 },
{ code: "node -version; function/**/node() {}; var version = 1;", value: null },
])(
"executes valid shell-like JavaScript without false rejection: %j",
async ({ code, value }) => {
const { config, catalogRef, tools } = createCodeModeHarness();
applyCodeModeCatalog({
tools: [...tools, pluginTool("fake_noop", "Noop")],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const details = resultDetails(
await expectDefined(tools[0], "tools[0] test invariant").execute(
"code-call-valid-shell-like-source",
{ code },
),
);
expect(details.status).toBe("completed");
expect(details.value).toBe(value);
},
);
it("runs JavaScript through QuickJS-WASI and resumes nested tool calls with wait", async () => {
const { config, catalogRef, tools: codeModeTools } = createCodeModeHarness();
const ticket = pluginTool("fake_create_ticket", "Create a fake ticket");
applyCodeModeCatalog({
tools: [...codeModeTools, ticket],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const details = await runUntilCompleted({
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
code: `
const hits = await tools.search("ticket", { limit: 1 });
const called = await tools.callValue(hits[0].id, { value: "ship" });
text("created");
return called;
`,
});
expect(details.status).toBe("completed");
expect(details.value).toEqual({
name: "fake_create_ticket",
input: { value: "ship" },
});
expect(details.output).toEqual([{ type: "text", text: "created" }]);
expect(details.telemetry).toMatchObject({ searchCount: 1, describeCount: 0, callCount: 1 });
expect(ticket.execute).toHaveBeenCalledTimes(1);
});
it("returns structured values from named tools while preserving the raw call envelope", async () => {
const { config, catalogRef, tools: codeModeTools } = createCodeModeHarness();
const ticket = pluginTool("fake_create_ticket", "Create a fake ticket");
applyCodeModeCatalog({
tools: [...codeModeTools, ticket],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const details = await runUntilCompleted({
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
code: `
const id = "openclaw:fake-code-mode:fake_create_ticket";
const input = { value: "ship" };
return {
named: await tools.fake_create_ticket(input),
value: await tools.callValue(id, input),
envelope: await tools.call(id, input),
};
`,
});
const expectedValue = {
name: "fake_create_ticket",
input: { value: "ship" },
};
expect(details.status).toBe("completed");
expect(details.value).toEqual({
named: expectedValue,
value: expectedValue,
envelope: {
tool: expect.objectContaining({
id: "openclaw:fake-code-mode:fake_create_ticket",
name: "fake_create_ticket",
}),
result: expect.objectContaining({ details: expectedValue }),
},
});
expect(details.telemetry).toMatchObject({ callCount: 3 });
expect(ticket.execute).toHaveBeenCalledTimes(3);
});
it("uses tools recovery guidance for guessed tool ids", async () => {
const { config, catalogRef, tools: codeModeTools } = createCodeModeHarness();
const writeTool = pluginTool("write", "Write a file to the workspace");
applyCodeModeCatalog({
tools: [...codeModeTools, writeTool],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const details = await runUntilCompleted({
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
code: `
try {
await tools.call("file_write", {
path: "memory/2026-05-22.md",
content: "remember this",
});
return "unexpected success";
} catch (error) {
return error.message;
}
`,
});
expect(details.status).toBe("completed");
expect(details.value).toBe(
"Unknown tool id: file_write. Did you mean: write? Use tools.search to find a tool, tools.describe to inspect it, then tools.call with the exact id or name.",
);
expect(writeTool.execute).not.toHaveBeenCalled();
});
it("uses tools recovery guidance when no generic Code Mode suggestion matches", async () => {
const { config, catalogRef, tools: codeModeTools } = createCodeModeHarness();
applyCodeModeCatalog({
tools: codeModeTools,
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const details = await runUntilCompleted({
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
code: `
try {
await tools.call("missing_tool", {});
return "unexpected success";
} catch (error) {
return error.message;
}
`,
});
expect(details.status).toBe("completed");
expect(details.value).toBe(
"Unknown tool id: missing_tool. Use tools.search to find a tool, tools.describe to inspect it, then tools.call with the exact id or name.",
);
});
it("does not load TypeScript for plain JavaScript code mode runs", async () => {
const { config, catalogRef, tools: codeModeTools } = createCodeModeHarness();
applyCodeModeCatalog({
tools: [...codeModeTools, pluginTool("fake_noop", "Noop")],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const details = await runUntilCompleted({
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
code: "return 42;",
});
expect(details.status).toBe("completed");
expect(details.value).toBe(42);
expect(testing.getTypescriptRuntimePromise()).toBeNull();
});
it("allows identifiers and strings that contain import without module access", async () => {
const { config, catalogRef, tools: codeModeTools } = createCodeModeHarness();
applyCodeModeCatalog({
tools: [...codeModeTools, pluginTool("fake_noop", "Noop")],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const details = await runUntilCompleted({
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
code: `
const important = 41;
const message = "import docs later";
return important + (message.includes("import") ? 1 : 0);
`,
});
expect(details.status).toBe("completed");
expect(details.value).toBe(42);
});
it.each([
{
name: "template-literal import text",
code: "return `import('node:fs')`;",
value: "import('node:fs')",
},
{
name: "template-literal require text",
code: "return `require('node:fs')`;",
value: "require('node:fs')",
},
{
name: "nested template-literal module text",
code: "return `outer ${`require('node:fs')`}`;",
value: "outer require('node:fs')",
},
{
name: "regular-expression module text",
code: 'return /import.meta/.test("import.meta");',
value: true,
},
{
name: "regular-expression module text inside interpolation",
code: 'return `${/import.meta/.test("import.meta")}`;',
value: "true",
},
{
name: "ordinary import method",
code: "const api = { import(value) { return value; } }; return api.import(42);",
value: 42,
},
{
name: "ordinary require method",
code: "const api = { require(value) { return value; } }; return api.require(42);",
value: 42,
},
{
name: "optional ordinary import method",
code: "const api = { import(value) { return value; } }; return api?.import?.(42);",
value: 42,
},
{
name: "computed ordinary require method",
code: 'const api = { require(value) { return value; } }; return api["require"](42);',
value: 42,
},
{
name: "ordinary import metadata property",
code: "const api = { import: { meta: 42 } }; return api.import.meta;",
value: 42,
},
])("executes harmless $name in the real guest worker", async ({ code, value }) => {
const { config, catalogRef, tools: codeModeTools } = createCodeModeHarness();
applyCodeModeCatalog({
tools: [...codeModeTools, pluginTool("fake_noop", "Noop")],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const details = await runUntilCompleted({
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
code,
});
expect(details).toMatchObject({ status: "completed", value });
expect(testing.activeRuns.size).toBe(0);
});
it("never exposes Node module-loader globals to the real guest worker", async () => {
const { config, catalogRef, tools: codeModeTools } = createCodeModeHarness();
applyCodeModeCatalog({
tools: [...codeModeTools, pluginTool("fake_noop", "Noop")],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const details = await runUntilCompleted({
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
code: "return [typeof process, typeof module, typeof require];",
});
expect(details).toMatchObject({
status: "completed",
value: ["undefined", "undefined", "undefined"],
});
expect(testing.activeRuns.size).toBe(0);
});
it("isolates and cleans up 12 concurrent real guest workers", async () => {
const { config, catalogRef, tools: codeModeTools } = createCodeModeHarness();
applyCodeModeCatalog({
tools: [...codeModeTools, pluginTool("fake_noop", "Noop")],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const execTool = expectDefined(codeModeTools[0], "codeModeTools[0] test invariant");
const results = await Promise.all(
Array.from({ length: 12 }, async (_, index) =>
resultDetails(
await execTool.execute(`code-call-concurrent-worker-${index}`, {
code: `return { index: ${index}, message: \`require('node:fs')\` };`,
}),
),
),
);
expect(results).toEqual(
Array.from({ length: 12 }, (_, index) =>
expect.objectContaining({
status: "completed",
value: { index, message: "require('node:fs')" },
}),
),
);
expect(testing.activeRuns.size).toBe(0);
expect(testing.resumingRunIds.size).toBe(0);
});
it("fails pending promises that have no host bridge work", async () => {
const { config, catalogRef, tools: codeModeTools } = createCodeModeHarness();
applyCodeModeCatalog({
tools: [...codeModeTools, pluginTool("fake_noop", "Noop")],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const beforeRunCount = testing.activeRuns.size;
const details = resultDetails(
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
"code-call-empty-wait",
{
code: "await new Promise(() => undefined); return 'never';",
},
),
);
expect(details.status).toBe("failed");
expect(String(details.error)).toContain("pending without host work");
expect(testing.activeRuns.size).toBe(beforeRunCount);
});
it("surfaces the QuickJS error name and message for guest syntax errors", async () => {
const { config, catalogRef, tools: codeModeTools } = createCodeModeHarness();
applyCodeModeCatalog({
tools: [...codeModeTools, pluginTool("fake_noop", "Noop")],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const details = resultDetails(
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
"code-call-syntax",
{ code: "const x = ;" },
),
);
expect(details.status).toBe("failed");
const error = String(details.error);
// Regression guard: QuickJS stacks are frames only, so the error used to
// collapse to a bare "at openclaw-code-mode:user.js:..." location with the
// actual cause dropped. The model now sees the name and message.
expect(error).toContain("SyntaxError");
expect(error).toContain("unexpected token");
expect(error.startsWith("at ")).toBe(false);
});
it("surfaces the QuickJS error name and message for guest runtime errors", async () => {
const { config, catalogRef, tools: codeModeTools } = createCodeModeHarness();
applyCodeModeCatalog({
tools: [...codeModeTools, pluginTool("fake_noop", "Noop")],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const details = resultDetails(
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
"code-call-runtime",
{ code: "return missingFn();" },
),
);
expect(details.status).toBe("failed");
const error = String(details.error);
expect(error).toContain("ReferenceError");
expect(error).toContain("missingFn is not defined");
expect(error.startsWith("at ")).toBe(false);
});
it("does not expose the raw host request callback", async () => {
const { config, catalogRef, tools: codeModeTools } = createCodeModeHarness();
applyCodeModeCatalog({
tools: [...codeModeTools, pluginTool("fake_noop", "Noop")],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const details = resultDetails(
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
"code-hidden-host-request",
{ code: "return typeof globalThis.__openclawHostRequest;" },
),
);
expect(details).toMatchObject({
status: "completed",
value: "undefined",
});
});
it("clamps omitted code-mode catalog search limits to maxSearchLimit", async () => {
const catalogRef = createToolSearchCatalogRef();
const config = {
tools: {
codeMode: {
enabled: true,
maxSearchLimit: 3,
},
},
} as never;
const ctx = {
config,
runtimeConfig: config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
};
const codeModeTools = createCodeModeTools(ctx);
applyCodeModeCatalog({
tools: [
...codeModeTools,
pluginTool("fake_ticket_one", "ticket helper"),
pluginTool("fake_ticket_two", "ticket helper"),
pluginTool("fake_ticket_three", "ticket helper"),
pluginTool("fake_ticket_four", "ticket helper"),
pluginTool("fake_ticket_five", "ticket helper"),
],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const details = await runUntilCompleted({
execTool: expectDefined(codeModeTools[0], "codeModeTools[0] test invariant"),
waitTool: expectDefined(codeModeTools[1], "codeModeTools[1] test invariant"),
code: 'const hits = await tools.search("ticket"); return hits.length;',
});
expect(details.status).toBe("completed");
expect(details.value).toBe(3);
});
it.each([
"const fs = require('node:fs'); return fs;",
String.raw`return r\u0065quire('node:fs');`,
"return require?.('node:fs');",
"return (require)('node:fs');",
"return (0, require)('node:fs');",
"const load = require; return load('node:fs');",
"return module.require('node:fs');",
"return process.getBuiltinModule('node:fs');",
"return import('node:fs');",
"return import.meta.url;",
"return `${import('node:fs')}`;",
"return `${require('node:fs')}`;",
"return `${`nested ${import('node:fs')}`}`;",
"return `${`nested ${require('node:fs')}`}`;",
"return `${({ value: import('node:fs') }).value}`;",
"const message = `import('node:fs')`; return require('node:fs');",
"const pattern = /import.meta/; return import('node:fs');",
"let value = 1; return value++ / import('node:fs');",
"let value = 1; return value-- / import('node:fs');",
"const value = { of: 1 }; return value.of / import('node:fs');",
"const value = { return: 1 }; return value.return / import('node:fs');",
"const value = { if() { return 1; } }; return value.if() / import('node:fs');",
"const value = { return: 1 }; return value?.return / import('node:fs') / 1;",
"const value = { return: 1 }; return value?.return / require('node:fs') / 1;",
"const value = { if() { return 1; } }; return value?.if() / import('node:fs');",
"function run() { const await = 1; return await / (globalThis.pending = import('node:fs')); } run(); return globalThis.pending;",
"class Guest { #return = 1; run() { return this.#return / (globalThis.pending = import('node:fs')); } } new Guest().run(); return globalThis.pending;",
])("rejects module access: %s", async (code) => {
const { config, catalogRef, tools: codeModeTools } = createCodeModeHarness();
applyCodeModeCatalog({
tools: [...codeModeTools, pluginTool("fake_noop", "Noop")],
config,
sessionId: "session-code-mode",
sessionKey: "agent:main:main",
runId: "run-code-mode",
catalogRef,
});
const details = resultDetails(
await expectDefined(codeModeTools[0], "codeModeTools[0] test invariant").execute(
"code-call-import",
{
code,
},
),
);
expect(details.status).toBe("failed");
expect(String(details.error)).toContain("module access is disabled");
});
});