@chat-adapter/slack 4.7.2 → 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/README.md +104 -5
- package/dist/index.d.ts +78 -4
- package/dist/index.js +420 -97
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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,
|
|
@@ -209,7 +261,7 @@ var SlackFormatConverter = class extends BaseFormatConverter {
|
|
|
209
261
|
* @name → <@name>
|
|
210
262
|
*/
|
|
211
263
|
convertMentionsToSlack(text) {
|
|
212
|
-
return text.replace(
|
|
264
|
+
return text.replace(/(?<!<)@(\w+)/g, "<@$1>");
|
|
213
265
|
}
|
|
214
266
|
/**
|
|
215
267
|
* Override renderPostable to convert @mentions in plain strings.
|
|
@@ -258,7 +310,7 @@ var SlackFormatConverter = class extends BaseFormatConverter {
|
|
|
258
310
|
return getNodeChildren(node).map((child) => this.nodeToMrkdwn(child)).join("");
|
|
259
311
|
}
|
|
260
312
|
if (isTextNode(node)) {
|
|
261
|
-
return node.value.replace(
|
|
313
|
+
return node.value.replace(/(?<!<)@(\w+)/g, "<@$1>");
|
|
262
314
|
}
|
|
263
315
|
if (isStrongNode(node)) {
|
|
264
316
|
const content = getNodeChildren(node).map((child) => this.nodeToMrkdwn(child)).join("");
|
|
@@ -393,7 +445,7 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
393
445
|
userName;
|
|
394
446
|
client;
|
|
395
447
|
signingSecret;
|
|
396
|
-
|
|
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.
|
|
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(
|
|
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
|
|
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.
|
|
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
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
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
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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
|
-
|
|
996
|
-
|
|
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
|
-
|
|
1016
|
-
|
|
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
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
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
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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
|
-
|
|
1134
|
-
|
|
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
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
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
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
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(
|
|
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
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
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
|