@aarekaz/switchboard-slack 0.3.1

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 ADDED
@@ -0,0 +1,779 @@
1
+ // src/register.ts
2
+ import { registry } from "@aarekaz/switchboard-core";
3
+
4
+ // src/adapter.ts
5
+ import bolt from "@slack/bolt";
6
+ import { LRUCache } from "lru-cache";
7
+ import {
8
+ ok,
9
+ err,
10
+ ConnectionError,
11
+ MessageSendError,
12
+ MessageEditError,
13
+ MessageDeleteError,
14
+ ReactionError
15
+ } from "@aarekaz/switchboard-core";
16
+
17
+ // src/normalizers.ts
18
+ function normalizeMessage(slackMessage) {
19
+ const attachments = [];
20
+ if (slackMessage.files && Array.isArray(slackMessage.files)) {
21
+ for (const file of slackMessage.files) {
22
+ attachments.push({
23
+ id: file.id,
24
+ filename: file.name || "unknown",
25
+ url: file.url_private || file.permalink || "",
26
+ mimeType: file.mimetype || "application/octet-stream",
27
+ size: file.size || 0
28
+ });
29
+ }
30
+ }
31
+ return {
32
+ id: slackMessage.ts || slackMessage.message_ts || slackMessage.event_ts,
33
+ channelId: slackMessage.channel || slackMessage.channel_id || "",
34
+ userId: slackMessage.user || slackMessage.bot_id || "unknown",
35
+ text: extractPlainText(slackMessage),
36
+ timestamp: new Date(parseFloat(slackMessage.ts || slackMessage.event_ts || "0") * 1e3),
37
+ threadId: slackMessage.thread_ts,
38
+ attachments: attachments.length > 0 ? attachments : void 0,
39
+ platform: "slack",
40
+ _raw: slackMessage
41
+ };
42
+ }
43
+ function extractPlainText(message) {
44
+ if (message.blocks && Array.isArray(message.blocks)) {
45
+ const texts = [];
46
+ for (const block of message.blocks) {
47
+ if (block.type === "section" && block.text) {
48
+ texts.push(block.text.text || "");
49
+ } else if (block.type === "context" && block.elements) {
50
+ for (const element of block.elements) {
51
+ if (element.text) {
52
+ texts.push(element.text);
53
+ }
54
+ }
55
+ }
56
+ }
57
+ if (texts.length > 0) {
58
+ return texts.join("\\n");
59
+ }
60
+ }
61
+ return message.text || "";
62
+ }
63
+ function normalizeEmoji(slackEmoji) {
64
+ if (!/^:.+:$/.test(slackEmoji)) {
65
+ return slackEmoji;
66
+ }
67
+ return slackEmoji.replace(/^:|:$/g, "");
68
+ }
69
+ var EMOJI_MAP = {
70
+ "\u{1F44D}": "thumbsup",
71
+ "\u{1F44E}": "thumbsdown",
72
+ "\u2764\uFE0F": "heart",
73
+ "\u{1F602}": "joy",
74
+ "\u{1F60A}": "blush",
75
+ "\u{1F60D}": "heart_eyes",
76
+ "\u{1F389}": "tada",
77
+ "\u{1F525}": "fire",
78
+ "\u2705": "white_check_mark",
79
+ "\u274C": "x",
80
+ "\u2B50": "star",
81
+ "\u{1F4AF}": "100",
82
+ "\u{1F680}": "rocket",
83
+ "\u{1F440}": "eyes",
84
+ "\u{1F914}": "thinking_face",
85
+ "\u{1F62D}": "sob",
86
+ "\u{1F631}": "scream",
87
+ "\u{1F64F}": "pray",
88
+ "\u{1F4AA}": "muscle",
89
+ "\u{1F44F}": "clap",
90
+ "\u{1F3AF}": "dart",
91
+ "\u2728": "sparkles",
92
+ "\u{1F91D}": "handshake",
93
+ "\u{1F4A1}": "bulb",
94
+ "\u{1F41B}": "bug",
95
+ "\u26A1": "zap",
96
+ "\u{1F527}": "wrench",
97
+ "\u{1F4DD}": "memo",
98
+ "\u{1F3A8}": "art",
99
+ "\u267B\uFE0F": "recycle",
100
+ "\u{1F512}": "lock",
101
+ "\u{1F513}": "unlock",
102
+ "\u270F\uFE0F": "pencil2",
103
+ "\u{1F5D1}\uFE0F": "wastebasket"
104
+ };
105
+ function toSlackEmoji(emoji) {
106
+ if (/^:.+:$/.test(emoji)) {
107
+ return emoji.slice(1, -1);
108
+ }
109
+ if (EMOJI_MAP[emoji]) {
110
+ return EMOJI_MAP[emoji];
111
+ }
112
+ if (/^[a-z0-9_+-]+$/i.test(emoji)) {
113
+ return emoji;
114
+ }
115
+ const stripped = emoji.replace(/[\uFE00-\uFE0F]/g, "");
116
+ if (EMOJI_MAP[stripped]) {
117
+ return EMOJI_MAP[stripped];
118
+ }
119
+ return emoji;
120
+ }
121
+ function normalizeMessageEvent(event) {
122
+ return {
123
+ type: "message",
124
+ message: normalizeMessage(event)
125
+ };
126
+ }
127
+ function normalizeReactionEvent(event, action) {
128
+ return {
129
+ type: "reaction",
130
+ messageId: event.item.ts,
131
+ userId: event.user,
132
+ emoji: normalizeEmoji(event.reaction),
133
+ action
134
+ };
135
+ }
136
+
137
+ // src/adapter.ts
138
+ var { App } = bolt;
139
+ var SlackAdapter = class {
140
+ name = "slack-adapter";
141
+ platform = "slack";
142
+ app = null;
143
+ eventHandlers = /* @__PURE__ */ new Set();
144
+ config;
145
+ messageCache;
146
+ cacheHits = 0;
147
+ cacheMisses = 0;
148
+ constructor(config = {}) {
149
+ this.config = {
150
+ cacheSize: config.cacheSize || 1e3,
151
+ cacheTTL: config.cacheTTL || 1e3 * 60 * 60,
152
+ // 1 hour default
153
+ ...config
154
+ };
155
+ this.messageCache = new LRUCache({
156
+ max: this.config.cacheSize,
157
+ ttl: this.config.cacheTTL
158
+ });
159
+ }
160
+ /**
161
+ * Connect to Slack
162
+ */
163
+ async connect(credentials) {
164
+ const slackCreds = credentials;
165
+ if (!slackCreds.botToken) {
166
+ throw new ConnectionError(
167
+ "slack",
168
+ new Error("Slack bot token is required")
169
+ );
170
+ }
171
+ try {
172
+ const useSocketMode = !!(slackCreds.appToken || this.config.socketMode);
173
+ const appOptions = {
174
+ token: slackCreds.botToken
175
+ };
176
+ if (useSocketMode) {
177
+ if (!slackCreds.appToken) {
178
+ throw new ConnectionError(
179
+ "slack",
180
+ new Error("App token (appToken) is required for Socket Mode")
181
+ );
182
+ }
183
+ appOptions.socketMode = true;
184
+ appOptions.appToken = slackCreds.appToken;
185
+ } else if (slackCreds.signingSecret) {
186
+ appOptions.signingSecret = slackCreds.signingSecret;
187
+ if (this.config.port) {
188
+ appOptions.port = this.config.port;
189
+ }
190
+ } else {
191
+ throw new ConnectionError(
192
+ "slack",
193
+ new Error(
194
+ "Either appToken (for Socket Mode) or signingSecret (for Events API) is required"
195
+ )
196
+ );
197
+ }
198
+ this.app = new App(appOptions);
199
+ this.setupEventListeners();
200
+ await this.app.start();
201
+ if (process.env.NODE_ENV !== "production") {
202
+ console.log(
203
+ `\u2705 Slack adapter connected (${useSocketMode ? "Socket Mode" : "Events API"})`
204
+ );
205
+ }
206
+ } catch (error) {
207
+ throw new ConnectionError("slack", error);
208
+ }
209
+ }
210
+ /**
211
+ * Disconnect from Slack
212
+ */
213
+ async disconnect() {
214
+ if (this.app) {
215
+ await this.app.stop();
216
+ this.app = null;
217
+ }
218
+ this.messageCache.clear();
219
+ }
220
+ /**
221
+ * Check if connected
222
+ */
223
+ isConnected() {
224
+ return this.app !== null;
225
+ }
226
+ /**
227
+ * Send a message to a channel
228
+ */
229
+ async sendMessage(channelId, text, options) {
230
+ if (!this.app) {
231
+ return err(new ConnectionError("slack", new Error("Not connected")));
232
+ }
233
+ try {
234
+ const slackOptions = options?.slack;
235
+ const result = await this.app.client.chat.postMessage({
236
+ channel: channelId,
237
+ text,
238
+ thread_ts: options?.threadId || slackOptions?.thread_ts,
239
+ blocks: slackOptions?.blocks,
240
+ unfurl_links: slackOptions?.unfurl_links,
241
+ unfurl_media: slackOptions?.unfurl_media,
242
+ metadata: slackOptions?.metadata
243
+ });
244
+ if (!result.ok || !result.message) {
245
+ return err(
246
+ new MessageSendError(
247
+ "slack",
248
+ channelId,
249
+ new Error(result.error || "Failed to send message")
250
+ )
251
+ );
252
+ }
253
+ const unifiedMessage = normalizeMessage(result.message);
254
+ if (!unifiedMessage.channelId) {
255
+ unifiedMessage.channelId = channelId;
256
+ }
257
+ this.cacheMessage(unifiedMessage);
258
+ return ok(unifiedMessage);
259
+ } catch (error) {
260
+ return err(
261
+ new MessageSendError(
262
+ "slack",
263
+ channelId,
264
+ error instanceof Error ? error : new Error(String(error))
265
+ )
266
+ );
267
+ }
268
+ }
269
+ /**
270
+ * Edit a message
271
+ */
272
+ async editMessage(messageRef, newText) {
273
+ if (!this.app) {
274
+ return err(new ConnectionError("slack", new Error("Not connected")));
275
+ }
276
+ try {
277
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
278
+ let channelId;
279
+ if (typeof messageRef === "string") {
280
+ const context = this.messageCache.get(messageRef);
281
+ if (!context) {
282
+ this.cacheMisses++;
283
+ this.logCacheStats();
284
+ return err(
285
+ new MessageEditError(
286
+ "slack",
287
+ messageRef,
288
+ new Error(
289
+ 'Cannot edit message: channel context not found.\\n\\nThis happens when:\\n1. The message is older than 1 hour (cache expired)\\n2. The bot restarted since the message was sent\\n3. The message was sent by another bot instance\\n\\nSolution: Pass the full message object instead:\\n bot.editMessage(message, "text") // \u2705 Works reliably\\n bot.editMessage(message.id, "text") // \u274C May fail on Slack'
290
+ )
291
+ )
292
+ );
293
+ }
294
+ this.cacheHits++;
295
+ this.logCacheStats();
296
+ channelId = context.channelId;
297
+ } else {
298
+ channelId = messageRef.channelId;
299
+ }
300
+ const result = await this.app.client.chat.update({
301
+ channel: channelId,
302
+ ts: messageId,
303
+ text: newText
304
+ });
305
+ if (!result.ok || !result.message) {
306
+ return err(
307
+ new MessageEditError(
308
+ "slack",
309
+ messageId,
310
+ new Error(result.error || "Failed to edit message")
311
+ )
312
+ );
313
+ }
314
+ return ok(normalizeMessage(result.message));
315
+ } catch (error) {
316
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
317
+ return err(
318
+ new MessageEditError(
319
+ "slack",
320
+ messageId,
321
+ error instanceof Error ? error : new Error(String(error))
322
+ )
323
+ );
324
+ }
325
+ }
326
+ /**
327
+ * Delete a message
328
+ */
329
+ async deleteMessage(messageRef) {
330
+ if (!this.app) {
331
+ return err(new ConnectionError("slack", new Error("Not connected")));
332
+ }
333
+ try {
334
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
335
+ let channelId;
336
+ if (typeof messageRef === "string") {
337
+ const context = this.messageCache.get(messageRef);
338
+ if (!context) {
339
+ this.cacheMisses++;
340
+ this.logCacheStats();
341
+ return err(
342
+ new MessageDeleteError(
343
+ "slack",
344
+ messageRef,
345
+ new Error(
346
+ "Cannot delete message: channel context not found.\\n\\nSolution: Pass the full message object instead:\\n bot.deleteMessage(message) // \u2705 Works reliably\\n bot.deleteMessage(message.id) // \u274C May fail on Slack"
347
+ )
348
+ )
349
+ );
350
+ }
351
+ this.cacheHits++;
352
+ this.logCacheStats();
353
+ channelId = context.channelId;
354
+ } else {
355
+ channelId = messageRef.channelId;
356
+ }
357
+ const result = await this.app.client.chat.delete({
358
+ channel: channelId,
359
+ ts: messageId
360
+ });
361
+ if (!result.ok) {
362
+ return err(
363
+ new MessageDeleteError(
364
+ "slack",
365
+ messageId,
366
+ new Error(result.error || "Failed to delete message")
367
+ )
368
+ );
369
+ }
370
+ this.messageCache.delete(messageId);
371
+ return ok(void 0);
372
+ } catch (error) {
373
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
374
+ return err(
375
+ new MessageDeleteError(
376
+ "slack",
377
+ messageId,
378
+ error instanceof Error ? error : new Error(String(error))
379
+ )
380
+ );
381
+ }
382
+ }
383
+ /**
384
+ * Add a reaction to a message
385
+ */
386
+ async addReaction(messageRef, emoji) {
387
+ if (!this.app) {
388
+ return err(new ConnectionError("slack", new Error("Not connected")));
389
+ }
390
+ try {
391
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
392
+ let channelId;
393
+ if (typeof messageRef === "string") {
394
+ const context = this.messageCache.get(messageRef);
395
+ if (!context) {
396
+ this.cacheMisses++;
397
+ this.logCacheStats();
398
+ return err(
399
+ new ReactionError(
400
+ "slack",
401
+ messageRef,
402
+ emoji,
403
+ new Error(
404
+ "Cannot add reaction: channel context not found.\\n\\nSolution: Pass the full message object instead:\\n bot.addReaction(message, emoji) // \u2705 Works reliably\\n bot.addReaction(message.id, emoji) // \u274C May fail on Slack"
405
+ )
406
+ )
407
+ );
408
+ }
409
+ this.cacheHits++;
410
+ this.logCacheStats();
411
+ channelId = context.channelId;
412
+ } else {
413
+ channelId = messageRef.channelId;
414
+ }
415
+ const slackEmoji = toSlackEmoji(emoji);
416
+ const result = await this.app.client.reactions.add({
417
+ channel: channelId,
418
+ timestamp: messageId,
419
+ name: slackEmoji.replace(/^:|:$/g, "")
420
+ // Slack API wants emoji without colons
421
+ });
422
+ if (!result.ok) {
423
+ return err(
424
+ new ReactionError(
425
+ "slack",
426
+ messageId,
427
+ emoji,
428
+ new Error(result.error || "Failed to add reaction")
429
+ )
430
+ );
431
+ }
432
+ return ok(void 0);
433
+ } catch (error) {
434
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
435
+ return err(
436
+ new ReactionError(
437
+ "slack",
438
+ messageId,
439
+ emoji,
440
+ error instanceof Error ? error : new Error(String(error))
441
+ )
442
+ );
443
+ }
444
+ }
445
+ /**
446
+ * Remove a reaction from a message
447
+ */
448
+ async removeReaction(messageRef, emoji) {
449
+ if (!this.app) {
450
+ return err(new ConnectionError("slack", new Error("Not connected")));
451
+ }
452
+ try {
453
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
454
+ let channelId;
455
+ if (typeof messageRef === "string") {
456
+ const context = this.messageCache.get(messageRef);
457
+ if (!context) {
458
+ this.cacheMisses++;
459
+ this.logCacheStats();
460
+ return err(
461
+ new ReactionError(
462
+ "slack",
463
+ messageRef,
464
+ emoji,
465
+ new Error(
466
+ "Cannot remove reaction: channel context not found.\\n\\nSolution: Pass the full message object instead:\\n bot.removeReaction(message, emoji) // \u2705 Works reliably\\n bot.removeReaction(message.id, emoji) // \u274C May fail on Slack"
467
+ )
468
+ )
469
+ );
470
+ }
471
+ this.cacheHits++;
472
+ this.logCacheStats();
473
+ channelId = context.channelId;
474
+ } else {
475
+ channelId = messageRef.channelId;
476
+ }
477
+ const slackEmoji = toSlackEmoji(emoji);
478
+ const result = await this.app.client.reactions.remove({
479
+ channel: channelId,
480
+ timestamp: messageId,
481
+ name: slackEmoji.replace(/^:|:$/g, "")
482
+ // Slack API wants emoji without colons
483
+ });
484
+ if (!result.ok) {
485
+ return err(
486
+ new ReactionError(
487
+ "slack",
488
+ messageId,
489
+ emoji,
490
+ new Error(result.error || "Failed to remove reaction")
491
+ )
492
+ );
493
+ }
494
+ return ok(void 0);
495
+ } catch (error) {
496
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
497
+ return err(
498
+ new ReactionError(
499
+ "slack",
500
+ messageId,
501
+ emoji,
502
+ error instanceof Error ? error : new Error(String(error))
503
+ )
504
+ );
505
+ }
506
+ }
507
+ /**
508
+ * Create a thread (reply to a message)
509
+ */
510
+ async createThread(messageRef, text) {
511
+ if (!this.app) {
512
+ return err(new ConnectionError("slack", new Error("Not connected")));
513
+ }
514
+ try {
515
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
516
+ let channelId;
517
+ if (typeof messageRef === "string") {
518
+ const context = this.messageCache.get(messageRef);
519
+ if (!context) {
520
+ this.cacheMisses++;
521
+ this.logCacheStats();
522
+ return err(
523
+ new MessageSendError(
524
+ "slack",
525
+ "unknown",
526
+ new Error(
527
+ "Cannot create thread: channel context not found.\\n\\nSolution: Pass the full message object instead:\\n bot.createThread(message, text) // \u2705 Works reliably\\n bot.createThread(message.id, text) // \u274C May fail on Slack"
528
+ )
529
+ )
530
+ );
531
+ }
532
+ this.cacheHits++;
533
+ this.logCacheStats();
534
+ channelId = context.channelId;
535
+ } else {
536
+ channelId = messageRef.channelId;
537
+ }
538
+ const result = await this.app.client.chat.postMessage({
539
+ channel: channelId,
540
+ text,
541
+ thread_ts: messageId
542
+ // This creates/replies in a thread
543
+ });
544
+ if (!result.ok || !result.message) {
545
+ return err(
546
+ new MessageSendError(
547
+ "slack",
548
+ channelId,
549
+ new Error(result.error || "Failed to create thread")
550
+ )
551
+ );
552
+ }
553
+ const unifiedMessage = normalizeMessage(result.message);
554
+ this.cacheMessage(unifiedMessage);
555
+ return ok(unifiedMessage);
556
+ } catch (error) {
557
+ const channelId = typeof messageRef === "string" ? "unknown" : messageRef.channelId;
558
+ return err(
559
+ new MessageSendError(
560
+ "slack",
561
+ channelId,
562
+ error instanceof Error ? error : new Error(String(error))
563
+ )
564
+ );
565
+ }
566
+ }
567
+ /**
568
+ * Upload a file to a channel
569
+ */
570
+ async uploadFile(channelId, file, options) {
571
+ if (!this.app) {
572
+ return err(new ConnectionError("slack", new Error("Not connected")));
573
+ }
574
+ try {
575
+ return err(
576
+ new MessageSendError(
577
+ "slack",
578
+ channelId,
579
+ new Error("File upload not yet implemented for Slack adapter")
580
+ )
581
+ );
582
+ } catch (error) {
583
+ return err(
584
+ new MessageSendError(
585
+ "slack",
586
+ channelId,
587
+ error instanceof Error ? error : new Error(String(error))
588
+ )
589
+ );
590
+ }
591
+ }
592
+ /**
593
+ * Subscribe to platform events
594
+ */
595
+ onEvent(handler) {
596
+ this.eventHandlers.add(handler);
597
+ }
598
+ /**
599
+ * Get list of channels
600
+ */
601
+ async getChannels() {
602
+ if (!this.app) {
603
+ return err(new ConnectionError("slack", new Error("Not connected")));
604
+ }
605
+ try {
606
+ const result = await this.app.client.conversations.list({
607
+ types: "public_channel,private_channel"
608
+ });
609
+ if (!result.ok || !result.channels) {
610
+ return err(
611
+ new Error(result.error || "Failed to fetch channels")
612
+ );
613
+ }
614
+ const channels = result.channels.map((channel) => ({
615
+ id: channel.id,
616
+ name: channel.name || "unknown",
617
+ type: "text",
618
+ isPrivate: channel.is_private || false,
619
+ topic: channel.topic?.value
620
+ }));
621
+ return ok(channels);
622
+ } catch (error) {
623
+ return err(error instanceof Error ? error : new Error(String(error)));
624
+ }
625
+ }
626
+ /**
627
+ * Get list of users
628
+ */
629
+ async getUsers(channelId) {
630
+ if (!this.app) {
631
+ return err(new ConnectionError("slack", new Error("Not connected")));
632
+ }
633
+ try {
634
+ if (channelId) {
635
+ const result = await this.app.client.conversations.members({
636
+ channel: channelId
637
+ });
638
+ if (!result.ok || !result.members) {
639
+ return err(
640
+ new Error(result.error || "Failed to fetch channel members")
641
+ );
642
+ }
643
+ const users = [];
644
+ for (const userId of result.members) {
645
+ const userInfo = await this.app.client.users.info({ user: userId });
646
+ if (userInfo.ok && userInfo.user) {
647
+ users.push({
648
+ id: userInfo.user.id,
649
+ username: userInfo.user.name || "unknown",
650
+ displayName: userInfo.user.real_name || userInfo.user.profile?.display_name,
651
+ isBot: userInfo.user.is_bot || false,
652
+ avatarUrl: userInfo.user.profile?.image_512
653
+ });
654
+ }
655
+ }
656
+ return ok(users);
657
+ } else {
658
+ const result = await this.app.client.users.list();
659
+ if (!result.ok || !result.members) {
660
+ return err(
661
+ new Error(result.error || "Failed to fetch users")
662
+ );
663
+ }
664
+ const users = result.members.map((user) => ({
665
+ id: user.id,
666
+ username: user.name || "unknown",
667
+ displayName: user.real_name || user.profile?.display_name,
668
+ isBot: user.is_bot || false,
669
+ avatarUrl: user.profile?.image_512
670
+ }));
671
+ return ok(users);
672
+ }
673
+ } catch (error) {
674
+ return err(error instanceof Error ? error : new Error(String(error)));
675
+ }
676
+ }
677
+ /**
678
+ * Normalize platform message to UnifiedMessage
679
+ */
680
+ normalizeMessage(platformMessage) {
681
+ return normalizeMessage(platformMessage);
682
+ }
683
+ /**
684
+ * Normalize platform event to UnifiedEvent
685
+ */
686
+ normalizeEvent(platformEvent) {
687
+ const event = platformEvent;
688
+ if (event.type === "message" || event.message) {
689
+ return normalizeMessageEvent(event);
690
+ }
691
+ if (event.type === "reaction_added") {
692
+ return normalizeReactionEvent(event, "added");
693
+ }
694
+ if (event.type === "reaction_removed") {
695
+ return normalizeReactionEvent(event, "removed");
696
+ }
697
+ return null;
698
+ }
699
+ /**
700
+ * Set up event listeners for Slack
701
+ */
702
+ setupEventListeners() {
703
+ if (!this.app) return;
704
+ this.app.message(async ({ message }) => {
705
+ if ("subtype" in message && message.subtype !== void 0) {
706
+ return;
707
+ }
708
+ const unifiedMessage = normalizeMessage(message);
709
+ this.cacheMessage(unifiedMessage);
710
+ const event = {
711
+ type: "message",
712
+ message: unifiedMessage
713
+ };
714
+ this.eventHandlers.forEach((handler) => {
715
+ try {
716
+ handler(event);
717
+ } catch (error) {
718
+ console.error("[Switchboard] Error in message handler:", error);
719
+ }
720
+ });
721
+ });
722
+ this.app.event("reaction_added", async ({ event }) => {
723
+ const reactionEvent = normalizeReactionEvent(event, "added");
724
+ this.eventHandlers.forEach((handler) => {
725
+ try {
726
+ handler(reactionEvent);
727
+ } catch (error) {
728
+ console.error("[Switchboard] Error in reaction handler:", error);
729
+ }
730
+ });
731
+ });
732
+ this.app.event("reaction_removed", async ({ event }) => {
733
+ const reactionEvent = normalizeReactionEvent(event, "removed");
734
+ this.eventHandlers.forEach((handler) => {
735
+ try {
736
+ handler(reactionEvent);
737
+ } catch (error) {
738
+ console.error("[Switchboard] Error in reaction handler:", error);
739
+ }
740
+ });
741
+ });
742
+ }
743
+ /**
744
+ * Cache a message's context
745
+ */
746
+ cacheMessage(message) {
747
+ this.messageCache.set(message.id, {
748
+ channelId: message.channelId,
749
+ threadId: message.threadId,
750
+ timestamp: message.timestamp
751
+ });
752
+ }
753
+ /**
754
+ * Log cache statistics (every 1000 operations)
755
+ */
756
+ logCacheStats() {
757
+ const total = this.cacheHits + this.cacheMisses;
758
+ if (total > 0 && total % 1e3 === 0) {
759
+ const hitRate = (this.cacheHits / total * 100).toFixed(1);
760
+ console.log(`[Switchboard] Slack cache hit rate: ${hitRate}% (${this.cacheHits}/${total})`);
761
+ }
762
+ }
763
+ };
764
+
765
+ // src/register.ts
766
+ var slackAdapter = new SlackAdapter();
767
+ registry.register("slack", slackAdapter);
768
+ if (process.env.NODE_ENV !== "production") {
769
+ console.log("[Switchboard] Slack adapter registered");
770
+ }
771
+ export {
772
+ SlackAdapter,
773
+ normalizeEmoji,
774
+ normalizeMessage,
775
+ normalizeMessageEvent,
776
+ normalizeReactionEvent,
777
+ toSlackEmoji
778
+ };
779
+ //# sourceMappingURL=index.js.map