diff --git a/extensions/qqbot/src/engine/commands/builtin/log-helpers.test.ts b/extensions/qqbot/src/engine/commands/builtin/log-helpers.test.ts index 20a04624d71b..5133065d0621 100644 --- a/extensions/qqbot/src/engine/commands/builtin/log-helpers.test.ts +++ b/extensions/qqbot/src/engine/commands/builtin/log-helpers.test.ts @@ -35,6 +35,7 @@ describe("buildBotLogsResult", () => { }); afterEach(() => { + vi.restoreAllMocks(); vi.useRealTimers(); fs.rmSync(tempHome, { recursive: true, force: true }); }); @@ -59,4 +60,39 @@ describe("buildBotLogsResult", () => { expect(fs.readFileSync(first.filePath, "utf8")).toContain("line 1"); expect(fs.readFileSync(second.filePath, "utf8")).toContain("line 2"); }); + + it("completes short fs.readSync tail windows before selecting lines", () => { + const logDir = path.join(tempHome, ".openclaw", "logs"); + fs.mkdirSync(logDir, { recursive: true }); + const logFile = path.join(logDir, "gateway.log"); + const lines = Array.from( + { length: 40 }, + (_, index) => `line ${String(index + 1).padStart(2, "0")}`, + ); + const contents = `${lines.join("\n")}\n`; + fs.writeFileSync(logFile, contents, "utf8"); + + const realReadSync = fs.readSync.bind(fs) as typeof fs.readSync; + const readSpy = vi.spyOn(fs, "readSync").mockImplementation((( + fd: number, + buffer: NodeJS.ArrayBufferView, + offset: number, + length: number, + position: number | null, + ) => { + return realReadSync(fd, buffer, offset, Math.min(length, 7), position); + }) as typeof fs.readSync); + + const result = buildBotLogsResult(); + + expect(readSpy.mock.calls.length).toBeGreaterThan(1); + expect(typeof result).toBe("object"); + if (!result || typeof result === "string") { + throw new Error("expected file upload result"); + } + const exportedLogs = fs.readFileSync(result.filePath, "utf8"); + expect(exportedLogs).toContain("line 01"); + expect(exportedLogs).toContain("line 40"); + expect(exportedLogs).not.toContain("\0"); + }); }); diff --git a/extensions/qqbot/src/engine/commands/builtin/log-helpers.ts b/extensions/qqbot/src/engine/commands/builtin/log-helpers.ts index c59f2159d731..17db71b1e698 100644 --- a/extensions/qqbot/src/engine/commands/builtin/log-helpers.ts +++ b/extensions/qqbot/src/engine/commands/builtin/log-helpers.ts @@ -230,11 +230,25 @@ function tailFileLines( const readSize = Math.min(CHUNK_SIZE, position); position -= readSize; const buf = Buffer.alloc(readSize); - fs.readSync(fd, buf, 0, readSize, position); - chunks.unshift(buf); - bytesRead += readSize; + let actualRead = 0; + while (actualRead < readSize) { + const justRead = fs.readSync( + fd, + buf, + actualRead, + readSize - actualRead, + position + actualRead, + ); + if (justRead === 0) { + throw new Error(`Could not complete log read for ${filePath}`); + } + actualRead += justRead; + } - for (let i = 0; i < readSize; i++) { + chunks.unshift(buf); + bytesRead += actualRead; + + for (let i = 0; i < actualRead; i++) { if (buf[i] === 0x0a) { newlineCount++; }