diff --git a/docs/channels/slack.md b/docs/channels/slack.md index 4c46555b8bf..4e99b42bc25 100644 --- a/docs/channels/slack.md +++ b/docs/channels/slack.md @@ -23,17 +23,17 @@ Production-ready for DMs and channels via Slack app integrations. Default mode i Both transports are production-ready and reach feature parity for messaging, slash commands, App Home, and interactivity. Pick by deployment shape, not features. -| Concern | Socket Mode (default) | HTTP Request URLs | -| ---------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | -| Public Gateway URL | Not required | Required (DNS, TLS, reverse proxy or tunnel) | -| Outbound network | Outbound WSS to `wss-primary.slack.com` must be reachable | No outbound WS; inbound HTTPS only | -| Tokens needed | Bot token (`xoxb-...`) + App-Level Token (`xapp-...`) with `connections:write` | Bot token (`xoxb-...`) + Signing Secret | -| Dev laptop / behind firewall | Works as-is | Needs a public tunnel (ngrok, Cloudflare Tunnel, Tailscale Funnel) or staging Gateway | -| Horizontal scaling | One Socket Mode session per app per host; multiple Gateways need separate Slack apps | Stateless POST handler; multiple Gateway replicas can share one app behind a load balancer | -| Multi-account on one Gateway | Supported; each account opens its own WS | Supported; each account needs a unique `webhookPath` (default `/slack/events`) so registrations do not collide | -| Slash command transport | Delivered over the WS connection; `slash_commands[].url` is ignored | Slack POSTs to `slash_commands[].url`; field is required for the command to dispatch | -| Request signing | Not used (auth is the App-Level Token) | Slack signs every request; OpenClaw verifies with `signingSecret` | -| Recovery on connection drop | Slack SDK auto-reconnects; the gateway's pong-timeout transport tuning applies | No persistent connection to drop; retries are per-request from Slack | +| Concern | Socket Mode (default) | HTTP Request URLs | +| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | +| Public Gateway URL | Not required | Required (DNS, TLS, reverse proxy or tunnel) | +| Outbound network | Outbound WSS to `wss-primary.slack.com` must be reachable | No outbound WS; inbound HTTPS only | +| Tokens needed | Bot token (`xoxb-...`) + App-Level Token (`xapp-...`) with `connections:write` | Bot token (`xoxb-...`) + Signing Secret | +| Dev laptop / behind firewall | Works as-is | Needs a public tunnel (ngrok, Cloudflare Tunnel, Tailscale Funnel) or staging Gateway | +| Horizontal scaling | One Socket Mode session per app per host; multiple Gateways need separate Slack apps | Stateless POST handler; multiple Gateway replicas can share one app behind a load balancer | +| Multi-account on one Gateway | Supported; each account opens its own WS | Supported; each account needs a unique `webhookPath` (default `/slack/events`) so registrations do not collide | +| Slash command transport | Delivered over the WS connection; `slash_commands[].url` is ignored | Slack POSTs to `slash_commands[].url`; field is required for the command to dispatch | +| Request signing | Not used (auth is the App-Level Token) | Slack signs every request; OpenClaw verifies with `signingSecret` | +| Recovery on connection drop | Slack SDK auto-reconnect is enabled; OpenClaw also restarts failed Socket Mode sessions with bounded backoff. Pong-timeout transport tuning applies. | No persistent connection to drop; retries are per-request from Slack | **Pick Socket Mode** for single-Gateway hosts, dev laptops, and on-prem networks that can reach `*.slack.com` outbound but cannot accept inbound HTTPS. @@ -462,6 +462,13 @@ OpenClaw sets the Slack SDK client pong timeout to 15 seconds by default for Soc Use this only for Socket Mode workspaces that log Slack websocket pong/server-ping timeouts or run on hosts with known event-loop starvation. `clientPingTimeout` is the pong wait after the SDK sends a client ping; `serverPingTimeout` is the wait for Slack server pings. App messages and events remain application state, not transport liveness signals. +Notes: + +- `socketMode` is ignored in HTTP Request URL mode. +- Base `channels.slack.socketMode` settings apply to all Slack accounts unless overridden. Per-account overrides use `channels.slack.accounts..socketMode`; because this is an object override, include every socket tuning field you want for that account. +- Only `clientPingTimeout` has an OpenClaw default (`15000`). `serverPingTimeout` and `pingPongLoggingEnabled` are passed to the Slack SDK only when configured. +- Socket Mode restart backoff starts around 2 seconds and caps around 30 seconds. Consecutive recoverable start/start-wait failures stop after 12 attempts; after a successful connection, later recoverable disconnects start a fresh retry cycle. Non-recoverable Slack auth errors such as `invalid_auth`, revoked tokens, or missing scopes fail fast instead of retrying forever. + ## Manifest and scope checklist The base Slack app manifest is the same for Socket Mode and HTTP Request URLs. Only the `settings` block (and the slash command `url`) differs. @@ -931,8 +938,9 @@ Current Slack message actions include `send`, `upload-file`, `download-file`, `r - Slack route bindings accept raw peer IDs plus Slack target forms such as `channel:C12345678`, `user:U12345678`, and `<@U12345678>`. - With default `session.dmScope=main`, Slack DMs collapse to agent main session. - Channel sessions: `agent::slack:channel:`. -- Thread replies can create thread session suffixes (`:thread:`) when applicable. -- In channels where OpenClaw handles top-level messages without requiring an explicit mention, non-`off` `replyToMode` routes each handled root into `agent::slack:channel::thread:` so the visible Slack thread maps to one OpenClaw session from the first turn. +- Ordinary top-level channel messages stay on the per-channel session, even when `replyToMode` is non-`off`. +- Slack thread replies use the parent Slack `thread_ts` for session suffixes (`:thread:`), even when outbound reply threading is disabled with `replyToMode="off"`. +- OpenClaw seeds an eligible top-level channel root into `agent::slack:channel::thread:` when that root is expected to start a visible Slack thread, so the root and later thread replies share one OpenClaw session. This applies to `app_mention` events, explicit bot or configured mention-pattern matches, and `requireMention: false` channels with non-`off` `replyToMode`. - `channels.slack.thread.historyScope` default is `thread`; `thread.inheritParent` default is `false`. - `channels.slack.thread.initialHistoryLimit` controls how many existing thread messages are fetched when a new thread session starts (default `20`; set `0` to disable). - `channels.slack.thread.requireExplicitMention` (default `false`): when `true`, suppress implicit thread mentions so the bot only responds to explicit `@bot` mentions inside threads, even when the bot already participated in the thread. Without this, replies in a bot-participated thread bypass `requireMention` gating. @@ -953,7 +961,7 @@ For explicit Slack thread replies from the `message` tool, set `replyBroadcast: When a `message` tool call runs inside a Slack thread and targets the same channel, OpenClaw normally inherits the current Slack thread according to `replyToMode`. Set `topLevel: true` on `action: "send"` or `action: "upload-file"` to force a new parent-channel message instead. `threadId: null` is accepted as the same top-level opt-out. -`replyToMode="off"` disables **all** reply threading in Slack, including explicit `[[reply_to_*]]` tags. This differs from Telegram, where explicit tags are still honored in `"off"` mode. Slack threads hide messages from the channel while Telegram replies stay visible inline. +`replyToMode="off"` disables outbound Slack reply threading, including explicit `[[reply_to_*]]` tags. It does not flatten inbound Slack thread sessions: messages already posted inside a Slack thread still route to the `:thread:` session. This differs from Telegram, where explicit tags are still honored in `"off"` mode. Slack threads hide messages from the channel while Telegram replies stay visible inline. ## Ack reactions @@ -1258,6 +1266,17 @@ Primary reference: [Configuration reference - Slack](/gateway/config-channels#sl - channel allowlist (`channels.slack.channels`) — **keys must be channel IDs** (`C12345678`), not names (`#channel-name`). Name-based keys silently fail under `groupPolicy: "allowlist"` because channel routing is ID-first by default. To find an ID: right-click the channel in Slack → **Copy link** — the `C...` value at the end of the URL is the channel ID. - `requireMention` - per-channel `users` allowlist + - `messages.groupChat.visibleReplies`: if it is `"message_tool"` and logs show assistant text with no `message(action=send)` call, the turn was processed but the final answer was kept private. Set it to `"automatic"` if you want normal assistant final replies posted back to Slack channels. + +```json5 +{ + messages: { + groupChat: { + visibleReplies: "automatic", + }, + }, +} +``` Useful commands: @@ -1274,7 +1293,8 @@ openclaw doctor - `channels.slack.dm.enabled` - `channels.slack.dmPolicy` (or legacy `channels.slack.dm.policy`) - - pairing approvals / allowlist entries + - pairing approvals / allowlist entries (`dmPolicy: "open"` still requires `channels.slack.allowFrom: ["*"]`) + - group DMs use MPIM handling; enable `channels.slack.dm.groupEnabled` and, if configured, include the MPIM in `channels.slack.dm.groupChannels` - Slack Assistant DM events: verbose logs mentioning `drop message_changed` usually mean Slack sent an edited Assistant-thread event without a recoverable human sender in message metadata @@ -1287,12 +1307,19 @@ openclaw pairing list slack Validate bot + app tokens and Socket Mode enablement in Slack app settings. + The `xapp-...` App-Level Token needs `connections:write`, and the `xoxb-...` + bot token must belong to the same Slack app/workspace as the app token. If `openclaw channels status --probe --json` shows `botTokenStatus` or `appTokenStatus: "configured_unavailable"`, the Slack account is configured but the current runtime could not resolve the SecretRef-backed value. + Logs such as `slack socket mode failed to start; retry ...` are recoverable + start failures. Missing scopes, revoked tokens, and invalid auth fail fast + instead. A `slack token mismatch ...` log means the bot token and app token + appear to belong to different Slack apps; fix the Slack app credentials. + @@ -1302,11 +1329,16 @@ openclaw pairing list slack - webhook path - Slack Request URLs (Events + Interactivity + Slash Commands) - unique `webhookPath` per HTTP account + - the public URL terminates TLS and forwards requests to the Gateway path + - the Slack app `request_url` path exactly matches `channels.slack.webhookPath` (default `/slack/events`) If `signingSecretStatus: "configured_unavailable"` appears in account snapshots, the HTTP account is configured but the current runtime could not resolve the SecretRef-backed signing secret. + A repeated `slack: webhook path ... already registered` log means two HTTP + accounts are using the same `webhookPath`; give each account a distinct path. + @@ -1315,7 +1347,14 @@ openclaw pairing list slack - native command mode (`channels.slack.commands.native: true`) with matching slash commands registered in Slack - or single slash command mode (`channels.slack.slashCommand.enabled: true`) - Also check `commands.useAccessGroups` and channel/user allowlists. + Slack does not create or remove slash commands automatically. `commands.native: "auto"` does not enable Slack native commands; use `true` and create the matching commands in the Slack app. In HTTP mode, every Slack slash command must include the Gateway URL. In Socket Mode, command payloads arrive over the websocket and Slack ignores `slash_commands[].url`. + + Also check `commands.useAccessGroups`, DM authorization, channel allowlists, + and per-channel `users` allowlists. Slack returns ephemeral errors for + blocked slash-command senders, including: + + - `This channel is not allowed.` + - `You are not authorized to use this command here.` diff --git a/docs/gateway/config-channels.md b/docs/gateway/config-channels.md index aa742ab0b70..b22615980b3 100644 --- a/docs/gateway/config-channels.md +++ b/docs/gateway/config-channels.md @@ -476,7 +476,7 @@ WhatsApp runs through the gateway's web channel (Baileys Web). It starts automat - **Socket mode** requires both `botToken` and `appToken` (`SLACK_BOT_TOKEN` + `SLACK_APP_TOKEN` for default account env fallback). - **HTTP mode** requires `botToken` plus `signingSecret` (at root or per-account). -- `socketMode` passes Slack SDK Socket Mode transport tuning through to the public Bolt receiver API. Use it only when investigating ping/pong timeout or stale websocket behavior. +- `socketMode` passes Slack SDK Socket Mode transport tuning through to the public Bolt receiver API. Use it only when investigating ping/pong timeout or stale websocket behavior. `clientPingTimeout` defaults to `15000`; `serverPingTimeout` and `pingPongLoggingEnabled` are passed only when configured. - `botToken`, `appToken`, `signingSecret`, and `userToken` accept plaintext strings or SecretRef objects. - Slack account snapshots expose per-credential source/status fields such as