mirror of
https://github.com/openclaw/openclaw.git
synced 2026-09-28 05:54:09 +08:00
129 lines
5.3 KiB
TypeScript
129 lines
5.3 KiB
TypeScript
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 { vitestWorkerRuntimeAssets } from "../scripts/lib/vitest-worker-declarations.mts";
|
|
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 });
|
|
for (const filename of ["package.json", ...vitestWorkerRuntimeAssets]) {
|
|
await fs.copyFile(path.join(repoRoot, filename), path.join(installedRoot, filename));
|
|
}
|
|
// 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);
|
|
});
|