From 329d6c8bb607f5b52ceba576d65eb573d86bade0 Mon Sep 17 00:00:00 2001 From: huntharo Date: Sun, 15 Mar 2026 17:47:40 -0400 Subject: [PATCH] Discord: bind group DM interactions by channel --- .../discord/src/monitor/agent-components.ts | 7 ++-- .../discord/src/monitor/monitor.test.ts | 37 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/extensions/discord/src/monitor/agent-components.ts b/extensions/discord/src/monitor/agent-components.ts index 08562aeb2c7..e28bd17b70e 100644 --- a/extensions/discord/src/monitor/agent-components.ts +++ b/extensions/discord/src/monitor/agent-components.ts @@ -804,9 +804,10 @@ async function dispatchPluginDiscordInteractiveEvent(params: { fields?: Array<{ id: string; name: string; values: string[] }>; messageId?: string; }): Promise<"handled" | "unmatched"> { - const normalizedConversationId = params.interactionCtx.rawGuildId - ? `channel:${params.interactionCtx.channelId}` - : `user:${params.interactionCtx.userId}`; + const normalizedConversationId = + params.interactionCtx.rawGuildId || params.channelCtx.channelType === ChannelType.GroupDM + ? `channel:${params.interactionCtx.channelId}` + : `user:${params.interactionCtx.userId}`; let responded = false; const respond = { acknowledge: async () => { diff --git a/extensions/discord/src/monitor/monitor.test.ts b/extensions/discord/src/monitor/monitor.test.ts index ae829e33c50..da916c4bd2b 100644 --- a/extensions/discord/src/monitor/monitor.test.ts +++ b/extensions/discord/src/monitor/monitor.test.ts @@ -5,6 +5,7 @@ import type { StringSelectMenuInteraction, } from "@buape/carbon"; import type { Client } from "@buape/carbon"; +import { ChannelType } from "discord-api-types/v10"; import type { GatewayPresenceUpdate } from "discord-api-types/v10"; import { beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../../../src/config/config.js"; @@ -641,6 +642,42 @@ describe("discord component interactions", () => { expect(dispatchReplyMock).not.toHaveBeenCalled(); }); + it("routes plugin Discord interactions in group DMs by channel id instead of sender id", async () => { + registerDiscordComponentEntries({ + entries: [createButtonEntry({ callbackData: "codex:approve" })], + modals: [], + }); + dispatchPluginInteractiveHandlerMock.mockResolvedValue({ + matched: true, + handled: true, + duplicate: false, + }); + + const button = createDiscordComponentButton(createComponentContext()); + const { interaction } = createComponentButtonInteraction({ + rawData: { + channel_id: "group-dm-1", + id: "interaction-group-dm-1", + } as unknown as ButtonInteraction["rawData"], + channel: { + id: "group-dm-1", + type: ChannelType.GroupDM, + } as unknown as ButtonInteraction["channel"], + }); + + await button.run(interaction, { cid: "btn_1" } as ComponentData); + + expect(dispatchPluginInteractiveHandlerMock).toHaveBeenCalledWith( + expect.objectContaining({ + ctx: expect.objectContaining({ + conversationId: "channel:group-dm-1", + senderId: "123456789", + }), + }), + ); + expect(dispatchReplyMock).not.toHaveBeenCalled(); + }); + it("does not fall through to Claw when a plugin Discord interaction already replied", async () => { registerDiscordComponentEntries({ entries: [createButtonEntry({ callbackData: "codex:approve" })],