@chat-adapter/slack 4.17.0 → 4.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +323 -9
- package/dist/index.d.ts +15 -1
- package/dist/index.js +405 -74
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -3,15 +3,17 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@chat-adapter/slack)
|
|
4
4
|
[](https://www.npmjs.com/package/@chat-adapter/slack)
|
|
5
5
|
|
|
6
|
-
Slack adapter for [Chat SDK](https://chat-sdk.dev
|
|
6
|
+
Slack adapter for [Chat SDK](https://chat-sdk.dev). Configure single-workspace or multi-workspace OAuth deployments.
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
|
|
11
|
+
pnpm add @chat-adapter/slack
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## Single-workspace mode
|
|
15
|
+
|
|
16
|
+
For bots deployed to a single Slack workspace. The adapter auto-detects `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET` from environment variables:
|
|
15
17
|
|
|
16
18
|
```typescript
|
|
17
19
|
import { Chat } from "chat";
|
|
@@ -20,10 +22,7 @@ import { createSlackAdapter } from "@chat-adapter/slack";
|
|
|
20
22
|
const bot = new Chat({
|
|
21
23
|
userName: "mybot",
|
|
22
24
|
adapters: {
|
|
23
|
-
slack: createSlackAdapter(
|
|
24
|
-
botToken: process.env.SLACK_BOT_TOKEN!,
|
|
25
|
-
signingSecret: process.env.SLACK_SIGNING_SECRET!,
|
|
26
|
-
}),
|
|
25
|
+
slack: createSlackAdapter(),
|
|
27
26
|
},
|
|
28
27
|
});
|
|
29
28
|
|
|
@@ -32,9 +31,324 @@ bot.onNewMention(async (thread, message) => {
|
|
|
32
31
|
});
|
|
33
32
|
```
|
|
34
33
|
|
|
35
|
-
##
|
|
34
|
+
## Multi-workspace mode
|
|
35
|
+
|
|
36
|
+
For apps installed across multiple Slack workspaces via OAuth, omit `botToken` and provide OAuth credentials instead. The adapter resolves tokens dynamically from your state adapter using the `team_id` from incoming webhooks.
|
|
37
|
+
|
|
38
|
+
When you pass any auth-related config (like `clientId`), the adapter won't fall back to env vars for other auth fields, preventing accidental mixing of auth modes.
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { createSlackAdapter } from "@chat-adapter/slack";
|
|
42
|
+
import { createRedisState } from "@chat-adapter/state-redis";
|
|
43
|
+
|
|
44
|
+
const slackAdapter = createSlackAdapter({
|
|
45
|
+
clientId: process.env.SLACK_CLIENT_ID!,
|
|
46
|
+
clientSecret: process.env.SLACK_CLIENT_SECRET!,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const bot = new Chat({
|
|
50
|
+
userName: "mybot",
|
|
51
|
+
adapters: { slack: slackAdapter },
|
|
52
|
+
state: createRedisState(),
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### OAuth callback
|
|
57
|
+
|
|
58
|
+
The adapter handles the full Slack OAuth V2 exchange. Point your OAuth redirect URL to a route that calls `handleOAuthCallback`:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { slackAdapter } from "@/lib/bot";
|
|
62
|
+
|
|
63
|
+
export async function GET(request: Request) {
|
|
64
|
+
const { teamId } = await slackAdapter.handleOAuthCallback(request);
|
|
65
|
+
return new Response(`Installed for team ${teamId}!`);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Using the adapter outside webhooks
|
|
70
|
+
|
|
71
|
+
During webhook handling, the adapter resolves tokens automatically from `team_id`. Outside that context (e.g. cron jobs or background workers), use `getInstallation` and `withBotToken`:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const install = await slackAdapter.getInstallation(teamId);
|
|
75
|
+
if (!install) throw new Error("Workspace not installed");
|
|
76
|
+
|
|
77
|
+
await slackAdapter.withBotToken(install.botToken, async () => {
|
|
78
|
+
const thread = bot.thread("slack:C12345:1234567890.123456");
|
|
79
|
+
await thread.post("Hello from a cron job!");
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`withBotToken` uses `AsyncLocalStorage` under the hood, so concurrent calls with different tokens are isolated.
|
|
84
|
+
|
|
85
|
+
### Removing installations
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
await slackAdapter.deleteInstallation(teamId);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Token encryption
|
|
92
|
+
|
|
93
|
+
Pass a base64-encoded 32-byte key as `encryptionKey` to encrypt bot tokens at rest using AES-256-GCM:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
openssl rand -base64 32
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
When `encryptionKey` is set, `setInstallation()` encrypts the token before storing and `getInstallation()` decrypts it transparently.
|
|
100
|
+
|
|
101
|
+
## Slack app setup
|
|
102
|
+
|
|
103
|
+
### 1. Create a Slack app from manifest
|
|
104
|
+
|
|
105
|
+
1. Go to [api.slack.com/apps](https://api.slack.com/apps)
|
|
106
|
+
2. Click **Create New App** then **From an app manifest**
|
|
107
|
+
3. Select your workspace and paste the following manifest:
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
display_information:
|
|
111
|
+
name: My Bot
|
|
112
|
+
description: A bot built with chat-sdk
|
|
113
|
+
|
|
114
|
+
features:
|
|
115
|
+
bot_user:
|
|
116
|
+
display_name: My Bot
|
|
117
|
+
always_online: true
|
|
118
|
+
|
|
119
|
+
oauth_config:
|
|
120
|
+
scopes:
|
|
121
|
+
bot:
|
|
122
|
+
- app_mentions:read
|
|
123
|
+
- channels:history
|
|
124
|
+
- channels:read
|
|
125
|
+
- chat:write
|
|
126
|
+
- groups:history
|
|
127
|
+
- groups:read
|
|
128
|
+
- im:history
|
|
129
|
+
- im:read
|
|
130
|
+
- mpim:history
|
|
131
|
+
- mpim:read
|
|
132
|
+
- reactions:read
|
|
133
|
+
- reactions:write
|
|
134
|
+
- users:read
|
|
135
|
+
|
|
136
|
+
settings:
|
|
137
|
+
event_subscriptions:
|
|
138
|
+
request_url: https://your-domain.com/api/webhooks/slack
|
|
139
|
+
bot_events:
|
|
140
|
+
- app_mention
|
|
141
|
+
- message.channels
|
|
142
|
+
- message.groups
|
|
143
|
+
- message.im
|
|
144
|
+
- message.mpim
|
|
145
|
+
- member_joined_channel
|
|
146
|
+
- assistant_thread_started
|
|
147
|
+
- assistant_thread_context_changed
|
|
148
|
+
interactivity:
|
|
149
|
+
is_enabled: true
|
|
150
|
+
request_url: https://your-domain.com/api/webhooks/slack
|
|
151
|
+
org_deploy_enabled: false
|
|
152
|
+
socket_mode_enabled: false
|
|
153
|
+
token_rotation_enabled: false
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
4. Replace `https://your-domain.com/api/webhooks/slack` with your deployed webhook URL
|
|
157
|
+
5. Click **Create**
|
|
158
|
+
|
|
159
|
+
### 2. Get credentials
|
|
160
|
+
|
|
161
|
+
After creating the app, go to **Basic Information** → **App Credentials** and copy:
|
|
162
|
+
|
|
163
|
+
- **Signing Secret** as `SLACK_SIGNING_SECRET`
|
|
164
|
+
- **Client ID** as `SLACK_CLIENT_ID` (multi-workspace only)
|
|
165
|
+
- **Client Secret** as `SLACK_CLIENT_SECRET` (multi-workspace only)
|
|
166
|
+
|
|
167
|
+
**Single workspace:** Go to **OAuth & Permissions**, click **Install to Workspace**, and copy the **Bot User OAuth Token** (`xoxb-...`) as `SLACK_BOT_TOKEN`.
|
|
168
|
+
|
|
169
|
+
**Multi-workspace:** Enable **Manage Distribution** under **Basic Information** and set up an OAuth redirect URL pointing to your callback route.
|
|
170
|
+
|
|
171
|
+
### 3. Configure slash commands (optional)
|
|
172
|
+
|
|
173
|
+
1. Go to **Slash Commands** in your app settings
|
|
174
|
+
2. Click **Create New Command**
|
|
175
|
+
3. Set **Command** (e.g., `/feedback`)
|
|
176
|
+
4. Set **Request URL** to `https://your-domain.com/api/webhooks/slack`
|
|
177
|
+
5. Add a description and click **Save**
|
|
178
|
+
|
|
179
|
+
## Configuration
|
|
180
|
+
|
|
181
|
+
All options are auto-detected from environment variables when not provided. You can call `createSlackAdapter()` with no arguments if the env vars are set.
|
|
182
|
+
|
|
183
|
+
| Option | Required | Description |
|
|
184
|
+
|--------|----------|-------------|
|
|
185
|
+
| `botToken` | No | Bot token (`xoxb-...`). Auto-detected from `SLACK_BOT_TOKEN` |
|
|
186
|
+
| `signingSecret` | No* | Signing secret for webhook verification. Auto-detected from `SLACK_SIGNING_SECRET` |
|
|
187
|
+
| `clientId` | No | App client ID for multi-workspace OAuth. Auto-detected from `SLACK_CLIENT_ID` |
|
|
188
|
+
| `clientSecret` | No | App client secret for multi-workspace OAuth. Auto-detected from `SLACK_CLIENT_SECRET` |
|
|
189
|
+
| `encryptionKey` | No | AES-256-GCM key for encrypting stored tokens. Auto-detected from `SLACK_ENCRYPTION_KEY` |
|
|
190
|
+
| `installationKeyPrefix` | No | Prefix for the state key used to store workspace installations. Defaults to `slack:installation`. The full key is `{prefix}:{teamId}` |
|
|
191
|
+
| `logger` | No | Logger instance (defaults to `ConsoleLogger("info")`) |
|
|
192
|
+
|
|
193
|
+
*`signingSecret` is required — either via config or `SLACK_SIGNING_SECRET` env var.
|
|
194
|
+
|
|
195
|
+
## Environment variables
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
SLACK_BOT_TOKEN=xoxb-... # Single-workspace only
|
|
199
|
+
SLACK_SIGNING_SECRET=...
|
|
200
|
+
SLACK_CLIENT_ID=... # Multi-workspace only
|
|
201
|
+
SLACK_CLIENT_SECRET=... # Multi-workspace only
|
|
202
|
+
SLACK_ENCRYPTION_KEY=... # Optional, for token encryption
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Features
|
|
206
|
+
|
|
207
|
+
### Messaging
|
|
208
|
+
|
|
209
|
+
| Feature | Supported |
|
|
210
|
+
|---------|-----------|
|
|
211
|
+
| Post message | Yes |
|
|
212
|
+
| Edit message | Yes |
|
|
213
|
+
| Delete message | Yes |
|
|
214
|
+
| File uploads | Yes |
|
|
215
|
+
| Streaming | Native API |
|
|
216
|
+
|
|
217
|
+
### Rich content
|
|
218
|
+
|
|
219
|
+
| Feature | Supported |
|
|
220
|
+
|---------|-----------|
|
|
221
|
+
| Card format | Block Kit |
|
|
222
|
+
| Buttons | Yes |
|
|
223
|
+
| Link buttons | Yes |
|
|
224
|
+
| Select menus | Yes |
|
|
225
|
+
| Tables | Block Kit |
|
|
226
|
+
| Fields | Yes |
|
|
227
|
+
| Images in cards | Yes |
|
|
228
|
+
| Modals | Yes |
|
|
229
|
+
|
|
230
|
+
### Conversations
|
|
231
|
+
|
|
232
|
+
| Feature | Supported |
|
|
233
|
+
|---------|-----------|
|
|
234
|
+
| Slash commands | Yes |
|
|
235
|
+
| Mentions | Yes |
|
|
236
|
+
| Add reactions | Yes |
|
|
237
|
+
| Remove reactions | Yes |
|
|
238
|
+
| Typing indicator | Yes |
|
|
239
|
+
| DMs | Yes |
|
|
240
|
+
| Ephemeral messages | Yes (native) |
|
|
241
|
+
|
|
242
|
+
### Message history
|
|
243
|
+
|
|
244
|
+
| Feature | Supported |
|
|
245
|
+
|---------|-----------|
|
|
246
|
+
| Fetch messages | Yes |
|
|
247
|
+
| Fetch single message | Yes |
|
|
248
|
+
| Fetch thread info | Yes |
|
|
249
|
+
| Fetch channel messages | Yes |
|
|
250
|
+
| List threads | Yes |
|
|
251
|
+
| Fetch channel info | Yes |
|
|
252
|
+
| Post channel message | Yes |
|
|
253
|
+
|
|
254
|
+
### Platform-specific
|
|
255
|
+
|
|
256
|
+
| Feature | Supported |
|
|
257
|
+
|---------|-----------|
|
|
258
|
+
| Assistants API | Yes |
|
|
259
|
+
| Member joined channel | Yes |
|
|
260
|
+
| App Home tab | Yes |
|
|
261
|
+
|
|
262
|
+
## Slack Assistants API
|
|
263
|
+
|
|
264
|
+
The adapter supports Slack's [Assistants API](https://api.slack.com/docs/apps/ai) for building AI-powered assistant experiences. This enables suggested prompts, status indicators, and thread titles in assistant DM threads.
|
|
265
|
+
|
|
266
|
+
### Event handlers
|
|
267
|
+
|
|
268
|
+
Register handlers on the `Chat` instance:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
bot.onAssistantThreadStarted(async (event) => {
|
|
272
|
+
const slack = bot.getAdapter("slack") as SlackAdapter;
|
|
273
|
+
await slack.setSuggestedPrompts(event.channelId, event.threadTs, [
|
|
274
|
+
{ title: "Summarize", message: "Summarize this channel" },
|
|
275
|
+
{ title: "Draft", message: "Help me draft a message" },
|
|
276
|
+
]);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
bot.onAssistantContextChanged(async (event) => {
|
|
280
|
+
// User navigated to a different channel with the assistant panel open
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Adapter methods
|
|
285
|
+
|
|
286
|
+
The `SlackAdapter` exposes these methods for the Assistants API:
|
|
287
|
+
|
|
288
|
+
| Method | Description |
|
|
289
|
+
|--------|-------------|
|
|
290
|
+
| `setSuggestedPrompts(channelId, threadTs, prompts, title?)` | Show prompt suggestions in the thread |
|
|
291
|
+
| `setAssistantStatus(channelId, threadTs, status)` | Show a thinking/status indicator |
|
|
292
|
+
| `setAssistantTitle(channelId, threadTs, title)` | Set the thread title (shown in History) |
|
|
293
|
+
| `publishHomeView(userId, view)` | Publish a Home tab view for a user |
|
|
294
|
+
| `startTyping(threadId, status)` | Show a custom loading status (requires `assistant:write` scope) |
|
|
295
|
+
|
|
296
|
+
### Required scopes and events
|
|
297
|
+
|
|
298
|
+
Add these to your Slack app manifest for Assistants API support:
|
|
299
|
+
|
|
300
|
+
```yaml
|
|
301
|
+
oauth_config:
|
|
302
|
+
scopes:
|
|
303
|
+
bot:
|
|
304
|
+
- assistant:write
|
|
305
|
+
|
|
306
|
+
settings:
|
|
307
|
+
event_subscriptions:
|
|
308
|
+
bot_events:
|
|
309
|
+
- assistant_thread_started
|
|
310
|
+
- assistant_thread_context_changed
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Stream with stop blocks
|
|
314
|
+
|
|
315
|
+
When streaming in an assistant thread, you can attach Block Kit elements to the final message:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
await thread.stream(textStream, {
|
|
319
|
+
stopBlocks: [
|
|
320
|
+
{ type: "actions", elements: [{ type: "button", text: { type: "plain_text", text: "Retry" }, action_id: "retry" }] },
|
|
321
|
+
],
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Troubleshooting
|
|
326
|
+
|
|
327
|
+
### `handleOAuthCallback` throws "Adapter not initialized"
|
|
328
|
+
|
|
329
|
+
- Call `await bot.initialize()` before `handleOAuthCallback()` in your callback route.
|
|
330
|
+
- In a Next.js app, this ensures:
|
|
331
|
+
- state adapter is connected
|
|
332
|
+
- the Slack adapter is attached to Chat
|
|
333
|
+
- installation writes succeed
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
const slackAdapter = bot.getAdapter("slack");
|
|
337
|
+
|
|
338
|
+
await bot.initialize();
|
|
339
|
+
await slackAdapter.handleOAuthCallback(request);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### "Invalid signature" error
|
|
343
|
+
|
|
344
|
+
- Verify `SLACK_SIGNING_SECRET` is correct
|
|
345
|
+
- Check that the request timestamp is within 5 minutes (clock sync issue)
|
|
346
|
+
|
|
347
|
+
### Bot not responding to messages
|
|
36
348
|
|
|
37
|
-
|
|
349
|
+
- Verify event subscriptions are configured
|
|
350
|
+
- Check that the bot has been added to the channel
|
|
351
|
+
- Ensure the webhook URL is correct and accessible
|
|
38
352
|
|
|
39
353
|
## License
|
|
40
354
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CardElement, BaseFormatConverter, AdapterPostableMessage, Root, Logger, Adapter, ChatInstance, WebhookOptions, RawMessage, EphemeralMessage, ModalElement, EmojiValue, StreamChunk, StreamOptions, FetchOptions, FetchResult, ThreadInfo, Message, ListThreadsOptions, ListThreadsResult, ChannelInfo, FormattedContent } from 'chat';
|
|
1
|
+
import { CardElement, BaseFormatConverter, AdapterPostableMessage, Root, Logger, Adapter, ChatInstance, WebhookOptions, RawMessage, EphemeralMessage, ScheduledMessage, ModalElement, EmojiValue, StreamChunk, StreamOptions, FetchOptions, FetchResult, ThreadInfo, Message, ListThreadsOptions, ListThreadsResult, ChannelInfo, FormattedContent } from 'chat';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Slack Block Kit converter for cross-platform cards.
|
|
@@ -59,6 +59,12 @@ declare class SlackFormatConverter extends BaseFormatConverter {
|
|
|
59
59
|
* Parse Slack mrkdwn into an AST.
|
|
60
60
|
*/
|
|
61
61
|
toAst(mrkdwn: string): Root;
|
|
62
|
+
/**
|
|
63
|
+
* Convert AST to Slack blocks, using a native table block for the first table.
|
|
64
|
+
* Returns null if the AST contains no tables (caller should use regular text).
|
|
65
|
+
* Slack allows at most one table block per message; additional tables use ASCII.
|
|
66
|
+
*/
|
|
67
|
+
toBlocksWithTable(ast: Root): SlackBlock[] | null;
|
|
62
68
|
private nodeToMrkdwn;
|
|
63
69
|
}
|
|
64
70
|
|
|
@@ -307,8 +313,16 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
|
|
|
307
313
|
* Includes a fetchData method that uses the bot token for auth.
|
|
308
314
|
*/
|
|
309
315
|
private createAttachment;
|
|
316
|
+
/**
|
|
317
|
+
* Try to render a message using native Slack table blocks.
|
|
318
|
+
* Returns blocks + fallback text if the message contains tables, null otherwise.
|
|
319
|
+
*/
|
|
320
|
+
private renderWithTableBlocks;
|
|
310
321
|
postMessage(threadId: string, message: AdapterPostableMessage): Promise<RawMessage<unknown>>;
|
|
311
322
|
postEphemeral(threadId: string, userId: string, message: AdapterPostableMessage): Promise<EphemeralMessage>;
|
|
323
|
+
scheduleMessage(threadId: string, message: AdapterPostableMessage, options: {
|
|
324
|
+
postAt: Date;
|
|
325
|
+
}): Promise<ScheduledMessage>;
|
|
312
326
|
openModal(triggerId: string, modal: ModalElement, contextId?: string): Promise<{
|
|
313
327
|
viewId: string;
|
|
314
328
|
}>;
|