72322a4d72
## Summary Lets workspace members talk to the Twenty CRM agent from Slack — `@mention` the bot in a channel or DM it, and it answers in-thread using the `slack-assistant` agent and its assigned role. ## How it works Slack Events webhook → app route verifies signature → **ack in <3s** and enqueue a `slackAssistantRequest` → worker posts a placeholder immediately, then fetches recent thread/DM history (excluding the current message and placeholder), runs `runAgent`, and updates the placeholder with the answer. After a successful reply, the thread stays subscribed (24h TTL, renewed on each reply) so follow-ups work without re-mentioning. ## App-owned orchestration Protocol + orchestration live in `twenty-apps/public/twenty-slack` (events resolver, enqueue, worker, team claim KV, thread subscription). The server provides shared primitives (app routes, `runAgent`, app KV, connection OAuth). ## Notes - Agent role is bound via `roleUniversalIdentifier` on install. Default **Slack Assistant** role: read/create/update/soft-delete on people, companies, opportunities, notes, and tasks; **workspace members stay read-only**; hard destroy stays off. Admins can tighten the role in Settings. - Setup (signing secret, event subscriptions, scopes) is in the app README. - Long-lived Slack bot tokens (no refresh token) are treated as non-expiring. - Multi-turn: recent Slack thread/DM messages are prepended into the agent prompt. - Replies are non-streaming for now (placeholder + final `chat.update`); progressive streaming is a follow-up. ## Follow-ups - **Streaming replies** — progressive edits while the agent runs. - **Per-user / per-channel permissions** — Slack→Twenty user mapping and optional channel rules (open by default; admins can narrow). - **Other platforms** — Discord/Teams can reuse the same patterns; only Slack protocol is in this PR. ## Screenshots https://github.com/user-attachments/assets/3a72770a-93fa-411d-b4aa-2f741afbcee1 <img width="426" height="686" alt="Screenshot 2026-07-27 at 3 58 38 PM" src="https://github.com/user-attachments/assets/b0a62e7c-c5e4-4c96-9389-5e47d7ef8c77" /> <img width="1053" height="726" alt="Screenshot 2026-07-29 at 12 54 45 AM" src="https://github.com/user-attachments/assets/4e14b3fb-fbe5-4f4d-a380-cc45cc60a01a" />
93 lines
5.9 KiB
Markdown
93 lines
5.9 KiB
Markdown
# Setup
|
|
|
|
Two parts: a **Slack app** you create, and the **Twenty side** where you paste its credentials and connect. The conversational assistant needs a third part on top.
|
|
|
|
## 1. Slack app
|
|
|
|
1. Create a Slack app at [api.slack.com/apps](https://api.slack.com/apps). Use a dedicated app — do not reuse one across Twenty apps.
|
|
|
|
2. **OAuth & Permissions → Bot Token Scopes.** Twenty uses Slack's bot OAuth (`oauth/v2/authorize` with `scope=…`), so scopes must be added here and not only under **User Token Scopes**, otherwise Slack refuses the install with *"doesn't have a bot user to install"*.
|
|
|
|
The scopes requested at connect time must all appear under **Bot Token Scopes** (Slack validates the set):
|
|
|
|
| Scope | Used for |
|
|
|-------|----------|
|
|
| `channels:read` | `conversations.list` and the channel picker (public channels) |
|
|
| `chat:write` | post, update, delete, ephemeral |
|
|
| `chat:write.public` | post to public channels without the bot joining |
|
|
| `groups:read` | list private channels the bot is in |
|
|
| `reactions:write` | add reactions |
|
|
| `app_mentions:read` | assistant: mentions of the bot |
|
|
| `channels:history` | assistant: thread follow-ups in public channels |
|
|
| `groups:history` | assistant: thread follow-ups in private channels |
|
|
| `im:history` | assistant: direct messages |
|
|
|
|
Adding or removing scopes later means existing installs must re-authorize: disconnect and **Add connection** again.
|
|
|
|
3. **Redirect URL.** Set it to `<YOUR_TWENTY_SERVER_URL>/auth/apps/callback` — the origin your Twenty **server** uses for API routes, not the SPA. Local monorepo dev is usually `http://localhost:3000` (confirm the port `twenty-server` / `SERVER_URL` actually uses).
|
|
|
|
**PKCE and `localhost`:** if you enable **PKCE** on the Slack app, Slack treats `http://localhost…` as a *desktop* redirect, and desktop redirects cannot request bot scopes — OAuth will fail. For local dev either leave Slack's PKCE opt-in disabled, or use an `https://` redirect (ngrok, Cloudflare Tunnel), register it in the Slack app, and point `SERVER_URL` at the same base URL. See Slack's [Using PKCE](https://docs.slack.dev/authentication/using-pkce) docs. This is separate from Twenty sending a PKCE challenge on the authorize request.
|
|
|
|
4. Copy the **Client ID** and **Client Secret**.
|
|
|
|
## 2. Twenty
|
|
|
|
1. Install this app (`slack`) on your Twenty server.
|
|
2. **Settings → Applications → Twenty Slack → Application registration** (admin only), set `SLACK_CLIENT_ID` and `SLACK_CLIENT_SECRET`.
|
|
3. **Connections → Add connection**, choose **Just for me** or **Workspace shared**, complete the Slack sign-in.
|
|
|
|
Workflow steps then use that connection's access token: a workspace connection is preferred when present, otherwise the first connection returned for the Slack provider.
|
|
|
|
For posting, either invite the bot to the channel or rely on `chat:write.public` for public channels. Private channels always require membership.
|
|
|
|
## 3. Conversational assistant
|
|
|
|
The assistant reuses the same Slack connection — no second bot identity.
|
|
|
|
1. **Signing secret.** In **Application registration**, set `SLACK_WEBHOOK_SECRET` from your Slack app (**Basic Information → App Credentials**). The server verifies every Slack Events request with it.
|
|
|
|
2. **Event subscriptions.** On the Slack app, enable **Event Subscriptions** and set the Request URL to:
|
|
|
|
```text
|
|
<YOUR_TWENTY_SERVER_URL>/webhooks/server/9ad6fa20-dff5-4d3f-ad5f-084f3c8b0b09
|
|
```
|
|
|
|
That ID is the `slack-events-resolver` logic function. Slack signs the handshake, so `SLACK_WEBHOOK_SECRET` must be set first or Slack reports *"didn't respond with the value of the challenge parameter."*
|
|
|
|
Under **Subscribe to bot events**, add:
|
|
|
|
- `app_mention` — mentions of the bot in a channel
|
|
- `message.im` — direct messages to the bot
|
|
- `message.channels` — replies in public-channel threads, for un-mentioned follow-ups
|
|
- `message.groups` — same, for private channels the bot is in
|
|
|
|
Invite the bot to any channel where it should follow threads. Slack may ask you to reinstall after changing subscriptions.
|
|
|
|
3. **Reconnect** so the token picks up the assistant scopes.
|
|
|
|
4. **Role.** The `slack-assistant` agent binds to the app's **Slack Assistant** role automatically on install and upgrade. Anyone who can message the bot acts with that role — Slack users are not mapped to individual Twenty members yet, so keep the role scoped to what you're comfortable exposing.
|
|
|
|
## Behaviour notes
|
|
|
|
- **Thread memory.** After a successful reply the bot stays active in that thread, so follow-ups need no mention. Channel threads stay active for 24 hours after the last reply (each reply renews it); DM threads never expire.
|
|
- **One Slack workspace per Twenty workspace.** Connecting Slack claims that Slack team for the connecting Twenty workspace. On the same server, a second Twenty workspace connecting the same Slack team is rejected. The claim is not released on disconnect yet, so moving a Slack workspace needs a server admin.
|
|
|
|
## Workflow field names (for step authors)
|
|
|
|
Fields use camelCase in the step UI:
|
|
|
|
- `slackChannelId` — channel or DM, name or ID
|
|
- `messageText`, `newMessageText` — body to post / the replacement on update
|
|
- `messageTimestamp` — Slack's per-message id, same value as the tool output `slackTs` when chaining steps
|
|
- `parentMessageTimestamp` — thread replies only
|
|
- `messageFormat` — `markdown` sends the body as Slack `markdown_text` (`**bold**`), `plain` sends `text` with markup disabled, omitted uses Slack's default for `text`
|
|
- `recipientSlackUserId` — ephemeral steps
|
|
- `emojiName` — Slack shortcode, for example `white_check_mark`
|
|
|
|
## HTTP routes
|
|
|
|
The **Send Slack message** command menu item is backed by two app routes, both requiring an authenticated Twenty user and using the same Slack connection as the workflow steps:
|
|
|
|
- `GET /slack/channels` — lists channels visible to the bot
|
|
- `POST /slack/messages` — posts a message
|