fix(imessage): drop dangling surrogates in debounced merge preview and probe snippet#98065
fix(imessage): drop dangling surrogates in debounced merge preview and probe snippet#98065wangmiao0668000666 wants to merge 1 commit into
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 30, 2026, 3:21 AM ET / 07:21 UTC. Summary PR surface: Source +4, Tests +70. Total +74 across 4 files. Reproducibility: yes. at source level. Current main uses raw UTF-16 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 iMessage helper substitution and regression tests after ordinary maintainer review and required-check gating. Do we have a high-confidence way to reproduce the issue? Yes, at source level. Current main uses raw UTF-16 Is this the best way to solve the issue? Yes. Reusing the existing Plugin SDK UTF-16 helpers at the exact iMessage call sites is the narrow maintainable fix; custom local truncation logic would duplicate an established shared helper. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 56c2d637d940. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +4, Tests +70. Total +74 across 4 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/imessage/src/monitor/monitor-provider.ts:731andextensions/imessage/src/probe.ts:161use raw.slice(0, N)on inbound message bodies for debug-log previews and JSONL-parse-failure diagnostics. When an emoji or other astral character straddles positionN, the slice keeps only the high-surrogate half (0xD83Cfor🎉), producing malformed UTF-16 (?/�) in verbose logs.Note:
extensions/imessage/src/monitor/coalesce.ts:139already usessliceUtf16Safe(commit352f47f888by llagy009 on 2026-06-29). This PR covers the two remaining iMessage sites that still used raw.slice().Why This Change Was Made
The plugin SDK provides
sliceUtf16Safe/truncateUtf16Safeinopenclaw/plugin-sdk/text-utility-runtimefor exactly this pattern. Alix-007 landed the same fix for Twitch (#98008) and Signal (#97982); their PR bodies explicitly list "Discord, Feishu, Telegram, Signal, Mattermost, Slack" as already using these helpers. iMessage had one site fixed (coalesce.ts) and two still raw; this PR closes the remaining gap.The two call sites map to the two helpers:
monitor-provider.ts:731—text.slice(0, 50)followed by a conditional"..."ellipsis at L732. The conditionaltext.length > 50must keep firing against the originaltext.length, so the safe slice returns 49 when a lone surrogate is dropped (one code unit less thantext.length). →sliceUtf16Safe(text, 0, 50)keeps the existing ellipsis logic intact.probe.ts:161—lines[0]?.slice(0, 120)standalone diagnostic snippet. The optional chain becomes a ternary becausetruncateUtf16Safedoesn't acceptundefined. →lines[0] ? truncateUtf16Safe(lines[0], 120) : undefined.Evidence
Boundary probe — input is
🎉(U+1F389, surrogate pair0xD83C 0xDF89) placed immediately afterN-1ASCIIacharacters so it straddles the truncation point:🎉= U+1F389, high surrogate0xD83C, low surrogate0xDF89. The loned83chalf in the BEFORE rows is the malformed UTF-16 the previous code emits; the AFTER rows show a clean ASCII tail with the surrogate pair dropped whole.After-fix Proof
node scripts/run-vitest.mjs extensions/imessage/src/monitor/monitor-provider.truncation.test.ts extensions/imessage/src/probe.test.ts --reporter=verbosenode scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.scripts.json extensions/imessage/src/monitor/monitor-provider.ts extensions/imessage/src/probe.ts extensions/imessage/src/probe.test.ts extensions/imessage/src/monitor/monitor-provider.truncation.test.ts→ EXIT=0git diff --check openclaw/main...HEAD→ cleangit diff --numstat openclaw/main...HEAD→ 4 files changed (2 source + 2 test)Diff scope
Security & Privacy
No user-facing behavior change for ASCII input. The previews render identically for non-emoji text. For emoji-straddling input, the preview is one code unit shorter because the surrogate pair is dropped whole instead of leaving a lone high surrogate — this is the intended UTF-16 boundary fix, not a content loss.
Compatibility
Plugin SDK export (
openclaw/plugin-sdk/text-utility-runtime) is already inpackage.jsonexports for@openclaw/plugin-sdk(re-exported fromsrc/plugin-sdk/text-utility-runtime.ts). No new dependency added at the package level. The helpers are dependency-free persrc/shared/utf16-slice.ts:1-5(nonode:imports), so they bundle cleanly into both server and UI builds.What was not tested
Live iMessage message flow with a real surrogate-pair payload. The fix is a one-line surrogate-pair guard per call site; the helpers themselves are 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), plus iMessage coalesce (commit 352f47f). iMessage debounced-merge and probe are the new additions.
Related
truncateUtf16Safeon a debug-log preview.truncateUtf16Safeon a verbose-log preview.sliceUtf16Safefor the merged-text truncation marker.src/shared/utf16-slice.ts(read-only reference, already merged).