diff --git a/src/agents/exec-auto-reviewer.test.ts b/src/agents/exec-auto-reviewer.test.ts index 1e6752fe6496..6009f68c4410 100644 --- a/src/agents/exec-auto-reviewer.test.ts +++ b/src/agents/exec-auto-reviewer.test.ts @@ -118,6 +118,24 @@ describe("parseExecAutoReviewResponse", () => { }); } }); + + it("does not split surrogate pairs when truncating rationale", () => { + const rationale = "x".repeat(499) + "🚀tail"; + + expect( + parseExecAutoReviewResponse( + JSON.stringify({ + decision: "ask", + risk: "medium", + rationale, + }), + ), + ).toEqual({ + decision: "ask", + risk: "medium", + rationale: "x".repeat(499), + }); + }); }); describe("createModelExecAutoReviewer", () => { diff --git a/src/agents/exec-auto-reviewer.ts b/src/agents/exec-auto-reviewer.ts index a0da89591f8e..d8ecc1fc3935 100644 --- a/src/agents/exec-auto-reviewer.ts +++ b/src/agents/exec-auto-reviewer.ts @@ -6,6 +6,7 @@ */ import { resolveTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; +import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice"; import { z } from "zod"; import type { AgentModelConfig } from "../config/types.agents-shared.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; @@ -76,7 +77,7 @@ function buildReviewerUserPrompt(input: ExecAutoReviewInput): string { function normalizeRationale(value: unknown, fallback: string): string { const text = normalizeOptionalString(typeof value === "string" ? value : undefined); - return (text ?? fallback).slice(0, 500); + return truncateUtf16Safe(text ?? fallback, 500); } function textLooksLikeReviewerDirective(value: string): boolean {