fix(mattermost): dedupe repeated model picker selects

This commit is contained in:
Vincent Koc
2026-04-13 17:47:21 +01:00
parent b2589ac451
commit a7ac3c666c
2 changed files with 53 additions and 1 deletions

View File

@@ -3,6 +3,7 @@ import { describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../runtime-api.js";
import { resolveMattermostAccount } from "./accounts.js";
import {
buildMattermostModelPickerSelectMessageSid,
evaluateMattermostMentionGate,
MattermostRetryableInboundError,
processMattermostReplayGuardedPost,
@@ -378,6 +379,41 @@ describe("processMattermostReplayGuardedPost", () => {
});
});
describe("buildMattermostModelPickerSelectMessageSid", () => {
it("stays stable for the same picker selection", () => {
expect(
buildMattermostModelPickerSelectMessageSid({
postId: "post-1",
provider: "OpenAI",
model: " GPT-5 ",
}),
).toBe("interaction:post-1:select:openai/gpt-5");
expect(
buildMattermostModelPickerSelectMessageSid({
postId: "post-1",
provider: "openai",
model: "gpt-5",
}),
).toBe("interaction:post-1:select:openai/gpt-5");
});
it("keeps different model selections distinct", () => {
expect(
buildMattermostModelPickerSelectMessageSid({
postId: "post-1",
provider: "openai",
model: "gpt-5",
}),
).not.toBe(
buildMattermostModelPickerSelectMessageSid({
postId: "post-1",
provider: "openai",
model: "gpt-4.1",
}),
);
});
});
describe("resolveMattermostReactionChannelId", () => {
it("prefers broadcast channel_id when present", () => {
expect(

View File

@@ -139,6 +139,16 @@ export class MattermostRetryableInboundError extends Error {
}
}
export function buildMattermostModelPickerSelectMessageSid(params: {
postId: string;
provider: string;
model: string;
}): string {
const provider = normalizeLowercaseStringOrEmpty(params.provider);
const model = normalizeLowercaseStringOrEmpty(params.model);
return `interaction:${params.postId}:select:${provider}/${model}`;
}
function buildMattermostInboundReplayKeys(params: {
accountId: string;
messageIds: string[];
@@ -698,6 +708,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
roomLabel: string;
teamId?: string;
postId: string;
messageSid?: string;
effectiveReplyToId?: string;
deliverReplies?: boolean;
}): Promise<string> => {
@@ -731,7 +742,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
SenderId: params.senderId,
Provider: "mattermost" as const,
Surface: "mattermost" as const,
MessageSid: `interaction:${params.postId}:${Date.now()}`,
MessageSid: params.messageSid ?? `interaction:${params.postId}:${Date.now()}`,
ReplyToId: params.effectiveReplyToId,
MessageThreadId: params.effectiveReplyToId,
Timestamp: Date.now(),
@@ -1023,6 +1034,11 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
roomLabel,
teamId,
postId: params.payload.post_id,
messageSid: buildMattermostModelPickerSelectMessageSid({
postId: params.payload.post_id,
provider: pickerState.provider,
model: pickerState.model,
}),
effectiveReplyToId: threadContext.effectiveReplyToId,
deliverReplies: true,
});