From 720aeb1547fec89ad030dc2d8f2ef151e6ce65f7 Mon Sep 17 00:00:00 2001 From: lsr911 Date: Thu, 9 Jul 2026 16:28:25 +0800 Subject: [PATCH] fix(config): use truncateUtf16Safe for allowed-values hint text (#102512) * fix(config): use truncateUtf16Safe for allowed-values hint text Replace naive .slice(0, N) with truncateUtf16Safe() to prevent surrogate pair splitting in config validation hint messages shown to users. Co-Authored-By: Claude * test(config): prove UTF-16 allowed-value hint boundary --------- Co-authored-by: Claude Co-authored-by: Peter Steinberger --- src/config/allowed-values.test.ts | 11 +++++++++++ src/config/allowed-values.ts | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) 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 {