From 9c9010efcaf91bd2fc4c80c44f45e89218df4a4d Mon Sep 17 00:00:00 2001 From: mushuiyu886 Date: Sat, 11 Jul 2026 00:42:25 +0800 Subject: [PATCH] fix(diagnostics-otel): preserve JSON Unicode boundaries (#103646) * fix(diagnostics-otel): preserve JSON Unicode boundaries * test(diagnostics-otel): consolidate Unicode boundary coverage * docs(changelog): credit OTEL Unicode fix --------- Co-authored-by: Peter Steinberger --- CHANGELOG.md | 2 +- extensions/diagnostics-otel/src/service.test.ts | 10 ++++++++-- extensions/diagnostics-otel/src/service.ts | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9df6dfd66dbd..652925d3d15a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,7 +49,7 @@ Docs: https://docs.openclaw.ai - **Apple timeout recovery:** return promptly from shared operation deadlines and caller cancellation even when platform work ignores cancellation, while isolating late Gateway handshakes and cleaning up location and permission waiters. (#103066) Thanks @NianJiuZst. - **Claude CLI warm sessions:** preserve managed stdio continuity when Claude writes no native transcript, fall back to bounded OpenClaw history only when the exact live child disappears or changes, and keep stateless runs from persisting CLI bindings. (#96841) Thanks @bradreaves. - **CLI plugin listing:** skip state-migration runtime loading when no legacy inputs exist, reducing packaged cold-start memory while preserving migrations for legacy plugin indexes and configured session stores. -- **Unicode-safe bounded text:** preserve complete UTF-16 surrogate pairs when shortening previews, prompts, diagnostics, labels, session keys, link metadata, and identity values across Control UI, CLI, Gateway, plugins, QA, memory, and Android surfaces. (#102625, #102626, #102627, #102656, #102816, #102823, #102833, #102877, #102949, #102963, #102969, #102988, #103010, #103034, #103210, #103341, #103487, #103543, #103580) Thanks @zhangguiping-xydt, @wings1029, @wangyan2026, @Pandah97, @MoerAI, @SunnyShu0925, @zhangqueping, @zw-xysk, @cxbAsDev, @lzyyzznl, @coder-master-0915, @LeonidasLux, @mushuiyu886, @ly85206559, @Simon-XYDT, and @lsr911. +- **Unicode-safe bounded text:** preserve complete UTF-16 surrogate pairs when shortening previews, prompts, diagnostics, labels, session keys, link metadata, and identity values across Control UI, CLI, Gateway, plugins, QA, memory, and Android surfaces. (#102625, #102626, #102627, #102656, #102816, #102823, #102833, #102877, #102949, #102963, #102969, #102988, #103010, #103034, #103210, #103341, #103487, #103543, #103580, #103646) Thanks @zhangguiping-xydt, @wings1029, @wangyan2026, @Pandah97, @MoerAI, @SunnyShu0925, @zhangqueping, @zw-xysk, @cxbAsDev, @lzyyzznl, @coder-master-0915, @LeonidasLux, @mushuiyu886, @ly85206559, @Simon-XYDT, and @lsr911. - **CLI model tables:** sanitize, truncate, and pad model-list cells by rendered terminal width so emoji, CJK, and other wide graphemes keep columns aligned. (#102819) Thanks @Kevin23-design and @vincentkoc. - **Skills prompt compaction:** preserve every included skill identity before using the remaining prompt budget for shortened, UTF-16-safe descriptions, retaining trigger guidance without exceeding the hard limit. (#88426) Thanks @abel-zer0. - **Channel Markdown code tables:** size columns by rendered display width so CJK, emoji, and mixed-width cells stay aligned across shared Telegram and Discord output. (#55596, #55512) Thanks @sparkyrider. diff --git a/extensions/diagnostics-otel/src/service.test.ts b/extensions/diagnostics-otel/src/service.test.ts index 5324c326f4db..050f48d40441 100644 --- a/extensions/diagnostics-otel/src/service.test.ts +++ b/extensions/diagnostics-otel/src/service.test.ts @@ -5321,6 +5321,9 @@ describe("diagnostics-otel service", () => { }); await service.start(ctx); + // The 8,192-character candidate budget leaves an 8,178-character text prefix; + // place a surrogate pair across that boundary so serialized JSON must stay valid. + const surrogateBoundaryPrefix = "x".repeat(8177); emitTrustedModelCallCompletedWithContent( { runId: "run-1", @@ -5333,7 +5336,9 @@ describe("diagnostics-otel service", () => { inputMessages: [ { role: "user", - content: `single-message-${"x".repeat(MAX_TEST_OTEL_CONTENT_ATTRIBUTE_CHARS)}`, + content: `${surrogateBoundaryPrefix}🚀${"y".repeat( + MAX_TEST_OTEL_CONTENT_ATTRIBUTE_CHARS, + )}`, }, ], toolDefinitions: [ @@ -5364,13 +5369,14 @@ describe("diagnostics-otel service", () => { const toolDefinitions = stringAttribute(attrs, "gen_ai.tool.definitions"); expect(genAiInput.length).toBeLessThanOrEqual(MAX_TEST_OTEL_CONTENT_ATTRIBUTE_CHARS); expect(toolDefinitions.length).toBeLessThanOrEqual(MAX_TEST_OTEL_CONTENT_ATTRIBUTE_CHARS); + expect(genAiInput).not.toContain("\\ud83d"); expect(JSON.parse(genAiInput)).toEqual([ { role: "user", parts: [ { type: "text", - content: expect.stringContaining("single-message-"), + content: `${surrogateBoundaryPrefix}...(truncated)`, }, ], }, diff --git a/extensions/diagnostics-otel/src/service.ts b/extensions/diagnostics-otel/src/service.ts index 8cf12549b1c4..cea8ae81334a 100644 --- a/extensions/diagnostics-otel/src/service.ts +++ b/extensions/diagnostics-otel/src/service.ts @@ -935,7 +935,7 @@ function truncateJsonTextForOtelAttribute(value: string, maxChars: number): stri } const suffixBudget = Math.min(TRUNCATED_JSON_TEXT_SUFFIX.length, maxChars); const prefixBudget = Math.max(0, maxChars - suffixBudget); - return `${redacted.slice(0, prefixBudget)}${TRUNCATED_JSON_TEXT_SUFFIX.slice( + return `${truncateUtf16Safe(redacted, prefixBudget)}${TRUNCATED_JSON_TEXT_SUFFIX.slice( TRUNCATED_JSON_TEXT_SUFFIX.length - suffixBudget, )}`; }