Skip to content

Commit 7c3661d

Browse files
NIOcursoragent
andcommitted
fix(slack): keep thread label snippet truncation UTF-16 safe
Replace raw .slice(0, 80) thread label snippets with truncateUtf16Safe so emoji straddling the cap are dropped whole instead of leaving unpaired surrogates in Slack thread labels. Co-authored-by: Cursor <[email protected]>
1 parent 94da0a9 commit 7c3661d

3 files changed

Lines changed: 12 additions & 2 deletions

File tree

extensions/slack/src/monitor/message-handler/prepare-thread-context-root.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,4 +244,11 @@ describe("formatSlackBotStarterThreadLabel", () => {
244244
}),
245245
).toBe("Slack thread DM (assistant root): Line one Line two");
246246
});
247+
248+
it("drops a surrogate-pair emoji whole when it straddles the 80-char snippet limit", () => {
249+
const starterText = `${"a".repeat(79)}🐱tail`;
250+
const label = formatSlackBotStarterThreadLabel({ roomLabel: "DM", starterText });
251+
expect(label).toBe(`Slack thread DM (assistant root): ${"a".repeat(79)}`);
252+
expect(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/.test(label)).toBe(false);
253+
});
247254
});

extensions/slack/src/monitor/message-handler/prepare-thread-context-root.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// Slack plugin module implements prepare thread context root behavior.
2+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
3+
24
export type SlackBotAuthorIdentity = {
35
botUserId?: string;
46
botId?: string;
@@ -107,7 +109,7 @@ export function formatSlackBotStarterThreadLabel(params: {
107109
if (!params.starterText) {
108110
return base;
109111
}
110-
const snippet = params.starterText.replace(/\s+/g, " ").slice(0, 80).trim();
112+
const snippet = truncateUtf16Safe(params.starterText.replace(/\s+/g, " "), 80).trim();
111113
if (!snippet) {
112114
return base;
113115
}

extensions/slack/src/monitor/message-handler/prepare-thread-context.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
filterSupplementalContextItems,
99
shouldIncludeSupplementalContext,
1010
} from "openclaw/plugin-sdk/security-runtime";
11+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
1112
import type { ResolvedSlackAccount } from "../../accounts.js";
1213
import type { SlackMessageEvent } from "../../types.js";
1314
import { resolveSlackAllowListMatch } from "../allow-list.js";
@@ -224,7 +225,7 @@ export async function resolveSlackThreadContextData(params: {
224225

225226
if (starter?.text && includeStarterContext) {
226227
threadStarterBody = starter.text;
227-
const snippet = starter.text.replace(/\s+/g, " ").slice(0, 80);
228+
const snippet = truncateUtf16Safe(starter.text.replace(/\s+/g, " "), 80);
228229
threadLabel = `Slack thread ${params.roomLabel}${snippet ? `: ${snippet}` : ""}`;
229230
// Root media seeds a new thread session once. Rehydrating it later makes
230231
// old files look like current-turn uploads and repeats media processing.

0 commit comments

Comments
 (0)