From f77c2aafa09598cf378effc5bca7830f82cc2727 Mon Sep 17 00:00:00 2001 From: NIO Date: Thu, 9 Jul 2026 11:05:03 +0800 Subject: [PATCH] fix(mattermost): truncate inbound preview on code-point boundary (#101630) Co-authored-by: NIO Co-authored-by: Cursor --- .../monitor.inbound-system-event.test.ts | 52 ++++++++++++++++++- .../mattermost/src/mattermost/monitor.ts | 3 +- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/extensions/mattermost/src/mattermost/monitor.inbound-system-event.test.ts b/extensions/mattermost/src/mattermost/monitor.inbound-system-event.test.ts index c8dde3d59a3..19d89a1c5a1 100644 --- a/extensions/mattermost/src/mattermost/monitor.inbound-system-event.test.ts +++ b/extensions/mattermost/src/mattermost/monitor.inbound-system-event.test.ts @@ -159,6 +159,7 @@ function createRuntimeCore( shouldHandleTextCommands?: () => boolean; textHasControlCommand?: (text?: string) => boolean; createInboundDebouncer?: typeof createInboundDebouncer; + verboseDebug?: (message: string) => void; } = {}, ) { const dispatchPreparedForTest = vi.fn( @@ -223,9 +224,9 @@ function createRuntimeCore( current: () => cfg, }, logging: { - shouldLogVerbose: () => false, + shouldLogVerbose: () => Boolean(overrides.verboseDebug), getChildLogger: () => ({ - debug: vi.fn(), + debug: overrides.verboseDebug ?? vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), @@ -447,6 +448,53 @@ describe("mattermost inbound user posts", () => { expect(ctx?.Provider).toBe("mattermost"); }); + it("keeps verbose inbound previews on complete UTF-16 boundaries", async () => { + const socket = new FakeWebSocket(); + const abortController = new AbortController(); + const verboseDebug = vi.fn(); + mockState.abortController = abortController; + mockState.runtimeCore = createRuntimeCore(testConfig, undefined, { verboseDebug }); + + const monitor = monitorMattermostProvider({ + config: testConfig, + runtime: testRuntime(), + abortSignal: abortController.signal, + webSocketFactory: () => socket, + }); + + await vi.waitFor(() => { + expect(socket.openListenerCount).toBeGreaterThan(0); + }); + socket.emitOpen(); + + await socket.emitMessage({ + event: "posted", + data: { + channel_id: "chan-1", + channel_name: "town-square", + channel_display_name: "Town Square", + sender_name: "alice", + post: JSON.stringify({ + id: "post-verbose-preview", + channel_id: "chan-1", + user_id: "user-1", + message: `${"a".repeat(199)}😀tail`, + create_at: 1_714_000_000_000, + }), + }, + broadcast: { + channel_id: "chan-1", + user_id: "user-1", + }, + }); + socket.emitClose(1000); + await monitor; + + expect(verboseDebug).toHaveBeenCalledWith( + `mattermost inbound: from=mattermost:channel:chan-1 len=205 preview="${"a".repeat(199)}"`, + ); + }); + it("dispatches a bare bot mention whose body is empty after normalization as a wake event", async () => { const socket = new FakeWebSocket(); const abortController = new AbortController(); diff --git a/extensions/mattermost/src/mattermost/monitor.ts b/extensions/mattermost/src/mattermost/monitor.ts index 7abb09bdf5f..aafce12ac72 100644 --- a/extensions/mattermost/src/mattermost/monitor.ts +++ b/extensions/mattermost/src/mattermost/monitor.ts @@ -24,6 +24,7 @@ import { normalizeTrimmedStringList, uniqueStrings, } from "openclaw/plugin-sdk/string-coerce-runtime"; +import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime"; import { getMattermostRuntime } from "../runtime.js"; import { resolveMattermostAccount, @@ -1677,7 +1678,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} agentId: route.agentId, }); - const previewLine = bodyText.slice(0, 200).replace(/\n/g, "\\n"); + const previewLine = truncateUtf16Safe(bodyText, 200).replace(/\n/g, "\\n"); logVerboseMessage( `mattermost inbound: from=${ctxPayload.From} len=${bodyText.length} preview="${previewLine}"`, );