























@@ -141,6 +141,65 @@ describe("channelsLogsCommand", () => {
141141expect(payload.lines.map((line) => line.message)).toEqual(["current sent"]);
142142});
143143144+it("returns the first line of the tail window when start aligns with a line boundary", async () => {
145+// MAX_BYTES in readTailLines is 1_000_000. We build a file of 2_000_000 bytes
146+// made of 10_000 lines each exactly 200 bytes (199 payload + "\n"), so the
147+// read window starts at byte offset 1_000_000 which is exactly on a line
148+// boundary (byte 999_999 is the trailing "\n" of the previous line).
149+// Without checking the byte before the window, readTailLines drops line 5000 silently.
150+const LINE_SIZE = 200;
151+const TOTAL_LINES = 10_000;
152+const FIRST_INDEX = 5000; // first line of the tail window after alignment
153+154+const buildLine = (message: string) => {
155+const base = logLine({
156+module: "gateway/channels/slack/send",
157+ message,
158+});
159+const payloadLen = LINE_SIZE - 1; // reserve 1 byte for newline
160+// Re-emit with a padded message so total byte length is constant.
161+const padNeeded = payloadLen - Buffer.byteLength(base);
162+if (padNeeded < 0) {
163+throw new Error(`base log line too long: ${Buffer.byteLength(base)} > ${payloadLen}`);
164+}
165+const padded = logLine({
166+module: "gateway/channels/slack/send",
167+message: message + " ".repeat(padNeeded),
168+});
169+if (Buffer.byteLength(padded) !== payloadLen) {
170+throw new Error(`padded line wrong size: ${Buffer.byteLength(padded)} vs ${payloadLen}`);
171+}
172+return padded + "\n";
173+};
174+175+const handle = await fs.open(logPath, "w");
176+try {
177+for (let i = 0; i < TOTAL_LINES; i++) {
178+let message: string;
179+if (i === FIRST_INDEX) {
180+message = "first-line-in-window";
181+} else if (i === TOTAL_LINES - 1) {
182+message = "last-line";
183+} else {
184+message = "filler";
185+}
186+await handle.write(buildLine(message));
187+}
188+} finally {
189+await handle.close();
190+}
191+192+await channelsLogsCommand(
193+{ channel: "slack", json: true, lines: String(TOTAL_LINES) },
194+runtime,
195+);
196+197+const payload = readJsonPayload();
198+const messages = payload.lines.map((line) => line.message.trimEnd());
199+expect(messages[0]).toBe("first-line-in-window");
200+expect(messages[messages.length - 1]).toBe("last-line");
201+});
202+144203it("does not fall back to rolling logs for a missing custom log file", async () => {
145204const configuredFile = path.join(tempDir, "custom-channel.log");
146205const fallbackFile = path.join(tempDir, "openclaw-2026-04-25.log");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。