mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(release): bound cross-os agent log fallback reads
This commit is contained in:
@@ -10,7 +10,10 @@ import {
|
|||||||
createWriteStream,
|
createWriteStream,
|
||||||
existsSync,
|
existsSync,
|
||||||
mkdirSync,
|
mkdirSync,
|
||||||
|
closeSync,
|
||||||
|
openSync,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
|
readSync,
|
||||||
readdirSync,
|
readdirSync,
|
||||||
realpathSync,
|
realpathSync,
|
||||||
rmSync,
|
rmSync,
|
||||||
@@ -53,6 +56,7 @@ export const CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS = parsePositiveIntegerEnv(
|
|||||||
600,
|
600,
|
||||||
);
|
);
|
||||||
export const CROSS_OS_COMMAND_CAPTURE_TAIL_BYTES = 16 * 1024 * 1024;
|
export const CROSS_OS_COMMAND_CAPTURE_TAIL_BYTES = 16 * 1024 * 1024;
|
||||||
|
const CROSS_OS_AGENT_LOG_FALLBACK_TAIL_BYTES = 2 * 1024 * 1024;
|
||||||
const CROSS_OS_PROCESS_TREE_KILL_AFTER_MS = parsePositiveIntegerEnv(
|
const CROSS_OS_PROCESS_TREE_KILL_AFTER_MS = parsePositiveIntegerEnv(
|
||||||
"OPENCLAW_CROSS_OS_PROCESS_TREE_KILL_AFTER_MS",
|
"OPENCLAW_CROSS_OS_PROCESS_TREE_KILL_AFTER_MS",
|
||||||
15_000,
|
15_000,
|
||||||
@@ -3276,12 +3280,8 @@ export function shouldSkipOptionalCrossOsAgentTurnError(error, logPath) {
|
|||||||
if (!/Agent output did not contain the expected OK marker/u.test(message)) {
|
if (!/Agent output did not contain the expected OK marker/u.test(message)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
const log = readLogTextTail(logPath);
|
||||||
const log = readFileSync(logPath, "utf8");
|
return /"status"\s*:\s*"timeout"|Request timed out before a response was generated/u.test(log);
|
||||||
return /"status"\s*:\s*"timeout"|Request timed out before a response was generated/u.test(log);
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildReleaseAgentTurnArgs(sessionId) {
|
function buildReleaseAgentTurnArgs(sessionId) {
|
||||||
@@ -3313,7 +3313,7 @@ export function agentTurnUsedEmbeddedFallback(result, options = {}) {
|
|||||||
typeof options.logText === "string"
|
typeof options.logText === "string"
|
||||||
? options.logText
|
? options.logText
|
||||||
: typeof options.logPath === "string"
|
: typeof options.logPath === "string"
|
||||||
? safeReadTextFile(options.logPath)
|
? readLogTextTail(options.logPath)
|
||||||
: "";
|
: "";
|
||||||
return /EMBEDDED FALLBACK:/u.test(`${result.stdout ?? ""}\n${result.stderr ?? ""}\n${logText}`);
|
return /EMBEDDED FALLBACK:/u.test(`${result.stdout ?? ""}\n${result.stderr ?? ""}\n${logText}`);
|
||||||
}
|
}
|
||||||
@@ -3330,12 +3330,8 @@ export function agentOutputHasExpectedOkMarker(stdout, options = {}) {
|
|||||||
if (typeof options.logPath !== "string") {
|
if (typeof options.logPath !== "string") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
const logTexts = parseAgentPayloadTexts(readLogTextTail(options.logPath));
|
||||||
const logTexts = parseAgentPayloadTexts(readFileSync(options.logPath, "utf8"));
|
return logTexts.some((text) => text.trim() === "OK");
|
||||||
return logTexts.some((text) => text.trim() === "OK");
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function readLogFileSize(logPath) {
|
function readLogFileSize(logPath) {
|
||||||
@@ -3347,19 +3343,52 @@ function readLogFileSize(logPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function readLogTextSince(logPath, offsetBytes) {
|
function readLogTextSince(logPath, offsetBytes) {
|
||||||
|
return readLogTextWindow(logPath, {
|
||||||
|
offsetBytes,
|
||||||
|
maxBytes: CROSS_OS_AGENT_LOG_FALLBACK_TAIL_BYTES,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function readLogTextTail(logPath) {
|
||||||
|
return readLogTextWindow(logPath, {
|
||||||
|
maxBytes: CROSS_OS_AGENT_LOG_FALLBACK_TAIL_BYTES,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function readLogTextWindow(logPath, options = {}) {
|
||||||
|
const maxBytes = Math.max(
|
||||||
|
1,
|
||||||
|
Math.floor(options.maxBytes ?? CROSS_OS_AGENT_LOG_FALLBACK_TAIL_BYTES),
|
||||||
|
);
|
||||||
|
const offsetBytes =
|
||||||
|
typeof options.offsetBytes === "number" && Number.isFinite(options.offsetBytes)
|
||||||
|
? Math.max(0, Math.floor(options.offsetBytes))
|
||||||
|
: 0;
|
||||||
|
let stat;
|
||||||
try {
|
try {
|
||||||
return readFileSync(logPath).subarray(offsetBytes).toString("utf8");
|
stat = statSync(logPath);
|
||||||
} catch {
|
} catch {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
if (!stat.isFile() || stat.size <= 0) {
|
||||||
|
|
||||||
function safeReadTextFile(logPath) {
|
|
||||||
try {
|
|
||||||
return readFileSync(logPath, "utf8");
|
|
||||||
} catch {
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tailStart = Math.max(0, stat.size - maxBytes);
|
||||||
|
const start = Math.min(stat.size, Math.max(offsetBytes, tailStart));
|
||||||
|
const length = stat.size - start;
|
||||||
|
if (length <= 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const fd = openSync(logPath, "r");
|
||||||
|
try {
|
||||||
|
const buffer = Buffer.alloc(length);
|
||||||
|
const bytesRead = readSync(fd, buffer, 0, length, start);
|
||||||
|
return buffer.subarray(0, bytesRead).toString("utf8");
|
||||||
|
} finally {
|
||||||
|
closeSync(fd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseAgentPayloadTexts(stdout) {
|
function parseAgentPayloadTexts(stdout) {
|
||||||
|
|||||||
@@ -255,6 +255,29 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("ignores stale OK markers outside the recent agent log tail", () => {
|
||||||
|
const dir = mkdtempSync(join(tmpdir(), "openclaw-cross-os-agent-output-tail-"));
|
||||||
|
try {
|
||||||
|
const logPath = join(dir, "agent.log");
|
||||||
|
writeFileSync(
|
||||||
|
logPath,
|
||||||
|
[
|
||||||
|
JSON.stringify({
|
||||||
|
payloads: [{ type: "text", text: "OK" }],
|
||||||
|
}),
|
||||||
|
"x".repeat(2_200_000),
|
||||||
|
JSON.stringify({
|
||||||
|
payloads: [{ type: "text", text: "still working" }],
|
||||||
|
}),
|
||||||
|
].join("\n"),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(agentOutputHasExpectedOkMarker("", { logPath })).toBe(false);
|
||||||
|
} finally {
|
||||||
|
rmSync(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("retries transient agent-turn failures", () => {
|
it("retries transient agent-turn failures", () => {
|
||||||
expect(
|
expect(
|
||||||
shouldRetryCrossOsAgentTurnError(
|
shouldRetryCrossOsAgentTurnError(
|
||||||
@@ -365,6 +388,33 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not classify stale timeout logs as current optional agent-turn failures", () => {
|
||||||
|
const dir = mkdtempSync(join(tmpdir(), "openclaw-cross-os-agent-skip-tail-"));
|
||||||
|
try {
|
||||||
|
const logPath = join(dir, "agent.log");
|
||||||
|
writeFileSync(
|
||||||
|
logPath,
|
||||||
|
[
|
||||||
|
JSON.stringify({
|
||||||
|
status: "timeout",
|
||||||
|
result: { payloads: [{ text: "Request timed out before a response was generated." }] },
|
||||||
|
}),
|
||||||
|
"x".repeat(2_200_000),
|
||||||
|
JSON.stringify({ status: "error", message: "document-extract failed" }),
|
||||||
|
].join("\n"),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
shouldSkipOptionalCrossOsAgentTurnError(
|
||||||
|
new Error("Agent output did not contain the expected OK marker."),
|
||||||
|
logPath,
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
} finally {
|
||||||
|
rmSync(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("only skips opted-in cross-OS live agent turns after retry exhaustion", () => {
|
it("only skips opted-in cross-OS live agent turns after retry exhaustion", () => {
|
||||||
const dir = mkdtempSync(join(tmpdir(), "openclaw-cross-os-agent-skip-retry-"));
|
const dir = mkdtempSync(join(tmpdir(), "openclaw-cross-os-agent-skip-retry-"));
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user