@chat-adapter/slack 4.8.0 → 4.9.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/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/index.ts
2
+ import { AsyncLocalStorage } from "async_hooks";
2
3
  import { createHmac, timingSafeEqual } from "crypto";
3
4
  import {
4
5
  AdapterRateLimitError,
@@ -185,6 +186,57 @@ function cardToFallbackText(card) {
185
186
  });
186
187
  }
187
188
 
189
+ // src/crypto.ts
190
+ import crypto from "crypto";
191
+ var ALGORITHM = "aes-256-gcm";
192
+ var IV_LENGTH = 12;
193
+ var AUTH_TAG_LENGTH = 16;
194
+ function encryptToken(plaintext, key) {
195
+ const iv = crypto.randomBytes(IV_LENGTH);
196
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv, {
197
+ authTagLength: AUTH_TAG_LENGTH
198
+ });
199
+ const ciphertext = Buffer.concat([
200
+ cipher.update(plaintext, "utf8"),
201
+ cipher.final()
202
+ ]);
203
+ const tag = cipher.getAuthTag();
204
+ return {
205
+ iv: iv.toString("base64"),
206
+ data: ciphertext.toString("base64"),
207
+ tag: tag.toString("base64")
208
+ };
209
+ }
210
+ function decryptToken(encrypted, key) {
211
+ const iv = Buffer.from(encrypted.iv, "base64");
212
+ const ciphertext = Buffer.from(encrypted.data, "base64");
213
+ const tag = Buffer.from(encrypted.tag, "base64");
214
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, {
215
+ authTagLength: AUTH_TAG_LENGTH
216
+ });
217
+ decipher.setAuthTag(tag);
218
+ return Buffer.concat([
219
+ decipher.update(ciphertext),
220
+ decipher.final()
221
+ ]).toString("utf8");
222
+ }
223
+ function isEncryptedTokenData(value) {
224
+ if (!value || typeof value !== "object") return false;
225
+ const obj = value;
226
+ return typeof obj.iv === "string" && typeof obj.data === "string" && typeof obj.tag === "string";
227
+ }
228
+ function decodeKey(rawKey) {
229
+ const trimmed = rawKey.trim();
230
+ const isHex = /^[0-9a-fA-F]{64}$/.test(trimmed);
231
+ const key = Buffer.from(trimmed, isHex ? "hex" : "base64");
232
+ if (key.length !== 32) {
233
+ throw new Error(
234
+ `Encryption key must decode to exactly 32 bytes (received ${key.length}). Use a 64-char hex string or 44-char base64 string.`
235
+ );
236
+ }
237
+ return key;
238
+ }
239
+
188
240
  // src/markdown.ts
