mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
fix(talk): use truncateUtf16Safe for LLM-prompt-facing text truncation (#102477)
* fix(talk): use truncateUtf16Safe for snippet text truncation fast-context-runtime.ts normalizeSnippet and heartbeat-events-filter.ts buildExecEventPrompt used raw .slice(0, N), which can produce lone surrogates when truncation boundaries cross emoji surrogate pairs. * test(talk): cover UTF-16 snippet boundary --------- Co-authored-by: hailory <hailory@xydigit.com> Co-authored-by: Peter Steinberger <steipete@gmail.com> Co-authored-by: Peter Steinberger <peter@steipete.me>
This commit is contained in:
@@ -79,4 +79,44 @@ describe("resolveRealtimeVoiceFastContextConsult", () => {
|
||||
);
|
||||
expect(vi.getTimerCount()).toBe(0);
|
||||
});
|
||||
|
||||
it("does not split a surrogate pair at the fast-context snippet limit", async () => {
|
||||
const safePrefix = "x".repeat(698);
|
||||
mocks.getActiveMemorySearchManager.mockResolvedValue({
|
||||
manager: {
|
||||
search: vi.fn().mockResolvedValue([
|
||||
{
|
||||
path: "memory/test.md",
|
||||
startLine: 1,
|
||||
endLine: 1,
|
||||
snippet: `${safePrefix}🚀tail`,
|
||||
source: "memory",
|
||||
score: 1,
|
||||
},
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
const result = await resolveRealtimeVoiceFastContextConsult({
|
||||
cfg: {},
|
||||
agentId: "main",
|
||||
sessionKey: "voice:15550001234",
|
||||
config: {
|
||||
enabled: true,
|
||||
timeoutMs: 1_000,
|
||||
maxResults: 1,
|
||||
sources: ["memory"],
|
||||
fallbackToConsult: true,
|
||||
},
|
||||
args: { question: "What do you remember?" },
|
||||
logger: {},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
handled: true,
|
||||
result: {
|
||||
text: expect.stringContaining(`1. [memory] memory/test.md:1-1\n${safePrefix}...`),
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* back to the normal consult flow.
|
||||
*/
|
||||
import { resolveTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion";
|
||||
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { getActiveMemorySearchManager } from "../plugins/memory-runtime.js";
|
||||
@@ -69,7 +70,7 @@ function normalizeSnippet(text: string): string {
|
||||
}
|
||||
// Keep individual memory snippets bounded so several hits still fit in a
|
||||
// short realtime response prompt.
|
||||
return `${normalized.slice(0, MAX_SNIPPET_CHARS - 1).trimEnd()}...`;
|
||||
return `${truncateUtf16Safe(normalized, MAX_SNIPPET_CHARS - 1).trimEnd()}...`;
|
||||
}
|
||||
|
||||
function buildSearchQuery(args: unknown): string {
|
||||
|
||||
Reference in New Issue
Block a user