mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-18 00:11:40 +00:00
fix(exec): long retained process output corrupts boundary emoji (#104531)
* fix(process): preserve Unicode in captured output tails * test(process): consolidate captured output coverage --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -345,21 +345,25 @@ describe("process supervisor", () => {
|
||||
expect(exit.stdout).toBe("");
|
||||
});
|
||||
|
||||
it("bounds retained stdout and stderr while streaming full chunks", async () => {
|
||||
it("bounds retained output on UTF-16 boundaries while streaming full chunks", async () => {
|
||||
const adapter = createStubChildAdapter();
|
||||
createChildAdapterMock.mockResolvedValue(adapter);
|
||||
|
||||
const supervisor = createProcessSupervisor();
|
||||
let streamedStdout = "";
|
||||
let streamedStderr = "";
|
||||
const stdoutChunk = `${"a".repeat(300)}stdout-tail`;
|
||||
const stderrChunk = `${"b".repeat(300)}stderr-tail`;
|
||||
const maxCapturedOutputChars = 256;
|
||||
const stdoutMarker = `[openclaw: captured stdout truncated to last ${maxCapturedOutputChars} chars]\n`;
|
||||
const stderrMarker = `[openclaw: captured stderr truncated to last ${maxCapturedOutputChars} chars]\n`;
|
||||
const retainedChars = maxCapturedOutputChars - stdoutMarker.length - 1;
|
||||
const stdoutChunk = `${"a".repeat(stdoutMarker.length)}😀${"s".repeat(retainedChars)}`;
|
||||
const stderrChunk = `${"b".repeat(stderrMarker.length)}😀${"e".repeat(retainedChars)}`;
|
||||
const run = await spawnChild(supervisor, {
|
||||
sessionId: "s-capture-cap",
|
||||
argv: createWriteStdoutArgv(stdoutChunk),
|
||||
timeoutMs: 1_000,
|
||||
stdinMode: "pipe-closed",
|
||||
maxCapturedOutputChars: 256,
|
||||
maxCapturedOutputChars,
|
||||
onStdout: (chunk) => {
|
||||
streamedStdout += chunk;
|
||||
},
|
||||
@@ -375,11 +379,7 @@ describe("process supervisor", () => {
|
||||
const exit = await run.wait();
|
||||
expect(streamedStdout).toBe(stdoutChunk);
|
||||
expect(streamedStderr).toBe(stderrChunk);
|
||||
expect(exit.stdout.length).toBeLessThanOrEqual(256);
|
||||
expect(exit.stderr.length).toBeLessThanOrEqual(256);
|
||||
expect(exit.stdout).toContain("captured stdout truncated");
|
||||
expect(exit.stderr).toContain("captured stderr truncated");
|
||||
expect(exit.stdout.endsWith("stdout-tail")).toBe(true);
|
||||
expect(exit.stderr.endsWith("stderr-tail")).toBe(true);
|
||||
expect(exit.stdout).toBe(`${stdoutMarker}${"s".repeat(retainedChars)}`);
|
||||
expect(exit.stderr).toBe(`${stderrMarker}${"e".repeat(retainedChars)}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import crypto from "node:crypto";
|
||||
import { performance } from "node:perf_hooks";
|
||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||
import { sliceUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
|
||||
import { getShellConfig } from "../../agents/shell-utils.js";
|
||||
import { createLazyRuntimeModule } from "../../shared/lazy-runtime.js";
|
||||
import { createChildAdapter } from "./adapters/child.js";
|
||||
@@ -54,7 +55,7 @@ function appendCapturedOutput(
|
||||
}
|
||||
const marker = `[openclaw: captured ${stream} truncated to last ${maxChars} chars]\n`;
|
||||
const tailChars = Math.max(0, maxChars - marker.length);
|
||||
return `${marker}${next.slice(-tailChars)}`;
|
||||
return `${marker}${sliceUtf16Safe(next, -tailChars)}`;
|
||||
}
|
||||
|
||||
function isTimeoutReason(reason: TerminationReason) {
|
||||
|
||||
Reference in New Issue
Block a user