@chat-adapter/slack 4.13.1 → 4.13.2

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 CHANGED
@@ -1,6 +1,9 @@
1
1
  # @chat-adapter/slack
2
2
 
3
- Slack adapter for the [chat](https://github.com/vercel-labs/chat) SDK.
3
+ [![npm version](https://img.shields.io/npm/v/@chat-adapter/slack)](https://www.npmjs.com/package/@chat-adapter/slack)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@chat-adapter/slack)](https://www.npmjs.com/package/@chat-adapter/slack)
5
+
6
+ Slack adapter for [Chat SDK](https://chat-sdk.dev/docs). Supports single-workspace and multi-workspace OAuth deployments.
4
7
 
5
8
  ## Installation
6
9
 
@@ -8,13 +11,13 @@ Slack adapter for the [chat](https://github.com/vercel-labs/chat) SDK.
8
11
  npm install chat @chat-adapter/slack
9
12
  ```
10
13
 
11
- ## Usage (Single Workspace)
14
+ ## Usage
12
15
 
13
16
  ```typescript
14
17
  import { Chat } from "chat";
15
18
  import { createSlackAdapter } from "@chat-adapter/slack";
16
19
 
17
- const chat = new Chat({
20
+ const bot = new Chat({
18
21
  userName: "mybot",
19
22
  adapters: {
20
23
  slack: createSlackAdapter({
@@ -24,203 +27,14 @@ const chat = new Chat({
24
27
  },
25
28
  });
26
29
 
27
- // Handle @mentions
28
- chat.onNewMention(async (thread, message) => {
30
+ bot.onNewMention(async (thread, message) => {
29
31
  await thread.post("Hello from Slack!");
30
32
  });
31
33
  ```
32
34
 
33
- ## Multi-Workspace Mode
34
-
35
- For apps installed across multiple Slack workspaces, omit `botToken` and let the adapter resolve tokens dynamically from your state adapter (e.g. Redis) using the `team_id` from incoming webhooks.
36
-
37
- ```typescript
38
- import { Chat } from "chat";
39
- import { createSlackAdapter } from "@chat-adapter/slack";
40
- import { createRedisState } from "@chat-adapter/state-redis";
41
-
42
- const slackAdapter = createSlackAdapter({
43
- signingSecret: process.env.SLACK_SIGNING_SECRET!,
44
- clientId: process.env.SLACK_CLIENT_ID!,
45
- clientSecret: process.env.SLACK_CLIENT_SECRET!,
46
- logger: logger,
47
- encryptionKey: process.env.SLACK_ENCRYPTION_KEY, // optional, encrypts tokens at rest
48
- });
49
-
50
- const chat = new Chat({
51
- userName: "mybot",
52
- adapters: { slack: slackAdapter },
53
- state: createRedisState({ url: process.env.REDIS_URL! }),
54
- // notice that there is no bot token
55
- });
56
- ```
57
-
58
- ### OAuth callback
59
-
60
- The adapter handles the full Slack OAuth V2 exchange. Pass `clientId` and `clientSecret` in the config, then point your OAuth redirect URL to a route that calls `handleOAuthCallback`:
61
-
62
- ```typescript
63
- import { slackAdapter } from "@/lib/chat"; // your adapter instance
64
-
65
- export async function GET(request: Request) {
66
- const { teamId } = await slackAdapter.handleOAuthCallback(request);
67
- return new Response(`Installed for team ${teamId}!`);
68
- }
69
- ```
70
-
71
- ### Webhook handling
72
-
73
- No changes needed — the adapter extracts `team_id` from incoming webhooks and resolves the token automatically:
74
-
75
- ```typescript
76
- export async function POST(request: Request) {
77
- return chat.webhooks.slack(request, { waitUntil });
78
- }
79
- ```
80
-
81
- ### Using the adapter outside a webhook (cron jobs, workflows)
82
-
83
- During webhook handling, the adapter resolves the token automatically from `team_id`. Outside that context (e.g. a cron job), use `getInstallation` to retrieve the token and `withBotToken` to scope it:
84
-
85
- ```typescript
86
- import { Chat } from "chat";
87
-
88
- // In a cron job or background worker:
89
- const install = await slackAdapter.getInstallation(teamId);
90
- if (!install) throw new Error("Workspace not installed");
91
-
92
- await slackAdapter.withBotToken(install.botToken, async () => {
93
- // All adapter calls inside this callback use the provided token.
94
- // You can use thread.post(), thread.subscribe(), etc. normally.
95
- const thread = chat.thread("slack:C12345:1234567890.123456");
96
- await thread.post("Hello from a cron job!");
97
- });
98
- ```
99
-
100
- `withBotToken` uses `AsyncLocalStorage` under the hood, so concurrent calls with different tokens are isolated from each other.
101
-
102
- ### Removing installations
103
-
104
- ```typescript
105
- await slackAdapter.deleteInstallation(teamId);
106
- ```
107
-
108
- ### Encryption
109
-
110
- Pass a base64-encoded 32-byte key as `encryptionKey` to encrypt bot tokens at rest using AES-256-GCM. You can generate a key with:
111
-
112
- ```bash
113
- openssl rand -base64 32
114
- ```
115
-
116
- When `encryptionKey` is set, `setInstallation()` encrypts the token before storing it and `getInstallation()` decrypts it transparently.
117
-
118
- ## Configuration
119
-
120
- | Option | Required | Description |
121
- |--------|----------|-------------|
122
- | `botToken` | No | Slack bot token (`xoxb-...`). Required for single-workspace mode. Omit for multi-workspace. |
123
- | `signingSecret` | Yes | Slack signing secret for webhook verification |
124
- | `clientId` | No | Slack app client ID (required for OAuth / multi-workspace) |
125
- | `clientSecret` | No | Slack app client secret (required for OAuth / multi-workspace) |
126
- | `encryptionKey` | No | Base64-encoded 32-byte AES-256-GCM key for encrypting stored tokens |
127
-
128
- ## Environment Variables
129
-
130
- ```bash
131
- SLACK_BOT_TOKEN=xoxb-... # single-workspace only
132
- SLACK_SIGNING_SECRET=...
133
- SLACK_CLIENT_ID=... # required for multi-workspace OAuth
134
- SLACK_CLIENT_SECRET=... # required for multi-workspace OAuth
135
- SLACK_ENCRYPTION_KEY=... # optional, for multi-workspace token encryption
136
- ```
137
-
138
- ## Slack App Setup
139
-
140
- ### 1. Create a Slack App
141
-
142
- 1. Go to [api.slack.com/apps](https://api.slack.com/apps)
143
- 2. Click **Create New App** → **From scratch**
144
- 3. Enter app name and select workspace
145
- 4. Click **Create App**
146
-
147
- ### 2. Configure Bot Token Scopes
148
-
149
- 1. Go to **OAuth & Permissions** in the sidebar
150
- 2. Under **Scopes** → **Bot Token Scopes**, add:
151
- - `app_mentions:read` - Receive @mention events
152
- - `channels:history` - Read messages in public channels
153
- - `channels:read` - View basic channel info
154
- - `chat:write` - Send messages
155
- - `groups:history` - Read messages in private channels
156
- - `groups:read` - View basic private channel info
157
- - `im:history` - Read direct messages
158
- - `im:read` - View basic DM info
159
- - `reactions:read` - View emoji reactions
160
- - `reactions:write` - Add/remove emoji reactions
161
- - `users:read` - View user info (for display names)
162
-
163
- ### 3. Install App to Workspace
164
-
165
- **Single workspace:** Install directly from the Slack dashboard.
166
-
167
- 1. Go to **OAuth & Permissions**
168
- 2. Click **Install to Workspace**
169
- 3. Authorize the app
170
- 4. Copy the **Bot User OAuth Token** (starts with `xoxb-`) → `SLACK_BOT_TOKEN`
171
-
172
- **Multi-workspace:** Enable **Manage Distribution** under **Basic Information**, then set up an [OAuth redirect URL](https://api.slack.com/authentication/oauth-v2) pointing to your callback route. The adapter handles the token exchange via `handleOAuthCallback()` (see [Multi-Workspace Mode](#multi-workspace-mode) above).
173
-
174
- ### 4. Get Signing Secret and OAuth Credentials
175
-
176
- 1. Go to **Basic Information**
177
- 2. Under **App Credentials**, copy:
178
- - **Signing Secret** → `SLACK_SIGNING_SECRET`
179
- - **Client ID** → `SLACK_CLIENT_ID` (multi-workspace only)
180
- - **Client Secret** → `SLACK_CLIENT_SECRET` (multi-workspace only)
181
-
182
- ### 5. Configure Event Subscriptions
183
-
184
- 1. Go to **Event Subscriptions**
185
- 2. Toggle **Enable Events** to On
186
- 3. Set **Request URL** to: `https://your-domain.com/api/webhooks/slack`
187
- - Slack will verify the URL immediately
188
- 4. Under **Subscribe to bot events**, add:
189
- - `app_mention` - When someone @mentions your bot
190
- - `message.channels` - Messages in public channels
191
- - `message.groups` - Messages in private channels
192
- - `message.im` - Direct messages
193
- 5. Click **Save Changes**
194
-
195
- ### 6. (Optional) Enable Interactivity
196
-
197
- If you want to use buttons, modals, or other interactive components:
198
-
199
- 1. Go to **Interactivity & Shortcuts**
200
- 2. Toggle **Interactivity** to On
201
- 3. Set **Request URL** to: `https://your-domain.com/api/webhooks/slack`
202
-
203
- ## Features
204
-
205
- - Multi-workspace support with OAuth V2 and encrypted token storage
206
- - Message posting and editing
207
- - Thread subscriptions
208
- - Reaction handling (add/remove/events)
209
- - File attachments
210
- - Rich cards (Block Kit)
211
- - Action callbacks (interactive components)
212
- - Direct messages
213
-
214
- ## Troubleshooting
215
-
216
- ### "Invalid signature" error
217
- - Verify `SLACK_SIGNING_SECRET` is correct
218
- - Check that the request timestamp is within 5 minutes (clock sync issue)
35
+ ## Documentation
219
36
 
220
- ### Bot not responding to messages
221
- - Verify Event Subscriptions are configured
222
- - Check that the bot has been added to the channel
223
- - Ensure the webhook URL is correct and accessible
37
+ Full setup instructions, configuration reference, and features at [chat-sdk.dev/docs/adapters/slack](https://chat-sdk.dev/docs/adapters/slack).
224
38
 
225
39
  ## License
226
40
 
package/dist/index.d.ts CHANGED
@@ -8,8 +8,8 @@ import { CardElement, BaseFormatConverter, AdapterPostableMessage, Root, Logger,
8
8
  */
9
9
 
10
10
  interface SlackBlock {
11
- type: string;
12
11
  block_id?: string;
12
+ type: string;
13
13
  [key: string]: unknown;
14
14
  }
15
15
  /**
@@ -23,8 +23,8 @@ declare function cardToBlockKit(card: CardElement): SlackBlock[];
23
23
  declare function cardToFallbackText(card: CardElement): string;
24
24
 
25
25
  interface EncryptedTokenData {
26
- iv: string;
27
26
  data: string;
27
+ iv: string;
28
28
  tag: string;
29
29
  }
30
30
  declare function decodeKey(rawKey: string): Buffer;
@@ -65,23 +65,23 @@ declare class SlackFormatConverter extends BaseFormatConverter {
65
65
  interface SlackAdapterConfig {
66
66
  /** Bot token (xoxb-...). Required for single-workspace mode. Omit for multi-workspace. */
67
67
  botToken?: string;
68
- /** Signing secret for webhook verification */
69
- signingSecret: string;
70
- /** Logger instance for error reporting */
71
- logger: Logger;
72
- /** Override bot username (optional) */
73
- userName?: string;
74
68
  /** Bot user ID (will be fetched if not provided) */
75
69
  botUserId?: string;
70
+ /** Slack app client ID (required for OAuth / multi-workspace) */
71
+ clientId?: string;
72
+ /** Slack app client secret (required for OAuth / multi-workspace) */
73
+ clientSecret?: string;
76
74
  /**
77
75
  * Base64-encoded 32-byte AES-256-GCM encryption key.
78
76
  * If provided, bot tokens stored via setInstallation() will be encrypted at rest.
79
77
  */
80
78
  encryptionKey?: string;
81
- /** Slack app client ID (required for OAuth / multi-workspace) */
82
- clientId?: string;
83
- /** Slack app client secret (required for OAuth / multi-workspace) */
84
- clientSecret?: string;
79
+ /** Logger instance for error reporting */
80
+ logger: Logger;
81
+ /** Signing secret for webhook verification */
82
+ signingSecret: string;
83
+ /** Override bot username (optional) */
84
+ userName?: string;
85
85
  }
86
86
  /** Data stored per Slack workspace installation */
87
87
  interface SlackInstallation {
@@ -96,20 +96,13 @@ interface SlackThreadId {
96
96
  }
97
97
  /** Slack event payload (raw message format) */
98
98
  interface SlackEvent {
99
- type: string;
100
- user?: string;
101
99
  bot_id?: string;
102
100
  channel?: string;
103
- text?: string;
104
- ts?: string;
105
- thread_ts?: string;
106
- subtype?: string;
107
- username?: string;
101
+ /** Channel type: "channel", "group", "mpim", or "im" (DM) */
102
+ channel_type?: string;
108
103
  edited?: {
109
104
  ts: string;
110
105
  };
111
- /** Channel type: "channel", "group", "mpim", or "im" (DM) */
112
- channel_type?: string;
113
106
  files?: Array<{
114
107
  id?: string;
115
108
  mimetype?: string;
@@ -119,42 +112,49 @@ interface SlackEvent {
119
112
  original_w?: number;
120
113
  original_h?: number;
121
114
  }>;
122
- team?: string;
123
- team_id?: string;
124
- /** Number of replies in the thread (present on thread parent messages) */
125
- reply_count?: number;
126
115
  /** Timestamp of the latest reply (present on thread parent messages) */
127
116
  latest_reply?: string;
117
+ /** Number of replies in the thread (present on thread parent messages) */
118
+ reply_count?: number;
119
+ subtype?: string;
120
+ team?: string;
121
+ team_id?: string;
122
+ text?: string;
123
+ thread_ts?: string;
124
+ ts?: string;
125
+ type: string;
126
+ user?: string;
127
+ username?: string;
128
128
  }
129
129
  /** Slack reaction event payload */
130
130
  interface SlackReactionEvent {
131
- type: "reaction_added" | "reaction_removed";
132
- user: string;
133
- reaction: string;
134
- item_user?: string;
131
+ event_ts: string;
135
132
  item: {
136
133
  type: string;
137
134
  channel: string;
138
135
  ts: string;
139
136
  };
140
- event_ts: string;
137
+ item_user?: string;
138
+ reaction: string;
139
+ type: "reaction_added" | "reaction_removed";
140
+ user: string;
141
141
  }
142
142
  declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
143
143
  readonly name = "slack";
144
144
  readonly userName: string;
145
- private client;
146
- private signingSecret;
147
- private defaultBotToken;
145
+ private readonly client;
146
+ private readonly signingSecret;
147
+ private readonly defaultBotToken;
148
148
  private chat;
149
- private logger;
149
+ private readonly logger;
150
150
  private _botUserId;
151
151
  private _botId;
152
- private formatConverter;
152
+ private readonly formatConverter;
153
153
  private static USER_CACHE_TTL_MS;
154
- private clientId;
155
- private clientSecret;
156
- private encryptionKey;
157
- private requestContext;
154
+ private readonly clientId;
155
+ private readonly clientSecret;
156
+ private readonly encryptionKey;
157
+ private readonly requestContext;
158
158
  /** Bot user ID (e.g., U_BOT_123) used for mention detection */
159
159
  get botUserId(): string | undefined;
160
160
  constructor(config: SlackAdapterConfig);
package/dist/index.js CHANGED
@@ -263,6 +263,7 @@ import crypto from "crypto";
263
263
  var ALGORITHM = "aes-256-gcm";
264
264
  var IV_LENGTH = 12;
265
265
  var AUTH_TAG_LENGTH = 16;
266
+ var HEX_KEY_PATTERN = /^[0-9a-fA-F]{64}$/;
266
267
  function encryptToken(plaintext, key) {
267
268
  const iv = crypto.randomBytes(IV_LENGTH);
268
269
  const cipher = crypto.createCipheriv(ALGORITHM, key, iv, {
@@ -293,13 +294,15 @@ function decryptToken(encrypted, key) {
293
294
  ]).toString("utf8");
294
295
  }
295
296
  function isEncryptedTokenData(value) {
296
- if (!value || typeof value !== "object") return false;
297
+ if (!value || typeof value !== "object") {
298
+ return false;
299
+ }
297
300
  const obj = value;
298
301
  return typeof obj.iv === "string" && typeof obj.data === "string" && typeof obj.tag === "string";
299
302
  }
300
303
  function decodeKey(rawKey) {
301
304
  const trimmed = rawKey.trim();
302
- const isHex = /^[0-9a-fA-F]{64}$/.test(trimmed);
305
+ const isHex = HEX_KEY_PATTERN.test(trimmed);
303
306
  const key = Buffer.from(trimmed, isHex ? "hex" : "base64");
304
307
  if (key.length !== 32) {
305
308
  throw new Error(
@@ -437,11 +440,15 @@ ${node.value}
437
440
 
438
441
  // src/modals.ts
439
442
  function encodeModalMetadata(meta) {
440
- if (!meta.contextId && !meta.privateMetadata) return void 0;
443
+ if (!(meta.contextId || meta.privateMetadata)) {
444
+ return void 0;
445
+ }
441
446
  return JSON.stringify({ c: meta.contextId, m: meta.privateMetadata });
442
447
  }
443
448
  function decodeModalMetadata(raw) {
444
- if (!raw) return {};
449
+ if (!raw) {
450
+ return {};
451
+ }
445
452
  try {
446
453
  const parsed = JSON.parse(raw);
447
454
  if (typeof parsed === "object" && parsed !== null && ("c" in parsed || "m" in parsed)) {
@@ -478,6 +485,10 @@ function modalChildToBlock(child) {
478
485
  return convertTextToBlock(child);
479
486
  case "fields":
480
487
  return convertFieldsToBlock(child);
488
+ default:
489
+ throw new Error(
490
+ `Unknown modal child type: ${child.type}`
491
+ );
481
492
  }
482
493
  }
483
494
  function textInputToBlock(input) {
@@ -595,7 +606,9 @@ var SlackAdapter = class _SlackAdapter {
595
606
  /** Bot user ID (e.g., U_BOT_123) used for mention detection */
596
607
  get botUserId() {
597
608
  const ctx = this.requestContext.getStore();
598
- if (ctx?.botUserId) return ctx.botUserId;
609
+ if (ctx?.botUserId) {
610
+ return ctx.botUserId;
611
+ }
599
612
  return this._botUserId || void 0;
600
613
  }
601
614
  constructor(config) {
@@ -617,8 +630,12 @@ var SlackAdapter = class _SlackAdapter {
617
630
  */
618
631
  getToken() {
619
632
  const ctx = this.requestContext.getStore();
620
- if (ctx?.token) return ctx.token;
621
- if (this.defaultBotToken) return this.defaultBotToken;
633
+ if (ctx?.token) {
634
+ return ctx.token;
635
+ }
636
+ if (this.defaultBotToken) {
637
+ return this.defaultBotToken;
638
+ }
622
639
  throw new ChatError(
623
640
  "No bot token available. In multi-workspace mode, ensure the webhook is being processed.",
624
641
  "MISSING_BOT_TOKEN"
@@ -696,7 +713,9 @@ var SlackAdapter = class _SlackAdapter {
696
713
  const state = this.chat.getState();
697
714
  const key = this.installationKey(teamId);
698
715
  const stored = await state.get(key);
699
- if (!stored) return null;
716
+ if (!stored) {
717
+ return null;
718
+ }
700
719
  if (this.encryptionKey && isEncryptedTokenData(stored.botToken)) {
701
720
  return {
702
721
  ...stored,
@@ -714,7 +733,7 @@ var SlackAdapter = class _SlackAdapter {
714
733
  * exchanges it for tokens, and saves the installation.
715
734
  */
716
735
  async handleOAuthCallback(request) {
717
- if (!this.clientId || !this.clientSecret) {
736
+ if (!(this.clientId && this.clientSecret)) {
718
737
  throw new ChatError(
719
738
  "clientId and clientSecret are required for OAuth. Pass them in createSlackAdapter().",
720
739
  "MISSING_OAUTH_CONFIG"
@@ -735,7 +754,7 @@ var SlackAdapter = class _SlackAdapter {
735
754
  code,
736
755
  redirect_uri: redirectUri
737
756
  });
738
- if (!result.ok || !result.access_token || !result.team?.id) {
757
+ if (!(result.ok && result.access_token && result.team?.id)) {
739
758
  throw new ChatError(
740
759
  `Slack OAuth failed: ${result.error || "missing access_token or team.id"}`,
741
760
  "OAUTH_FAILED"
@@ -803,7 +822,9 @@ var SlackAdapter = class _SlackAdapter {
803
822
  try {
804
823
  const params = new URLSearchParams(body);
805
824
  const payloadStr = params.get("payload");
806
- if (!payloadStr) return null;
825
+ if (!payloadStr) {
826
+ return null;
827
+ }
807
828
  const payload = JSON.parse(payloadStr);
808
829
  return payload.team?.id || payload.team_id || null;
809
830
  } catch {
@@ -894,9 +915,7 @@ var SlackAdapter = class _SlackAdapter {
894
915
  return new Response("Invalid JSON", { status: 400 });
895
916
  }
896
917
  if (payload.type === "url_verification" && payload.challenge) {
897
- return new Response(JSON.stringify({ challenge: payload.challenge }), {
898
- headers: { "Content-Type": "application/json" }
899
- });
918
+ return Response.json({ challenge: payload.challenge });
900
919
  }
901
920
  if (!this.defaultBotToken && payload.type === "event_callback") {
902
921
  const teamId = payload.team_id;
@@ -921,7 +940,7 @@ var SlackAdapter = class _SlackAdapter {
921
940
  const event = payload.event;
922
941
  if (event.type === "message" || event.type === "app_mention") {
923
942
  const slackEvent = event;
924
- if (!slackEvent.team && !slackEvent.team_id && payload.team_id) {
943
+ if (!(slackEvent.team || slackEvent.team_id) && payload.team_id) {
925
944
  slackEvent.team_id = payload.team_id;
926
945
  }
927
946
  this.handleMessageEvent(slackEvent, options);
@@ -1023,7 +1042,7 @@ var SlackAdapter = class _SlackAdapter {
1023
1042
  const messageTs = payload.message?.ts || payload.container?.message_ts;
1024
1043
  const threadTs = payload.message?.thread_ts || payload.container?.thread_ts || messageTs;
1025
1044
  const isViewAction = payload.container?.type === "view";
1026
- if (!isViewAction && !channel) {
1045
+ if (!(isViewAction || channel)) {
1027
1046
  this.logger.warn("Missing channel in block_actions", { channel });
1028
1047
  return;
1029
1048
  }
@@ -1176,11 +1195,11 @@ var SlackAdapter = class _SlackAdapter {
1176
1195
  return modal;
1177
1196
  }
1178
1197
  verifySignature(body, timestamp, signature) {
1179
- if (!timestamp || !signature) {
1198
+ if (!(timestamp && signature)) {
1180
1199
  return false;
1181
1200
  }
1182
1201
  const now = Math.floor(Date.now() / 1e3);
1183
- if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
1202
+ if (Math.abs(now - Number.parseInt(timestamp, 10)) > 300) {
1184
1203
  return false;
1185
1204
  }
1186
1205
  const sigBasestring = `v0:${timestamp}:${body}`;
@@ -1209,7 +1228,7 @@ var SlackAdapter = class _SlackAdapter {
1209
1228
  });
1210
1229
  return;
1211
1230
  }
1212
- if (!event.channel || !event.ts) {
1231
+ if (!(event.channel && event.ts)) {
1213
1232
  this.logger.debug("Ignoring event without channel or ts", {
1214
1233
  channel: event.channel,
1215
1234
  ts: event.ts
@@ -1443,11 +1462,15 @@ var SlackAdapter = class _SlackAdapter {
1443
1462
  userIds.add(match[1]);
1444
1463
  match = mentionPattern.exec(text);
1445
1464
  }
1446
- if (userIds.size === 0) return text;
1465
+ if (userIds.size === 0) {
1466
+ return text;
1467
+ }
1447
1468
  if (skipSelfMention && this._botUserId) {
1448
1469
  userIds.delete(this._botUserId);
1449
1470
  }
1450
- if (userIds.size === 0) return text;
1471
+ if (userIds.size === 0) {
1472
+ return text;
1473
+ }
1451
1474
  const lookups = await Promise.all(
1452
1475
  [...userIds].map(async (uid) => {
1453
1476
  const info = await this.lookupUser(uid);
@@ -1486,9 +1509,9 @@ var SlackAdapter = class _SlackAdapter {
1486
1509
  isMe
1487
1510
  },
1488
1511
  metadata: {
1489
- dateSent: new Date(parseFloat(event.ts || "0") * 1e3),
1512
+ dateSent: new Date(Number.parseFloat(event.ts || "0") * 1e3),
1490
1513
  edited: !!event.edited,
1491
- editedAt: event.edited ? new Date(parseFloat(event.edited.ts) * 1e3) : void 0
1514
+ editedAt: event.edited ? new Date(Number.parseFloat(event.edited.ts) * 1e3) : void 0
1492
1515
  },
1493
1516
  attachments: (event.files || []).map(
1494
1517
  (file) => this.createAttachment(file)
@@ -1543,7 +1566,7 @@ var SlackAdapter = class _SlackAdapter {
1543
1566
  await this.uploadFiles(files, channel, threadTs || void 0);
1544
1567
  const hasText = typeof message === "string" || typeof message === "object" && message !== null && ("raw" in message || "markdown" in message || "ast" in message);
1545
1568
  const card2 = extractCard(message);
1546
- if (!hasText && !card2) {
1569
+ if (!(hasText || card2)) {
1547
1570
  return {
1548
1571
  id: `file-${Date.now()}`,
1549
1572
  threadId,
@@ -1921,7 +1944,7 @@ var SlackAdapter = class _SlackAdapter {
1921
1944
  * Requires `recipientUserId` and `recipientTeamId` in options.
1922
1945
  */
1923
1946
  async stream(threadId, textStream, options) {
1924
- if (!options?.recipientUserId || !options?.recipientTeamId) {
1947
+ if (!(options?.recipientUserId && options?.recipientTeamId)) {
1925
1948
  throw new ChatError(
1926
1949
  "Slack streaming requires recipientUserId and recipientTeamId in options",
1927
1950
  "MISSING_STREAM_OPTIONS"
@@ -1993,7 +2016,7 @@ var SlackAdapter = class _SlackAdapter {
1993
2016
  const limit = options.limit || 100;
1994
2017
  try {
1995
2018
  if (direction === "forward") {
1996
- return this.fetchMessagesForward(
2019
+ return await this.fetchMessagesForward(
1997
2020
  channel,
1998
2021
  threadTs,
1999
2022
  threadId,
@@ -2001,7 +2024,7 @@ var SlackAdapter = class _SlackAdapter {
2001
2024
  options.cursor
2002
2025
  );
2003
2026
  }
2004
- return this.fetchMessagesBackward(
2027
+ return await this.fetchMessagesBackward(
2005
2028
  channel,
2006
2029
  threadTs,
2007
2030
  threadId,
@@ -2141,7 +2164,9 @@ var SlackAdapter = class _SlackAdapter {
2141
2164
  );
2142
2165
  const messages = result.messages || [];
2143
2166
  const target = messages.find((msg) => msg.ts === messageId);
2144
- if (!target) return null;
2167
+ if (!target) {
2168
+ return null;
2169
+ }
2145
2170
  return this.parseSlackMessage(target, threadId);
2146
2171
  } catch (error) {
2147
2172
  this.handleSlackError(error);
@@ -2203,9 +2228,9 @@ var SlackAdapter = class _SlackAdapter {
2203
2228
  isMe
2204
2229
  },
2205
2230
  metadata: {
2206
- dateSent: new Date(parseFloat(event.ts || "0") * 1e3),
2231
+ dateSent: new Date(Number.parseFloat(event.ts || "0") * 1e3),
2207
2232
  edited: !!event.edited,
2208
- editedAt: event.edited ? new Date(parseFloat(event.edited.ts) * 1e3) : void 0
2233
+ editedAt: event.edited ? new Date(Number.parseFloat(event.edited.ts) * 1e3) : void 0
2209
2234
  },
2210
2235
  attachments: (event.files || []).map(
2211
2236
  (file) => this.createAttachment(file)
@@ -2279,7 +2304,7 @@ var SlackAdapter = class _SlackAdapter {
2279
2304
  );
2280
2305
  let nextCursor;
2281
2306
  if (result.has_more && slackMessages.length > 0) {
2282
- const newest = slackMessages[slackMessages.length - 1];
2307
+ const newest = slackMessages.at(-1);
2283
2308
  if (newest?.ts) {
2284
2309
  nextCursor = newest.ts;
2285
2310
  }
@@ -2402,7 +2427,7 @@ var SlackAdapter = class _SlackAdapter {
2402
2427
  return {
2403
2428
  id: channelId,
2404
2429
  name: info?.name ? `#${info.name}` : void 0,
2405
- isDM: info?.is_im || info?.is_mpim || false,
2430
+ isDM: Boolean(info?.is_im || info?.is_mpim),
2406
2431
  memberCount: info?.num_members,
2407
2432
  metadata: {
2408
2433
  purpose: info?.purpose?.value,
@@ -2425,7 +2450,7 @@ var SlackAdapter = class _SlackAdapter {
2425
2450
  );
2426
2451
  }
2427
2452
  const syntheticThreadId = `slack:${channel}:`;
2428
- return this.postMessage(syntheticThreadId, message);
2453
+ return await this.postMessage(syntheticThreadId, message);
2429
2454
  }
2430
2455
  renderFormatted(content) {
2431
2456
  return this.formatConverter.fromAst(content);
@@ -2456,10 +2481,8 @@ var SlackAdapter = class _SlackAdapter {
2456
2481
  }
2457
2482
  handleSlackError(error) {
2458
2483
  const slackError = error;
2459
- if (slackError.code === "slack_webapi_platform_error") {
2460
- if (slackError.data?.error === "ratelimited") {
2461
- throw new AdapterRateLimitError("slack");
2462
- }
2484
+ if (slackError.code === "slack_webapi_platform_error" && slackError.data?.error === "ratelimited") {
2485
+ throw new AdapterRateLimitError("slack");
2463
2486
  }
2464
2487
  throw error;
2465
2488
  }
@@ -2476,9 +2499,13 @@ var SlackAdapter = class _SlackAdapter {
2476
2499
  * Returns null if the messageId is not an ephemeral encoding.
2477
2500
  */
2478
2501
  decodeEphemeralMessageId(messageId) {
2479
- if (!messageId.startsWith("ephemeral:")) return null;
2502
+ if (!messageId.startsWith("ephemeral:")) {
2503
+ return null;
2504
+ }
2480
2505
  const parts = messageId.split(":");
2481
- if (parts.length < 3) return null;
2506
+ if (parts.length < 3) {
2507
+ return null;
2508
+ }
2482
2509
  const messageTs = parts[1];
2483
2510
  const encodedData = parts.slice(2).join(":");
2484
2511
  try {