mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-30 07:13:39 +00:00
242 lines
7.5 KiB
JavaScript
242 lines
7.5 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import {
|
|
assertAgentReplyContainsMarker,
|
|
assertOpenAiRequestLogUsed,
|
|
} from "../agent-turn-output.mjs";
|
|
import { applyMockOpenAiModelConfig } from "../fixtures/mock-openai-config.mjs";
|
|
|
|
const command = process.argv[2];
|
|
|
|
const SCAN_CHUNK_BYTES = 64 * 1024;
|
|
const SCAN_CARRY_CHARS = 256;
|
|
const ERROR_DETAIL_TAIL_BYTES = 16 * 1024;
|
|
|
|
function assert(condition, message) {
|
|
if (!condition) {
|
|
throw new Error(message);
|
|
}
|
|
}
|
|
|
|
function readJson(file) {
|
|
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
}
|
|
|
|
function tailText(text, maxBytes = ERROR_DETAIL_TAIL_BYTES) {
|
|
if (Buffer.byteLength(text, "utf8") <= maxBytes) {
|
|
return text;
|
|
}
|
|
return Buffer.from(text, "utf8").subarray(-maxBytes).toString("utf8");
|
|
}
|
|
|
|
function readTextFileTail(file, maxBytes = ERROR_DETAIL_TAIL_BYTES) {
|
|
let stat;
|
|
try {
|
|
stat = fs.statSync(file);
|
|
} catch {
|
|
return "";
|
|
}
|
|
if (!stat.isFile() || stat.size <= 0) {
|
|
return "";
|
|
}
|
|
|
|
const length = Math.min(maxBytes, stat.size);
|
|
const start = stat.size - length;
|
|
const fd = fs.openSync(file, "r");
|
|
try {
|
|
const buffer = Buffer.alloc(length);
|
|
const bytesRead = fs.readSync(fd, buffer, 0, length, start);
|
|
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
} finally {
|
|
fs.closeSync(fd);
|
|
}
|
|
}
|
|
|
|
function fileContainsText(file, needle) {
|
|
let stat;
|
|
try {
|
|
stat = fs.statSync(file);
|
|
} catch {
|
|
return false;
|
|
}
|
|
if (!stat.isFile() || stat.size <= 0) {
|
|
return false;
|
|
}
|
|
|
|
const fd = fs.openSync(file, "r");
|
|
try {
|
|
const buffer = Buffer.alloc(Math.min(SCAN_CHUNK_BYTES, stat.size));
|
|
let carry = "";
|
|
let offset = 0;
|
|
while (offset < stat.size) {
|
|
const bytesToRead = Math.min(buffer.length, stat.size - offset);
|
|
const bytesRead = fs.readSync(fd, buffer, 0, bytesToRead, offset);
|
|
if (bytesRead <= 0) {
|
|
break;
|
|
}
|
|
offset += bytesRead;
|
|
const text = carry + buffer.subarray(0, bytesRead).toString("utf8");
|
|
if (text.includes(needle)) {
|
|
return true;
|
|
}
|
|
carry = text.slice(-Math.max(SCAN_CARRY_CHARS, needle.length - 1));
|
|
}
|
|
return false;
|
|
} finally {
|
|
fs.closeSync(fd);
|
|
}
|
|
}
|
|
|
|
function configPath() {
|
|
return (
|
|
process.env.OPENCLAW_CONFIG_PATH ??
|
|
path.join(process.env.HOME ?? "", ".openclaw", "openclaw.json")
|
|
);
|
|
}
|
|
|
|
function writeConfig(cfg) {
|
|
fs.writeFileSync(configPath(), `${JSON.stringify(cfg, null, 2)}\n`);
|
|
}
|
|
|
|
function authProfilesPath() {
|
|
return path.join(
|
|
process.env.HOME ?? "",
|
|
".openclaw",
|
|
"agents",
|
|
"main",
|
|
"agent",
|
|
"auth-profiles.json",
|
|
);
|
|
}
|
|
|
|
function readStateText() {
|
|
const paths = [configPath(), authProfilesPath()].filter((file) => fs.existsSync(file));
|
|
return paths.map((file) => fs.readFileSync(file, "utf8")).join("\n");
|
|
}
|
|
|
|
function configureMockOpenAi() {
|
|
const mockPort = Number(process.argv[3]);
|
|
const cfg = readJson(configPath());
|
|
applyMockOpenAiModelConfig(cfg, { mockPort, includeImageDefaults: true });
|
|
writeConfig(cfg);
|
|
}
|
|
|
|
function assertOpenAiEnvRef() {
|
|
const rawKey = process.argv[3];
|
|
const state = readStateText();
|
|
assert(state.includes("OPENAI_API_KEY"), "OpenAI env ref was not persisted");
|
|
assert(!state.includes(rawKey), "raw OpenAI key was persisted");
|
|
assert(fs.existsSync(configPath()), "openclaw.json missing");
|
|
}
|
|
|
|
function assertAgentTurn() {
|
|
const marker = process.argv[3];
|
|
const outputPath = process.argv[4];
|
|
const requestLogPath = process.argv[5];
|
|
assertAgentReplyContainsMarker(marker, outputPath);
|
|
assertOpenAiRequestLogUsed(requestLogPath, "mock OpenAI");
|
|
}
|
|
|
|
function assertFileContains() {
|
|
const file = process.argv[3];
|
|
const needle = process.argv[4];
|
|
assert(
|
|
fileContainsText(file, needle),
|
|
`${file} did not contain ${needle}. Output tail: ${readTextFileTail(file)}`,
|
|
);
|
|
}
|
|
|
|
function assertPackageVersion() {
|
|
const packageRoot = process.argv[3];
|
|
const expectedVersion = process.argv[4];
|
|
const label = process.argv[5] ?? "package";
|
|
assert(packageRoot, "missing package root");
|
|
assert(expectedVersion, "missing expected package version");
|
|
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
const packageJson = readJson(packageJsonPath);
|
|
assert(
|
|
packageJson.version === expectedVersion,
|
|
`${label} package version mismatch: expected ${expectedVersion}, got ${packageJson.version}`,
|
|
);
|
|
}
|
|
|
|
function assertImageDescribe() {
|
|
const outputPath = process.argv[3];
|
|
const requestLogPath = process.argv[4];
|
|
const payload = readJson(outputPath);
|
|
assert(payload.ok === true, `image describe failed: ${JSON.stringify(payload)}`);
|
|
assert(payload.capability === "image.describe", "wrong image describe capability");
|
|
const output = payload.outputs?.[0];
|
|
assert(output?.text?.includes("OPENCLAW_E2E_OK"), "image description marker missing");
|
|
assert(output.provider === "openai", `unexpected image provider: ${output?.provider}`);
|
|
assert(
|
|
fileContainsText(requestLogPath, "/v1/responses"),
|
|
"image describe did not hit Responses API",
|
|
);
|
|
}
|
|
|
|
function assertImageGenerate() {
|
|
const outputPath = process.argv[3];
|
|
const requestLogPath = process.argv[4];
|
|
const payload = readJson(outputPath);
|
|
assert(payload.ok === true, `image generation failed: ${JSON.stringify(payload)}`);
|
|
assert(payload.capability === "image.generate", "wrong image generation capability");
|
|
const output = payload.outputs?.[0];
|
|
assert(output?.path && fs.existsSync(output.path), `generated image missing: ${output?.path}`);
|
|
assert(output.mimeType === "image/png", `unexpected generated mime type: ${output.mimeType}`);
|
|
assert(payload.provider === "openai", `unexpected generation provider: ${payload.provider}`);
|
|
assert(
|
|
fileContainsText(requestLogPath, "/v1/images/generations"),
|
|
"image generation endpoint was not used",
|
|
);
|
|
}
|
|
|
|
function assertMemorySearch() {
|
|
const outputPath = process.argv[3];
|
|
const needle = process.argv[4];
|
|
const payload = readJson(outputPath);
|
|
const haystack = JSON.stringify(payload);
|
|
assert(haystack.includes(needle), `memory search missed ${needle}: ${haystack}`);
|
|
}
|
|
|
|
function assertPluginUninstalled() {
|
|
const pluginId = process.argv[3];
|
|
const cliRoot = process.argv[4];
|
|
const cfg = readJson(configPath());
|
|
const recordsPath = path.join(process.env.HOME ?? "", ".openclaw", "plugins", "installs.json");
|
|
const records = fs.existsSync(recordsPath) ? readJson(recordsPath) : {};
|
|
const installRecords = records.installRecords ?? records.records ?? {};
|
|
assert(!installRecords[pluginId], `install record still present for ${pluginId}`);
|
|
assert(!cfg.plugins?.entries?.[pluginId], `plugin config entry still present for ${pluginId}`);
|
|
const managedRoot = path.join(
|
|
process.env.HOME ?? "",
|
|
".openclaw",
|
|
"plugins",
|
|
"installed",
|
|
pluginId,
|
|
);
|
|
assert(!fs.existsSync(managedRoot), `managed plugin directory still present: ${managedRoot}`);
|
|
if (cliRoot) {
|
|
const list = JSON.stringify(records);
|
|
assert(!list.includes(cliRoot), `install records still mention CLI root ${cliRoot}`);
|
|
}
|
|
}
|
|
|
|
const commands = {
|
|
"configure-mock-openai": configureMockOpenAi,
|
|
"assert-openai-env-ref": assertOpenAiEnvRef,
|
|
"assert-agent-turn": assertAgentTurn,
|
|
"assert-file-contains": assertFileContains,
|
|
"assert-package-version": assertPackageVersion,
|
|
"assert-image-describe": assertImageDescribe,
|
|
"assert-image-generate": assertImageGenerate,
|
|
"assert-memory-search": assertMemorySearch,
|
|
"assert-plugin-uninstalled": assertPluginUninstalled,
|
|
};
|
|
|
|
const fn = commands[command];
|
|
if (!fn) {
|
|
throw new Error(`unknown release scenario assertion command: ${command ?? "<missing>"}`);
|
|
}
|
|
await fn();
|