mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-19 22:41:40 +00:00
fix(agents): keep tool_search_code stderr tail UTF-16 safe (#104767)
* fix(agents): keep tool_search_code stderr tail UTF-16 safe * test(agents): add real child-exit UTF-16 safety proof * test(agents): focus stderr UTF-16 regression --------- Co-authored-by: chengzhichao-xydt <chengzhichao-xydt@users.noreply.github.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
committed by
GitHub
parent
4e4cca2b99
commit
dfeb15261d
@@ -87,4 +87,47 @@ describe("tool-search code-mode stream errors", () => {
|
||||
});
|
||||
expect(spawnedChild?.kill).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("keeps stderr tail in exit error messages valid at UTF-16 boundaries", async () => {
|
||||
toolSearch.testing.setToolSearchCodeModeSupportedForTest(true);
|
||||
toolSearch.testing.setToolSearchMinCodeTimeoutMsForTest(1000);
|
||||
|
||||
spawnMock.mockImplementationOnce(
|
||||
(_command: string, _args: readonly string[], _options: SpawnOptions): ChildProcess => {
|
||||
const { child, stderr } = createMockSpawnChild();
|
||||
process.nextTick(() => {
|
||||
stderr?.emit("data", `${"a".repeat(500)}😀${"a".repeat(499)}`);
|
||||
process.nextTick(() => {
|
||||
child.emit("exit", 1, null);
|
||||
});
|
||||
});
|
||||
return child as unknown as ChildProcess;
|
||||
},
|
||||
);
|
||||
|
||||
const runtime = new toolSearch.ToolSearchRuntime(
|
||||
{},
|
||||
toolSearch.testing.resolveToolSearchConfig({}),
|
||||
);
|
||||
|
||||
let caught: Error | undefined;
|
||||
try {
|
||||
await toolSearch.testing.runCodeModeChild({
|
||||
code: "return 1;",
|
||||
config: toolSearch.testing.resolveToolSearchConfig({}),
|
||||
logs: [],
|
||||
parentToolCallId: "call-stderr-utf16",
|
||||
runtime,
|
||||
});
|
||||
} catch (error) {
|
||||
caught = error instanceof Error ? error : new Error(String(error));
|
||||
}
|
||||
|
||||
expect(caught).toBeDefined();
|
||||
expect(caught?.message).toMatch(/tool_search_code child exited with 1/);
|
||||
expect(caught?.message).not.toMatch(
|
||||
/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/,
|
||||
);
|
||||
expect(caught?.message.endsWith("a".repeat(499))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
uniqueStrings,
|
||||
uniqueValues,
|
||||
} from "@openclaw/normalization-core/string-normalization";
|
||||
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
|
||||
import { sliceUtf16Safe, truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
|
||||
import { Type } from "typebox";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { getPluginToolMeta, type PluginToolMcpMeta } from "../plugins/tools.js";
|
||||
@@ -2199,7 +2199,7 @@ function runCodeModeChild(params: {
|
||||
}
|
||||
const rejectOnExit = () => {
|
||||
const suffix = stderrTail.trim();
|
||||
const detail = suffix ? `: ${suffix.slice(-500)}` : "";
|
||||
const detail = suffix ? `: ${sliceUtf16Safe(suffix, -500)}` : "";
|
||||
settle(() =>
|
||||
reject(
|
||||
new Error(
|
||||
|
||||
Reference in New Issue
Block a user