From 9301398f8b0a853d0374696895c30747938435f3 Mon Sep 17 00:00:00 2001 From: Muhammed Mukhthar CM <56378562+mukhtharcm@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:52:41 +0530 Subject: [PATCH] fix: seal mattermost preview finalization --- CHANGELOG.md | 2 +- extensions/mattermost/src/mattermost/monitor.test.ts | 5 +++++ extensions/mattermost/src/mattermost/monitor.ts | 8 +++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05f1a03d191..9601ec56a73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai - Plugins/tasks: add a detached runtime registration contract so plugin executors can own detached task lifecycle and cancellation without reaching into core task internals. (#68915) Thanks @mbelinky. - Terminal/logging: optimize `sanitizeForLog()` by replacing the iterative control-character stripping loop with a single regex pass while preserving the existing ANSI-first sanitization behavior. (#67205) Thanks @bulutmuf. - QA/CI: make `openclaw qa suite` and `openclaw qa telegram` fail by default when scenarios fail, add `--allow-failures` for artifact-only runs, and tighten live-lane defaults for CI automation. (#69122) Thanks @joshavant. +- Mattermost: stream thinking, tool activity, and partial reply text into a single draft preview post that finalizes in place when safe. (#47838) thanks @ninjaa. ### Fixes @@ -256,7 +257,6 @@ Docs: https://docs.openclaw.ai - OpenAI Codex/models: add forward-compat support for `gpt-5.4-pro`, including Codex pricing/limits and list/status visibility before the upstream catalog catches up. (#66453) Thanks @jepson-liu. - Telegram/forum topics: surface human topic names in agent context, prompt metadata, and plugin hook metadata by learning names from Telegram forum service messages. (#65973) Thanks @ptahdunbar. -- Mattermost: stream thinking, tool activity, and partial reply text into a single draft preview post that finalizes in place when safe. (#47838) thanks @ninjaa. ### Fixes diff --git a/extensions/mattermost/src/mattermost/monitor.test.ts b/extensions/mattermost/src/mattermost/monitor.test.ts index fabd2c77e89..4369437c15c 100644 --- a/extensions/mattermost/src/mattermost/monitor.test.ts +++ b/extensions/mattermost/src/mattermost/monitor.test.ts @@ -66,6 +66,7 @@ function createDraftStreamMock(postId: string | undefined = "preview-post-1") { flush: vi.fn(async () => {}), postId: vi.fn(() => postId), clear: vi.fn(async () => {}), + stop: vi.fn(async () => {}), }; } @@ -315,6 +316,10 @@ describe("deliverMattermostReplyWithDraftPreview", () => { "preview-post-1", expect.objectContaining({ message: "Final answer" }), ); + expect(draftStream.stop).toHaveBeenCalledTimes(1); + expect(draftStream.stop.mock.invocationCallOrder[0]).toBeLessThan( + updateMattermostPostSpy.mock.invocationCallOrder[0] ?? Number.POSITIVE_INFINITY, + ); expect(deliverFinal).not.toHaveBeenCalled(); expect(draftStream.clear).not.toHaveBeenCalled(); }); diff --git a/extensions/mattermost/src/mattermost/monitor.ts b/extensions/mattermost/src/mattermost/monitor.ts index 90826dc49b0..8696ec337ae 100644 --- a/extensions/mattermost/src/mattermost/monitor.ts +++ b/extensions/mattermost/src/mattermost/monitor.ts @@ -274,7 +274,10 @@ type MattermostDraftPreviewDeliverParams = { payload: ReplyPayload; info: { kind: "tool" | "block" | "final" }; client: MattermostClient; - draftStream: Pick, "flush" | "postId" | "clear">; + draftStream: Pick< + ReturnType, + "flush" | "postId" | "clear" | "stop" + >; effectiveReplyToId?: string; resolvePreviewFinalText: (text?: string) => string | undefined; previewState: MattermostDraftPreviewState; @@ -310,6 +313,9 @@ export async function deliverMattermostReplyWithDraftPreview( }) ) { try { + // Seal the preview before the final edit so late draft events cannot + // patch over the finalized visible message. + await params.draftStream.stop(); await updateMattermostPost(params.client, previewPostId, { message: previewFinalText, });