Files
openclaw/extensions/slack/src/truncate.ts
ly-wang19 1069c60e1e fix(slack): truncate on code-point boundaries to avoid splitting surrogate pairs (#96382)
truncateSlackText sliced by UTF-16 code unit ('trimmed.slice(0, max - 1)'), so an
emoji or other astral character straddling the limit was cut in half, leaving a
lone high surrogate before the ellipsis — e.g. truncateSlackText('abc😀def', 5)
returned 'abc\uD83D…' instead of 'abc…'. That invalid half-character is sent in
live Slack payloads (message text and Block Kit section/button/header/option
labels, which truncate at limits as small as 75).

Use the repo's canonical sliceUtf16Safe (already re-exported from
plugin-sdk/text-utility-runtime, the module slack code imports from) so a
straddling pair is dropped whole. Behavior is byte-identical for all-BMP input.

Co-authored-by: ly-wang19 <ly-wang19@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:30:29 +08:00

17 lines
639 B
TypeScript

// Slack plugin module implements truncate behavior.
import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
export function truncateSlackText(value: string, max: number): string {
const trimmed = value.trim();
if (trimmed.length <= max) {
return trimmed;
}
// Slice on a code-point boundary so a surrogate pair (emoji / astral char)
// straddling the limit is dropped whole, instead of leaving a lone surrogate
// half that serializes to an invalid `\uD83D` in the Slack payload.
if (max <= 1) {
return sliceUtf16Safe(trimmed, 0, max);
}
return `${sliceUtf16Safe(trimmed, 0, max - 1)}`;
}