fix(voice-call): drop dangling surrogate in partial-transcript tail slice#98086
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 30, 2026, 4:27 AM ET / 08:27 UTC. Summary PR surface: Source +1, Tests +53. Total +54 across 2 files. Reproducibility: yes. Source inspection shows current main and Review metrics: none identified. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Next step before merge
Security Review detailsBest possible solution: Land the focused helper substitution after ordinary maintainer review and exact-head checks; keep sibling channel UTF-16 cleanups in their separate channel-specific PRs. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection shows current main and Is this the best way to solve the issue? Yes. Reusing the existing public AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 444a09359377. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +1, Tests +53. Total +54 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
Closing this as superseded by the broader UTF-16-safe truncation fix now on main: c16bb87. I kept this as one canonical pass instead of landing the one-off PRs separately so Browser, Discord, Feishu, iMessage, MS Teams, Signal, Twitch, and Voice Call preview/error truncation all use the shared UTF-16 helpers consistently. Thanks for flagging this surface. Proof for the landed commit:
|
What Problem This Solves
extensions/voice-call/src/webhook/realtime-handler.ts:157uses rawtext.slice(-MAX_PARTIAL_USER_TRANSCRIPT_CHARS)inlimitPartialUserTranscript()to take the tail of a partial user transcript before sending it to the agent. When an emoji or other astral character straddles the tail-start boundary, the slice keeps only the LOW-surrogate half (e.g.0xDF89for🎉), producing malformed UTF-16 (?/�) in the agent prompt.This is the same pattern that Alix-007 fixed for Twitch (#98008), Signal (#97982), and that we just fixed for msteams (#98058) and imessage (#98065). Voice-call was the remaining channel surface using raw
.slice()on a message body.Why This Change Was Made
The plugin SDK provides
sliceUtf16Safeinopenclaw/plugin-sdk/text-utility-runtimefor exactly this pattern. Its negative-start handling (start < 0clamps tolen + start, equivalent to a tail slice) AND its boundary surrogate-pair drop make it a drop-in replacement fortext.slice(-N).The shape mirrors our msteams PR (#98058) but is even smaller — 1 file, 1 import, 1 line replacement.
Evidence
Boundary probe — input is
🎉(U+1F389, surrogate pair0xD83C 0xDF89) placed so the LOW surrogate lands at the tail-start boundary:The first row is the bug —
🎉straddles the boundary at position 100/101, andslice(-200)returns the LOW surrogate half (0xDF89) at the start of the tail.sliceUtf16Safe(s, -200)detects this (input.charCodeAt(100) is the high surrogate, input.charCodeAt(101) is the low surrogate) and bumpsfromto 102, dropping the surrogate pair whole and returning a 199-char clean ASCII tail.After-fix Proof
node scripts/run-vitest.mjs extensions/voice-call/src/webhook/realtime-handler.transcript-utf16.test.ts --reporter=verbosenode scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.scripts.json extensions/voice-call/src/webhook/realtime-handler.ts extensions/voice-call/src/webhook/realtime-handler.transcript-utf16.test.ts→ EXIT=0git diff --check openclaw/main...HEAD→ cleanDiff scope
Security & Privacy
No user-facing behavior change for ASCII partial transcripts — the tail passes through verbatim. For emoji-straddling transcripts, the tail is one code unit shorter because the surrogate pair is dropped whole instead of leaving a lone surrogate half — this is the intended UTF-16 boundary fix, not a content loss. The partial-transcript cap itself (
MAX_PARTIAL_USER_TRANSCRIPT_CHARS) is unchanged.Compatibility
Plugin SDK export (
openclaw/plugin-sdk/text-utility-runtime) is already inpackage.jsonexports for@openclaw/plugin-sdk. The helper is dependency-free persrc/shared/utf16-slice.ts:1-5(nonode:imports), so it bundles cleanly into both server and UI builds.What was not tested
Live voice-call session with a real partial transcript that straddles a surrogate pair. The fix is a one-line surrogate-pair guard; the helper is covered by unit tests in
src/shared/utf16-slice.ts. Integration coverage at this layer is intentionally out of scope — Alix-007's PRs #98008 (Twitch) and #97982 (Signal) followed the same boundary and were accepted on the same evidence.Other channel plugins that already use the helper
Discord, Feishu, Telegram, Signal (#97982), Mattermost, Slack, Twitch (#98008), msteams (#98058), iMessage (#98065). voice-call is the new addition.
Related
text.slice(0, 100)debug-log preview.body.slice(0, 200)verbose-log preview.text.slice(0, 50)debug log +slice(0, 160)system event label.text.slice(0, 50)debounced merge preview +lines[0]?.slice(0, 120)probe snippet.src/shared/utf16-slice.ts(read-only reference, already merged).