fix(acp): use truncateUtf16Safe for event mapper error text truncation (#101535)

* fix(acp): use truncateUtf16Safe for event mapper error text truncation

.slice(0, 100) on ACP event argument values can split surrogate
pairs from emoji or CJK text, producing U+FFFD in error/warning
notices shown to operators.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(acp): cover UTF-16-safe tool titles

* refactor(acp): keep event mapper import leaf-only

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
lsr911
2026-07-07 19:10:28 +08:00
committed by GitHub
parent 3db8174f0e
commit 42fc9e8a92
2 changed files with 13 additions and 2 deletions

View File

@@ -1,6 +1,6 @@
/** Tests ACP tool-call location extraction limits. */
import { describe, expect, it } from "vitest";
import { extractToolCallLocations } from "./event-mapper.js";
import { extractToolCallLocations, formatToolTitle } from "./event-mapper.js";
describe("extractToolCallLocations", () => {
it("enforces the global node visit cap across nested structures", () => {
@@ -18,3 +18,13 @@ describe("extractToolCallLocations", () => {
expect(locations).toEqual([{ path: "/tmp/file-0.txt" }, { path: "/tmp/file-1.txt" }]);
});
});
describe("formatToolTitle", () => {
it("does not split surrogate pairs when truncating argument values", () => {
const title = formatToolTitle("exec", {
command: `${"x".repeat(99)}🚀tail`,
});
expect(title).toBe(`exec: command: ${"x".repeat(99)}...`);
});
});

View File

@@ -14,6 +14,7 @@ import {
normalizeOptionalString,
readStringValue,
} from "@openclaw/normalization-core/string-coerce";
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
type GatewayAttachment = {
type: string;
@@ -309,7 +310,7 @@ export function formatToolTitle(
}
const parts = Object.entries(args).map(([key, value]) => {
const raw = typeof value === "string" ? value : JSON.stringify(value);
const safe = raw.length > 100 ? `${raw.slice(0, 100)}...` : raw;
const safe = raw.length > 100 ? `${truncateUtf16Safe(raw, 100)}...` : raw;
return `${key}: ${safe}`;
});
// Sanitize at the source so session updates and permission requests never