





















@@ -19,6 +19,11 @@ import { msteamsRuntimeStub } from "./test-support/runtime.js";
19192020const previousStateDir = process.env.OPENCLAW_STATE_DIR;
212122+// Matches an unpaired UTF-16 surrogate (lone high or lone low), without relying
23+// on the ES2024 String.prototype.isWellFormed() runtime API.
24+const UNPAIRED_SURROGATE_RE =
25+/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
26+2227describe("buildFeedbackEvent", () => {
2328it("builds a well-formed custom event", () => {
2429const event = buildFeedbackEvent({
@@ -73,6 +78,26 @@ describe("buildReflectionPrompt", () => {
7378expect(prompt.length).toBeLessThan(longResponse.length + 500);
7479});
758081+it("does not split UTF-16 surrogate pairs when truncating a thumbed-down response", () => {
82+const thumbedDownResponse = `${"a".repeat(499)}🦞${"b".repeat(20)}`;
83+84+const prompt = buildReflectionPrompt({ thumbedDownResponse });
85+86+expect(prompt).not.toMatch(UNPAIRED_SURROGATE_RE);
87+expect(prompt).toContain(`${"a".repeat(499)}...`);
88+expect(prompt).not.toContain("\ud83e");
89+expect(prompt).not.toContain("\udd9e");
90+});
91+92+it("keeps a boundary emoji when it fully fits before the truncation cap", () => {
93+const thumbedDownResponse = `${"a".repeat(498)}🦞${"b".repeat(20)}`;
94+95+const prompt = buildReflectionPrompt({ thumbedDownResponse });
96+97+expect(prompt).not.toMatch(UNPAIRED_SURROGATE_RE);
98+expect(prompt).toContain(`${"a".repeat(498)}🦞...`);
99+});
100+76101it("includes user comment when provided", () => {
77102const prompt = buildReflectionPrompt({
78103thumbedDownResponse: "Some response",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。