fix(slack): use truncateUtf16Safe for message body preview truncation (#102612)

* fix(slack): use truncateUtf16Safe for message body preview truncation

One .slice(0, 160) truncation site in the Slack message handler may
cut UTF-16 surrogate pairs in half when the message body preview
contains multi-byte characters such as emoji. Replace it with
truncateUtf16Safe to keep the truncated output valid Unicode.

* test(slack): cover UTF-16 preview boundary

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
lzyyzznl 2026-07-09 19:37:49 +08:00 committed by GitHub
parent 0bdd646fc2
commit 0aae1ea816
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 10 additions and 1 deletions

View file

@ -188,6 +188,14 @@ describe("slack prepareSlackMessage inbound contract", () => {
expect(prepared.ctxPayload.BodyForAgent).toContain(body);
});
it("keeps a whole code point when the inbound preview boundary crosses an emoji", async () => {
const prefix = "a".repeat(159);
const prepared = await prepareWithDefaultCtx(createSlackMessage({ text: `${prefix}😀tail` }));
assertPrepared(prepared);
expect(prepared.preview).toBe(prefix);
});
it("logs inbound metadata without logging message content", async () => {
const body = "confidential acquisition target: northstar; do not include this text in logs";
shouldLogVerboseMock.mockReturnValue(true);

View file

@ -35,6 +35,7 @@ import {
normalizeOptionalString,
} from "openclaw/plugin-sdk/string-coerce-runtime";
import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
import { resolveSlackReplyToMode } from "../../account-reply-mode.js";
import type { ResolvedSlackAccount } from "../../accounts.js";
import { reactSlackMessage } from "../../actions.js";
@ -1210,7 +1211,7 @@ export async function prepareSlackMessage(params: {
const roomLabel = channelName ? `#${channelName}` : `#${message.channel}`;
const senderName = await resolveSenderName();
const preview = rawBody.replace(/\s+/g, " ").slice(0, 160);
const preview = truncateUtf16Safe(rawBody.replace(/\s+/g, " "), 160);
const inboundLabel = isDirectMessage
? `Slack DM from ${senderName}`
: `Slack message in ${roomLabel} from ${senderName}`;