diff --git a/src/config/allowed-values.test.ts b/src/config/allowed-values.test.ts index e08f8b762a96..ebe1660f4b56 100644 --- a/src/config/allowed-values.test.ts +++ b/src/config/allowed-values.test.ts @@ -23,6 +23,17 @@ describe("summarizeAllowedValues", () => { }); }); + it("counts the omitted UTF-16 units after preserving a surrogate pair", () => { + const prefix = "a".repeat(159); + const value = `${prefix}😀tail`; + + expect(summarizeAllowedValues([value])).toStrictEqual({ + formatted: `"${prefix}... (+6 chars)"`, + hiddenCount: 0, + values: [value], + }); + }); + it("returns empty label for undefined allowed value", () => { const summary = summarizeAllowedValues([undefined]); expect(summary).toStrictEqual({ diff --git a/src/config/allowed-values.ts b/src/config/allowed-values.ts index 11acbc962901..6f9e8cf65f66 100644 --- a/src/config/allowed-values.ts +++ b/src/config/allowed-values.ts @@ -1,5 +1,6 @@ // Defines allowed-value metadata for config validation and docs. import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; +import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice"; const MAX_ALLOWED_VALUES_HINT = 12; const MAX_ALLOWED_VALUE_CHARS = 160; @@ -14,7 +15,8 @@ function truncateHintText(text: string, limit: number): string { if (text.length <= limit) { return text; } - return `${text.slice(0, limit)}... (+${text.length - limit} chars)`; + const truncated = truncateUtf16Safe(text, limit); + return `${truncated}... (+${text.length - truncated.length} chars)`; } function safeStringify(value: unknown): string {