mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-14 15:36:07 +00:00
fix(signal): surface daemon errors split across output chunks (#104152)
* fix(signal): preserve daemon log lines across chunks * test(signal): cover split utf-8 daemon output --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
// Signal tests cover daemon plugin behavior.
|
||||
import { once } from "node:events";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { PassThrough } from "node:stream";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { testApi } from "./daemon.js";
|
||||
|
||||
@@ -45,4 +47,34 @@ describe("signal daemon log classification", () => {
|
||||
expect(testApi.classifySignalCliLogLine("ERROR DaemonCommand - startup failed")).toBe("error");
|
||||
expect(testApi.classifySignalCliLogLine("SEVERE Manager - database exception")).toBe("error");
|
||||
});
|
||||
|
||||
it("preserves log lines and UTF-8 across output chunk boundaries", async () => {
|
||||
const stream = new PassThrough();
|
||||
const logs: string[] = [];
|
||||
const errors: string[] = [];
|
||||
testApi.bindSignalCliOutput({
|
||||
stream,
|
||||
log: (message) => logs.push(message),
|
||||
error: (message) => errors.push(message),
|
||||
});
|
||||
|
||||
const ended = once(stream, "end");
|
||||
stream.write(Buffer.from("ER"));
|
||||
stream.write(
|
||||
Buffer.from("ROR DaemonCommand - startup failed\r\nWARN Manager - retrying\npartial"),
|
||||
);
|
||||
stream.write(" warning\n");
|
||||
const utf8Line = Buffer.from("INFO Manager - café ready");
|
||||
const splitCodePointAt = utf8Line.indexOf(0xc3) + 1;
|
||||
stream.write(utf8Line.subarray(0, splitCodePointAt));
|
||||
stream.end(utf8Line.subarray(splitCodePointAt));
|
||||
await ended;
|
||||
|
||||
expect(errors).toEqual(["signal-cli: ERROR DaemonCommand - startup failed"]);
|
||||
expect(logs).toEqual([
|
||||
"signal-cli: WARN Manager - retrying",
|
||||
"signal-cli: partial warning",
|
||||
"signal-cli: INFO Manager - café ready",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { createInterface } from "node:readline";
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
|
||||
type SignalDaemonOpts = {
|
||||
@@ -63,14 +64,18 @@ function bindSignalCliOutput(params: {
|
||||
log: (message: string) => void;
|
||||
error: (message: string) => void;
|
||||
}): void {
|
||||
params.stream?.on("data", (data) => {
|
||||
for (const line of data.toString().split(/\r?\n/)) {
|
||||
const kind = classifySignalCliLogLine(line);
|
||||
if (kind === "log") {
|
||||
params.log(`signal-cli: ${line.trim()}`);
|
||||
} else if (kind === "error") {
|
||||
params.error(`signal-cli: ${line.trim()}`);
|
||||
}
|
||||
if (!params.stream) {
|
||||
return;
|
||||
}
|
||||
// Process pipe chunks can split both log lines and UTF-8 code points. Readline owns that
|
||||
// framing so classification sees complete text, including the final unterminated line.
|
||||
const lines = createInterface({ input: params.stream });
|
||||
lines.on("line", (line) => {
|
||||
const kind = classifySignalCliLogLine(line);
|
||||
if (kind === "log") {
|
||||
params.log(`signal-cli: ${line.trim()}`);
|
||||
} else if (kind === "error") {
|
||||
params.error(`signal-cli: ${line.trim()}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -175,6 +180,7 @@ export function spawnSignalDaemon(opts: SignalDaemonOpts): SignalDaemonHandle {
|
||||
}
|
||||
|
||||
export const testApi = {
|
||||
bindSignalCliOutput,
|
||||
buildDaemonArgs,
|
||||
classifySignalCliLogLine,
|
||||
resolveSignalCliConfigPath,
|
||||
|
||||
Reference in New Issue
Block a user