diff --git a/extensions/mattermost/src/channel.ts b/extensions/mattermost/src/channel.ts index b151ed708d9..401ddabf38b 100644 --- a/extensions/mattermost/src/channel.ts +++ b/extensions/mattermost/src/channel.ts @@ -167,15 +167,24 @@ const mattermostMessageActions: ChannelMessageActionAdapter = { if (account.botToken) setInteractionSecret(account.botToken); const callbackUrl = resolveInteractionCallbackUrl(account.accountId, cfg); - const buttons = (params.buttons as Array>).map((btn) => ({ - id: String(btn.id ?? btn.callback_data ?? ""), - name: String(btn.text ?? btn.name ?? btn.label ?? ""), - style: (btn.style as "default" | "primary" | "danger") ?? "default", - context: - typeof btn.context === "object" && btn.context !== null - ? (btn.context as Record) - : undefined, - })); + // Flatten 2D array (rows of buttons) to 1D — core schema sends Array> + // but Mattermost doesn't have row layout, so we flatten all rows into a single list. + // Also supports 1D arrays for backward compatibility. + const rawButtons = (params.buttons as Array).flatMap((item) => + Array.isArray(item) ? item : [item], + ) as Array>; + + const buttons = rawButtons + .map((btn) => ({ + id: String(btn.id ?? btn.callback_data ?? ""), + name: String(btn.text ?? btn.name ?? btn.label ?? ""), + style: (btn.style as "default" | "primary" | "danger") ?? "default", + context: + typeof btn.context === "object" && btn.context !== null + ? (btn.context as Record) + : undefined, + })) + .filter((btn) => btn.id && btn.name); const attachmentText = typeof params.attachmentText === "string" ? params.attachmentText : undefined;