From 93e25774da9211e1219dc123b5f2ee1f706b7c90 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 15 Mar 2026 17:48:15 -0700 Subject: [PATCH] Plugins: add Slack shared interactive dispatcher --- src/plugins/interactive.ts | 106 +++++++++++++++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 5 deletions(-) diff --git a/src/plugins/interactive.ts b/src/plugins/interactive.ts index 66d79fd71ec..43e55d3ae78 100644 --- a/src/plugins/interactive.ts +++ b/src/plugins/interactive.ts @@ -9,6 +9,8 @@ import type { PluginInteractiveButtons, PluginInteractiveDiscordHandlerRegistration, PluginInteractiveHandlerRegistration, + PluginInteractiveSlackHandlerContext, + PluginInteractiveSlackHandlerRegistration, PluginInteractiveTelegramHandlerRegistration, PluginInteractiveTelegramHandlerContext, } from "./types.js"; @@ -59,6 +61,20 @@ type DiscordInteractiveDispatchContext = Omit< >; }; +type SlackInteractiveDispatchContext = Omit< + PluginInteractiveSlackHandlerContext, + | "interaction" + | "respond" + | "requestConversationBinding" + | "detachConversationBinding" + | "getCurrentConversationBinding" +> & { + interaction: Omit< + PluginInteractiveSlackHandlerContext["interaction"], + "data" | "namespace" | "payload" + >; +}; + const interactiveHandlers = new Map(); const callbackDedupe = createDedupeCache({ ttlMs: 5 * 60_000, @@ -181,11 +197,21 @@ export async function dispatchPluginInteractiveHandler(params: { respond: PluginInteractiveDiscordHandlerContext["respond"]; }): Promise; export async function dispatchPluginInteractiveHandler(params: { - channel: "telegram" | "discord"; + channel: "slack"; + data: string; + interactionId: string; + ctx: SlackInteractiveDispatchContext; + respond: PluginInteractiveSlackHandlerContext["respond"]; +}): Promise; +export async function dispatchPluginInteractiveHandler(params: { + channel: "telegram" | "discord" | "slack"; data: string; callbackId?: string; interactionId?: string; - ctx: TelegramInteractiveDispatchContext | DiscordInteractiveDispatchContext; + ctx: + | TelegramInteractiveDispatchContext + | DiscordInteractiveDispatchContext + | SlackInteractiveDispatchContext; respond: | { reply: (params: { text: string; buttons?: PluginInteractiveButtons }) => Promise; @@ -197,7 +223,8 @@ export async function dispatchPluginInteractiveHandler(params: { clearButtons: () => Promise; deleteMessage: () => Promise; } - | PluginInteractiveDiscordHandlerContext["respond"]; + | PluginInteractiveDiscordHandlerContext["respond"] + | PluginInteractiveSlackHandlerContext["respond"]; }): Promise { const match = resolveNamespaceMatch(params.channel, params.data); if (!match) { @@ -212,7 +239,8 @@ export async function dispatchPluginInteractiveHandler(params: { let result: | ReturnType - | ReturnType; + | ReturnType + | ReturnType; if (params.channel === "telegram") { const pluginRoot = match.registration.pluginRoot; const { callbackMessage, ...handlerContext } = params.ctx as TelegramInteractiveDispatchContext; @@ -284,7 +312,7 @@ export async function dispatchPluginInteractiveHandler(params: { }); }, }); - } else { + } else if (params.channel === "discord") { const pluginRoot = match.registration.pluginRoot; result = ( match.registration as RegisteredInteractiveHandler & @@ -352,6 +380,74 @@ export async function dispatchPluginInteractiveHandler(params: { }); }, }); + } else { + const pluginRoot = match.registration.pluginRoot; + const handlerContext = params.ctx as SlackInteractiveDispatchContext; + result = ( + match.registration as RegisteredInteractiveHandler & PluginInteractiveSlackHandlerRegistration + ).handler({ + ...handlerContext, + channel: "slack", + interaction: { + ...handlerContext.interaction, + data: params.data, + namespace: match.namespace, + payload: match.payload, + }, + respond: params.respond as PluginInteractiveSlackHandlerContext["respond"], + requestConversationBinding: async (bindingParams) => { + if (!pluginRoot) { + return { + status: "error", + message: "This interaction cannot bind the current conversation.", + }; + } + return requestPluginConversationBinding({ + pluginId: match.registration.pluginId, + pluginName: match.registration.pluginName, + pluginRoot, + requestedBySenderId: handlerContext.senderId, + conversation: { + channel: "slack", + accountId: handlerContext.accountId, + conversationId: handlerContext.conversationId, + parentConversationId: handlerContext.parentConversationId, + threadId: handlerContext.threadId, + }, + binding: bindingParams, + }); + }, + detachConversationBinding: async () => { + if (!pluginRoot) { + return { removed: false }; + } + return detachPluginConversationBinding({ + pluginRoot, + conversation: { + channel: "slack", + accountId: handlerContext.accountId, + conversationId: handlerContext.conversationId, + parentConversationId: handlerContext.parentConversationId, + threadId: handlerContext.threadId, + }, + }); + }, + getCurrentConversationBinding: async () => { + if (!pluginRoot) { + return null; + } + return getCurrentPluginConversationBinding({ + pluginRoot, + conversation: { + channel: "slack", + accountId: handlerContext.accountId, + conversationId: handlerContext.conversationId, + parentConversationId: handlerContext.parentConversationId, + threadId: handlerContext.threadId, + }, + }); + }, + }); } const resolved = await result; if (dedupeKey) {