Skip to content

Commit 7dd3d63

Browse files
wm0018steipete
authored andcommitted
fix(agents): keep exec auto-reviewer rationale truncation UTF-16 safe (openclaw#101513)
* fix(agents): keep exec auto-reviewer rationale truncation UTF-16 safe String.prototype.slice at offset 500 can split surrogate pairs in LLM-generated review rationale text, rendering broken U+FFFD in exec approval UI payloads. Replace raw slice(0, 500) with truncateUtf16Safe. * fix(agents): keep exec reviewer rationale and prompt template description truncation UTF-16 safe String.prototype.slice can split surrogate pairs at truncation boundaries, producing broken U+FFFD in both exec approval rationale (500 chars) and session prompt-template descriptions (60 chars). Replace raw slice with truncateUtf16Safe in both locations. * test(exec): cover UTF-16-safe reviewer rationale --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
1 parent 961640f commit 7dd3d63

2 files changed

Lines changed: 20 additions & 1 deletion

File tree

src/agents/exec-auto-reviewer.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,24 @@ describe("parseExecAutoReviewResponse", () => {
118118
});
119119
}
120120
});
121+
122+
it("does not split surrogate pairs when truncating rationale", () => {
123+
const rationale = "x".repeat(499) + "🚀tail";
124+
125+
expect(
126+
parseExecAutoReviewResponse(
127+
JSON.stringify({
128+
decision: "ask",
129+
risk: "medium",
130+
rationale,
131+
}),
132+
),
133+
).toEqual({
134+
decision: "ask",
135+
risk: "medium",
136+
rationale: "x".repeat(499),
137+
});
138+
});
121139
});
122140

123141
describe("createModelExecAutoReviewer", () => {

src/agents/exec-auto-reviewer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77
import { resolveTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion";
88
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
9+
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
910
import { z } from "zod";
1011
import type { AgentModelConfig } from "../config/types.agents-shared.js";
1112
import type { OpenClawConfig } from "../config/types.openclaw.js";
@@ -76,7 +77,7 @@ function buildReviewerUserPrompt(input: ExecAutoReviewInput): string {
7677

7778
function normalizeRationale(value: unknown, fallback: string): string {
7879
const text = normalizeOptionalString(typeof value === "string" ? value : undefined);
79-
return (text ?? fallback).slice(0, 500);
80+
return truncateUtf16Safe(text ?? fallback, 500);
8081
}
8182

8283
function textLooksLikeReviewerDirective(value: string): boolean {

0 commit comments

Comments
 (0)