




















@@ -8,6 +8,10 @@ import { applyMockOpenAiModelConfig } from "../fixtures/mock-openai-config.mjs";
8899const command = process.argv[2];
101011+const SCAN_CHUNK_BYTES = 64 * 1024;
12+const SCAN_CARRY_CHARS = 256;
13+const ERROR_DETAIL_TAIL_BYTES = 16 * 1024;
14+1115function assert(condition, message) {
1216if (!condition) {
1317throw new Error(message);
@@ -18,6 +22,71 @@ function readJson(file) {
1822return JSON.parse(fs.readFileSync(file, "utf8"));
1923}
202425+function tailText(text, maxBytes = ERROR_DETAIL_TAIL_BYTES) {
26+if (Buffer.byteLength(text, "utf8") <= maxBytes) {
27+return text;
28+}
29+return Buffer.from(text, "utf8").subarray(-maxBytes).toString("utf8");
30+}
31+32+function readTextFileTail(file, maxBytes = ERROR_DETAIL_TAIL_BYTES) {
33+let stat;
34+try {
35+stat = fs.statSync(file);
36+} catch {
37+return "";
38+}
39+if (!stat.isFile() || stat.size <= 0) {
40+return "";
41+}
42+43+const length = Math.min(maxBytes, stat.size);
44+const start = stat.size - length;
45+const fd = fs.openSync(file, "r");
46+try {
47+const buffer = Buffer.alloc(length);
48+const bytesRead = fs.readSync(fd, buffer, 0, length, start);
49+return buffer.subarray(0, bytesRead).toString("utf8");
50+} finally {
51+fs.closeSync(fd);
52+}
53+}
54+55+function fileContainsText(file, needle) {
56+let stat;
57+try {
58+stat = fs.statSync(file);
59+} catch {
60+return false;
61+}
62+if (!stat.isFile() || stat.size <= 0) {
63+return false;
64+}
65+66+const fd = fs.openSync(file, "r");
67+try {
68+const buffer = Buffer.alloc(Math.min(SCAN_CHUNK_BYTES, stat.size));
69+let carry = "";
70+let offset = 0;
71+while (offset < stat.size) {
72+const bytesToRead = Math.min(buffer.length, stat.size - offset);
73+const bytesRead = fs.readSync(fd, buffer, 0, bytesToRead, offset);
74+if (bytesRead <= 0) {
75+break;
76+}
77+offset += bytesRead;
78+const text = carry + buffer.subarray(0, bytesRead).toString("utf8");
79+if (text.includes(needle)) {
80+return true;
81+}
82+carry = text.slice(-Math.max(SCAN_CARRY_CHARS, needle.length - 1));
83+}
84+return false;
85+} finally {
86+fs.closeSync(fd);
87+}
88+}
89+2190function configPath() {
2291return (
2392process.env.OPENCLAW_CONFIG_PATH ??
@@ -71,8 +140,10 @@ function assertAgentTurn() {
71140function assertFileContains() {
72141const file = process.argv[3];
73142const needle = process.argv[4];
74-const raw = fs.readFileSync(file, "utf8");
75-assert(raw.includes(needle), `${file} did not contain ${needle}. Output: ${raw}`);
143+assert(
144+fileContainsText(file, needle),
145+`${file} did not contain ${needle}. Output tail: ${readTextFileTail(file)}`,
146+);
76147}
7714878149function assertPackageVersion() {
@@ -98,8 +169,10 @@ function assertImageDescribe() {
98169const output = payload.outputs?.[0];
99170assert(output?.text?.includes("OPENCLAW_E2E_OK"), "image description marker missing");
100171assert(output.provider === "openai", `unexpected image provider: ${output?.provider}`);
101-const requestLog = fs.existsSync(requestLogPath) ? fs.readFileSync(requestLogPath, "utf8") : "";
102-assert(requestLog.includes("/v1/responses"), "image describe did not hit Responses API");
172+assert(
173+fileContainsText(requestLogPath, "/v1/responses"),
174+"image describe did not hit Responses API",
175+);
103176}
104177105178function assertImageGenerate() {
@@ -112,8 +185,10 @@ function assertImageGenerate() {
112185assert(output?.path && fs.existsSync(output.path), `generated image missing: ${output?.path}`);
113186assert(output.mimeType === "image/png", `unexpected generated mime type: ${output.mimeType}`);
114187assert(payload.provider === "openai", `unexpected generation provider: ${payload.provider}`);
115-const requestLog = fs.existsSync(requestLogPath) ? fs.readFileSync(requestLogPath, "utf8") : "";
116-assert(requestLog.includes("/v1/images/generations"), "image generation endpoint was not used");
188+assert(
189+fileContainsText(requestLogPath, "/v1/images/generations"),
190+"image generation endpoint was not used",
191+);
117192}
118193119194function assertMemorySearch() {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。