From 06306501ab2d3ceaf389567234d4d0c5d821e9f2 Mon Sep 17 00:00:00 2001 From: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:06:33 +0800 Subject: [PATCH] Slack: register typed channel/group message event handlers --- CHANGELOG.md | 1 + src/slack/monitor/events/messages.test.ts | 36 +++++++++++++++++++++++ src/slack/monitor/events/messages.ts | 14 ++++++++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 793880a179f..7c761bac4a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,7 @@ Docs: https://docs.openclaw.ai - CLI/Cron: clarify `cron list` output by renaming `Agent` to `Agent ID` and adding a `Model` column for isolated agent-turn jobs. (#26259) Thanks @openperf. - Feishu/Reply media attachments: send Feishu reply `mediaUrl`/`mediaUrls` payloads as attachments alongside text/streamed replies in the reply dispatcher, including legacy fallback when `mediaUrls` is empty. (#28959) Thanks @icesword0760. - Slack/User-token resolution: normalize Slack account user-token sourcing through resolved account metadata (`SLACK_USER_TOKEN` env + config) so monitor reads, Slack actions, directory lookups, onboarding allow-from resolution, and capabilities probing consistently use the effective user token. (#28103) Thanks @Glucksberg. +- Slack/Channel message subscriptions: register explicit `message.channels` and `message.groups` monitor handlers (alongside generic `message`) so channel/group event subscriptions are consumed even when Slack dispatches typed message event names. Fixes #31674. - Feishu/Outbound session routing: stop assuming bare `oc_` identifiers are always group chats, honor explicit `dm:`/`group:` prefixes for `oc_` chat IDs, and default ambiguous bare `oc_` targets to direct routing to avoid DM session misclassification. (#10407) Thanks @Bermudarat. - Feishu/Group session routing: add configurable group session scopes (`group`, `group_sender`, `group_topic`, `group_topic_sender`) with legacy `topicSessionMode=enabled` compatibility so Feishu group conversations can isolate sessions by sender/topic as configured. (#17798) Thanks @yfge. - Feishu/Reply-in-thread routing: add `replyInThread` config (`disabled|enabled`) for group replies, propagate `reply_in_thread` across text/card/media/streaming sends, and align topic-scoped session routing so newly created reply threads stay on the same session root. (#27325) Thanks @kcinzgg. diff --git a/src/slack/monitor/events/messages.test.ts b/src/slack/monitor/events/messages.test.ts index cdf64b45ee8..b59b26c1633 100644 --- a/src/slack/monitor/events/messages.test.ts +++ b/src/slack/monitor/events/messages.test.ts @@ -33,6 +33,8 @@ function createMessageHandlers(overrides?: SlackSystemEventTestOverrides) { }); return { handler: harness.getHandler("message") as MessageHandler | null, + channelHandler: harness.getHandler("message.channels") as MessageHandler | null, + groupHandler: harness.getHandler("message.groups") as MessageHandler | null, handleSlackMessage, }; } @@ -156,4 +158,38 @@ describe("registerSlackMessageEvents", () => { expect(handleSlackMessage).toHaveBeenCalledTimes(1); expect(messageQueueMock).not.toHaveBeenCalled(); }); + + it("registers and forwards message.channels and message.groups events", async () => { + messageQueueMock.mockClear(); + messageAllowMock.mockReset().mockResolvedValue([]); + const { channelHandler, groupHandler, handleSlackMessage } = createMessageHandlers({ + dmPolicy: "open", + channelType: "channel", + }); + + expect(channelHandler).toBeTruthy(); + expect(groupHandler).toBeTruthy(); + + const channelMessage = { + type: "message", + channel: "C1", + channel_type: "channel", + user: "U1", + text: "hello channel", + ts: "123.100", + }; + await channelHandler!({ event: channelMessage, body: {} }); + await groupHandler!({ + event: { + ...channelMessage, + channel_type: "group", + channel: "G1", + ts: "123.200", + }, + body: {}, + }); + + expect(handleSlackMessage).toHaveBeenCalledTimes(2); + expect(messageQueueMock).not.toHaveBeenCalled(); + }); }); diff --git a/src/slack/monitor/events/messages.ts b/src/slack/monitor/events/messages.ts index 5d16bb967f6..d63ce780d30 100644 --- a/src/slack/monitor/events/messages.ts +++ b/src/slack/monitor/events/messages.ts @@ -27,7 +27,7 @@ export function registerSlackMessageEvents(params: { const resolveThreadBroadcastSenderId = (thread: SlackThreadBroadcastEvent): string | undefined => thread.user ?? thread.message?.user ?? thread.message?.bot_id; - ctx.app.event("message", async ({ event, body }: SlackEventMiddlewareArgs<"message">) => { + const handleIncomingMessageEvent = async ({ event, body }: { event: unknown; body: unknown }) => { try { if (ctx.shouldDropMismatchedSlackEvent(body)) { return; @@ -95,6 +95,18 @@ export function registerSlackMessageEvents(params: { } catch (err) { ctx.runtime.error?.(danger(`slack handler failed: ${String(err)}`)); } + }; + + ctx.app.event("message", async ({ event, body }: SlackEventMiddlewareArgs<"message">) => { + await handleIncomingMessageEvent({ event, body }); + }); + // Slack may dispatch channel/group message subscriptions under typed event + // names. Register explicit handlers so both delivery styles are supported. + ctx.app.event("message.channels", async ({ event, body }: SlackEventMiddlewareArgs) => { + await handleIncomingMessageEvent({ event, body }); + }); + ctx.app.event("message.groups", async ({ event, body }: SlackEventMiddlewareArgs) => { + await handleIncomingMessageEvent({ event, body }); }); ctx.app.event("app_mention", async ({ event, body }: SlackEventMiddlewareArgs<"app_mention">) => {