From be438cf887ce113ea939bcddfe4b0c75d2c0751e Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 19:11:13 -0700 Subject: [PATCH] fix(mattermost): suppress draft progress chatter --- CHANGELOG.md | 1 + .../mattermost/src/mattermost/monitor.test.ts | 32 +++++++++++++++++++ .../mattermost/src/mattermost/monitor.ts | 11 +++++++ 3 files changed, 44 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb49f5e790d..685bb183318 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ Docs: https://docs.openclaw.ai - Discord/Slack/Mattermost: align draft preview tool-progress config help with the runtime behavior that hides interim tool updates when `streaming.preview.toolProgress` is false. Thanks @vincentkoc. - Feishu: use the shared channel progress formatter for streaming-card tool status lines, including raw command/detail output and message-tool filtering. Thanks @vincentkoc. - Mattermost: use the shared progress draft formatter for tool status previews, including raw command/detail output when `agents.defaults.toolProgressDetail: "raw"` is enabled. Thanks @vincentkoc. +- Mattermost: suppress standalone default tool-progress messages while draft previews are active, including when draft tool lines are disabled. Thanks @vincentkoc. - OpenAI Codex: honor `auth.order.openai-codex` when starting app-server clients without an explicit auth profile, so status/model probes and implicit startup use the configured Codex account instead of falling back to the default profile. Thanks @vincentkoc. - OpenAI Codex: let SSRF-guarded provider requests inherit OpenClaw's undici IPv4/IPv6 fallback policy, so ChatGPT-backed Codex runs recover on IPv4-working hosts when DNS still returns unreachable IPv6 addresses. Fixes #76857. Thanks @jplavoiemtl and @SymbolStar. - Gateway/systemd: preserve operator-added secrets in the Gateway env file across re-stage while clearing OpenClaw-managed keys (such as `OPENCLAW_GATEWAY_TOKEN`) so a fresh staging value is never shadowed by a stale env-file copy; operator secrets are also retained when the state-dir `.env` is empty. Fixes #76860. Thanks @hclsys. diff --git a/extensions/mattermost/src/mattermost/monitor.test.ts b/extensions/mattermost/src/mattermost/monitor.test.ts index 7e72b3a844e..9a61e4c19b5 100644 --- a/extensions/mattermost/src/mattermost/monitor.test.ts +++ b/extensions/mattermost/src/mattermost/monitor.test.ts @@ -17,6 +17,7 @@ import { resolveMattermostThreadSessionContext, shouldFinalizeMattermostPreviewAfterDispatch, shouldClearMattermostDraftPreview, + shouldSuppressMattermostDefaultToolProgressMessages, shouldUpdateMattermostDraftToolProgress, type MattermostMentionGateInput, type MattermostRequireMentionResolverInput, @@ -314,6 +315,37 @@ describe("shouldUpdateMattermostDraftToolProgress", () => { }); }); +describe("shouldSuppressMattermostDefaultToolProgressMessages", () => { + type MattermostConfig = NonNullable["mattermost"]>; + + function resolveSuppressDefaultProgress(mattermostConfig: MattermostConfig) { + const account = resolveMattermostAccount({ + cfg: { + channels: { + mattermost: mattermostConfig, + }, + }, + accountId: "default", + allowUnresolvedSecretRef: true, + }); + return shouldSuppressMattermostDefaultToolProgressMessages(account); + } + + it("suppresses standalone progress messages while draft previews are active", () => { + expect(resolveSuppressDefaultProgress({ enabled: true })).toBe(true); + }); + + it("keeps standalone progress messages available when draft streaming is off", () => { + expect( + resolveSuppressDefaultProgress({ + streaming: { + mode: "off", + }, + }), + ).toBe(false); + }); +}); + describe("shouldClearMattermostDraftPreview", () => { it("deletes the preview after successful normal final delivery", () => { expect( diff --git a/extensions/mattermost/src/mattermost/monitor.ts b/extensions/mattermost/src/mattermost/monitor.ts index 291eb34daef..df1139dbf1b 100644 --- a/extensions/mattermost/src/mattermost/monitor.ts +++ b/extensions/mattermost/src/mattermost/monitor.ts @@ -127,6 +127,12 @@ export function shouldUpdateMattermostDraftToolProgress( ); } +export function shouldSuppressMattermostDefaultToolProgressMessages( + account: Pick, +): boolean { + return account.streamingMode !== "off"; +} + type MediaKind = "image" | "audio" | "video" | "document" | "unknown"; type MattermostReaction = { @@ -1648,6 +1654,8 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} }); const draftPreviewEnabled = account.streamingMode !== "off"; const draftToolProgressEnabled = shouldUpdateMattermostDraftToolProgress(account); + const suppressDefaultToolProgressMessages = + shouldSuppressMattermostDefaultToolProgressMessages(account); const draftStream = draftPreviewEnabled ? createMattermostDraftStream({ client, @@ -1844,6 +1852,9 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} replyOptions: { ...replyOptions, disableBlockStreaming: true, + ...(suppressDefaultToolProgressMessages + ? { suppressDefaultToolProgressMessages: true } + : {}), onModelSelected, onPartialReply: (payload) => { if (account.streamingMode !== "progress") {