openclaw / test /agent-exec-code-mode.live.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
63e0fa3 verified
Raw
History Blame Contribute Delete
5.23 kB
import { execFile } from "node:child_process";
import { randomUUID } from "node:crypto";
import { constants } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import { promisify } from "node:util";
import { afterEach, describe, expect, it } from "vitest";
import { collectRootPackageExcludedExtensionDirs } from "../scripts/lib/bundled-plugin-build-entries.mjs";
import { isLiveTestEnabled } from "../src/agents/live-test-helpers.js";
import type { AgentExecEnvelope } from "../src/commands/agent-exec-result.js";
import { useAutoCleanupTempDirTracker } from "./helpers/temp-dir.js";
const execFileAsync = promisify(execFile);
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
const openAiApiKey = process.env.OPENAI_API_KEY?.trim() ?? "";
const describeLive = isLiveTestEnabled() && openAiApiKey.length > 0 ? describe : describe.skip;
describeLive("agent exec Code Mode with environment authentication", () => {
it("starts without configuration and completes dependent filesystem calls", async () => {
const root = tempDirs.make("openclaw-agent-exec-code-mode-live-");
const home = path.join(root, "home");
const stateDir = path.join(root, "state");
const workspace = path.join(root, "workspace");
const tmpDir = path.join(root, "tmp");
await Promise.all([home, stateDir, workspace, tmpDir].map((dir) => fs.mkdir(dir)));
const repoRoot = path.resolve(import.meta.dirname, "..");
const installedRoot = path.join(root, "node_modules", "openclaw");
const sourceDist = path.join(repoRoot, "dist");
const excludedPlugins = collectRootPackageExcludedExtensionDirs({ cwd: repoRoot });
await fs.mkdir(installedRoot, { recursive: true });
await fs.copyFile(
path.join(repoRoot, "package.json"),
path.join(installedRoot, "package.json"),
);
// A source checkout discovers external plugins beside dist. Copy the built
// distribution with its package exclusions so discovery sees a clean install.
await fs.cp(sourceDist, path.join(installedRoot, "dist"), {
recursive: true,
mode: constants.COPYFILE_FICLONE,
filter: (source) => {
const [directory, pluginId] = path.relative(sourceDist, source).split(path.sep);
return (
directory !== "extensions" || pluginId === undefined || !excludedPlugins.has(pluginId)
);
},
});
await fs.symlink(
await fs.realpath(path.join(repoRoot, "node_modules")),
path.join(installedRoot, "node_modules"),
process.platform === "win32" ? "junction" : "dir",
);
const input = `code-mode-${randomUUID()}\n`;
await fs.writeFile(path.join(workspace, "input.txt"), input);
// Exercise production discovery: no inherited test shortcuts, authored
// config, installed harness, external CLI credentials, or runtime override.
const env: NodeJS.ProcessEnv = {
PATH: process.env.PATH,
SystemRoot: process.env.SystemRoot,
HOME: home,
USERPROFILE: home,
TMPDIR: tmpDir,
TMP: tmpDir,
TEMP: tmpDir,
OPENCLAW_STATE_DIR: stateDir,
OPENAI_API_KEY: openAiApiKey,
};
let stdout: string;
let stderr: string;
try {
({ stdout, stderr } = await execFileAsync(
process.execPath,
[
path.join(installedRoot, "dist", "entry.js"),
"agent",
"exec",
"Use Code Mode and the filesystem read and write tools to read input.txt, then write " +
"output.txt containing exactly the input bytes followed by processed and a newline. " +
"Do not use a shell command. Reply with exactly DONE after the write completes.",
"--auth-env-only",
"--model",
"openai/gpt-5.6-luna",
"--code-mode",
"code",
"--local-model-lean",
"--thinking",
"low",
"--cwd",
workspace,
"--state-dir",
stateDir,
"--timeout",
"180",
"--json",
],
{
cwd: workspace,
env,
encoding: "utf8",
maxBuffer: 2 * 1024 * 1024,
timeout: 300_000,
},
));
} catch (error) {
// Child-process failures retain stdout/stderr; never attach that raw
// error as a cause because live credentials could appear in diagnostics.
const message = error instanceof Error ? error.message : String(error);
// eslint-disable-next-line preserve-caught-error -- The raw cause can expose live credentials.
throw new Error(message.replaceAll(openAiApiKey, "[REDACTED]"));
}
expect(stdout.includes(openAiApiKey) || stderr.includes(openAiApiKey)).toBe(false);
const result = JSON.parse(stdout) as AgentExecEnvelope;
expect(result).toMatchObject({
ok: true,
status: "ok",
provider: "openai",
model: "gpt-5.6-luna",
codeModeEngaged: true,
final: "DONE",
});
expect(result.toolSummary?.tools).toContain("exec");
expect(result.bridgeCalls?.call).toBeGreaterThanOrEqual(2);
await expect(fs.readFile(path.join(workspace, "output.txt"), "utf8")).resolves.toBe(
`${input}processed\n`,
);
}, 330_000);
});