Skip to content

Commit 8e11262

Browse files
maweibinclaude
andcommitted
fix(agents): keep emoji / surrogate pairs intact during error observation truncation
Use truncateUtf16Safe() instead of .slice() to count full code points. Co-Authored-By: Claude Opus 4.8 <[email protected]>
1 parent 775ef96 commit 8e11262

2 files changed

Lines changed: 16 additions & 4 deletions

File tree

src/agents/embedded-agent-error-observation.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,14 @@ describe("sanitizeForConsole", () => {
207207
expect(sanitizeForConsole("run-1\nprovider\tmodel\rtest")).toBe("run-1 provider model test");
208208
});
209209
});
210+
211+
describe("observation truncation", () => {
212+
it("does not split surrogate pairs", async () => {
213+
const { truncateUtf16Safe } = await import("@openclaw/normalization-core/utf16-slice");
214+
const content = "x".repeat(77) + "🚀tail";
215+
const bad = content.slice(0, 78);
216+
expect(bad.endsWith("\uD83D")).toBe(true);
217+
const good = truncateUtf16Safe(content, 78);
218+
expect(good).toBe("x".repeat(77));
219+
});
220+
});

src/agents/embedded-agent-error-observation.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
/**
2-
* Builds structured observations for embedded-agent API/text failures.
3-
*/
41
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
52
import { readLoggingConfig } from "../logging/config.js";
63
import { redactIdentifier } from "../logging/redact-identifier.js";
74
import { getDefaultRedactPatterns, redactSensitiveText } from "../logging/redact.js";
5+
/**
6+
* Builds structured observations for embedded-agent API/text failures.
7+
*/
8+
import { truncateUtf16Safe } from "../utils.js";
89
import {
910
classifyProviderRuntimeFailureKind,
1011
getApiErrorPayloadFingerprint,
@@ -43,7 +44,7 @@ function truncateForObservation(text: string | undefined, maxChars: number): str
4344
if (!trimmed) {
4445
return undefined;
4546
}
46-
return trimmed.length > maxChars ? `${trimmed.slice(0, maxChars)}…` : trimmed;
47+
return trimmed.length > maxChars ? `${truncateUtf16Safe(trimmed, maxChars)}…` : trimmed;
4748
}
4849

4950
function boundObservationInput(text: string | undefined): string | undefined {

0 commit comments

Comments
 (0)