189
241
  import {
190
242
  BaseFormatConverter,
@@ -393,7 +445,7 @@ var SlackAdapter = class _SlackAdapter {
393
445
  userName;
394
446
  client;
395
447
  signingSecret;
396
- botToken;
448
+ defaultBotToken;
397
449
  chat = null;
398
450
  logger;
399
451
  _botUserId = null;
@@ -402,23 +454,56 @@ var SlackAdapter = class _SlackAdapter {
402
454
  formatConverter = new SlackFormatConverter();
403
455
  static USER_CACHE_TTL_MS = 60 * 60 * 1e3;
404
456
  // 1 hour
457
+ // Multi-workspace support
458
+ clientId;
459
+ clientSecret;
460
+ encryptionKey;
461
+ requestContext = new AsyncLocalStorage();
405
462
  /** Bot user ID (e.g., U_BOT_123) used for mention detection */
406
463
  get botUserId() {
464
+ const ctx = this.requestContext.getStore();
465
+ if (ctx?.botUserId) return ctx.botUserId;
407
466
  return this._botUserId || void 0;
408
467
  }
409
468
  constructor(config) {
410
469
  this.client = new WebClient(config.botToken);
411
470
  this.signingSecret = config.signingSecret;
412
- this.botToken = config.botToken;
471
+ this.defaultBotToken = config.botToken;
413
472
  this.logger = config.logger;
414
473
  this.userName = config.userName || "bot";
415
474
  this._botUserId = config.botUserId || null;
475
+ this.clientId = config.clientId;
476
+ this.clientSecret = config.clientSecret;
477
+ if (config.encryptionKey) {
478
+ this.encryptionKey = decodeKey(config.encryptionKey);
479
+ }
480
+ }
481
+ /**
482
+ * Get the current bot token for API calls.
483
+ * Checks request context (multi-workspace) → default token (single-workspace) → throws.
484
+ */
485
+ getToken() {
486
+ const ctx = this.requestContext.getStore();
487
+ if (ctx?.token) return ctx.token;
488
+ if (this.defaultBotToken) return this.defaultBotToken;
489
+ throw new ChatError(
490
+ "No bot token available. In multi-workspace mode, ensure the webhook is being processed.",
491
+ "MISSING_BOT_TOKEN"
492
+ );
493
+ }
494
+ /**
495
+ * Add the current token to API call options.
496
+ * Workaround for Slack WebClient types not including `token` in per-method args.
497
+ */
498
+ // biome-ignore lint/suspicious/noExplicitAny: Slack types don't include token in method args
499
+ withToken(options) {
500
+ return { ...options, token: this.getToken() };
416
501
  }
417
502
  async initialize(chat) {
418
503
  this.chat = chat;
419
- if (!this._botUserId) {
504
+ if (this.defaultBotToken && !this._botUserId) {
420
505
  try {
421
- const authResult = await this.client.auth.test();
506
+ const authResult = await this.client.auth.test(this.withToken({}));
422
507
  this._botUserId = authResult.user_id;
423
508
  this._botId = authResult.bot_id || null;
424
509
  if (authResult.user) {
@@ -432,6 +517,165 @@ var SlackAdapter = class _SlackAdapter {
432
517
  this.logger.warn("Could not fetch bot user ID", { error });
433
518
  }
434
519
  }
520
+ if (!this.defaultBotToken) {
521
+ this.logger.info("Slack adapter initialized in multi-workspace mode");
522
+ }
523
+ }
524
+ // ===========================================================================
525
+ // Multi-workspace installation management
526
+ // ===========================================================================
527
+ installationKey(teamId) {
528
+ return `slack:installation:${teamId}`;
529
+ }
530
+ /**
531
+ * Save a Slack workspace installation.
532
+ * Call this from your OAuth callback route after a successful installation.
533
+ */
534
+ async setInstallation(teamId, installation) {
535
+ if (!this.chat) {
536
+ throw new ChatError(
537
+ "Adapter not initialized. Ensure chat.initialize() has been called first.",
538
+ "NOT_INITIALIZED"
539
+ );
540
+ }
541
+ const state = this.chat.getState();
542
+ const key = this.installationKey(teamId);
543
+ const dataToStore = this.encryptionKey ? {
544
+ ...installation,
545
+ botToken: encryptToken(installation.botToken, this.encryptionKey)
546
+ } : installation;
547
+ await state.set(key, dataToStore);
548
+ this.logger.info("Slack installation saved", {
549
+ teamId,
550
+ teamName: installation.teamName
551
+ });
552
+ }
553
+ /**
554
+ * Retrieve a Slack workspace installation.
555
+ */
556
+ async getInstallation(teamId) {
557
+ if (!this.chat) {
558
+ throw new ChatError(
559
+ "Adapter not initialized. Ensure chat.initialize() has been called first.",
560
+ "NOT_INITIALIZED"
561
+ );
562
+ }
563
+ const state = this.chat.getState();
564
+ const key = this.installationKey(teamId);
565
+ const stored = await state.get(key);
566
+ if (!stored) return null;
567
+ if (this.encryptionKey && isEncryptedTokenData(stored.botToken)) {
568
+ return {
569
+ ...stored,
570
+ botToken: decryptToken(
571
+ stored.botToken,
572
+ this.encryptionKey
573
+ )
574
+ };
575
+ }
576
+ return stored;
577
+ }
578
+ /**
579
+ * Handle the Slack OAuth V2 callback.
580
+ * Accepts the incoming request, extracts the authorization code,
581
+ * exchanges it for tokens, and saves the installation.
582
+ */
583
+ async handleOAuthCallback(request) {
584
+ if (!this.clientId || !this.clientSecret) {
585
+ throw new ChatError(
586
+ "clientId and clientSecret are required for OAuth. Pass them in createSlackAdapter().",
587
+ "MISSING_OAUTH_CONFIG"
588
+ );
589
+ }
590
+ const url = new URL(request.url);
591
+ const code = url.searchParams.get("code");
592
+ if (!code) {
593
+ throw new ChatError(
594
+ "Missing 'code' query parameter in OAuth callback request.",
595
+ "MISSING_OAUTH_CODE"
596
+ );
597
+ }
598
+ const redirectUri = url.searchParams.get("redirect_uri") ?? void 0;
599
+ const result = await this.client.oauth.v2.access({
600
+ client_id: this.clientId,
601
+ client_secret: this.clientSecret,
602
+ code,
603
+ redirect_uri: redirectUri
604
+ });
605
+ if (!result.ok || !result.access_token || !result.team?.id) {
606
+ throw new ChatError(
607
+ `Slack OAuth failed: ${result.error || "missing access_token or team.id"}`,
608
+ "OAUTH_FAILED"
609
+ );
610
+ }
611
+ const teamId = result.team.id;
612
+ const installation = {
613
+ botToken: result.access_token,
614
+ botUserId: result.bot_user_id,
615
+ teamName: result.team.name
616
+ };
617
+ await this.setInstallation(teamId, installation);
618
+ return { teamId, installation };
619
+ }
620
+ /**
621
+ * Remove a Slack workspace installation.
622
+ */
623
+ async deleteInstallation(teamId) {
624
+ if (!this.chat) {
625
+ throw new ChatError(
626
+ "Adapter not initialized. Ensure chat.initialize() has been called first.",
627
+ "NOT_INITIALIZED"
628
+ );
629
+ }
630
+ const state = this.chat.getState();
631
+ await state.delete(this.installationKey(teamId));
632
+ this.logger.info("Slack installation deleted", { teamId });
633
+ }
634
+ /**
635
+ * Run a function with a specific bot token in context.
636
+ * Use this for operations outside webhook handling (cron jobs, workflows).
637
+ */
638
+ withBotToken(token, fn) {
639
+ return this.requestContext.run({ token }, fn);
640
+ }
641
+ // ===========================================================================
642
+ // Private helpers
643
+ // ===========================================================================
644
+ /**
645
+ * Resolve the bot token for a team from the state adapter.
646
+ */
647
+ async resolveTokenForTeam(teamId) {
648
+ try {
649
+ const installation = await this.getInstallation(teamId);
650
+ if (installation) {
651
+ return {
652
+ token: installation.botToken,
653
+ botUserId: installation.botUserId
654
+ };
655
+ }
656
+ this.logger.warn("No installation found for team", { teamId });
657
+ return null;
658
+ } catch (error) {
659
+ this.logger.error("Failed to resolve token for team", {
660
+ teamId,
661
+ error
662
+ });
663
+ return null;
664
+ }
665
+ }
666
+ /**
667
+ * Extract team_id from an interactive payload (form-urlencoded).
668
+ */
669
+ extractTeamIdFromInteractive(body) {
670
+ try {
671
+ const params = new URLSearchParams(body);
672
+ const payloadStr = params.get("payload");
673
+ if (!payloadStr) return null;
674
+ const payload = JSON.parse(payloadStr);
675
+ return payload.team?.id || payload.team_id || null;
676
+ } catch {
677
+ return null;
678
+ }
435
679
  }
436
680
  /**
437
681
  * Look up user info from Slack API with caching via state adapter.
@@ -446,7 +690,9 @@ var SlackAdapter = class _SlackAdapter {
446
690
  }
447
691
  }
448
692
  try {
449
- const result = await this.client.users.info({ user: userId });
693
+ const result = await this.client.users.info(
694
+ this.withToken({ user: userId })
695
+ );
450
696
  const user = result.user;
451
697
  const displayName = user?.profile?.display_name || user?.profile?.real_name || user?.real_name || user?.name || userId;
452
698
  const realName = user?.real_name || user?.profile?.real_name || displayName;
@@ -478,6 +724,19 @@ var SlackAdapter = class _SlackAdapter {
478
724
  }
479
725
  const contentType = request.headers.get("content-type") || "";
480
726
  if (contentType.includes("application/x-www-form-urlencoded")) {
727
+ if (!this.defaultBotToken) {
728
+ const teamId = this.extractTeamIdFromInteractive(body);
729
+ if (teamId) {
730
+ const ctx = await this.resolveTokenForTeam(teamId);
731
+ if (ctx) {
732
+ return this.requestContext.run(
733
+ ctx,
734
+ () => this.handleInteractivePayload(body, options)
735
+ );
736
+ }
737
+ }
738
+ this.logger.warn("Could not resolve token for interactive payload");
739
+ }
481
740
  return this.handleInteractivePayload(body, options);
482
741
  }
483
742
  let payload;
@@ -491,6 +750,25 @@ var SlackAdapter = class _SlackAdapter {
491
750
  headers: { "Content-Type": "application/json" }
492
751
  });
493
752
  }
753
+ if (!this.defaultBotToken && payload.type === "event_callback") {
754
+ const teamId = payload.team_id;
755
+ if (teamId) {
756
+ const ctx = await this.resolveTokenForTeam(teamId);
757
+ if (ctx) {
758
+ return this.requestContext.run(ctx, () => {
759
+ this.processEventPayload(payload, options);
760
+ return new Response("ok", { status: 200 });
761
+ });
762
+ }
763
+ this.logger.warn("Could not resolve token for team", { teamId });
764
+ return new Response("ok", { status: 200 });
765
+ }
766
+ }
767
+ this.processEventPayload(payload, options);
768
+ return new Response("ok", { status: 200 });
769
+ }
770
+ /** Extract and dispatch events from a validated payload */
771
+ processEventPayload(payload, options) {
494
772
  if (payload.type === "event_callback" && payload.event) {
495
773
  const event = payload.event;
496
774
  if (event.type === "message" || event.type === "app_mention") {
@@ -503,7 +781,6 @@ var SlackAdapter = class _SlackAdapter {
503
781
  this.handleReactionEvent(event, options);
504
782
  }
505
783
  }
506
- return new Response("ok", { status: 200 });
507
784
  }
508
785
  /**
509
786
  * Handle Slack interactive payloads (button clicks, view submissions, etc.).
@@ -757,7 +1034,8 @@ var SlackAdapter = class _SlackAdapter {
757
1034
  const messageId = event.item.ts;
758
1035
  const rawEmoji = event.reaction;
759
1036
  const normalizedEmoji = defaultEmojiResolver.fromSlack(rawEmoji);
760
- const isMe = this._botUserId !== null && event.user === this._botUserId || this._botId !== null && event.user === this._botId;
1037
+ const ctx = this.requestContext.getStore();
1038
+ const isMe = ctx?.botUserId && event.user === ctx.botUserId || this._botUserId !== null && event.user === this._botUserId || this._botId !== null && event.user === this._botId;
761
1039
  const reactionEvent = {
762
1040
  emoji: normalizedEmoji,
763
1041
  rawEmoji,
@@ -816,7 +1094,7 @@ var SlackAdapter = class _SlackAdapter {
816
1094
  */
817
1095
  createAttachment(file) {
818
1096
  const url = file.url_private;
819
- const botToken = this.botToken;
1097
+ const botToken = this.getToken();
820
1098
  let type = "file";
821
1099
  if (file.mimetype?.startsWith("image/")) {
822
1100
  type = "image";
@@ -875,15 +1153,17 @@ var SlackAdapter = class _SlackAdapter {
875
1153
  threadTs,
876
1154
  blockCount: blocks.length
877
1155
  });
878
- const result2 = await this.client.chat.postMessage({
879
- channel,
880
- thread_ts: threadTs,
881
- text: fallbackText,
882
- // Fallback for notifications
883
- blocks,
884
- unfurl_links: false,
885
- unfurl_media: false
886
- });
1156
+ const result2 = await this.client.chat.postMessage(
1157
+ this.withToken({
1158
+ channel,
1159
+ thread_ts: threadTs,
1160
+ text: fallbackText,
1161
+ // Fallback for notifications
1162
+ blocks,
1163
+ unfurl_links: false,
1164
+ unfurl_media: false
1165
+ })
1166
+ );
887
1167
  this.logger.debug("Slack API: chat.postMessage response", {
888
1168
  messageId: result2.ts,
889
1169
  ok: result2.ok
@@ -903,13 +1183,15 @@ var SlackAdapter = class _SlackAdapter {
903
1183
  threadTs,
904
1184
  textLength: text.length
905
1185
  });
906
- const result = await this.client.chat.postMessage({
907
- channel,
908
- thread_ts: threadTs,
909
- text,
910
- unfurl_links: false,
911
- unfurl_media: false
912
- });
1186
+ const result = await this.client.chat.postMessage(
1187
+ this.withToken({
1188
+ channel,
1189
+ thread_ts: threadTs,
1190
+ text,
1191
+ unfurl_links: false,
1192
+ unfurl_media: false
1193
+ })
1194
+ );
913
1195
  this.logger.debug("Slack API: chat.postMessage response", {
914
1196
  messageId: result.ts,
915
1197
  ok: result.ok
@@ -936,13 +1218,15 @@ var SlackAdapter = class _SlackAdapter {
936
1218
  userId,
937
1219
  blockCount: blocks.length
938
1220
  });
939
- const result2 = await this.client.chat.postEphemeral({
940
- channel,
941
- thread_ts: threadTs || void 0,
942
- user: userId,
943
- text: fallbackText,
944
- blocks
945
- });
1221
+ const result2 = await this.client.chat.postEphemeral(
1222
+ this.withToken({
1223
+ channel,
1224
+ thread_ts: threadTs || void 0,
1225
+ user: userId,
1226
+ text: fallbackText,
1227
+ blocks
1228
+ })
1229
+ );
946
1230
  this.logger.debug("Slack API: chat.postEphemeral response", {
947
1231
  messageTs: result2.message_ts,
948
1232
  ok: result2.ok
@@ -964,12 +1248,14 @@ var SlackAdapter = class _SlackAdapter {
964
1248
  userId,
965
1249
  textLength: text.length
966
1250
  });
967
- const result = await this.client.chat.postEphemeral({
968
- channel,
969
- thread_ts: threadTs || void 0,
970
- user: userId,
971
- text
972
- });
1251
+ const result = await this.client.chat.postEphemeral(
1252
+ this.withToken({
1253
+ channel,
1254
+ thread_ts: threadTs || void 0,
1255
+ user: userId,
1256
+ text
1257
+ })
1258
+ );
973
1259
  this.logger.debug("Slack API: chat.postEphemeral response", {
974
1260
  messageTs: result.message_ts,
975
1261
  ok: result.ok
@@ -991,10 +1277,12 @@ var SlackAdapter = class _SlackAdapter {
991
1277
  callbackId: modal.callbackId
992
1278
  });
993
1279
  try {
994
- const result = await this.client.views.open({
995
- trigger_id: triggerId,
996
- view
997
- });
1280
+ const result = await this.client.views.open(
1281
+ this.withToken({
1282
+ trigger_id: triggerId,
1283
+ view
1284
+ })
1285
+ );
998
1286
  this.logger.debug("Slack API: views.open response", {
999
1287
  viewId: result.view?.id,
1000
1288
  ok: result.ok
@@ -1011,10 +1299,12 @@ var SlackAdapter = class _SlackAdapter {
1011
1299
  callbackId: modal.callbackId
1012
1300
  });
1013
1301
  try {
1014
- const result = await this.client.views.update({
1015
- view_id: viewId,
1016
- view
1017
- });
1302
+ const result = await this.client.views.update(
1303
+ this.withToken({
1304
+ view_id: viewId,
1305
+ view
1306
+ })
1307
+ );
1018
1308
  this.logger.debug("Slack API: views.update response", {
1019
1309
  viewId: result.view?.id,
1020
1310
  ok: result.ok
@@ -1049,6 +1339,7 @@ var SlackAdapter = class _SlackAdapter {
1049
1339
  if (threadTs) {
1050
1340
  uploadArgs.thread_ts = threadTs;
1051
1341
  }
1342
+ uploadArgs.token = this.getToken();
1052
1343
  const result = await this.client.files.uploadV2(uploadArgs);
1053
1344
  this.logger.debug("Slack API: files.uploadV2 response", {
1054
1345
  ok: result.ok
@@ -1082,12 +1373,14 @@ var SlackAdapter = class _SlackAdapter {
1082
1373
  messageId,
1083
1374
  blockCount: blocks.length
1084
1375
  });
1085
- const result2 = await this.client.chat.update({
1086
- channel,
1087
- ts: messageId,
1088
- text: fallbackText,
1089
- blocks
1090
- });
1376
+ const result2 = await this.client.chat.update(
1377
+ this.withToken({
1378
+ channel,
1379
+ ts: messageId,
1380
+ text: fallbackText,
1381
+ blocks
1382
+ })
1383
+ );
1091
1384
  this.logger.debug("Slack API: chat.update response", {
1092
1385
  messageId: result2.ts,
1093
1386
  ok: result2.ok
@@ -1107,11 +1400,13 @@ var SlackAdapter = class _SlackAdapter {
1107
1400
  messageId,
1108
1401
  textLength: text.length
1109
1402
  });
1110
- const result = await this.client.chat.update({
1111
- channel,
1112
- ts: messageId,
1113
- text
1114
- });
1403
+ const result = await this.client.chat.update(
1404
+ this.withToken({
1405
+ channel,
1406
+ ts: messageId,
1407
+ text
1408
+ })
1409
+ );
1115
1410
  this.logger.debug("Slack API: chat.update response", {
1116
1411
  messageId: result.ts,
1117
1412
  ok: result.ok
@@ -1129,10 +1424,12 @@ var SlackAdapter = class _SlackAdapter {
1129
1424
  const { channel } = this.decodeThreadId(threadId);
1130
1425
  try {
1131
1426
  this.logger.debug("Slack API: chat.delete", { channel, messageId });
1132
- await this.client.chat.delete({
1133
- channel,
1134
- ts: messageId
1135
- });
1427
+ await this.client.chat.delete(
1428
+ this.withToken({
1429
+ channel,
1430
+ ts: messageId
1431
+ })
1432
+ );
1136
1433
  this.logger.debug("Slack API: chat.delete response", { ok: true });
1137
1434
  } catch (error) {
1138
1435
  this.handleSlackError(error);
@@ -1148,11 +1445,13 @@ var SlackAdapter = class _SlackAdapter {
1148
1445
  messageId,
1149
1446
  emoji: name
1150
1447
  });
1151
- await this.client.reactions.add({
1152
- channel,
1153
- timestamp: messageId,
1154
- name
1155
- });
1448
+ await this.client.reactions.add(
1449
+ this.withToken({
1450
+ channel,
1451
+ timestamp: messageId,
1452
+ name
1453
+ })
1454
+ );
1156
1455
  this.logger.debug("Slack API: reactions.add response", { ok: true });
1157
1456
  } catch (error) {
1158
1457
  this.handleSlackError(error);
@@ -1168,11 +1467,13 @@ var SlackAdapter = class _SlackAdapter {
1168
1467
  messageId,
1169
1468
  emoji: name
1170
1469
  });
1171
- await this.client.reactions.remove({
1172
- channel,
1173
- timestamp: messageId,
1174
- name
1175
- });
1470
+ await this.client.reactions.remove(
1471
+ this.withToken({
1472
+ channel,
1473
+ timestamp: messageId,
1474
+ name
1475
+ })
1476
+ );
1176
1477
  this.logger.debug("Slack API: reactions.remove response", { ok: true });
1177
1478
  } catch (error) {
1178
1479
  this.handleSlackError(error);
@@ -1195,14 +1496,21 @@ var SlackAdapter = class _SlackAdapter {
1195
1496
  }
1196
1497
  const { channel, threadTs } = this.decodeThreadId(threadId);
1197
1498
  this.logger.debug("Slack: starting stream", { channel, threadTs });
1499
+ const token = this.getToken();
1198
1500
  const streamer = this.client.chatStream({
1199
1501
  channel,
1200
1502
  thread_ts: threadTs,
1201
1503
  recipient_user_id: options.recipientUserId,
1202
1504
  recipient_team_id: options.recipientTeamId
1203
1505
  });
1506
+ let first = true;
1204
1507
  for await (const chunk of textStream) {
1205
- await streamer.append({ markdown_text: chunk });
1508
+ if (first) {
1509
+ await streamer.append({ markdown_text: chunk, token });
1510
+ first = false;
1511
+ } else {
1512
+ await streamer.append({ markdown_text: chunk });
1513
+ }
1206
1514
  }
1207
1515
  const result = await streamer.stop();
1208
1516
  const messageTs = result.message?.ts ?? result.ts;
@@ -1220,7 +1528,9 @@ var SlackAdapter = class _SlackAdapter {
1220
1528
  async openDM(userId) {
1221
1529
  try {
1222
1530
  this.logger.debug("Slack API: conversations.open", { userId });
1223
- const result = await this.client.conversations.open({ users: userId });
1531
+ const result = await this.client.conversations.open(
1532
+ this.withToken({ users: userId })
1533
+ );
1224
1534
  if (!result.channel?.id) {
1225
1535
  throw new NetworkError(
1226
1536
  "slack",
@@ -1277,12 +1587,14 @@ var SlackAdapter = class _SlackAdapter {
1277
1587
  limit,
1278
1588
  cursor
1279
1589
  });
1280
- const result = await this.client.conversations.replies({
1281
- channel,
1282
- ts: threadTs,
1283
- limit,
1284
- cursor
1285
- });
1590
+ const result = await this.client.conversations.replies(
1591
+ this.withToken({
1592
+ channel,
1593
+ ts: threadTs,
1594
+ limit,
1595
+ cursor
1596
+ })
1597
+ );
1286
1598
  const slackMessages = result.messages || [];
1287
1599
  const nextCursor = result.response_metadata?.next_cursor;
1288
1600
  this.logger.debug("Slack API: conversations.replies response", {
@@ -1318,14 +1630,16 @@ var SlackAdapter = class _SlackAdapter {
1318
1630
  latest
1319
1631
  });
1320
1632
  const fetchLimit = Math.min(1e3, Math.max(limit * 2, 200));
1321
- const result = await this.client.conversations.replies({
1322
- channel,
1323
- ts: threadTs,
1324
- limit: fetchLimit,
1325
- latest,
1326
- inclusive: false
1327
- // Don't include the cursor message itself
1328
- });
1633
+ const result = await this.client.conversations.replies(
1634
+ this.withToken({
1635
+ channel,
1636
+ ts: threadTs,
1637
+ limit: fetchLimit,
1638
+ latest,
1639
+ inclusive: false
1640
+ // Don't include the cursor message itself
1641
+ })
1642
+ );
1329
1643
  const slackMessages = result.messages || [];
1330
1644
  this.logger.debug("Slack API: conversations.replies response (backward)", {
1331
1645
  messageCount: slackMessages.length,
@@ -1353,7 +1667,9 @@ var SlackAdapter = class _SlackAdapter {
1353
1667
  const { channel, threadTs } = this.decodeThreadId(threadId);
1354
1668
  try {
1355
1669
  this.logger.debug("Slack API: conversations.info", { channel });
1356
- const result = await this.client.conversations.info({ channel });
1670
+ const result = await this.client.conversations.info(
1671
+ this.withToken({ channel })
1672
+ );
1357
1673
  const channelInfo = result.channel;
1358
1674
  this.logger.debug("Slack API: conversations.info response", {
1359
1675
  channelName: channelInfo?.name,
@@ -1378,13 +1694,15 @@ var SlackAdapter = class _SlackAdapter {
1378
1694
  async fetchMessage(threadId, messageId) {
1379
1695
  const { channel, threadTs } = this.decodeThreadId(threadId);
1380
1696
  try {
1381
- const result = await this.client.conversations.replies({
1382
- channel,
1383
- ts: threadTs,
1384
- oldest: messageId,
1385
- inclusive: true,
1386
- limit: 1
1387
- });
1697
+ const result = await this.client.conversations.replies(
1698
+ this.withToken({
1699
+ channel,
1700
+ ts: threadTs,
1701
+ oldest: messageId,
1702
+ inclusive: true,
1703
+ limit: 1
1704
+ })
1705
+ );
1388
1706
  const messages = result.messages || [];
1389
1707
  const target = messages.find((msg) => msg.ts === messageId);
1390
1708
  if (!target) return null;
@@ -1473,6 +1791,10 @@ var SlackAdapter = class _SlackAdapter {
1473
1791
  * - _botId is the bot ID (B_xxx) - matches event.bot_id
1474
1792
  */
1475
1793
  isMessageFromSelf(event) {
1794
+ const ctx = this.requestContext.getStore();
1795
+ if (ctx?.botUserId && event.user === ctx.botUserId) {
1796
+ return true;
1797
+ }
1476
1798
  if (this._botUserId && event.user === this._botUserId) {
1477
1799
  return true;
1478
1800
  }
@@ -1500,6 +1822,7 @@ export {
1500
1822
  SlackFormatConverter as SlackMarkdownConverter,
1501
1823
  cardToBlockKit,
1502
1824
  cardToFallbackText,
1503
- createSlackAdapter
1825
+ createSlackAdapter,
1826
+ decodeKey
1504
1827
  };
1505
1828
  //# sourceMappingURL=index.js.map