mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 00:57:27 +00:00
fix(mattermost): prevent duplicate messages when block streaming + threading are active (#41362)
* fix(mattermost): prevent duplicate messages when block streaming + threading are active Remove replyToId from createBlockReplyPayloadKey so identical content is deduplicated regardless of threading target. Add explicit threading dock to the Mattermost plugin with resolveReplyToMode reading from config (default "all"), and add replyToMode to the Mattermost config schema. Fixes #41219 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(mattermost): address PR review — per-account replyToMode and test clarity Read replyToMode from the merged per-account config via resolveMattermostAccount so account-level overrides are honored in multi-account setups. Add replyToMode to MattermostAccountConfig type. Rename misleading test to clarify it exercises shouldDropFinalPayloads short-circuit, not payload key dedup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Replies: keep block-pipeline reply targets distinct * Tests: cover block reply target-aware dedupe * Update CHANGELOG.md --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
@@ -270,6 +270,16 @@ export const mattermostPlugin: ChannelPlugin<ResolvedMattermostAccount> = {
|
||||
streaming: {
|
||||
blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 },
|
||||
},
|
||||
threading: {
|
||||
resolveReplyToMode: ({ cfg, accountId }) => {
|
||||
const account = resolveMattermostAccount({ cfg, accountId: accountId ?? "default" });
|
||||
const mode = account.config.replyToMode;
|
||||
if (mode === "off" || mode === "first") {
|
||||
return mode;
|
||||
}
|
||||
return "all";
|
||||
},
|
||||
},
|
||||
reload: { configPrefixes: ["channels.mattermost"] },
|
||||
configSchema: buildChannelConfigSchema(MattermostConfigSchema),
|
||||
config: {
|
||||
|
||||
@@ -43,6 +43,7 @@ const MattermostAccountSchemaBase = z
|
||||
chunkMode: z.enum(["length", "newline"]).optional(),
|
||||
blockStreaming: z.boolean().optional(),
|
||||
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
|
||||
replyToMode: z.enum(["off", "first", "all"]).optional(),
|
||||
responsePrefix: z.string().optional(),
|
||||
actions: z
|
||||
.object({
|
||||
|
||||
@@ -109,6 +109,29 @@ describe("mattermost mention gating", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveMattermostReplyRootId with block streaming payloads", () => {
|
||||
it("uses threadRootId for block-streamed payloads with replyToId", () => {
|
||||
// When block streaming sends a payload with replyToId from the threading
|
||||
// mode, the deliver callback should still use the existing threadRootId.
|
||||
expect(
|
||||
resolveMattermostReplyRootId({
|
||||
threadRootId: "thread-root-1",
|
||||
replyToId: "streamed-reply-id",
|
||||
}),
|
||||
).toBe("thread-root-1");
|
||||
});
|
||||
|
||||
it("falls back to payload replyToId when no threadRootId in block streaming", () => {
|
||||
// Top-level channel message: no threadRootId, payload carries the
|
||||
// inbound post id as replyToId from the "all" threading mode.
|
||||
expect(
|
||||
resolveMattermostReplyRootId({
|
||||
replyToId: "inbound-post-for-threading",
|
||||
}),
|
||||
).toBe("inbound-post-for-threading");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveMattermostReplyRootId", () => {
|
||||
it("uses replyToId for top-level replies", () => {
|
||||
expect(
|
||||
|
||||
@@ -52,6 +52,8 @@ export type MattermostAccountConfig = {
|
||||
blockStreaming?: boolean;
|
||||
/** Merge streamed block replies before sending. */
|
||||
blockStreamingCoalesce?: BlockStreamingCoalesceConfig;
|
||||
/** Control reply threading (off|first|all). Default: "all". */
|
||||
replyToMode?: "off" | "first" | "all";
|
||||
/** Outbound response prefix override for this channel/account. */
|
||||
responsePrefix?: string;
|
||||
/** Action toggles for this account. */
|
||||
|
||||
Reference in New Issue
Block a user