diff --git a/CHANGELOG.md b/CHANGELOG.md index 061809354..bf896af94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Slack/Bolt startup compatibility: remove invalid `message.channels` and `message.groups` event registrations so Slack providers no longer crash on startup with Bolt 4.6+; channel/group traffic continues through the unified `message` handler (`channel_type`). (#32033) Thanks @mahopan. - Sandbox/Docker setup command parsing: accept `agents.*.sandbox.docker.setupCommand` as either a string or a string array, and normalize arrays to newline-delimited shell scripts so multi-step setup commands no longer concatenate without separators. (#31953) Thanks @liuxiaopai-ai. - Gateway/Plugin HTTP route precedence: run explicit plugin HTTP routes before the Control UI SPA catch-all so registered plugin webhook/custom paths remain reachable, while unmatched paths still fall through to Control UI handling. (#31885) Thanks @Sid-Qin. - Security/Node exec approvals: preserve shell/dispatch-wrapper argv semantics during approval hardening so approved wrapper commands (for example `env sh -c ...`) cannot drift into a different runtime command shape, and add regression coverage for both approval-plan generation and approved runtime execution paths. Thanks @tdjackey for reporting. diff --git a/src/slack/monitor/events/messages.test.ts b/src/slack/monitor/events/messages.test.ts index 4b7cc4038..989c4b3b9 100644 --- a/src/slack/monitor/events/messages.test.ts +++ b/src/slack/monitor/events/messages.test.ts @@ -33,8 +33,6 @@ 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, }; } @@ -159,17 +157,17 @@ describe("registerSlackMessageEvents", () => { expect(messageQueueMock).not.toHaveBeenCalled(); }); - it("registers and forwards message.channels and message.groups events", async () => { + it("handles channel and group messages via the unified message handler", async () => { messageQueueMock.mockClear(); messageAllowMock.mockReset().mockResolvedValue([]); - const { channelHandler, groupHandler, handleSlackMessage } = createMessageHandlers({ + const { handler, handleSlackMessage } = createMessageHandlers({ dmPolicy: "open", channelType: "channel", }); - expect(channelHandler).toBeTruthy(); - expect(groupHandler).toBeTruthy(); + expect(handler).toBeTruthy(); + // channel_type distinguishes the source; all arrive as event type "message" const channelMessage = { type: "message", channel: "C1", @@ -178,8 +176,8 @@ describe("registerSlackMessageEvents", () => { text: "hello channel", ts: "123.100", }; - await channelHandler!({ event: channelMessage, body: {} }); - await groupHandler!({ + await handler!({ event: channelMessage, body: {} }); + await handler!({ event: { ...channelMessage, channel_type: "group", @@ -193,17 +191,19 @@ describe("registerSlackMessageEvents", () => { expect(messageQueueMock).not.toHaveBeenCalled(); }); - it("applies subtype system-event handling for message.channels events", async () => { + it("applies subtype system-event handling for channel messages", async () => { messageQueueMock.mockClear(); messageAllowMock.mockReset().mockResolvedValue([]); - const { channelHandler, handleSlackMessage } = createMessageHandlers({ + const { handler, handleSlackMessage } = createMessageHandlers({ dmPolicy: "open", channelType: "channel", }); - expect(channelHandler).toBeTruthy(); + expect(handler).toBeTruthy(); - await channelHandler!({ + // message_changed events from channels arrive via the generic "message" + // handler with channel_type:"channel" — not a separate event type. + await handler!({ event: { ...makeChangedEvent({ channel: "C1", user: "U1" }), channel_type: "channel", diff --git a/src/slack/monitor/events/messages.ts b/src/slack/monitor/events/messages.ts index fac307416..40308570d 100644 --- a/src/slack/monitor/events/messages.ts +++ b/src/slack/monitor/events/messages.ts @@ -46,23 +46,15 @@ export function registerSlackMessageEvents(params: { } }; + // NOTE: Slack Event Subscriptions use names like "message.channels" and + // "message.groups" to control *which* message events are delivered, but the + // actual event payload always arrives with `type: "message"`. The + // `channel_type` field ("channel" | "group" | "im" | "mpim") distinguishes + // the source. Bolt rejects `app.event("message.channels")` since v4.6 + // because it is a subscription label, not a valid event type. 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<"message.channels">) => { - await handleIncomingMessageEvent({ event, body }); - }, - ); - ctx.app.event( - "message.groups", - async ({ event, body }: SlackEventMiddlewareArgs<"message.groups">) => { - await handleIncomingMessageEvent({ event, body }); - }, - ); ctx.app.event("app_mention", async ({ event, body }: SlackEventMiddlewareArgs<"app_mention">) => { try {