diff --git a/extensions/qa-lab/src/gateway-child.test.ts b/extensions/qa-lab/src/gateway-child.test.ts index 7c2295ed0089..567c59475449 100644 --- a/extensions/qa-lab/src/gateway-child.test.ts +++ b/extensions/qa-lab/src/gateway-child.test.ts @@ -206,6 +206,18 @@ describe("formatQaGatewayProcessBoundaryStartupFailure", () => { expect(message).not.toContain("s".repeat(100)); expect(message).not.toContain(prefix); }); + + it("preserves complete Unicode code points at the retained log-tail boundary", () => { + const message = testing.formatQaGatewayProcessBoundaryStartupFailure( + new Error("launcher exited before identity"), + `P😀${"z".repeat(8_191)}`, + ); + + expect(message).not.toMatch( + /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(? { diff --git a/extensions/qa-lab/src/gateway-child.ts b/extensions/qa-lab/src/gateway-child.ts index edba1decf248..2f8c051ad354 100644 --- a/extensions/qa-lab/src/gateway-child.ts +++ b/extensions/qa-lab/src/gateway-child.ts @@ -18,6 +18,7 @@ import { uniqueStrings, } from "openclaw/plugin-sdk/string-coerce-runtime"; import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path"; +import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime"; import { createQaBundledPluginsDir, resolveQaBundledPluginSourceDir, @@ -493,7 +494,10 @@ function monitorQaGatewayChildFailure(child: ChildProcess, output: { push(chunk: const QA_GATEWAY_PROCESS_BOUNDARY_LOG_TAIL_CHARS = 8_192; function formatQaGatewayProcessBoundaryStartupFailure(error: unknown, logs: string) { - const logTail = redactQaGatewayDebugText(logs).slice(-QA_GATEWAY_PROCESS_BOUNDARY_LOG_TAIL_CHARS); + const logTail = sliceUtf16Safe( + redactQaGatewayDebugText(logs), + -QA_GATEWAY_PROCESS_BOUNDARY_LOG_TAIL_CHARS, + ); return `${formatErrorMessage(error)}${formatQaGatewayLogsForError(logTail)}`; }