@elizaos/plugin-slack 2.0.0-alpha

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,3211 @@
1
+ // src/index.ts
2
+ import { logger } from "@elizaos/core";
3
+
4
+ // src/actions/deleteMessage.ts
5
+ import {
6
+ composePromptFromState,
7
+ ModelType,
8
+ parseJSONObjectFromText
9
+ } from "@elizaos/core";
10
+
11
+ // src/types.ts
12
+ var SlackEventTypes;
13
+ ((SlackEventTypes2) => {
14
+ SlackEventTypes2["MESSAGE_RECEIVED"] = "SLACK_MESSAGE_RECEIVED";
15
+ SlackEventTypes2["MESSAGE_SENT"] = "SLACK_MESSAGE_SENT";
16
+ SlackEventTypes2["REACTION_ADDED"] = "SLACK_REACTION_ADDED";
17
+ SlackEventTypes2["REACTION_REMOVED"] = "SLACK_REACTION_REMOVED";
18
+ SlackEventTypes2["CHANNEL_JOINED"] = "SLACK_CHANNEL_JOINED";
19
+ SlackEventTypes2["CHANNEL_LEFT"] = "SLACK_CHANNEL_LEFT";
20
+ SlackEventTypes2["MEMBER_JOINED_CHANNEL"] = "SLACK_MEMBER_JOINED_CHANNEL";
21
+ SlackEventTypes2["MEMBER_LEFT_CHANNEL"] = "SLACK_MEMBER_LEFT_CHANNEL";
22
+ SlackEventTypes2["APP_MENTION"] = "SLACK_APP_MENTION";
23
+ SlackEventTypes2["SLASH_COMMAND"] = "SLACK_SLASH_COMMAND";
24
+ SlackEventTypes2["FILE_SHARED"] = "SLACK_FILE_SHARED";
25
+ SlackEventTypes2["THREAD_REPLY"] = "SLACK_THREAD_REPLY";
26
+ })(SlackEventTypes ||= {});
27
+ var SLACK_SERVICE_NAME = "slack";
28
+ var ServiceType = {
29
+ SLACK: "slack"
30
+ };
31
+
32
+ class SlackPluginError extends Error {
33
+ code;
34
+ constructor(message, code) {
35
+ super(message);
36
+ this.code = code;
37
+ this.name = "SlackPluginError";
38
+ }
39
+ }
40
+
41
+ class SlackServiceNotInitializedError extends SlackPluginError {
42
+ constructor() {
43
+ super("Slack service is not initialized", "SERVICE_NOT_INITIALIZED");
44
+ this.name = "SlackServiceNotInitializedError";
45
+ }
46
+ }
47
+
48
+ class SlackClientNotAvailableError extends SlackPluginError {
49
+ constructor() {
50
+ super("Slack client is not available", "CLIENT_NOT_AVAILABLE");
51
+ this.name = "SlackClientNotAvailableError";
52
+ }
53
+ }
54
+
55
+ class SlackConfigurationError extends SlackPluginError {
56
+ constructor(missingConfig) {
57
+ super(`Missing required configuration: ${missingConfig}`, "MISSING_CONFIG");
58
+ this.name = "SlackConfigurationError";
59
+ }
60
+ }
61
+
62
+ class SlackApiError extends SlackPluginError {
63
+ apiErrorCode;
64
+ constructor(message, apiErrorCode) {
65
+ super(message, "API_ERROR");
66
+ this.apiErrorCode = apiErrorCode;
67
+ this.name = "SlackApiError";
68
+ }
69
+ }
70
+ function isValidChannelId(id) {
71
+ return /^[CGD][A-Z0-9]{8,}$/i.test(id);
72
+ }
73
+ function isValidUserId(id) {
74
+ return /^[UW][A-Z0-9]{8,}$/i.test(id);
75
+ }
76
+ function isValidTeamId(id) {
77
+ return /^T[A-Z0-9]{8,}$/i.test(id);
78
+ }
79
+ function isValidMessageTs(ts) {
80
+ return /^\d+\.\d{6}$/.test(ts);
81
+ }
82
+ function parseSlackMessageLink(link) {
83
+ const match = link.match(/\/archives\/([CGD][A-Z0-9]+)\/p(\d+)/i);
84
+ if (!match)
85
+ return null;
86
+ const channelId = match[1];
87
+ const ts = match[2];
88
+ const messageTs = `${ts.slice(0, 10)}.${ts.slice(10)}`;
89
+ return { channelId, messageTs };
90
+ }
91
+ function formatMessageTsForLink(ts) {
92
+ return `p${ts.replace(".", "")}`;
93
+ }
94
+ function getSlackUserDisplayName(user) {
95
+ return user.profile.displayName || user.profile.realName || user.name;
96
+ }
97
+ function getSlackChannelType(channel) {
98
+ if (channel.isIm)
99
+ return "im";
100
+ if (channel.isMpim)
101
+ return "mpim";
102
+ if (channel.isGroup || channel.isPrivate)
103
+ return "group";
104
+ return "channel";
105
+ }
106
+ var MAX_SLACK_MESSAGE_LENGTH = 4000;
107
+ var MAX_SLACK_BLOCKS = 50;
108
+ var MAX_SLACK_FILE_SIZE = 1024 * 1024 * 1024;
109
+
110
+ // src/actions/deleteMessage.ts
111
+ var deleteMessageTemplate = `You are helping to extract delete message parameters for Slack.
112
+
113
+ The user wants to delete a Slack message.
114
+
115
+ Recent conversation:
116
+ {{recentMessages}}
117
+
118
+ Extract the following:
119
+ 1. messageTs: The message timestamp to delete (format: 1234567890.123456)
120
+ 2. channelId: The channel ID (optional, defaults to current channel)
121
+
122
+ Respond with a JSON object like:
123
+ {
124
+ "messageTs": "1234567890.123456",
125
+ "channelId": null
126
+ }
127
+
128
+ Only respond with the JSON object, no other text.`;
129
+ var deleteMessage = {
130
+ name: "SLACK_DELETE_MESSAGE",
131
+ similes: ["REMOVE_SLACK_MESSAGE", "DELETE_MESSAGE", "SLACK_REMOVE"],
132
+ description: "Delete a Slack message",
133
+ validate: async (_runtime, message, _state) => {
134
+ return message.content.source === "slack";
135
+ },
136
+ handler: async (runtime, message, state, _options, callback) => {
137
+ const slackService = runtime.getService(SLACK_SERVICE_NAME);
138
+ if (!slackService || !slackService.client) {
139
+ await callback?.({
140
+ text: "Slack service is not available.",
141
+ source: "slack"
142
+ });
143
+ return { success: false, error: "Slack service not available" };
144
+ }
145
+ const prompt = composePromptFromState({
146
+ state,
147
+ template: deleteMessageTemplate
148
+ });
149
+ let deleteInfo = null;
150
+ for (let attempt = 0;attempt < 3; attempt++) {
151
+ const response2 = await runtime.useModel(ModelType.TEXT_SMALL, {
152
+ prompt
153
+ });
154
+ const parsedResponse = parseJSONObjectFromText(response2);
155
+ if (parsedResponse?.messageTs) {
156
+ deleteInfo = {
157
+ messageTs: String(parsedResponse.messageTs),
158
+ channelId: parsedResponse.channelId ? String(parsedResponse.channelId) : null
159
+ };
160
+ break;
161
+ }
162
+ }
163
+ if (!deleteInfo || !deleteInfo.messageTs) {
164
+ runtime.logger.debug({ src: "plugin:slack:action:delete-message" }, "[SLACK_DELETE_MESSAGE] Could not extract delete info");
165
+ await callback?.({
166
+ text: "I couldn't understand which message to delete. Please specify the message timestamp.",
167
+ source: "slack"
168
+ });
169
+ return { success: false, error: "Could not extract delete parameters" };
170
+ }
171
+ if (!isValidMessageTs(deleteInfo.messageTs)) {
172
+ await callback?.({
173
+ text: "The message timestamp format is invalid. Please provide a valid Slack message timestamp.",
174
+ source: "slack"
175
+ });
176
+ return { success: false, error: "Invalid message timestamp" };
177
+ }
178
+ const stateData = state?.data;
179
+ const room = stateData?.room || await runtime.getRoom(message.roomId);
180
+ const channelId = deleteInfo.channelId || room?.channelId;
181
+ if (!channelId) {
182
+ await callback?.({
183
+ text: "I couldn't determine the channel for the message deletion.",
184
+ source: "slack"
185
+ });
186
+ return { success: false, error: "Could not determine channel" };
187
+ }
188
+ await slackService.deleteMessage(channelId, deleteInfo.messageTs);
189
+ const response = {
190
+ text: "Message deleted successfully.",
191
+ source: message.content.source
192
+ };
193
+ runtime.logger.debug({
194
+ src: "plugin:slack:action:delete-message",
195
+ messageTs: deleteInfo.messageTs,
196
+ channelId
197
+ }, "[SLACK_DELETE_MESSAGE] Message deleted");
198
+ await callback?.(response);
199
+ return {
200
+ success: true,
201
+ data: {
202
+ messageTs: deleteInfo.messageTs,
203
+ channelId
204
+ }
205
+ };
206
+ },
207
+ examples: [
208
+ [
209
+ {
210
+ name: "{{user1}}",
211
+ content: {
212
+ text: "Delete that last message I sent"
213
+ }
214
+ },
215
+ {
216
+ name: "{{agent}}",
217
+ content: {
218
+ text: "I'll delete that message for you.",
219
+ actions: ["SLACK_DELETE_MESSAGE"]
220
+ }
221
+ }
222
+ ]
223
+ ]
224
+ };
225
+ var deleteMessage_default = deleteMessage;
226
+
227
+ // src/actions/editMessage.ts
228
+ import {
229
+ composePromptFromState as composePromptFromState2,
230
+ ModelType as ModelType2,
231
+ parseJSONObjectFromText as parseJSONObjectFromText2
232
+ } from "@elizaos/core";
233
+ var editMessageTemplate = `You are helping to extract edit message parameters for Slack.
234
+
235
+ The user wants to edit an existing Slack message.
236
+
237
+ Recent conversation:
238
+ {{recentMessages}}
239
+
240
+ Extract the following:
241
+ 1. messageTs: The message timestamp to edit (format: 1234567890.123456)
242
+ 2. newText: The new text content for the message
243
+ 3. channelId: The channel ID (optional, defaults to current channel)
244
+
245
+ Respond with a JSON object like:
246
+ {
247
+ "messageTs": "1234567890.123456",
248
+ "newText": "The updated message content",
249
+ "channelId": null
250
+ }
251
+
252
+ Only respond with the JSON object, no other text.`;
253
+ var editMessage = {
254
+ name: "SLACK_EDIT_MESSAGE",
255
+ similes: [
256
+ "UPDATE_SLACK_MESSAGE",
257
+ "MODIFY_MESSAGE",
258
+ "CHANGE_MESSAGE",
259
+ "SLACK_UPDATE"
260
+ ],
261
+ description: "Edit an existing Slack message",
262
+ validate: async (_runtime, message, _state) => {
263
+ return message.content.source === "slack";
264
+ },
265
+ handler: async (runtime, message, state, _options, callback) => {
266
+ const slackService = runtime.getService(SLACK_SERVICE_NAME);
267
+ if (!slackService || !slackService.client) {
268
+ await callback?.({
269
+ text: "Slack service is not available.",
270
+ source: "slack"
271
+ });
272
+ return { success: false, error: "Slack service not available" };
273
+ }
274
+ const prompt = composePromptFromState2({
275
+ state,
276
+ template: editMessageTemplate
277
+ });
278
+ let editInfo = null;
279
+ for (let attempt = 0;attempt < 3; attempt++) {
280
+ const response2 = await runtime.useModel(ModelType2.TEXT_SMALL, {
281
+ prompt
282
+ });
283
+ const parsedResponse = parseJSONObjectFromText2(response2);
284
+ if (parsedResponse?.messageTs && parsedResponse?.newText) {
285
+ editInfo = {
286
+ messageTs: String(parsedResponse.messageTs),
287
+ newText: String(parsedResponse.newText),
288
+ channelId: parsedResponse.channelId ? String(parsedResponse.channelId) : null
289
+ };
290
+ break;
291
+ }
292
+ }
293
+ if (!editInfo || !editInfo.messageTs || !editInfo.newText) {
294
+ runtime.logger.debug({ src: "plugin:slack:action:edit-message" }, "[SLACK_EDIT_MESSAGE] Could not extract edit info");
295
+ await callback?.({
296
+ text: "I couldn't understand the edit request. Please specify the message timestamp and new content.",
297
+ source: "slack"
298
+ });
299
+ return { success: false, error: "Could not extract edit parameters" };
300
+ }
301
+ if (!isValidMessageTs(editInfo.messageTs)) {
302
+ await callback?.({
303
+ text: "The message timestamp format is invalid. Please provide a valid Slack message timestamp.",
304
+ source: "slack"
305
+ });
306
+ return { success: false, error: "Invalid message timestamp" };
307
+ }
308
+ const stateData = state?.data;
309
+ const room = stateData?.room || await runtime.getRoom(message.roomId);
310
+ const channelId = editInfo.channelId || room?.channelId;
311
+ if (!channelId) {
312
+ await callback?.({
313
+ text: "I couldn't determine the channel for the message edit.",
314
+ source: "slack"
315
+ });
316
+ return { success: false, error: "Could not determine channel" };
317
+ }
318
+ await slackService.editMessage(channelId, editInfo.messageTs, editInfo.newText);
319
+ const response = {
320
+ text: "Message edited successfully.",
321
+ source: message.content.source
322
+ };
323
+ runtime.logger.debug({
324
+ src: "plugin:slack:action:edit-message",
325
+ messageTs: editInfo.messageTs,
326
+ channelId
327
+ }, "[SLACK_EDIT_MESSAGE] Message edited");
328
+ await callback?.(response);
329
+ return {
330
+ success: true,
331
+ data: {
332
+ messageTs: editInfo.messageTs,
333
+ channelId,
334
+ newText: editInfo.newText
335
+ }
336
+ };
337
+ },
338
+ examples: [
339
+ [
340
+ {
341
+ name: "{{user1}}",
342
+ content: {
343
+ text: "Edit that message to say 'Meeting at 3pm' instead"
344
+ }
345
+ },
346
+ {
347
+ name: "{{agent}}",
348
+ content: {
349
+ text: "I'll update that message for you.",
350
+ actions: ["SLACK_EDIT_MESSAGE"]
351
+ }
352
+ }
353
+ ]
354
+ ]
355
+ };
356
+ var editMessage_default = editMessage;
357
+
358
+ // src/actions/emojiList.ts
359
+ var emojiList = {
360
+ name: "SLACK_EMOJI_LIST",
361
+ similes: [
362
+ "LIST_SLACK_EMOJI",
363
+ "SHOW_EMOJI",
364
+ "GET_CUSTOM_EMOJI",
365
+ "CUSTOM_EMOJI",
366
+ "WORKSPACE_EMOJI"
367
+ ],
368
+ description: "List custom emoji available in the Slack workspace",
369
+ validate: async (_runtime, message, _state) => {
370
+ return message.content.source === "slack";
371
+ },
372
+ handler: async (runtime, message, _state, _options, callback) => {
373
+ const slackService = runtime.getService(SLACK_SERVICE_NAME);
374
+ if (!slackService || !slackService.client) {
375
+ await callback?.({
376
+ text: "Slack service is not available.",
377
+ source: "slack"
378
+ });
379
+ return { success: false, error: "Slack service not available" };
380
+ }
381
+ const emoji = await slackService.getEmojiList();
382
+ const emojiNames = Object.keys(emoji).sort();
383
+ if (emojiNames.length === 0) {
384
+ const response2 = {
385
+ text: "There are no custom emoji in this workspace.",
386
+ source: message.content.source
387
+ };
388
+ await callback?.(response2);
389
+ return {
390
+ success: true,
391
+ data: {
392
+ emojiCount: 0,
393
+ emoji: {}
394
+ }
395
+ };
396
+ }
397
+ const _chunkSize = 20;
398
+ const displayCount = Math.min(emojiNames.length, 100);
399
+ const displayEmoji = emojiNames.slice(0, displayCount);
400
+ const aliases = [];
401
+ const custom = [];
402
+ for (const name of displayEmoji) {
403
+ const value = emoji[name];
404
+ if (value.startsWith("alias:")) {
405
+ aliases.push(name);
406
+ } else {
407
+ custom.push(name);
408
+ }
409
+ }
410
+ const emojiDisplay = custom.map((name) => `:${name}:`).join(" ");
411
+ const aliasDisplay = aliases.length > 0 ? `
412
+
413
+ Aliases: ${aliases.map((name) => `:${name}:`).join(" ")}` : "";
414
+ const truncationNote = emojiNames.length > displayCount ? `
415
+
416
+ (Showing ${displayCount} of ${emojiNames.length} total custom emoji)` : "";
417
+ const response = {
418
+ text: `Custom emoji in this workspace (${emojiNames.length} total):
419
+
420
+ ${emojiDisplay}${aliasDisplay}${truncationNote}`,
421
+ source: message.content.source
422
+ };
423
+ runtime.logger.debug({
424
+ src: "plugin:slack:action:emoji-list",
425
+ emojiCount: emojiNames.length
426
+ }, "[SLACK_EMOJI_LIST] Emoji listed");
427
+ await callback?.(response);
428
+ return {
429
+ success: true,
430
+ data: {
431
+ emojiCount: emojiNames.length,
432
+ emoji: Object.fromEntries(displayEmoji.map((name) => [name, emoji[name]]))
433
+ }
434
+ };
435
+ },
436
+ examples: [
437
+ [
438
+ {
439
+ name: "{{user1}}",
440
+ content: {
441
+ text: "Show me the custom emoji in this workspace"
442
+ }
443
+ },
444
+ {
445
+ name: "{{agent}}",
446
+ content: {
447
+ text: "I'll list the custom emoji available.",
448
+ actions: ["SLACK_EMOJI_LIST"]
449
+ }
450
+ }
451
+ ],
452
+ [
453
+ {
454
+ name: "{{user1}}",
455
+ content: {
456
+ text: "What emoji can I use here?"
457
+ }
458
+ },
459
+ {
460
+ name: "{{agent}}",
461
+ content: {
462
+ text: "Let me show you the custom emoji in this workspace.",
463
+ actions: ["SLACK_EMOJI_LIST"]
464
+ }
465
+ }
466
+ ]
467
+ ]
468
+ };
469
+ var emojiList_default = emojiList;
470
+
471
+ // src/actions/getUserInfo.ts
472
+ import {
473
+ composePromptFromState as composePromptFromState3,
474
+ ModelType as ModelType3,
475
+ parseJSONObjectFromText as parseJSONObjectFromText3
476
+ } from "@elizaos/core";
477
+ var getUserInfoTemplate = `You are helping to extract user info parameters for Slack.
478
+
479
+ The user wants to get information about a Slack user.
480
+
481
+ Recent conversation:
482
+ {{recentMessages}}
483
+
484
+ Extract the following:
485
+ 1. userId: The Slack user ID to look up (format: U followed by alphanumeric characters, e.g., U0123456789)
486
+
487
+ Respond with a JSON object like:
488
+ {
489
+ "userId": "U0123456789"
490
+ }
491
+
492
+ Only respond with the JSON object, no other text.`;
493
+ var getUserInfo = {
494
+ name: "SLACK_GET_USER_INFO",
495
+ similes: [
496
+ "GET_SLACK_USER",
497
+ "USER_INFO",
498
+ "SLACK_USER",
499
+ "MEMBER_INFO",
500
+ "WHO_IS"
501
+ ],
502
+ description: "Get information about a Slack user",
503
+ validate: async (_runtime, message, _state) => {
504
+ return message.content.source === "slack";
505
+ },
506
+ handler: async (runtime, message, state, _options, callback) => {
507
+ const slackService = runtime.getService(SLACK_SERVICE_NAME);
508
+ if (!slackService || !slackService.client) {
509
+ await callback?.({
510
+ text: "Slack service is not available.",
511
+ source: "slack"
512
+ });
513
+ return { success: false, error: "Slack service not available" };
514
+ }
515
+ const prompt = composePromptFromState3({
516
+ state,
517
+ template: getUserInfoTemplate
518
+ });
519
+ let userInfo = null;
520
+ for (let attempt = 0;attempt < 3; attempt++) {
521
+ const response2 = await runtime.useModel(ModelType3.TEXT_SMALL, {
522
+ prompt
523
+ });
524
+ const parsedResponse = parseJSONObjectFromText3(response2);
525
+ if (parsedResponse?.userId) {
526
+ userInfo = {
527
+ userId: String(parsedResponse.userId)
528
+ };
529
+ break;
530
+ }
531
+ }
532
+ if (!userInfo || !userInfo.userId) {
533
+ runtime.logger.debug({ src: "plugin:slack:action:get-user-info" }, "[SLACK_GET_USER_INFO] Could not extract user info");
534
+ await callback?.({
535
+ text: "I couldn't determine which user to look up. Please specify a user ID.",
536
+ source: "slack"
537
+ });
538
+ return { success: false, error: "Could not extract user ID" };
539
+ }
540
+ if (!isValidUserId(userInfo.userId)) {
541
+ await callback?.({
542
+ text: "The user ID format is invalid. Slack user IDs start with U followed by alphanumeric characters.",
543
+ source: "slack"
544
+ });
545
+ return { success: false, error: "Invalid user ID format" };
546
+ }
547
+ const user = await slackService.getUser(userInfo.userId);
548
+ if (!user) {
549
+ await callback?.({
550
+ text: `I couldn't find a user with ID ${userInfo.userId}.`,
551
+ source: "slack"
552
+ });
553
+ return { success: false, error: "User not found" };
554
+ }
555
+ const displayName = getSlackUserDisplayName(user);
556
+ const roles = [];
557
+ if (user.isAdmin)
558
+ roles.push("Admin");
559
+ if (user.isOwner)
560
+ roles.push("Owner");
561
+ if (user.isPrimaryOwner)
562
+ roles.push("Primary Owner");
563
+ if (user.isBot)
564
+ roles.push("Bot");
565
+ if (user.isRestricted)
566
+ roles.push("Guest");
567
+ const userDetails = [
568
+ `**Name:** ${displayName}`,
569
+ user.profile.realName && user.profile.realName !== displayName ? `**Real Name:** ${user.profile.realName}` : null,
570
+ `**Username:** @${user.name}`,
571
+ user.profile.title ? `**Title:** ${user.profile.title}` : null,
572
+ user.profile.email ? `**Email:** ${user.profile.email}` : null,
573
+ user.tz ? `**Timezone:** ${user.tzLabel || user.tz}` : null,
574
+ user.profile.statusText ? `**Status:** ${user.profile.statusEmoji || ""} ${user.profile.statusText}` : null,
575
+ roles.length > 0 ? `**Roles:** ${roles.join(", ")}` : null
576
+ ].filter(Boolean).join(`
577
+ `);
578
+ const response = {
579
+ text: `User information for ${displayName}:
580
+
581
+ ${userDetails}`,
582
+ source: message.content.source
583
+ };
584
+ runtime.logger.debug({
585
+ src: "plugin:slack:action:get-user-info",
586
+ userId: userInfo.userId,
587
+ displayName
588
+ }, "[SLACK_GET_USER_INFO] User info retrieved");
589
+ await callback?.(response);
590
+ return {
591
+ success: true,
592
+ data: {
593
+ userId: user.id,
594
+ name: user.name,
595
+ displayName,
596
+ realName: user.profile.realName,
597
+ title: user.profile.title,
598
+ email: user.profile.email,
599
+ timezone: user.tz,
600
+ isAdmin: user.isAdmin,
601
+ isOwner: user.isOwner,
602
+ isBot: user.isBot,
603
+ statusText: user.profile.statusText,
604
+ statusEmoji: user.profile.statusEmoji,
605
+ avatar: user.profile.image192 || user.profile.image72
606
+ }
607
+ };
608
+ },
609
+ examples: [
610
+ [
611
+ {
612
+ name: "{{user1}}",
613
+ content: {
614
+ text: "Who is U0123456789?"
615
+ }
616
+ },
617
+ {
618
+ name: "{{agent}}",
619
+ content: {
620
+ text: "Let me look up that user for you.",
621
+ actions: ["SLACK_GET_USER_INFO"]
622
+ }
623
+ }
624
+ ],
625
+ [
626
+ {
627
+ name: "{{user1}}",
628
+ content: {
629
+ text: "Get information about the user who sent the last message"
630
+ }
631
+ },
632
+ {
633
+ name: "{{agent}}",
634
+ content: {
635
+ text: "I'll fetch their profile information.",
636
+ actions: ["SLACK_GET_USER_INFO"]
637
+ }
638
+ }
639
+ ]
640
+ ]
641
+ };
642
+ var getUserInfo_default = getUserInfo;
643
+
644
+ // src/actions/listChannels.ts
645
+ var listChannels = {
646
+ name: "SLACK_LIST_CHANNELS",
647
+ similes: [
648
+ "LIST_SLACK_CHANNELS",
649
+ "SHOW_CHANNELS",
650
+ "GET_CHANNELS",
651
+ "CHANNELS_LIST"
652
+ ],
653
+ description: "List available Slack channels in the workspace",
654
+ validate: async (_runtime, message, _state) => {
655
+ return message.content.source === "slack";
656
+ },
657
+ handler: async (runtime, message, _state, _options, callback) => {
658
+ const slackService = runtime.getService(SLACK_SERVICE_NAME);
659
+ if (!slackService || !slackService.client) {
660
+ await callback?.({
661
+ text: "Slack service is not available.",
662
+ source: "slack"
663
+ });
664
+ return { success: false, error: "Slack service not available" };
665
+ }
666
+ const channels = await slackService.listChannels({
667
+ types: "public_channel,private_channel",
668
+ limit: 100
669
+ });
670
+ const sortedChannels = channels.filter((ch) => !ch.isArchived).sort((a, b) => a.name.localeCompare(b.name));
671
+ const channelList = sortedChannels.map((ch) => {
672
+ const memberCount = ch.numMembers !== undefined ? ` (${ch.numMembers} members)` : "";
673
+ const privateIndicator = ch.isPrivate ? " \uD83D\uDD12" : "";
674
+ const topic = ch.topic?.value ? ` - ${ch.topic.value.slice(0, 50)}${ch.topic.value.length > 50 ? "..." : ""}` : "";
675
+ return `• #${ch.name}${privateIndicator}${memberCount}${topic}`;
676
+ });
677
+ const response = {
678
+ text: `Found ${sortedChannels.length} channels:
679
+
680
+ ${channelList.join(`
681
+ `)}`,
682
+ source: message.content.source
683
+ };
684
+ runtime.logger.debug({
685
+ src: "plugin:slack:action:list-channels",
686
+ channelCount: sortedChannels.length
687
+ }, "[SLACK_LIST_CHANNELS] Channels listed");
688
+ await callback?.(response);
689
+ return {
690
+ success: true,
691
+ data: {
692
+ channelCount: sortedChannels.length,
693
+ channels: sortedChannels.map((ch) => ({
694
+ id: ch.id,
695
+ name: ch.name,
696
+ isPrivate: ch.isPrivate,
697
+ numMembers: ch.numMembers,
698
+ topic: ch.topic?.value,
699
+ purpose: ch.purpose?.value
700
+ }))
701
+ }
702
+ };
703
+ },
704
+ examples: [
705
+ [
706
+ {
707
+ name: "{{user1}}",
708
+ content: {
709
+ text: "Show me all the channels in this workspace"
710
+ }
711
+ },
712
+ {
713
+ name: "{{agent}}",
714
+ content: {
715
+ text: "I'll list all the available channels.",
716
+ actions: ["SLACK_LIST_CHANNELS"]
717
+ }
718
+ }
719
+ ],
720
+ [
721
+ {
722
+ name: "{{user1}}",
723
+ content: {
724
+ text: "What channels can I join?"
725
+ }
726
+ },
727
+ {
728
+ name: "{{agent}}",
729
+ content: {
730
+ text: "Let me show you the available channels.",
731
+ actions: ["SLACK_LIST_CHANNELS"]
732
+ }
733
+ }
734
+ ]
735
+ ]
736
+ };
737
+ var listChannels_default = listChannels;
738
+
739
+ // src/actions/listPins.ts
740
+ import {
741
+ composePromptFromState as composePromptFromState4,
742
+ ModelType as ModelType4,
743
+ parseJSONObjectFromText as parseJSONObjectFromText4
744
+ } from "@elizaos/core";
745
+ var listPinsTemplate = `You are helping to extract list pins parameters for Slack.
746
+
747
+ The user wants to see pinned messages in a Slack channel.
748
+
749
+ Recent conversation:
750
+ {{recentMessages}}
751
+
752
+ Extract the following:
753
+ 1. channelRef: The channel to list pins from (default: "current" for the current channel, or a channel name/ID)
754
+
755
+ Respond with a JSON object like:
756
+ {
757
+ "channelRef": "current"
758
+ }
759
+
760
+ Only respond with the JSON object, no other text.`;
761
+ var listPins = {
762
+ name: "SLACK_LIST_PINS",
763
+ similes: [
764
+ "LIST_SLACK_PINS",
765
+ "SHOW_PINS",
766
+ "GET_PINNED_MESSAGES",
767
+ "PINNED_MESSAGES"
768
+ ],
769
+ description: "List pinned messages in a Slack channel",
770
+ validate: async (_runtime, message, _state) => {
771
+ return message.content.source === "slack";
772
+ },
773
+ handler: async (runtime, message, state, _options, callback) => {
774
+ const slackService = runtime.getService(SLACK_SERVICE_NAME);
775
+ if (!slackService || !slackService.client) {
776
+ await callback?.({
777
+ text: "Slack service is not available.",
778
+ source: "slack"
779
+ });
780
+ return { success: false, error: "Slack service not available" };
781
+ }
782
+ const prompt = composePromptFromState4({
783
+ state,
784
+ template: listPinsTemplate
785
+ });
786
+ let listInfo = null;
787
+ for (let attempt = 0;attempt < 3; attempt++) {
788
+ const response2 = await runtime.useModel(ModelType4.TEXT_SMALL, {
789
+ prompt
790
+ });
791
+ const parsedResponse = parseJSONObjectFromText4(response2);
792
+ if (parsedResponse) {
793
+ listInfo = {
794
+ channelRef: parsedResponse.channelRef ? String(parsedResponse.channelRef) : "current"
795
+ };
796
+ break;
797
+ }
798
+ }
799
+ if (!listInfo) {
800
+ listInfo = { channelRef: "current" };
801
+ }
802
+ const stateData = state?.data;
803
+ const room = stateData?.room || await runtime.getRoom(message.roomId);
804
+ if (!room || !room.channelId) {
805
+ await callback?.({
806
+ text: "I couldn't determine the current channel.",
807
+ source: "slack"
808
+ });
809
+ return { success: false, error: "Could not determine channel" };
810
+ }
811
+ let targetChannelId = room.channelId;
812
+ if (listInfo.channelRef && listInfo.channelRef !== "current") {
813
+ const channels = await slackService.listChannels();
814
+ const targetChannel = channels.find((ch) => {
815
+ const channelName2 = ch.name?.toLowerCase() || "";
816
+ const searchTerm = listInfo?.channelRef?.toLowerCase() || "";
817
+ return channelName2 === searchTerm || channelName2 === searchTerm.replace(/^#/, "") || ch.id === listInfo?.channelRef;
818
+ });
819
+ if (targetChannel) {
820
+ targetChannelId = targetChannel.id;
821
+ }
822
+ }
823
+ const pins = await slackService.listPins(targetChannelId);
824
+ if (pins.length === 0) {
825
+ const channelInfo2 = await slackService.getChannel(targetChannelId);
826
+ const channelName2 = channelInfo2?.name || targetChannelId;
827
+ const response2 = {
828
+ text: `There are no pinned messages in #${channelName2}.`,
829
+ source: message.content.source
830
+ };
831
+ await callback?.(response2);
832
+ return {
833
+ success: true,
834
+ data: {
835
+ channelId: targetChannelId,
836
+ pinCount: 0,
837
+ pins: []
838
+ }
839
+ };
840
+ }
841
+ const formattedPins = pins.map((pin, index) => {
842
+ const timestamp = new Date(parseFloat(pin.ts) * 1000).toISOString();
843
+ const user = pin.user || "unknown";
844
+ const text = pin.text?.slice(0, 100) || "[no text]";
845
+ const truncated = pin.text && pin.text.length > 100 ? "..." : "";
846
+ return `${index + 1}. [${timestamp}] ${user}: ${text}${truncated}`;
847
+ });
848
+ const channelInfo = await slackService.getChannel(targetChannelId);
849
+ const channelName = channelInfo?.name || targetChannelId;
850
+ const response = {
851
+ text: `Pinned messages in #${channelName} (${pins.length}):
852
+
853
+ ${formattedPins.join(`
854
+
855
+ `)}`,
856
+ source: message.content.source
857
+ };
858
+ runtime.logger.debug({
859
+ src: "plugin:slack:action:list-pins",
860
+ channelId: targetChannelId,
861
+ pinCount: pins.length
862
+ }, "[SLACK_LIST_PINS] Pins listed");
863
+ await callback?.(response);
864
+ return {
865
+ success: true,
866
+ data: {
867
+ channelId: targetChannelId,
868
+ channelName,
869
+ pinCount: pins.length,
870
+ pins: pins.map((p) => ({
871
+ ts: p.ts,
872
+ user: p.user,
873
+ text: p.text
874
+ }))
875
+ }
876
+ };
877
+ },
878
+ examples: [
879
+ [
880
+ {
881
+ name: "{{user1}}",
882
+ content: {
883
+ text: "Show me the pinned messages in this channel"
884
+ }
885
+ },
886
+ {
887
+ name: "{{agent}}",
888
+ content: {
889
+ text: "I'll list the pinned messages.",
890
+ actions: ["SLACK_LIST_PINS"]
891
+ }
892
+ }
893
+ ],
894
+ [
895
+ {
896
+ name: "{{user1}}",
897
+ content: {
898
+ text: "What's pinned in #announcements?"
899
+ }
900
+ },
901
+ {
902
+ name: "{{agent}}",
903
+ content: {
904
+ text: "Let me check the pins in #announcements.",
905
+ actions: ["SLACK_LIST_PINS"]
906
+ }
907
+ }
908
+ ]
909
+ ]
910
+ };
911
+ var listPins_default = listPins;
912
+
913
+ // src/actions/pinMessage.ts
914
+ import {
915
+ composePromptFromState as composePromptFromState5,
916
+ ModelType as ModelType5,
917
+ parseJSONObjectFromText as parseJSONObjectFromText5
918
+ } from "@elizaos/core";
919
+ var pinMessageTemplate = `You are helping to extract pin message parameters for Slack.
920
+
921
+ The user wants to pin a message in a Slack channel.
922
+
923
+ Recent conversation:
924
+ {{recentMessages}}
925
+
926
+ Extract the following:
927
+ 1. messageTs: The message timestamp to pin (format: 1234567890.123456)
928
+ 2. channelId: The channel ID (optional, defaults to current channel)
929
+
930
+ Respond with a JSON object like:
931
+ {
932
+ "messageTs": "1234567890.123456",
933
+ "channelId": null
934
+ }
935
+
936
+ Only respond with the JSON object, no other text.`;
937
+ var pinMessage = {
938
+ name: "SLACK_PIN_MESSAGE",
939
+ similes: ["PIN_SLACK_MESSAGE", "PIN_MESSAGE", "SLACK_PIN", "SAVE_MESSAGE"],
940
+ description: "Pin a message in a Slack channel",
941
+ validate: async (_runtime, message, _state) => {
942
+ return message.content.source === "slack";
943
+ },
944
+ handler: async (runtime, message, state, _options, callback) => {
945
+ const slackService = runtime.getService(SLACK_SERVICE_NAME);
946
+ if (!slackService || !slackService.client) {
947
+ await callback?.({
948
+ text: "Slack service is not available.",
949
+ source: "slack"
950
+ });
951
+ return { success: false, error: "Slack service not available" };
952
+ }
953
+ const prompt = composePromptFromState5({
954
+ state,
955
+ template: pinMessageTemplate
956
+ });
957
+ let pinInfo = null;
958
+ for (let attempt = 0;attempt < 3; attempt++) {
959
+ const response2 = await runtime.useModel(ModelType5.TEXT_SMALL, {
960
+ prompt
961
+ });
962
+ const parsedResponse = parseJSONObjectFromText5(response2);
963
+ if (parsedResponse?.messageTs) {
964
+ pinInfo = {
965
+ messageTs: String(parsedResponse.messageTs),
966
+ channelId: parsedResponse.channelId ? String(parsedResponse.channelId) : null
967
+ };
968
+ break;
969
+ }
970
+ }
971
+ if (!pinInfo || !pinInfo.messageTs) {
972
+ runtime.logger.debug({ src: "plugin:slack:action:pin-message" }, "[SLACK_PIN_MESSAGE] Could not extract pin info");
973
+ await callback?.({
974
+ text: "I couldn't understand which message to pin. Please specify the message timestamp.",
975
+ source: "slack"
976
+ });
977
+ return { success: false, error: "Could not extract pin parameters" };
978
+ }
979
+ if (!isValidMessageTs(pinInfo.messageTs)) {
980
+ await callback?.({
981
+ text: "The message timestamp format is invalid. Please provide a valid Slack message timestamp.",
982
+ source: "slack"
983
+ });
984
+ return { success: false, error: "Invalid message timestamp" };
985
+ }
986
+ const stateData = state?.data;
987
+ const room = stateData?.room || await runtime.getRoom(message.roomId);
988
+ const channelId = pinInfo.channelId || room?.channelId;
989
+ if (!channelId) {
990
+ await callback?.({
991
+ text: "I couldn't determine the channel for pinning the message.",
992
+ source: "slack"
993
+ });
994
+ return { success: false, error: "Could not determine channel" };
995
+ }
996
+ await slackService.pinMessage(channelId, pinInfo.messageTs);
997
+ const response = {
998
+ text: "Message pinned successfully.",
999
+ source: message.content.source
1000
+ };
1001
+ runtime.logger.debug({
1002
+ src: "plugin:slack:action:pin-message",
1003
+ messageTs: pinInfo.messageTs,
1004
+ channelId
1005
+ }, "[SLACK_PIN_MESSAGE] Message pinned");
1006
+ await callback?.(response);
1007
+ return {
1008
+ success: true,
1009
+ data: {
1010
+ messageTs: pinInfo.messageTs,
1011
+ channelId
1012
+ }
1013
+ };
1014
+ },
1015
+ examples: [
1016
+ [
1017
+ {
1018
+ name: "{{user1}}",
1019
+ content: {
1020
+ text: "Pin that important announcement"
1021
+ }
1022
+ },
1023
+ {
1024
+ name: "{{agent}}",
1025
+ content: {
1026
+ text: "I'll pin that message to the channel.",
1027
+ actions: ["SLACK_PIN_MESSAGE"]
1028
+ }
1029
+ }
1030
+ ]
1031
+ ]
1032
+ };
1033
+ var pinMessage_default = pinMessage;
1034
+
1035
+ // src/actions/reactToMessage.ts
1036
+ import {
1037
+ composePromptFromState as composePromptFromState6,
1038
+ ModelType as ModelType6,
1039
+ parseJSONObjectFromText as parseJSONObjectFromText6
1040
+ } from "@elizaos/core";
1041
+ var reactToMessageTemplate = `You are helping to extract reaction parameters for Slack.
1042
+
1043
+ The user wants to add a reaction (emoji) to a Slack message.
1044
+
1045
+ Recent conversation:
1046
+ {{recentMessages}}
1047
+
1048
+ Extract the following:
1049
+ 1. emoji: The emoji name to react with (without colons, e.g., "thumbsup" not ":thumbsup:")
1050
+ 2. messageTs: The message timestamp to react to (format: 1234567890.123456)
1051
+ 3. channelId: The channel ID (optional, defaults to current channel)
1052
+ 4. remove: Whether to remove the reaction instead of adding it (default: false)
1053
+
1054
+ Respond with a JSON object like:
1055
+ {
1056
+ "emoji": "thumbsup",
1057
+ "messageTs": "1234567890.123456",
1058
+ "channelId": null,
1059
+ "remove": false
1060
+ }
1061
+
1062
+ Only respond with the JSON object, no other text.`;
1063
+ var reactToMessage = {
1064
+ name: "SLACK_REACT_TO_MESSAGE",
1065
+ similes: [
1066
+ "ADD_SLACK_REACTION",
1067
+ "REACT_SLACK",
1068
+ "SLACK_EMOJI",
1069
+ "ADD_EMOJI",
1070
+ "REMOVE_REACTION"
1071
+ ],
1072
+ description: "Add or remove an emoji reaction to a Slack message",
1073
+ validate: async (_runtime, message, _state) => {
1074
+ return message.content.source === "slack";
1075
+ },
1076
+ handler: async (runtime, message, state, _options, callback) => {
1077
+ const slackService = runtime.getService(SLACK_SERVICE_NAME);
1078
+ if (!slackService || !slackService.client) {
1079
+ await callback?.({
1080
+ text: "Slack service is not available.",
1081
+ source: "slack"
1082
+ });
1083
+ return { success: false, error: "Slack service not available" };
1084
+ }
1085
+ const prompt = composePromptFromState6({
1086
+ state,
1087
+ template: reactToMessageTemplate
1088
+ });
1089
+ let reactionInfo = null;
1090
+ for (let attempt = 0;attempt < 3; attempt++) {
1091
+ const response2 = await runtime.useModel(ModelType6.TEXT_SMALL, {
1092
+ prompt
1093
+ });
1094
+ const parsedResponse = parseJSONObjectFromText6(response2);
1095
+ if (parsedResponse?.emoji && parsedResponse?.messageTs) {
1096
+ reactionInfo = {
1097
+ emoji: String(parsedResponse.emoji),
1098
+ messageTs: String(parsedResponse.messageTs),
1099
+ channelId: parsedResponse.channelId ? String(parsedResponse.channelId) : null,
1100
+ remove: Boolean(parsedResponse.remove)
1101
+ };
1102
+ break;
1103
+ }
1104
+ }
1105
+ if (!reactionInfo || !reactionInfo.emoji || !reactionInfo.messageTs) {
1106
+ runtime.logger.debug({ src: "plugin:slack:action:react-to-message" }, "[SLACK_REACT_TO_MESSAGE] Could not extract reaction info");
1107
+ await callback?.({
1108
+ text: "I couldn't understand the reaction request. Please specify the emoji and message to react to.",
1109
+ source: "slack"
1110
+ });
1111
+ return { success: false, error: "Could not extract reaction parameters" };
1112
+ }
1113
+ if (!isValidMessageTs(reactionInfo.messageTs)) {
1114
+ await callback?.({
1115
+ text: "The message timestamp format is invalid. Please provide a valid Slack message timestamp.",
1116
+ source: "slack"
1117
+ });
1118
+ return { success: false, error: "Invalid message timestamp" };
1119
+ }
1120
+ const stateData = state?.data;
1121
+ const room = stateData?.room || await runtime.getRoom(message.roomId);
1122
+ const channelId = reactionInfo.channelId || room?.channelId;
1123
+ if (!channelId) {
1124
+ await callback?.({
1125
+ text: "I couldn't determine the channel for the reaction.",
1126
+ source: "slack"
1127
+ });
1128
+ return { success: false, error: "Could not determine channel" };
1129
+ }
1130
+ if (reactionInfo.remove) {
1131
+ await slackService.removeReaction(channelId, reactionInfo.messageTs, reactionInfo.emoji);
1132
+ } else {
1133
+ await slackService.sendReaction(channelId, reactionInfo.messageTs, reactionInfo.emoji);
1134
+ }
1135
+ const actionWord = reactionInfo.remove ? "removed" : "added";
1136
+ const response = {
1137
+ text: `Reaction :${reactionInfo.emoji}: ${actionWord} successfully.`,
1138
+ source: message.content.source
1139
+ };
1140
+ runtime.logger.debug({
1141
+ src: "plugin:slack:action:react-to-message",
1142
+ emoji: reactionInfo.emoji,
1143
+ messageTs: reactionInfo.messageTs,
1144
+ channelId,
1145
+ remove: reactionInfo.remove
1146
+ }, `[SLACK_REACT_TO_MESSAGE] Reaction ${actionWord}`);
1147
+ await callback?.(response);
1148
+ return {
1149
+ success: true,
1150
+ data: {
1151
+ emoji: reactionInfo.emoji,
1152
+ messageTs: reactionInfo.messageTs,
1153
+ channelId,
1154
+ action: reactionInfo.remove ? "removed" : "added"
1155
+ }
1156
+ };
1157
+ },
1158
+ examples: [
1159
+ [
1160
+ {
1161
+ name: "{{user1}}",
1162
+ content: {
1163
+ text: "React to the last message with a thumbs up"
1164
+ }
1165
+ },
1166
+ {
1167
+ name: "{{agent}}",
1168
+ content: {
1169
+ text: "I'll add a thumbs up reaction to that message.",
1170
+ actions: ["SLACK_REACT_TO_MESSAGE"]
1171
+ }
1172
+ }
1173
+ ],
1174
+ [
1175
+ {
1176
+ name: "{{user1}}",
1177
+ content: {
1178
+ text: "Add a :tada: emoji to that announcement"
1179
+ }
1180
+ },
1181
+ {
1182
+ name: "{{agent}}",
1183
+ content: {
1184
+ text: "Adding the tada emoji reaction now.",
1185
+ actions: ["SLACK_REACT_TO_MESSAGE"]
1186
+ }
1187
+ }
1188
+ ]
1189
+ ]
1190
+ };
1191
+ var reactToMessage_default = reactToMessage;
1192
+
1193
+ // src/actions/readChannel.ts
1194
+ import {
1195
+ composePromptFromState as composePromptFromState7,
1196
+ ModelType as ModelType7,
1197
+ parseJSONObjectFromText as parseJSONObjectFromText7
1198
+ } from "@elizaos/core";
1199
+ var readChannelTemplate = `You are helping to extract read channel parameters for Slack.
1200
+
1201
+ The user wants to read message history from a Slack channel.
1202
+
1203
+ Recent conversation:
1204
+ {{recentMessages}}
1205
+
1206
+ Extract the following:
1207
+ 1. channelRef: The channel to read from (default: "current" for the current channel, or a channel name/ID)
1208
+ 2. limit: Number of messages to retrieve (default: 10, max: 100)
1209
+ 3. before: Optional message timestamp to fetch messages before
1210
+ 4. after: Optional message timestamp to fetch messages after
1211
+
1212
+ Respond with a JSON object like:
1213
+ {
1214
+ "channelRef": "current",
1215
+ "limit": 10,
1216
+ "before": null,
1217
+ "after": null
1218
+ }
1219
+
1220
+ Only respond with the JSON object, no other text.`;
1221
+ var readChannel = {
1222
+ name: "SLACK_READ_CHANNEL",
1223
+ similes: [
1224
+ "READ_SLACK_MESSAGES",
1225
+ "GET_CHANNEL_HISTORY",
1226
+ "SLACK_HISTORY",
1227
+ "FETCH_MESSAGES",
1228
+ "LIST_MESSAGES"
1229
+ ],
1230
+ description: "Read message history from a Slack channel",
1231
+ validate: async (_runtime, message, _state) => {
1232
+ return message.content.source === "slack";
1233
+ },
1234
+ handler: async (runtime, message, state, _options, callback) => {
1235
+ const slackService = runtime.getService(SLACK_SERVICE_NAME);
1236
+ if (!slackService || !slackService.client) {
1237
+ await callback?.({
1238
+ text: "Slack service is not available.",
1239
+ source: "slack"
1240
+ });
1241
+ return { success: false, error: "Slack service not available" };
1242
+ }
1243
+ const prompt = composePromptFromState7({
1244
+ state,
1245
+ template: readChannelTemplate
1246
+ });
1247
+ let readInfo = null;
1248
+ for (let attempt = 0;attempt < 3; attempt++) {
1249
+ const response2 = await runtime.useModel(ModelType7.TEXT_SMALL, {
1250
+ prompt
1251
+ });
1252
+ const parsedResponse = parseJSONObjectFromText7(response2);
1253
+ if (parsedResponse) {
1254
+ readInfo = {
1255
+ channelRef: parsedResponse.channelRef ? String(parsedResponse.channelRef) : "current",
1256
+ limit: parsedResponse.limit ? Math.min(Number(parsedResponse.limit), 100) : 10,
1257
+ before: parsedResponse.before ? String(parsedResponse.before) : undefined,
1258
+ after: parsedResponse.after ? String(parsedResponse.after) : undefined
1259
+ };
1260
+ break;
1261
+ }
1262
+ }
1263
+ if (!readInfo) {
1264
+ readInfo = { channelRef: "current", limit: 10 };
1265
+ }
1266
+ const stateData = state?.data;
1267
+ const room = stateData?.room || await runtime.getRoom(message.roomId);
1268
+ if (!room || !room.channelId) {
1269
+ await callback?.({
1270
+ text: "I couldn't determine the current channel.",
1271
+ source: "slack"
1272
+ });
1273
+ return { success: false, error: "Could not determine channel" };
1274
+ }
1275
+ let targetChannelId = room.channelId;
1276
+ if (readInfo.channelRef && readInfo.channelRef !== "current") {
1277
+ const channels = await slackService.listChannels();
1278
+ const targetChannel = channels.find((ch) => {
1279
+ const channelName2 = ch.name?.toLowerCase() || "";
1280
+ const searchTerm = readInfo?.channelRef?.toLowerCase() || "";
1281
+ return channelName2 === searchTerm || channelName2 === searchTerm.replace(/^#/, "") || ch.id === readInfo?.channelRef;
1282
+ });
1283
+ if (targetChannel) {
1284
+ targetChannelId = targetChannel.id;
1285
+ }
1286
+ }
1287
+ const messages = await slackService.readHistory(targetChannelId, {
1288
+ limit: readInfo.limit,
1289
+ before: readInfo.before || undefined,
1290
+ after: readInfo.after || undefined
1291
+ });
1292
+ const formattedMessages = messages.map((msg) => {
1293
+ const timestamp = new Date(parseFloat(msg.ts) * 1000).toISOString();
1294
+ const user = msg.user || "unknown";
1295
+ const text = msg.text || "[no text]";
1296
+ return `[${timestamp}] ${user}: ${text}`;
1297
+ });
1298
+ const channelInfo = await slackService.getChannel(targetChannelId);
1299
+ const channelName = channelInfo?.name || targetChannelId;
1300
+ const response = {
1301
+ text: `Here are the last ${messages.length} messages from #${channelName}:
1302
+
1303
+ ${formattedMessages.join(`
1304
+ `)}`,
1305
+ source: message.content.source
1306
+ };
1307
+ runtime.logger.debug({
1308
+ src: "plugin:slack:action:read-channel",
1309
+ channelId: targetChannelId,
1310
+ messageCount: messages.length
1311
+ }, "[SLACK_READ_CHANNEL] Channel history retrieved");
1312
+ await callback?.(response);
1313
+ return {
1314
+ success: true,
1315
+ data: {
1316
+ channelId: targetChannelId,
1317
+ channelName,
1318
+ messageCount: messages.length,
1319
+ messages: messages.map((m) => ({
1320
+ ts: m.ts,
1321
+ user: m.user,
1322
+ text: m.text,
1323
+ threadTs: m.threadTs
1324
+ }))
1325
+ }
1326
+ };
1327
+ },
1328
+ examples: [
1329
+ [
1330
+ {
1331
+ name: "{{user1}}",
1332
+ content: {
1333
+ text: "Show me the last 5 messages in this channel"
1334
+ }
1335
+ },
1336
+ {
1337
+ name: "{{agent}}",
1338
+ content: {
1339
+ text: "I'll fetch the recent messages for you.",
1340
+ actions: ["SLACK_READ_CHANNEL"]
1341
+ }
1342
+ }
1343
+ ],
1344
+ [
1345
+ {
1346
+ name: "{{user1}}",
1347
+ content: {
1348
+ text: "What's been happening in #announcements?"
1349
+ }
1350
+ },
1351
+ {
1352
+ name: "{{agent}}",
1353
+ content: {
1354
+ text: "Let me check the recent messages in #announcements.",
1355
+ actions: ["SLACK_READ_CHANNEL"]
1356
+ }
1357
+ }
1358
+ ]
1359
+ ]
1360
+ };
1361
+ var readChannel_default = readChannel;
1362
+
1363
+ // src/actions/sendMessage.ts
1364
+ import {
1365
+ composePromptFromState as composePromptFromState8,
1366
+ ModelType as ModelType8,
1367
+ parseJSONObjectFromText as parseJSONObjectFromText8
1368
+ } from "@elizaos/core";
1369
+ var sendMessageTemplate = `You are helping to extract send message parameters for Slack.
1370
+
1371
+ The user wants to send a message to a Slack channel.
1372
+
1373
+ Recent conversation:
1374
+ {{recentMessages}}
1375
+
1376
+ Extract the following:
1377
+ 1. text: The message text to send
1378
+ 2. channelRef: The channel to send to (default: "current" for the current channel, or a channel name/ID)
1379
+ 3. threadTs: Optional thread timestamp to reply in a thread (default: null)
1380
+
1381
+ Respond with a JSON object like:
1382
+ {
1383
+ "text": "The message to send",
1384
+ "channelRef": "current",
1385
+ "threadTs": null
1386
+ }
1387
+
1388
+ Only respond with the JSON object, no other text.`;
1389
+ var sendMessage = {
1390
+ name: "SLACK_SEND_MESSAGE",
1391
+ similes: [
1392
+ "SEND_SLACK_MESSAGE",
1393
+ "POST_TO_SLACK",
1394
+ "MESSAGE_SLACK",
1395
+ "SLACK_POST",
1396
+ "SEND_TO_CHANNEL"
1397
+ ],
1398
+ description: "Send a message to a Slack channel or thread",
1399
+ validate: async (_runtime, message, _state) => {
1400
+ return message.content.source === "slack";
1401
+ },
1402
+ handler: async (runtime, message, state, _options, callback) => {
1403
+ const slackService = runtime.getService(SLACK_SERVICE_NAME);
1404
+ if (!slackService || !slackService.client) {
1405
+ await callback?.({
1406
+ text: "Slack service is not available.",
1407
+ source: "slack"
1408
+ });
1409
+ return { success: false, error: "Slack service not available" };
1410
+ }
1411
+ const prompt = composePromptFromState8({
1412
+ state,
1413
+ template: sendMessageTemplate
1414
+ });
1415
+ let messageInfo = null;
1416
+ for (let attempt = 0;attempt < 3; attempt++) {
1417
+ const response2 = await runtime.useModel(ModelType8.TEXT_SMALL, {
1418
+ prompt
1419
+ });
1420
+ const parsedResponse = parseJSONObjectFromText8(response2);
1421
+ if (parsedResponse?.text) {
1422
+ messageInfo = {
1423
+ text: String(parsedResponse.text),
1424
+ channelRef: parsedResponse.channelRef ? String(parsedResponse.channelRef) : "current",
1425
+ threadTs: parsedResponse.threadTs ? String(parsedResponse.threadTs) : undefined
1426
+ };
1427
+ break;
1428
+ }
1429
+ }
1430
+ if (!messageInfo || !messageInfo.text) {
1431
+ runtime.logger.debug({ src: "plugin:slack:action:send-message" }, "[SLACK_SEND_MESSAGE] Could not extract message info");
1432
+ await callback?.({
1433
+ text: "I couldn't understand what message you want me to send. Please try again with a clearer request.",
1434
+ source: "slack"
1435
+ });
1436
+ return { success: false, error: "Could not extract message parameters" };
1437
+ }
1438
+ const stateData = state?.data;
1439
+ const room = stateData?.room || await runtime.getRoom(message.roomId);
1440
+ if (!room || !room.channelId) {
1441
+ await callback?.({
1442
+ text: "I couldn't determine the current channel.",
1443
+ source: "slack"
1444
+ });
1445
+ return { success: false, error: "Could not determine channel" };
1446
+ }
1447
+ let targetChannelId = room.channelId;
1448
+ if (messageInfo.channelRef && messageInfo.channelRef !== "current") {
1449
+ const channels = await slackService.listChannels();
1450
+ const targetChannel = channels.find((ch) => {
1451
+ const channelName = ch.name?.toLowerCase() || "";
1452
+ const searchTerm = messageInfo?.channelRef?.toLowerCase() || "";
1453
+ return channelName === searchTerm || channelName === searchTerm.replace(/^#/, "") || ch.id === messageInfo?.channelRef;
1454
+ });
1455
+ if (targetChannel) {
1456
+ targetChannelId = targetChannel.id;
1457
+ }
1458
+ }
1459
+ const result = await slackService.sendMessage(targetChannelId, messageInfo.text, {
1460
+ threadTs: messageInfo.threadTs || undefined,
1461
+ replyBroadcast: undefined,
1462
+ unfurlLinks: undefined,
1463
+ unfurlMedia: undefined,
1464
+ mrkdwn: undefined,
1465
+ attachments: undefined,
1466
+ blocks: undefined
1467
+ });
1468
+ const response = {
1469
+ text: "Message sent successfully.",
1470
+ source: message.content.source
1471
+ };
1472
+ runtime.logger.debug({
1473
+ src: "plugin:slack:action:send-message",
1474
+ messageTs: result.ts,
1475
+ channelId: targetChannelId
1476
+ }, "[SLACK_SEND_MESSAGE] Message sent successfully");
1477
+ await callback?.(response);
1478
+ return {
1479
+ success: true,
1480
+ data: {
1481
+ messageTs: result.ts,
1482
+ channelId: targetChannelId
1483
+ }
1484
+ };
1485
+ },
1486
+ examples: [
1487
+ [
1488
+ {
1489
+ name: "{{user1}}",
1490
+ content: {
1491
+ text: "Send a message to #general saying 'Hello everyone!'"
1492
+ }
1493
+ },
1494
+ {
1495
+ name: "{{agent}}",
1496
+ content: {
1497
+ text: "I'll send that message to #general for you.",
1498
+ actions: ["SLACK_SEND_MESSAGE"]
1499
+ }
1500
+ }
1501
+ ],
1502
+ [
1503
+ {
1504
+ name: "{{user1}}",
1505
+ content: {
1506
+ text: "Post 'Meeting starts in 5 minutes' to this channel"
1507
+ }
1508
+ },
1509
+ {
1510
+ name: "{{agent}}",
1511
+ content: {
1512
+ text: "I'll post that announcement here.",
1513
+ actions: ["SLACK_SEND_MESSAGE"]
1514
+ }
1515
+ }
1516
+ ]
1517
+ ]
1518
+ };
1519
+ var sendMessage_default = sendMessage;
1520
+
1521
+ // src/actions/unpinMessage.ts
1522
+ import {
1523
+ composePromptFromState as composePromptFromState9,
1524
+ ModelType as ModelType9,
1525
+ parseJSONObjectFromText as parseJSONObjectFromText9
1526
+ } from "@elizaos/core";
1527
+ var unpinMessageTemplate = `You are helping to extract unpin message parameters for Slack.
1528
+
1529
+ The user wants to unpin a message from a Slack channel.
1530
+
1531
+ Recent conversation:
1532
+ {{recentMessages}}
1533
+
1534
+ Extract the following:
1535
+ 1. messageTs: The message timestamp to unpin (format: 1234567890.123456)
1536
+ 2. channelId: The channel ID (optional, defaults to current channel)
1537
+
1538
+ Respond with a JSON object like:
1539
+ {
1540
+ "messageTs": "1234567890.123456",
1541
+ "channelId": null
1542
+ }
1543
+
1544
+ Only respond with the JSON object, no other text.`;
1545
+ var unpinMessage = {
1546
+ name: "SLACK_UNPIN_MESSAGE",
1547
+ similes: [
1548
+ "UNPIN_SLACK_MESSAGE",
1549
+ "UNPIN_MESSAGE",
1550
+ "SLACK_UNPIN",
1551
+ "REMOVE_PIN"
1552
+ ],
1553
+ description: "Unpin a message from a Slack channel",
1554
+ validate: async (_runtime, message, _state) => {
1555
+ return message.content.source === "slack";
1556
+ },
1557
+ handler: async (runtime, message, state, _options, callback) => {
1558
+ const slackService = runtime.getService(SLACK_SERVICE_NAME);
1559
+ if (!slackService || !slackService.client) {
1560
+ await callback?.({
1561
+ text: "Slack service is not available.",
1562
+ source: "slack"
1563
+ });
1564
+ return { success: false, error: "Slack service not available" };
1565
+ }
1566
+ const prompt = composePromptFromState9({
1567
+ state,
1568
+ template: unpinMessageTemplate
1569
+ });
1570
+ let unpinInfo = null;
1571
+ for (let attempt = 0;attempt < 3; attempt++) {
1572
+ const response2 = await runtime.useModel(ModelType9.TEXT_SMALL, {
1573
+ prompt
1574
+ });
1575
+ const parsedResponse = parseJSONObjectFromText9(response2);
1576
+ if (parsedResponse?.messageTs) {
1577
+ unpinInfo = {
1578
+ messageTs: String(parsedResponse.messageTs),
1579
+ channelId: parsedResponse.channelId ? String(parsedResponse.channelId) : null
1580
+ };
1581
+ break;
1582
+ }
1583
+ }
1584
+ if (!unpinInfo || !unpinInfo.messageTs) {
1585
+ runtime.logger.debug({ src: "plugin:slack:action:unpin-message" }, "[SLACK_UNPIN_MESSAGE] Could not extract unpin info");
1586
+ await callback?.({
1587
+ text: "I couldn't understand which message to unpin. Please specify the message timestamp.",
1588
+ source: "slack"
1589
+ });
1590
+ return { success: false, error: "Could not extract unpin parameters" };
1591
+ }
1592
+ if (!isValidMessageTs(unpinInfo.messageTs)) {
1593
+ await callback?.({
1594
+ text: "The message timestamp format is invalid. Please provide a valid Slack message timestamp.",
1595
+ source: "slack"
1596
+ });
1597
+ return { success: false, error: "Invalid message timestamp" };
1598
+ }
1599
+ const stateData = state?.data;
1600
+ const room = stateData?.room || await runtime.getRoom(message.roomId);
1601
+ const channelId = unpinInfo.channelId || room?.channelId;
1602
+ if (!channelId) {
1603
+ await callback?.({
1604
+ text: "I couldn't determine the channel for unpinning the message.",
1605
+ source: "slack"
1606
+ });
1607
+ return { success: false, error: "Could not determine channel" };
1608
+ }
1609
+ await slackService.unpinMessage(channelId, unpinInfo.messageTs);
1610
+ const response = {
1611
+ text: "Message unpinned successfully.",
1612
+ source: message.content.source
1613
+ };
1614
+ runtime.logger.debug({
1615
+ src: "plugin:slack:action:unpin-message",
1616
+ messageTs: unpinInfo.messageTs,
1617
+ channelId
1618
+ }, "[SLACK_UNPIN_MESSAGE] Message unpinned");
1619
+ await callback?.(response);
1620
+ return {
1621
+ success: true,
1622
+ data: {
1623
+ messageTs: unpinInfo.messageTs,
1624
+ channelId
1625
+ }
1626
+ };
1627
+ },
1628
+ examples: [
1629
+ [
1630
+ {
1631
+ name: "{{user1}}",
1632
+ content: {
1633
+ text: "Unpin that old announcement"
1634
+ }
1635
+ },
1636
+ {
1637
+ name: "{{agent}}",
1638
+ content: {
1639
+ text: "I'll remove the pin from that message.",
1640
+ actions: ["SLACK_UNPIN_MESSAGE"]
1641
+ }
1642
+ }
1643
+ ]
1644
+ ]
1645
+ };
1646
+ var unpinMessage_default = unpinMessage;
1647
+
1648
+ // src/providers/channelState.ts
1649
+ var channelStateProvider = {
1650
+ name: "slackChannelState",
1651
+ description: "Provides information about the current Slack channel context",
1652
+ get: async (runtime, message, state) => {
1653
+ const room = state.data?.room ?? await runtime.getRoom(message.roomId);
1654
+ if (!room) {
1655
+ return {
1656
+ data: {},
1657
+ values: {},
1658
+ text: ""
1659
+ };
1660
+ }
1661
+ if (message.content.source !== "slack") {
1662
+ return {
1663
+ data: {},
1664
+ values: {},
1665
+ text: ""
1666
+ };
1667
+ }
1668
+ const agentName = state?.agentName || "The agent";
1669
+ const senderName = state?.senderName || "someone";
1670
+ let responseText = "";
1671
+ let channelType = "";
1672
+ let workspaceName = "";
1673
+ let channelName = "";
1674
+ const channelId = room.channelId ?? "";
1675
+ const threadTs = room.metadata?.threadTs;
1676
+ const slackService = runtime.getService(ServiceType.SLACK);
1677
+ if (!slackService || !slackService.client) {
1678
+ runtime.logger.warn({
1679
+ src: "plugin:slack:provider:channelState",
1680
+ agentId: runtime.agentId,
1681
+ channelId
1682
+ }, "No Slack client found");
1683
+ return {
1684
+ data: {
1685
+ room,
1686
+ channelType: "unknown",
1687
+ channelId
1688
+ },
1689
+ values: {
1690
+ channelType: "unknown",
1691
+ channelId
1692
+ },
1693
+ text: ""
1694
+ };
1695
+ }
1696
+ const channel = channelId ? await slackService.getChannel(channelId) : null;
1697
+ if (channel) {
1698
+ channelName = channel.name;
1699
+ const slackChannelType = getSlackChannelType(channel);
1700
+ if (slackChannelType === "im") {
1701
+ channelType = "DM";
1702
+ responseText = `${agentName} is currently in a direct message conversation with ${senderName} on Slack. ${agentName} should engage in conversation, responding to messages that are addressed to them.`;
1703
+ } else if (slackChannelType === "mpim") {
1704
+ channelType = "GROUP_DM";
1705
+ responseText = `${agentName} is currently in a group direct message on Slack. ${agentName} should be aware that multiple people can see this conversation.`;
1706
+ } else {
1707
+ channelType = slackChannelType === "group" ? "PRIVATE_CHANNEL" : "PUBLIC_CHANNEL";
1708
+ if (threadTs) {
1709
+ responseText = `${agentName} is currently in a thread within the channel #${channelName} on Slack.`;
1710
+ responseText += `
1711
+ ${agentName} should keep responses focused on the thread topic and be mindful of thread etiquette.`;
1712
+ } else {
1713
+ responseText = `${agentName} is currently having a conversation in the Slack channel #${channelName}.`;
1714
+ responseText += `
1715
+ ${agentName} is in a channel with other users and should only participate when directly addressed or when the conversation is relevant to them.`;
1716
+ }
1717
+ if (channel.topic?.value) {
1718
+ responseText += `
1719
+ Channel topic: ${channel.topic.value}`;
1720
+ }
1721
+ if (channel.purpose?.value) {
1722
+ responseText += `
1723
+ Channel purpose: ${channel.purpose.value}`;
1724
+ }
1725
+ }
1726
+ } else {
1727
+ channelType = "unknown";
1728
+ responseText = `${agentName} is in a Slack conversation but couldn't retrieve channel details.`;
1729
+ }
1730
+ const teamId = slackService.getTeamId();
1731
+ if (teamId && room.worldId) {
1732
+ const world = await runtime.getWorld(room.worldId);
1733
+ if (world) {
1734
+ workspaceName = world.name;
1735
+ responseText += `
1736
+ Workspace: ${workspaceName}`;
1737
+ }
1738
+ }
1739
+ if (threadTs) {
1740
+ responseText += `
1741
+ This is a threaded conversation (thread timestamp: ${threadTs}).`;
1742
+ }
1743
+ return {
1744
+ data: {
1745
+ room,
1746
+ channelType,
1747
+ workspaceName,
1748
+ channelName,
1749
+ channelId,
1750
+ threadTs,
1751
+ isThread: Boolean(threadTs),
1752
+ topic: channel?.topic?.value,
1753
+ purpose: channel?.purpose?.value,
1754
+ isPrivate: channel?.isPrivate,
1755
+ numMembers: channel?.numMembers
1756
+ },
1757
+ values: {
1758
+ channelType,
1759
+ workspaceName,
1760
+ channelName,
1761
+ channelId,
1762
+ isThread: Boolean(threadTs)
1763
+ },
1764
+ text: responseText
1765
+ };
1766
+ }
1767
+ };
1768
+
1769
+ // src/providers/memberList.ts
1770
+ var memberListProvider = {
1771
+ name: "slackMemberList",
1772
+ description: "Provides information about members in the current Slack channel",
1773
+ get: async (runtime, message, state) => {
1774
+ if (message.content.source !== "slack") {
1775
+ return {
1776
+ data: {},
1777
+ values: {},
1778
+ text: ""
1779
+ };
1780
+ }
1781
+ const room = state.data?.room ?? await runtime.getRoom(message.roomId);
1782
+ if (!room || !room.channelId) {
1783
+ return {
1784
+ data: {},
1785
+ values: {},
1786
+ text: ""
1787
+ };
1788
+ }
1789
+ const slackService = runtime.getService(ServiceType.SLACK);
1790
+ if (!slackService || !slackService.client) {
1791
+ return {
1792
+ data: {},
1793
+ values: {},
1794
+ text: ""
1795
+ };
1796
+ }
1797
+ const channelId = room.channelId;
1798
+ const membersResult = await slackService.client.conversations.members({
1799
+ channel: channelId,
1800
+ limit: 100
1801
+ });
1802
+ const memberIds = membersResult.members || [];
1803
+ if (memberIds.length === 0) {
1804
+ return {
1805
+ data: {
1806
+ channelId,
1807
+ memberCount: 0,
1808
+ members: []
1809
+ },
1810
+ values: {
1811
+ memberCount: 0
1812
+ },
1813
+ text: "No members found in this channel."
1814
+ };
1815
+ }
1816
+ const memberLimit = 20;
1817
+ const limitedMemberIds = memberIds.slice(0, memberLimit);
1818
+ const members = [];
1819
+ for (const memberId of limitedMemberIds) {
1820
+ const user = await slackService.getUser(memberId);
1821
+ if (user) {
1822
+ members.push({
1823
+ id: user.id,
1824
+ name: user.name,
1825
+ displayName: getSlackUserDisplayName(user),
1826
+ isBot: user.isBot,
1827
+ isAdmin: user.isAdmin || user.isOwner
1828
+ });
1829
+ }
1830
+ }
1831
+ const channel = await slackService.getChannel(channelId);
1832
+ const channelName = channel?.name || channelId;
1833
+ const botUserId = slackService.getBotUserId();
1834
+ const memberDescriptions = members.map((m) => {
1835
+ const tags = [];
1836
+ if (m.id === botUserId)
1837
+ tags.push("this bot");
1838
+ if (m.isBot && m.id !== botUserId)
1839
+ tags.push("bot");
1840
+ if (m.isAdmin)
1841
+ tags.push("admin");
1842
+ const tagStr = tags.length > 0 ? ` (${tags.join(", ")})` : "";
1843
+ return `- ${m.displayName} (@${m.name})${tagStr}`;
1844
+ });
1845
+ const truncationNote = memberIds.length > memberLimit ? `
1846
+
1847
+ (Showing ${memberLimit} of ${memberIds.length} total members)` : "";
1848
+ const responseText = `Members in #${channelName}:
1849
+ ${memberDescriptions.join(`
1850
+ `)}${truncationNote}`;
1851
+ return {
1852
+ data: {
1853
+ channelId,
1854
+ channelName,
1855
+ memberCount: memberIds.length,
1856
+ members,
1857
+ hasMoreMembers: memberIds.length > memberLimit
1858
+ },
1859
+ values: {
1860
+ channelId,
1861
+ channelName,
1862
+ memberCount: memberIds.length
1863
+ },
1864
+ text: responseText
1865
+ };
1866
+ }
1867
+ };
1868
+
1869
+ // src/providers/workspaceInfo.ts
1870
+ var workspaceInfoProvider = {
1871
+ name: "slackWorkspaceInfo",
1872
+ description: "Provides information about the Slack workspace",
1873
+ get: async (runtime, message, state) => {
1874
+ if (message.content.source !== "slack") {
1875
+ return {
1876
+ data: {},
1877
+ values: {},
1878
+ text: ""
1879
+ };
1880
+ }
1881
+ const slackService = runtime.getService(ServiceType.SLACK);
1882
+ if (!slackService || !slackService.client) {
1883
+ return {
1884
+ data: {},
1885
+ values: {},
1886
+ text: ""
1887
+ };
1888
+ }
1889
+ const teamId = slackService.getTeamId();
1890
+ const botUserId = slackService.getBotUserId();
1891
+ const isConnected = slackService.isServiceConnected();
1892
+ let workspaceName = "";
1893
+ let domain = "";
1894
+ const room = state.data?.room ?? await runtime.getRoom(message.roomId);
1895
+ if (room?.worldId) {
1896
+ const world = await runtime.getWorld(room.worldId);
1897
+ if (world) {
1898
+ workspaceName = world.name;
1899
+ const worldMetadata = world.metadata;
1900
+ domain = worldMetadata?.domain || "";
1901
+ }
1902
+ }
1903
+ const channels = await slackService.listChannels({
1904
+ types: "public_channel,private_channel"
1905
+ });
1906
+ const publicChannels = channels.filter((ch) => !ch.isPrivate && !ch.isArchived);
1907
+ const privateChannels = channels.filter((ch) => ch.isPrivate && !ch.isArchived);
1908
+ const memberChannels = channels.filter((ch) => ch.isMember && !ch.isArchived);
1909
+ const allowedChannelIds = slackService.getAllowedChannelIds();
1910
+ const hasChannelRestrictions = allowedChannelIds.length > 0;
1911
+ const agentName = state?.agentName || "The agent";
1912
+ let responseText = `${agentName} is connected to the Slack workspace`;
1913
+ if (workspaceName) {
1914
+ responseText += ` "${workspaceName}"`;
1915
+ }
1916
+ if (domain) {
1917
+ responseText += ` (${domain}.slack.com)`;
1918
+ }
1919
+ responseText += ".";
1920
+ responseText += `
1921
+
1922
+ Workspace statistics:`;
1923
+ responseText += `
1924
+ - Public channels: ${publicChannels.length}`;
1925
+ responseText += `
1926
+ - Private channels: ${privateChannels.length}`;
1927
+ responseText += `
1928
+ - Channels the bot is a member of: ${memberChannels.length}`;
1929
+ if (hasChannelRestrictions) {
1930
+ responseText += `
1931
+
1932
+ Note: The bot is restricted to ${allowedChannelIds.length} specific channel(s).`;
1933
+ }
1934
+ return {
1935
+ data: {
1936
+ teamId,
1937
+ botUserId,
1938
+ workspaceName,
1939
+ domain,
1940
+ isConnected,
1941
+ publicChannelCount: publicChannels.length,
1942
+ privateChannelCount: privateChannels.length,
1943
+ memberChannelCount: memberChannels.length,
1944
+ hasChannelRestrictions,
1945
+ allowedChannelIds
1946
+ },
1947
+ values: {
1948
+ teamId: teamId || "",
1949
+ botUserId: botUserId || "",
1950
+ workspaceName,
1951
+ domain,
1952
+ isConnected,
1953
+ publicChannelCount: publicChannels.length,
1954
+ privateChannelCount: privateChannels.length,
1955
+ memberChannelCount: memberChannels.length
1956
+ },
1957
+ text: responseText
1958
+ };
1959
+ }
1960
+ };
1961
+
1962
+ // src/service.ts
1963
+ import {
1964
+ ChannelType,
1965
+ createUniqueUuid,
1966
+ Service,
1967
+ stringToUuid
1968
+ } from "@elizaos/core";
1969
+ import { App, LogLevel } from "@slack/bolt";
1970
+ var getMessageService = (runtime) => {
1971
+ if ("messageService" in runtime) {
1972
+ const withMessageService = runtime;
1973
+ return withMessageService.messageService ?? null;
1974
+ }
1975
+ return null;
1976
+ };
1977
+
1978
+ class SlackService extends Service {
1979
+ static serviceType = SLACK_SERVICE_NAME;
1980
+ capabilityDescription = "The agent is able to send and receive messages on Slack";
1981
+ app = null;
1982
+ client = null;
1983
+ character;
1984
+ botUserId = null;
1985
+ teamId = null;
1986
+ settings;
1987
+ botToken = null;
1988
+ appToken = null;
1989
+ signingSecret = null;
1990
+ allowedChannelIds = new Set;
1991
+ dynamicChannelIds = new Set;
1992
+ userCache = new Map;
1993
+ channelCache = new Map;
1994
+ isStarting = false;
1995
+ isConnected = false;
1996
+ constructor(runtime) {
1997
+ super(runtime);
1998
+ this.character = runtime.character;
1999
+ this.settings = this.loadSettings();
2000
+ const channelIdsRaw = runtime.getSetting("SLACK_CHANNEL_IDS");
2001
+ if (channelIdsRaw?.trim()) {
2002
+ channelIdsRaw.split(",").map((s) => s.trim()).filter((s) => s.length > 0 && isValidChannelId(s)).forEach((id) => this.allowedChannelIds.add(id));
2003
+ this.runtime.logger.debug({
2004
+ src: "plugin:slack",
2005
+ agentId: this.runtime.agentId,
2006
+ allowedChannelIds: Array.from(this.allowedChannelIds)
2007
+ }, "Channel restrictions enabled");
2008
+ }
2009
+ }
2010
+ loadSettings() {
2011
+ const ignoreBotMessages = this.runtime.getSetting("SLACK_SHOULD_IGNORE_BOT_MESSAGES");
2012
+ const respondOnlyToMentions = this.runtime.getSetting("SLACK_SHOULD_RESPOND_ONLY_TO_MENTIONS");
2013
+ return {
2014
+ allowedChannelIds: undefined,
2015
+ shouldIgnoreBotMessages: ignoreBotMessages === "true" || ignoreBotMessages === true,
2016
+ shouldRespondOnlyToMentions: respondOnlyToMentions === "true" || respondOnlyToMentions === true
2017
+ };
2018
+ }
2019
+ static async start(runtime) {
2020
+ const service = new SlackService(runtime);
2021
+ const botToken = runtime.getSetting("SLACK_BOT_TOKEN");
2022
+ const appToken = runtime.getSetting("SLACK_APP_TOKEN");
2023
+ const signingSecret = runtime.getSetting("SLACK_SIGNING_SECRET");
2024
+ const userToken = runtime.getSetting("SLACK_USER_TOKEN");
2025
+ if (!botToken || !botToken.trim()) {
2026
+ runtime.logger.warn({ src: "plugin:slack", agentId: runtime.agentId }, "SLACK_BOT_TOKEN not provided, Slack service will not start");
2027
+ return service;
2028
+ }
2029
+ if (!appToken || !appToken.trim()) {
2030
+ runtime.logger.warn({ src: "plugin:slack", agentId: runtime.agentId }, "SLACK_APP_TOKEN not provided, Socket Mode will not work");
2031
+ return service;
2032
+ }
2033
+ service.botToken = botToken;
2034
+ service.appToken = appToken;
2035
+ service.signingSecret = signingSecret || undefined;
2036
+ service.userToken = userToken || undefined;
2037
+ await service.initialize();
2038
+ return service;
2039
+ }
2040
+ static async stop(runtime) {
2041
+ const service = runtime.getService(SLACK_SERVICE_NAME);
2042
+ if (service) {
2043
+ await service.shutdown();
2044
+ }
2045
+ }
2046
+ async stop() {
2047
+ await this.shutdown();
2048
+ }
2049
+ async initialize() {
2050
+ if (this.isStarting || this.isConnected) {
2051
+ return;
2052
+ }
2053
+ this.isStarting = true;
2054
+ this.runtime.logger.info({ src: "plugin:slack", agentId: this.runtime.agentId }, "Initializing Slack service with Socket Mode");
2055
+ this.app = new App({
2056
+ token: this.botToken,
2057
+ appToken: this.appToken,
2058
+ socketMode: true,
2059
+ logLevel: LogLevel.INFO,
2060
+ ...this.signingSecret ? { signingSecret: this.signingSecret } : {}
2061
+ });
2062
+ this.client = this.app.client;
2063
+ const authResult = await this.client.auth.test();
2064
+ this.botUserId = authResult.user_id;
2065
+ this.teamId = authResult.team_id;
2066
+ this.runtime.logger.info({
2067
+ src: "plugin:slack",
2068
+ agentId: this.runtime.agentId,
2069
+ botUserId: this.botUserId,
2070
+ teamId: this.teamId
2071
+ }, "Slack bot authenticated");
2072
+ this.registerEventHandlers();
2073
+ await this.app.start();
2074
+ this.isConnected = true;
2075
+ this.isStarting = false;
2076
+ this.runtime.logger.info({ src: "plugin:slack", agentId: this.runtime.agentId }, "Slack service started successfully");
2077
+ await this.ensureWorkspaceExists();
2078
+ }
2079
+ async shutdown() {
2080
+ if (this.app) {
2081
+ await this.app.stop();
2082
+ this.app = null;
2083
+ this.client = null;
2084
+ this.isConnected = false;
2085
+ this.runtime.logger.info({ src: "plugin:slack", agentId: this.runtime.agentId }, "Slack service stopped");
2086
+ }
2087
+ }
2088
+ registerEventHandlers() {
2089
+ if (!this.app)
2090
+ return;
2091
+ this.app.message(async ({ message, client }) => {
2092
+ await this.handleMessage(message, client);
2093
+ });
2094
+ this.app.event("app_mention", async ({ event, client }) => {
2095
+ await this.handleAppMention(event, client);
2096
+ });
2097
+ this.app.event("reaction_added", async ({ event }) => {
2098
+ await this.handleReactionAdded(event);
2099
+ });
2100
+ this.app.event("reaction_removed", async ({ event }) => {
2101
+ await this.handleReactionRemoved(event);
2102
+ });
2103
+ this.app.event("member_joined_channel", async ({ event }) => {
2104
+ await this.handleMemberJoinedChannel(event);
2105
+ });
2106
+ this.app.event("member_left_channel", async ({ event }) => {
2107
+ await this.handleMemberLeftChannel(event);
2108
+ });
2109
+ this.app.event("file_shared", async ({ event }) => {
2110
+ await this.handleFileShared(event);
2111
+ });
2112
+ }
2113
+ async handleMessage(message, _client) {
2114
+ if (this.settings.shouldIgnoreBotMessages && message.bot_id) {
2115
+ return;
2116
+ }
2117
+ if (message.user === this.botUserId) {
2118
+ return;
2119
+ }
2120
+ if (!this.isChannelAllowed(message.channel)) {
2121
+ this.runtime.logger.debug({
2122
+ src: "plugin:slack",
2123
+ agentId: this.runtime.agentId,
2124
+ channelId: message.channel
2125
+ }, "Message received in non-allowed channel, ignoring");
2126
+ return;
2127
+ }
2128
+ const isMentioned = message.text?.includes(`<@${this.botUserId}>`);
2129
+ if (this.settings.shouldRespondOnlyToMentions && !isMentioned) {
2130
+ return;
2131
+ }
2132
+ const _isThreadReply = Boolean(message.thread_ts && message.thread_ts !== message.ts);
2133
+ const memory = await this.buildMemoryFromMessage(message);
2134
+ if (!memory)
2135
+ return;
2136
+ const room = await this.ensureRoomExists(message.channel, message.thread_ts);
2137
+ await this.runtime.createMemory(memory, "messages");
2138
+ await this.runtime.emitEvent("SLACK_MESSAGE_RECEIVED" /* MESSAGE_RECEIVED */, {
2139
+ runtime: this.runtime,
2140
+ source: "slack"
2141
+ });
2142
+ await this.processAgentMessage(memory, room, message.channel, message.thread_ts || message.ts);
2143
+ }
2144
+ async handleAppMention(event, _client) {
2145
+ if (!event.user)
2146
+ return;
2147
+ const memory = await this.buildMemoryFromMention({
2148
+ user: event.user,
2149
+ text: event.text,
2150
+ channel: event.channel,
2151
+ ts: event.ts,
2152
+ thread_ts: event.thread_ts
2153
+ });
2154
+ if (!memory)
2155
+ return;
2156
+ const room = await this.ensureRoomExists(event.channel, event.thread_ts);
2157
+ await this.runtime.createMemory(memory, "messages");
2158
+ await this.runtime.emitEvent("SLACK_APP_MENTION" /* APP_MENTION */, {
2159
+ runtime: this.runtime,
2160
+ source: "slack"
2161
+ });
2162
+ await this.processAgentMessage(memory, room, event.channel, event.thread_ts || event.ts);
2163
+ }
2164
+ async handleReactionAdded(_event) {
2165
+ await this.runtime.emitEvent("SLACK_REACTION_ADDED" /* REACTION_ADDED */, {
2166
+ runtime: this.runtime,
2167
+ source: "slack"
2168
+ });
2169
+ }
2170
+ async handleReactionRemoved(_event) {
2171
+ await this.runtime.emitEvent("SLACK_REACTION_REMOVED" /* REACTION_REMOVED */, {
2172
+ runtime: this.runtime,
2173
+ source: "slack"
2174
+ });
2175
+ }
2176
+ async handleMemberJoinedChannel(event) {
2177
+ if (event.user === this.botUserId) {
2178
+ this.dynamicChannelIds.add(event.channel);
2179
+ await this.ensureRoomExists(event.channel);
2180
+ }
2181
+ await this.runtime.emitEvent("SLACK_MEMBER_JOINED_CHANNEL" /* MEMBER_JOINED_CHANNEL */, {
2182
+ runtime: this.runtime,
2183
+ source: "slack"
2184
+ });
2185
+ }
2186
+ async handleMemberLeftChannel(event) {
2187
+ if (event.user === this.botUserId) {
2188
+ this.dynamicChannelIds.delete(event.channel);
2189
+ }
2190
+ await this.runtime.emitEvent("SLACK_MEMBER_LEFT_CHANNEL" /* MEMBER_LEFT_CHANNEL */, {
2191
+ runtime: this.runtime,
2192
+ source: "slack"
2193
+ });
2194
+ }
2195
+ async handleFileShared(_event) {
2196
+ await this.runtime.emitEvent("SLACK_FILE_SHARED" /* FILE_SHARED */, {
2197
+ runtime: this.runtime,
2198
+ source: "slack"
2199
+ });
2200
+ }
2201
+ isChannelAllowed(channelId) {
2202
+ if (this.allowedChannelIds.size === 0 && this.dynamicChannelIds.size === 0) {
2203
+ return true;
2204
+ }
2205
+ return this.allowedChannelIds.has(channelId) || this.dynamicChannelIds.has(channelId);
2206
+ }
2207
+ async processAgentMessage(memory, room, channelId, threadTs) {
2208
+ const callback = async (response) => {
2209
+ await this.sendMessage(channelId, response.text || "", {
2210
+ threadTs,
2211
+ replyBroadcast: undefined,
2212
+ unfurlLinks: undefined,
2213
+ unfurlMedia: undefined,
2214
+ mrkdwn: undefined,
2215
+ attachments: undefined,
2216
+ blocks: undefined
2217
+ });
2218
+ const responseMemory = {
2219
+ id: createUniqueUuid(this.runtime, `slack-response-${Date.now()}`),
2220
+ agentId: this.runtime.agentId,
2221
+ roomId: room.id,
2222
+ entityId: this.runtime.agentId,
2223
+ content: {
2224
+ text: response.text || "",
2225
+ source: "slack",
2226
+ inReplyTo: memory.id
2227
+ },
2228
+ createdAt: Date.now()
2229
+ };
2230
+ await this.runtime.createMemory(responseMemory, "messages");
2231
+ await this.runtime.emitEvent("SLACK_MESSAGE_SENT" /* MESSAGE_SENT */, {
2232
+ runtime: this.runtime,
2233
+ source: "slack"
2234
+ });
2235
+ return [responseMemory];
2236
+ };
2237
+ const messageService = getMessageService(this.runtime);
2238
+ if (messageService) {
2239
+ await messageService.handleMessage(this.runtime, memory, callback);
2240
+ }
2241
+ }
2242
+ async buildMemoryFromMessage(message) {
2243
+ if (!message.user)
2244
+ return null;
2245
+ const roomId = await this.getRoomId(message.channel, message.thread_ts);
2246
+ const entityId = this.getEntityId(message.user);
2247
+ const user = await this.getUser(message.user);
2248
+ const displayName = user ? getSlackUserDisplayName(user) : message.user;
2249
+ const media = [];
2250
+ if ("files" in message && message.files) {
2251
+ for (const file of message.files) {
2252
+ media.push({
2253
+ id: file.id,
2254
+ url: file.urlPrivate,
2255
+ title: file.title || file.name,
2256
+ source: "slack",
2257
+ description: file.name
2258
+ });
2259
+ }
2260
+ }
2261
+ const memory = {
2262
+ id: createUniqueUuid(this.runtime, `slack-${message.ts}`),
2263
+ agentId: this.runtime.agentId,
2264
+ roomId,
2265
+ entityId,
2266
+ content: {
2267
+ text: message.text || "",
2268
+ source: "slack",
2269
+ name: displayName,
2270
+ ...media.length > 0 ? { attachments: media } : {}
2271
+ },
2272
+ createdAt: this.parseSlackTimestamp(message.ts)
2273
+ };
2274
+ return memory;
2275
+ }
2276
+ async buildMemoryFromMention(event) {
2277
+ const roomId = await this.getRoomId(event.channel, event.thread_ts);
2278
+ const entityId = this.getEntityId(event.user);
2279
+ const user = await this.getUser(event.user);
2280
+ const displayName = user ? getSlackUserDisplayName(user) : event.user;
2281
+ const cleanText = event.text.replace(`<@${this.botUserId}>`, "").trim();
2282
+ const memory = {
2283
+ id: createUniqueUuid(this.runtime, `slack-mention-${event.ts}`),
2284
+ agentId: this.runtime.agentId,
2285
+ roomId,
2286
+ entityId,
2287
+ content: {
2288
+ text: cleanText,
2289
+ source: "slack",
2290
+ name: displayName
2291
+ },
2292
+ createdAt: this.parseSlackTimestamp(event.ts)
2293
+ };
2294
+ return memory;
2295
+ }
2296
+ async getRoomId(channelId, threadTs) {
2297
+ const roomKey = threadTs ? `${channelId}-${threadTs}` : channelId;
2298
+ return createUniqueUuid(this.runtime, `slack-room-${roomKey}`);
2299
+ }
2300
+ getEntityId(userId) {
2301
+ return stringToUuid(`slack-user-${userId}`);
2302
+ }
2303
+ parseSlackTimestamp(ts) {
2304
+ const [seconds] = ts.split(".");
2305
+ return parseInt(seconds, 10) * 1000;
2306
+ }
2307
+ async ensureWorkspaceExists() {
2308
+ if (!this.teamId || !this.client)
2309
+ return;
2310
+ const worldId = createUniqueUuid(this.runtime, `slack-workspace-${this.teamId}`);
2311
+ const existingWorld = await this.runtime.getWorld(worldId);
2312
+ if (existingWorld)
2313
+ return;
2314
+ const teamInfo = await this.client.team.info();
2315
+ const team = teamInfo.team;
2316
+ const world = {
2317
+ id: worldId,
2318
+ name: team?.name || `Slack Workspace ${this.teamId}`,
2319
+ agentId: this.runtime.agentId,
2320
+ metadata: {
2321
+ type: "slack",
2322
+ extra: {
2323
+ teamId: this.teamId,
2324
+ domain: team?.domain
2325
+ }
2326
+ }
2327
+ };
2328
+ await this.runtime.createWorld(world);
2329
+ this.runtime.logger.info({
2330
+ src: "plugin:slack",
2331
+ agentId: this.runtime.agentId,
2332
+ worldId,
2333
+ teamId: this.teamId
2334
+ }, "Created Slack workspace world");
2335
+ }
2336
+ async ensureRoomExists(channelId, threadTs) {
2337
+ const roomId = await this.getRoomId(channelId, threadTs);
2338
+ const existingRoom = await this.runtime.getRoom(roomId);
2339
+ if (existingRoom)
2340
+ return existingRoom;
2341
+ const channel = await this.getChannel(channelId);
2342
+ const channelType = channel ? getSlackChannelType(channel) : "channel";
2343
+ const worldId = this.teamId ? createUniqueUuid(this.runtime, `slack-workspace-${this.teamId}`) : undefined;
2344
+ const elizaChannelType = channelType === "im" ? ChannelType.DM : channelType === "mpim" ? ChannelType.GROUP : ChannelType.GROUP;
2345
+ const room = {
2346
+ id: roomId,
2347
+ name: channel?.name || channelId,
2348
+ agentId: this.runtime.agentId,
2349
+ source: "slack",
2350
+ type: elizaChannelType,
2351
+ channelId,
2352
+ worldId,
2353
+ metadata: {
2354
+ slackChannelType: channelType,
2355
+ threadTs,
2356
+ topic: channel?.topic?.value,
2357
+ purpose: channel?.purpose?.value,
2358
+ serverId: this.teamId
2359
+ }
2360
+ };
2361
+ await this.runtime.createRoom(room);
2362
+ this.runtime.logger.debug({
2363
+ src: "plugin:slack",
2364
+ agentId: this.runtime.agentId,
2365
+ roomId,
2366
+ channelId,
2367
+ threadTs
2368
+ }, "Created Slack room");
2369
+ return room;
2370
+ }
2371
+ async getUser(userId) {
2372
+ if (this.userCache.has(userId)) {
2373
+ return this.userCache.get(userId);
2374
+ }
2375
+ if (!this.client)
2376
+ return null;
2377
+ const result = await this.client.users.info({ user: userId });
2378
+ if (!result.user)
2379
+ return null;
2380
+ const user = {
2381
+ id: result.user.id,
2382
+ teamId: result.user.team_id,
2383
+ name: result.user.name,
2384
+ deleted: result.user.deleted || false,
2385
+ realName: result.user.real_name,
2386
+ tz: result.user.tz,
2387
+ tzLabel: result.user.tz_label,
2388
+ tzOffset: result.user.tz_offset,
2389
+ profile: {
2390
+ title: result.user.profile?.title,
2391
+ phone: result.user.profile?.phone,
2392
+ skype: result.user.profile?.skype,
2393
+ realName: result.user.profile?.real_name,
2394
+ realNameNormalized: result.user.profile?.real_name_normalized,
2395
+ displayName: result.user.profile?.display_name,
2396
+ displayNameNormalized: result.user.profile?.display_name_normalized,
2397
+ statusText: result.user.profile?.status_text,
2398
+ statusEmoji: result.user.profile?.status_emoji,
2399
+ statusExpiration: result.user.profile?.status_expiration,
2400
+ avatarHash: result.user.profile?.avatar_hash,
2401
+ email: result.user.profile?.email,
2402
+ image24: result.user.profile?.image_24,
2403
+ image32: result.user.profile?.image_32,
2404
+ image48: result.user.profile?.image_48,
2405
+ image72: result.user.profile?.image_72,
2406
+ image192: result.user.profile?.image_192,
2407
+ image512: result.user.profile?.image_512,
2408
+ image1024: result.user.profile?.image_1024,
2409
+ imageOriginal: result.user.profile?.image_original,
2410
+ team: result.user.profile?.team
2411
+ },
2412
+ isAdmin: result.user.is_admin || false,
2413
+ isOwner: result.user.is_owner || false,
2414
+ isPrimaryOwner: result.user.is_primary_owner || false,
2415
+ isRestricted: result.user.is_restricted || false,
2416
+ isUltraRestricted: result.user.is_ultra_restricted || false,
2417
+ isBot: result.user.is_bot || false,
2418
+ isAppUser: result.user.is_app_user || false,
2419
+ updated: result.user.updated || 0
2420
+ };
2421
+ this.userCache.set(userId, user);
2422
+ return user;
2423
+ }
2424
+ async getChannel(channelId) {
2425
+ if (this.channelCache.has(channelId)) {
2426
+ return this.channelCache.get(channelId);
2427
+ }
2428
+ if (!this.client)
2429
+ return null;
2430
+ const result = await this.client.conversations.info({ channel: channelId });
2431
+ if (!result.channel)
2432
+ return null;
2433
+ const channel = {
2434
+ id: result.channel.id,
2435
+ name: result.channel.name || "",
2436
+ isChannel: result.channel.is_channel || false,
2437
+ isGroup: result.channel.is_group || false,
2438
+ isIm: result.channel.is_im || false,
2439
+ isMpim: result.channel.is_mpim || false,
2440
+ isPrivate: result.channel.is_private || false,
2441
+ isArchived: result.channel.is_archived || false,
2442
+ isGeneral: result.channel.is_general || false,
2443
+ isShared: result.channel.is_shared || false,
2444
+ isOrgShared: result.channel.is_org_shared || false,
2445
+ isMember: result.channel.is_member || false,
2446
+ topic: result.channel.topic ? {
2447
+ value: result.channel.topic.value,
2448
+ creator: result.channel.topic.creator,
2449
+ lastSet: result.channel.topic.last_set
2450
+ } : undefined,
2451
+ purpose: result.channel.purpose ? {
2452
+ value: result.channel.purpose.value,
2453
+ creator: result.channel.purpose.creator,
2454
+ lastSet: result.channel.purpose.last_set
2455
+ } : undefined,
2456
+ numMembers: result.channel.num_members,
2457
+ created: result.channel.created,
2458
+ creator: result.channel.creator
2459
+ };
2460
+ this.channelCache.set(channelId, channel);
2461
+ return channel;
2462
+ }
2463
+ async sendMessage(channelId, text, options) {
2464
+ if (!this.client) {
2465
+ throw new Error("Slack client not initialized");
2466
+ }
2467
+ const messages = this.splitMessage(text);
2468
+ let lastTs = "";
2469
+ for (const msg of messages) {
2470
+ const result = await this.client.chat.postMessage({
2471
+ channel: channelId,
2472
+ text: msg,
2473
+ thread_ts: options?.threadTs,
2474
+ reply_broadcast: options?.replyBroadcast,
2475
+ unfurl_links: options?.unfurlLinks,
2476
+ unfurl_media: options?.unfurlMedia,
2477
+ mrkdwn: options?.mrkdwn ?? true,
2478
+ attachments: options?.attachments,
2479
+ blocks: options?.blocks
2480
+ });
2481
+ lastTs = result.ts;
2482
+ }
2483
+ return { ts: lastTs, channelId };
2484
+ }
2485
+ async sendReaction(channelId, messageTs, emoji) {
2486
+ if (!this.client) {
2487
+ throw new Error("Slack client not initialized");
2488
+ }
2489
+ const cleanEmoji = emoji.replace(/^:/, "").replace(/:$/, "");
2490
+ await this.client.reactions.add({
2491
+ channel: channelId,
2492
+ timestamp: messageTs,
2493
+ name: cleanEmoji
2494
+ });
2495
+ }
2496
+ async removeReaction(channelId, messageTs, emoji) {
2497
+ if (!this.client) {
2498
+ throw new Error("Slack client not initialized");
2499
+ }
2500
+ const cleanEmoji = emoji.replace(/^:/, "").replace(/:$/, "");
2501
+ await this.client.reactions.remove({
2502
+ channel: channelId,
2503
+ timestamp: messageTs,
2504
+ name: cleanEmoji
2505
+ });
2506
+ }
2507
+ async editMessage(channelId, messageTs, text) {
2508
+ if (!this.client) {
2509
+ throw new Error("Slack client not initialized");
2510
+ }
2511
+ await this.client.chat.update({
2512
+ channel: channelId,
2513
+ ts: messageTs,
2514
+ text
2515
+ });
2516
+ }
2517
+ async deleteMessage(channelId, messageTs) {
2518
+ if (!this.client) {
2519
+ throw new Error("Slack client not initialized");
2520
+ }
2521
+ await this.client.chat.delete({
2522
+ channel: channelId,
2523
+ ts: messageTs
2524
+ });
2525
+ }
2526
+ async pinMessage(channelId, messageTs) {
2527
+ if (!this.client) {
2528
+ throw new Error("Slack client not initialized");
2529
+ }
2530
+ await this.client.pins.add({
2531
+ channel: channelId,
2532
+ timestamp: messageTs
2533
+ });
2534
+ }
2535
+ async unpinMessage(channelId, messageTs) {
2536
+ if (!this.client) {
2537
+ throw new Error("Slack client not initialized");
2538
+ }
2539
+ await this.client.pins.remove({
2540
+ channel: channelId,
2541
+ timestamp: messageTs
2542
+ });
2543
+ }
2544
+ async listPins(channelId) {
2545
+ if (!this.client) {
2546
+ throw new Error("Slack client not initialized");
2547
+ }
2548
+ const result = await this.client.pins.list({ channel: channelId });
2549
+ return (result.items || []).filter((item) => item.type === "message" && ("message" in item) && !!item.message).map((item) => ({
2550
+ type: item.message.type,
2551
+ subtype: item.message.subtype,
2552
+ ts: item.message.ts,
2553
+ user: item.message.user,
2554
+ text: item.message.text,
2555
+ threadTs: item.message.thread_ts,
2556
+ replyCount: item.message.reply_count,
2557
+ replyUsersCount: item.message.reply_users_count,
2558
+ latestReply: item.message.latest_reply,
2559
+ reactions: item.message.reactions,
2560
+ files: item.message.files,
2561
+ attachments: item.message.attachments,
2562
+ blocks: item.message.blocks
2563
+ }));
2564
+ }
2565
+ async readHistory(channelId, options) {
2566
+ if (!this.client) {
2567
+ throw new Error("Slack client not initialized");
2568
+ }
2569
+ const result = await this.client.conversations.history({
2570
+ channel: channelId,
2571
+ limit: options?.limit || 100,
2572
+ latest: options?.before,
2573
+ oldest: options?.after
2574
+ });
2575
+ return (result.messages || []).map((msg) => ({
2576
+ type: msg.type,
2577
+ subtype: msg.subtype,
2578
+ ts: msg.ts,
2579
+ user: msg.user,
2580
+ text: msg.text,
2581
+ threadTs: msg.thread_ts,
2582
+ replyCount: msg.reply_count,
2583
+ replyUsersCount: msg.reply_users_count,
2584
+ latestReply: msg.latest_reply,
2585
+ reactions: msg.reactions,
2586
+ files: msg.files,
2587
+ attachments: msg.attachments,
2588
+ blocks: msg.blocks
2589
+ }));
2590
+ }
2591
+ async listChannels(options) {
2592
+ if (!this.client) {
2593
+ throw new Error("Slack client not initialized");
2594
+ }
2595
+ const result = await this.client.conversations.list({
2596
+ types: options?.types || "public_channel,private_channel",
2597
+ limit: options?.limit || 1000
2598
+ });
2599
+ return (result.channels || []).map((ch) => ({
2600
+ id: ch.id,
2601
+ name: ch.name || "",
2602
+ isChannel: ch.is_channel || false,
2603
+ isGroup: ch.is_group || false,
2604
+ isIm: ch.is_im || false,
2605
+ isMpim: ch.is_mpim || false,
2606
+ isPrivate: ch.is_private || false,
2607
+ isArchived: ch.is_archived || false,
2608
+ isGeneral: ch.is_general || false,
2609
+ isShared: ch.is_shared || false,
2610
+ isOrgShared: ch.is_org_shared || false,
2611
+ isMember: ch.is_member || false,
2612
+ topic: ch.topic ? {
2613
+ value: ch.topic.value,
2614
+ creator: ch.topic.creator,
2615
+ lastSet: ch.topic.last_set
2616
+ } : undefined,
2617
+ purpose: ch.purpose ? {
2618
+ value: ch.purpose.value,
2619
+ creator: ch.purpose.creator,
2620
+ lastSet: ch.purpose.last_set
2621
+ } : undefined,
2622
+ numMembers: ch.num_members,
2623
+ created: ch.created || 0,
2624
+ creator: ch.creator || ""
2625
+ }));
2626
+ }
2627
+ async getEmojiList() {
2628
+ if (!this.client) {
2629
+ throw new Error("Slack client not initialized");
2630
+ }
2631
+ const result = await this.client.emoji.list();
2632
+ return result.emoji || {};
2633
+ }
2634
+ async uploadFile(channelId, content, filename, options) {
2635
+ if (!this.client) {
2636
+ throw new Error("Slack client not initialized");
2637
+ }
2638
+ const result = await this.client.files.uploadV2({
2639
+ channel_id: channelId,
2640
+ content: typeof content === "string" ? content : undefined,
2641
+ file: typeof content !== "string" ? content : undefined,
2642
+ filename,
2643
+ title: options?.title,
2644
+ initial_comment: options?.initialComment,
2645
+ thread_ts: options?.threadTs
2646
+ });
2647
+ const resultWithFile = result;
2648
+ const file = resultWithFile.file;
2649
+ return {
2650
+ fileId: file?.id || "",
2651
+ permalink: file?.permalink || ""
2652
+ };
2653
+ }
2654
+ splitMessage(text) {
2655
+ if (text.length <= MAX_SLACK_MESSAGE_LENGTH) {
2656
+ return [text];
2657
+ }
2658
+ const messages = [];
2659
+ let remaining = text;
2660
+ while (remaining.length > 0) {
2661
+ if (remaining.length <= MAX_SLACK_MESSAGE_LENGTH) {
2662
+ messages.push(remaining);
2663
+ break;
2664
+ }
2665
+ let splitIndex = MAX_SLACK_MESSAGE_LENGTH;
2666
+ const lastNewline = remaining.lastIndexOf(`
2667
+ `, MAX_SLACK_MESSAGE_LENGTH);
2668
+ if (lastNewline > MAX_SLACK_MESSAGE_LENGTH / 2) {
2669
+ splitIndex = lastNewline + 1;
2670
+ } else {
2671
+ const lastSpace = remaining.lastIndexOf(" ", MAX_SLACK_MESSAGE_LENGTH);
2672
+ if (lastSpace > MAX_SLACK_MESSAGE_LENGTH / 2) {
2673
+ splitIndex = lastSpace + 1;
2674
+ }
2675
+ }
2676
+ messages.push(remaining.slice(0, splitIndex));
2677
+ remaining = remaining.slice(splitIndex);
2678
+ }
2679
+ return messages;
2680
+ }
2681
+ addAllowedChannel(channelId) {
2682
+ if (isValidChannelId(channelId)) {
2683
+ this.dynamicChannelIds.add(channelId);
2684
+ }
2685
+ }
2686
+ removeAllowedChannel(channelId) {
2687
+ this.dynamicChannelIds.delete(channelId);
2688
+ }
2689
+ getAllowedChannelIds() {
2690
+ return [...this.allowedChannelIds, ...this.dynamicChannelIds];
2691
+ }
2692
+ isServiceConnected() {
2693
+ return this.isConnected && this.app !== null;
2694
+ }
2695
+ getBotUserId() {
2696
+ return this.botUserId;
2697
+ }
2698
+ getTeamId() {
2699
+ return this.teamId;
2700
+ }
2701
+ clearUserCache() {
2702
+ this.userCache.clear();
2703
+ }
2704
+ clearChannelCache() {
2705
+ this.channelCache.clear();
2706
+ }
2707
+ }
2708
+
2709
+ // src/accounts.ts
2710
+ var DEFAULT_ACCOUNT_ID = "default";
2711
+ function normalizeAccountId(accountId) {
2712
+ if (!accountId || typeof accountId !== "string") {
2713
+ return DEFAULT_ACCOUNT_ID;
2714
+ }
2715
+ const trimmed = accountId.trim().toLowerCase();
2716
+ return trimmed || DEFAULT_ACCOUNT_ID;
2717
+ }
2718
+ function normalizeSlackToken(raw, prefix) {
2719
+ const trimmed = raw?.trim();
2720
+ return trimmed?.startsWith(prefix) ? trimmed : undefined;
2721
+ }
2722
+ function resolveSlackBotToken(raw) {
2723
+ return normalizeSlackToken(raw, "xoxb-");
2724
+ }
2725
+ function resolveSlackAppToken(raw) {
2726
+ return normalizeSlackToken(raw, "xapp-");
2727
+ }
2728
+ function resolveSlackUserToken(raw) {
2729
+ return normalizeSlackToken(raw, "xoxp-");
2730
+ }
2731
+ function getMultiAccountConfig(runtime) {
2732
+ const characterSlack = runtime.character?.settings?.slack;
2733
+ return {
2734
+ enabled: characterSlack?.enabled,
2735
+ botToken: characterSlack?.botToken,
2736
+ appToken: characterSlack?.appToken,
2737
+ accounts: characterSlack?.accounts
2738
+ };
2739
+ }
2740
+ function listSlackAccountIds(runtime) {
2741
+ const config = getMultiAccountConfig(runtime);
2742
+ const accounts = config.accounts;
2743
+ if (!accounts || typeof accounts !== "object") {
2744
+ return [DEFAULT_ACCOUNT_ID];
2745
+ }
2746
+ const ids = Object.keys(accounts).filter(Boolean);
2747
+ if (ids.length === 0) {
2748
+ return [DEFAULT_ACCOUNT_ID];
2749
+ }
2750
+ return ids.slice().sort((a, b) => a.localeCompare(b));
2751
+ }
2752
+ function resolveDefaultSlackAccountId(runtime) {
2753
+ const ids = listSlackAccountIds(runtime);
2754
+ if (ids.includes(DEFAULT_ACCOUNT_ID)) {
2755
+ return DEFAULT_ACCOUNT_ID;
2756
+ }
2757
+ return ids[0] ?? DEFAULT_ACCOUNT_ID;
2758
+ }
2759
+ function getAccountConfig(runtime, accountId) {
2760
+ const config = getMultiAccountConfig(runtime);
2761
+ const accounts = config.accounts;
2762
+ if (!accounts || typeof accounts !== "object") {
2763
+ return;
2764
+ }
2765
+ return accounts[accountId];
2766
+ }
2767
+ function filterDefined(obj) {
2768
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
2769
+ }
2770
+ function mergeSlackAccountConfig(runtime, accountId) {
2771
+ const multiConfig = getMultiAccountConfig(runtime);
2772
+ const { accounts: _ignored, ...baseConfig } = multiConfig;
2773
+ const accountConfig = getAccountConfig(runtime, accountId) ?? {};
2774
+ const envChannelIds = runtime.getSetting("SLACK_CHANNEL_IDS");
2775
+ const envConfig = {
2776
+ shouldIgnoreBotMessages: runtime.getSetting("SLACK_SHOULD_IGNORE_BOT_MESSAGES")?.toLowerCase() === "true",
2777
+ shouldRespondOnlyToMentions: runtime.getSetting("SLACK_SHOULD_RESPOND_ONLY_TO_MENTIONS")?.toLowerCase() === "true",
2778
+ allowedChannelIds: envChannelIds ? envChannelIds.split(",").map((s) => s.trim()).filter(Boolean) : undefined
2779
+ };
2780
+ return {
2781
+ ...filterDefined(envConfig),
2782
+ ...filterDefined(baseConfig),
2783
+ ...filterDefined(accountConfig)
2784
+ };
2785
+ }
2786
+ function resolveSlackAccount(runtime, accountId) {
2787
+ const normalizedAccountId = normalizeAccountId(accountId);
2788
+ const multiConfig = getMultiAccountConfig(runtime);
2789
+ const baseEnabled = multiConfig.enabled !== false;
2790
+ const merged = mergeSlackAccountConfig(runtime, normalizedAccountId);
2791
+ const accountEnabled = merged.enabled !== false;
2792
+ const enabled = baseEnabled && accountEnabled;
2793
+ const allowEnv = normalizedAccountId === DEFAULT_ACCOUNT_ID;
2794
+ const envBotToken = allowEnv ? resolveSlackBotToken(runtime.getSetting("SLACK_BOT_TOKEN")) : undefined;
2795
+ const configBotToken = resolveSlackBotToken(merged.botToken);
2796
+ const botToken = configBotToken ?? envBotToken;
2797
+ const botTokenSource = configBotToken ? "config" : envBotToken ? "env" : "none";
2798
+ const envAppToken = allowEnv ? resolveSlackAppToken(runtime.getSetting("SLACK_APP_TOKEN")) : undefined;
2799
+ const configAppToken = resolveSlackAppToken(merged.appToken);
2800
+ const appToken = configAppToken ?? envAppToken;
2801
+ const appTokenSource = configAppToken ? "config" : envAppToken ? "env" : "none";
2802
+ const signingSecret = merged.signingSecret ?? runtime.getSetting("SLACK_SIGNING_SECRET");
2803
+ const envUserToken = allowEnv ? resolveSlackUserToken(runtime.getSetting("SLACK_USER_TOKEN")) : undefined;
2804
+ const configUserToken = resolveSlackUserToken(merged.userToken);
2805
+ const userToken = configUserToken ?? envUserToken;
2806
+ return {
2807
+ accountId: normalizedAccountId,
2808
+ enabled,
2809
+ name: merged.name?.trim() || undefined,
2810
+ botToken,
2811
+ appToken,
2812
+ signingSecret,
2813
+ userToken,
2814
+ botTokenSource,
2815
+ appTokenSource,
2816
+ config: merged
2817
+ };
2818
+ }
2819
+ function listEnabledSlackAccounts(runtime) {
2820
+ return listSlackAccountIds(runtime).map((accountId) => resolveSlackAccount(runtime, accountId)).filter((account) => account.enabled && account.botToken);
2821
+ }
2822
+ function isMultiAccountEnabled(runtime) {
2823
+ const accounts = listEnabledSlackAccounts(runtime);
2824
+ return accounts.length > 1;
2825
+ }
2826
+ function resolveSlackReplyToMode(account, chatType) {
2827
+ const normalized = chatType?.toLowerCase().trim();
2828
+ if (normalized && account.config.replyToModeByChatType?.[normalized] !== undefined) {
2829
+ return account.config.replyToModeByChatType[normalized] ?? "off";
2830
+ }
2831
+ if (normalized === "direct" || normalized === "im") {
2832
+ if (account.config.dm?.replyToMode !== undefined) {
2833
+ return account.config.dm.replyToMode;
2834
+ }
2835
+ }
2836
+ return account.config.replyToMode ?? "off";
2837
+ }
2838
+ // src/formatting.ts
2839
+ function escapeSlackMrkdwnSegment(text) {
2840
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2841
+ }
2842
+ var SLACK_ANGLE_TOKEN_RE = /<[^>\n]+>/g;
2843
+ function isAllowedSlackAngleToken(token) {
2844
+ if (!token.startsWith("<") || !token.endsWith(">")) {
2845
+ return false;
2846
+ }
2847
+ const inner = token.slice(1, -1);
2848
+ return inner.startsWith("@") || inner.startsWith("#") || inner.startsWith("!") || inner.startsWith("mailto:") || inner.startsWith("tel:") || inner.startsWith("http://") || inner.startsWith("https://") || inner.startsWith("slack://");
2849
+ }
2850
+ function escapeSlackMrkdwnContent(text) {
2851
+ if (!text.includes("&") && !text.includes("<") && !text.includes(">")) {
2852
+ return text;
2853
+ }
2854
+ SLACK_ANGLE_TOKEN_RE.lastIndex = 0;
2855
+ const out = [];
2856
+ let lastIndex = 0;
2857
+ for (let match = SLACK_ANGLE_TOKEN_RE.exec(text);match; match = SLACK_ANGLE_TOKEN_RE.exec(text)) {
2858
+ const matchIndex = match.index ?? 0;
2859
+ out.push(escapeSlackMrkdwnSegment(text.slice(lastIndex, matchIndex)));
2860
+ const token = match[0] ?? "";
2861
+ out.push(isAllowedSlackAngleToken(token) ? token : escapeSlackMrkdwnSegment(token));
2862
+ lastIndex = matchIndex + token.length;
2863
+ }
2864
+ out.push(escapeSlackMrkdwnSegment(text.slice(lastIndex)));
2865
+ return out.join("");
2866
+ }
2867
+ function escapeSlackMrkdwn(text) {
2868
+ if (!text.includes("&") && !text.includes("<") && !text.includes(">")) {
2869
+ return text;
2870
+ }
2871
+ return text.split(`
2872
+ `).map((line) => {
2873
+ if (line.startsWith("> ")) {
2874
+ return `> ${escapeSlackMrkdwnContent(line.slice(2))}`;
2875
+ }
2876
+ return escapeSlackMrkdwnContent(line);
2877
+ }).join(`
2878
+ `);
2879
+ }
2880
+ var BOLD_PLACEHOLDER = "\x00BOLD\x00";
2881
+ function convertBold(text) {
2882
+ return text.replace(/\*\*(.+?)\*\*/g, `${BOLD_PLACEHOLDER}$1${BOLD_PLACEHOLDER}`);
2883
+ }
2884
+ function convertItalic(text) {
2885
+ const converted = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "_$1_");
2886
+ return converted.replace(new RegExp(BOLD_PLACEHOLDER, "g"), "*");
2887
+ }
2888
+ function convertStrikethrough(text) {
2889
+ return text.replace(/~~(.+?)~~/g, "~$1~");
2890
+ }
2891
+ function convertCodeBlocks(text) {
2892
+ return text.replace(/```(\w*)\n?([\s\S]*?)```/g, "```\n$2```");
2893
+ }
2894
+ function convertLinks(text) {
2895
+ return text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, linkText, url) => {
2896
+ const trimmedUrl = url.trim();
2897
+ const trimmedText = linkText.trim();
2898
+ if (trimmedText === trimmedUrl || trimmedText === trimmedUrl.replace(/^mailto:/, "")) {
2899
+ return `<${escapeSlackMrkdwnSegment(trimmedUrl)}>`;
2900
+ }
2901
+ return `<${escapeSlackMrkdwnSegment(trimmedUrl)}|${escapeSlackMrkdwnSegment(trimmedText)}>`;
2902
+ });
2903
+ }
2904
+ function convertHeadings(text) {
2905
+ return text.replace(/^#{1,6}\s+(.+)$/gm, `${BOLD_PLACEHOLDER}$1${BOLD_PLACEHOLDER}`);
2906
+ }
2907
+ function markdownToSlackMrkdwn(markdown) {
2908
+ if (!markdown) {
2909
+ return "";
2910
+ }
2911
+ let result = convertCodeBlocks(markdown);
2912
+ result = convertLinks(result);
2913
+ result = convertHeadings(result);
2914
+ result = convertBold(result);
2915
+ result = convertItalic(result);
2916
+ result = convertStrikethrough(result);
2917
+ result = escapeSlackMrkdwn(result);
2918
+ return result;
2919
+ }
2920
+ var DEFAULT_MAX_CHARS = 4000;
2921
+ function chunkSlackText(text, maxChars = DEFAULT_MAX_CHARS) {
2922
+ if (!text) {
2923
+ return [];
2924
+ }
2925
+ if (text.length <= maxChars) {
2926
+ return [text];
2927
+ }
2928
+ const chunks = [];
2929
+ let remaining = text;
2930
+ let inCodeBlock = false;
2931
+ while (remaining.length > 0) {
2932
+ if (remaining.length <= maxChars) {
2933
+ chunks.push(remaining);
2934
+ break;
2935
+ }
2936
+ let breakPoint = maxChars;
2937
+ const codeBlockCount = (remaining.slice(0, maxChars).match(/```/g) || []).length;
2938
+ inCodeBlock = codeBlockCount % 2 !== 0;
2939
+ const newlineIndex = remaining.lastIndexOf(`
2940
+ `, maxChars);
2941
+ if (newlineIndex > maxChars * 0.5) {
2942
+ breakPoint = newlineIndex + 1;
2943
+ } else {
2944
+ const spaceIndex = remaining.lastIndexOf(" ", maxChars);
2945
+ if (spaceIndex > maxChars * 0.5) {
2946
+ breakPoint = spaceIndex + 1;
2947
+ }
2948
+ }
2949
+ let chunk = remaining.slice(0, breakPoint);
2950
+ if (inCodeBlock) {
2951
+ chunk += "\n```";
2952
+ }
2953
+ chunks.push(chunk);
2954
+ remaining = remaining.slice(breakPoint);
2955
+ if (inCodeBlock) {
2956
+ remaining = `\`\`\`
2957
+ ${remaining}`;
2958
+ }
2959
+ }
2960
+ return chunks;
2961
+ }
2962
+ function markdownToSlackMrkdwnChunks(markdown, limit) {
2963
+ return chunkSlackText(markdownToSlackMrkdwn(markdown), limit);
2964
+ }
2965
+ function formatSlackUserMention(userId) {
2966
+ return `<@${userId}>`;
2967
+ }
2968
+ function formatSlackChannelMention(channelId) {
2969
+ return `<#${channelId}>`;
2970
+ }
2971
+ function formatSlackUserGroupMention(groupId) {
2972
+ return `<!subteam^${groupId}>`;
2973
+ }
2974
+ function formatSlackSpecialMention(type) {
2975
+ return `<!${type}>`;
2976
+ }
2977
+ function formatSlackLink(url, text) {
2978
+ const safeUrl = escapeSlackMrkdwnSegment(url);
2979
+ if (text && text !== url) {
2980
+ return `<${safeUrl}|${escapeSlackMrkdwnSegment(text)}>`;
2981
+ }
2982
+ return `<${safeUrl}>`;
2983
+ }
2984
+ function formatSlackDate(timestamp, format = "{date_short_pretty} at {time}", fallbackText) {
2985
+ const unix = Math.floor((typeof timestamp === "number" ? timestamp : timestamp.getTime()) / 1000);
2986
+ const fallback = fallbackText || new Date(unix * 1000).toISOString();
2987
+ return `<!date^${unix}^${format}|${fallback}>`;
2988
+ }
2989
+ function extractUserIdFromMention(mention) {
2990
+ const match = mention.match(/^<@([UW][A-Z0-9]+)(?:\|[^>]*)?>$/i);
2991
+ return match ? match[1] : null;
2992
+ }
2993
+ function extractChannelIdFromMention(mention) {
2994
+ const match = mention.match(/^<#([CGD][A-Z0-9]+)(?:\|[^>]*)?>$/i);
2995
+ return match ? match[1] : null;
2996
+ }
2997
+ function extractUrlFromSlackLink(link) {
2998
+ const match = link.match(/^<(https?:\/\/[^|>]+)(?:\|[^>]*)?>$/);
2999
+ return match ? match[1] : null;
3000
+ }
3001
+ function formatSlackUserDisplayName(user) {
3002
+ return user.profile.displayName || user.profile.realName || user.name;
3003
+ }
3004
+ function formatSlackChannel(channel) {
3005
+ if (channel.isIm) {
3006
+ return "Direct Message";
3007
+ }
3008
+ if (channel.isMpim) {
3009
+ return `Group DM: ${channel.name}`;
3010
+ }
3011
+ return `#${channel.name}`;
3012
+ }
3013
+ function getChannelTypeString(channel) {
3014
+ if (channel.isIm) {
3015
+ return "DM";
3016
+ }
3017
+ if (channel.isMpim) {
3018
+ return "Group DM";
3019
+ }
3020
+ if (channel.isPrivate || channel.isGroup) {
3021
+ return "Private Channel";
3022
+ }
3023
+ return "Channel";
3024
+ }
3025
+ function resolveSlackSystemLocation(channel, teamName) {
3026
+ const channelType = getChannelTypeString(channel);
3027
+ const channelName = formatSlackChannel(channel);
3028
+ if (teamName) {
3029
+ return `${teamName} - ${channelType}: ${channelName}`;
3030
+ }
3031
+ return `${channelType}: ${channelName}`;
3032
+ }
3033
+ function isDirectMessage(channel) {
3034
+ return channel.isIm;
3035
+ }
3036
+ function isGroupDm(channel) {
3037
+ return channel.isMpim;
3038
+ }
3039
+ function isPrivateChannel(channel) {
3040
+ return channel.isPrivate || channel.isGroup;
3041
+ }
3042
+ function truncateText(text, maxLength, ellipsis = "…") {
3043
+ if (text.length <= maxLength) {
3044
+ return text;
3045
+ }
3046
+ return text.slice(0, maxLength - ellipsis.length) + ellipsis;
3047
+ }
3048
+ function stripSlackFormatting(text) {
3049
+ return text.replace(/```[\s\S]*?```/g, "").replace(/\*([^*]+)\*/g, "$1").replace(/_([^_]+)_/g, "$1").replace(/~([^~]+)~/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/<@[UW][A-Z0-9]+(?:\|[^>]*)?>/gi, "").replace(/<#[CGD][A-Z0-9]+(?:\|[^>]*)?>/gi, "").replace(/<!subteam\^[A-Z0-9]+(?:\|[^>]*)?>/gi, "").replace(/<!(?:here|channel|everyone)(?:\|[^>]*)?>/gi, "").replace(/<(https?:\/\/[^|>]+)(?:\|([^>]*))?>/, "$2").replace(/<(https?:\/\/[^>]+)>/, "$1").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").trim();
3050
+ }
3051
+ function buildSlackMessagePermalink(workspaceDomain, channelId, messageTs) {
3052
+ const formattedTs = `p${messageTs.replace(".", "")}`;
3053
+ return `https://${workspaceDomain}.slack.com/archives/${channelId}/${formattedTs}`;
3054
+ }
3055
+ function parseSlackMessagePermalink(link) {
3056
+ const match = link.match(/^https?:\/\/([^.]+)\.slack\.com\/archives\/([CGD][A-Z0-9]+)\/p(\d+)/i);
3057
+ if (!match) {
3058
+ return null;
3059
+ }
3060
+ const ts = match[3];
3061
+ const messageTs = `${ts.slice(0, 10)}.${ts.slice(10)}`;
3062
+ return {
3063
+ workspaceDomain: match[1],
3064
+ channelId: match[2],
3065
+ messageTs
3066
+ };
3067
+ }
3068
+
3069
+ // src/index.ts
3070
+ var slackPlugin = {
3071
+ name: "slack",
3072
+ description: "Slack integration plugin for ElizaOS with Socket Mode support",
3073
+ services: [SlackService],
3074
+ actions: [
3075
+ sendMessage_default,
3076
+ reactToMessage_default,
3077
+ readChannel_default,
3078
+ editMessage_default,
3079
+ deleteMessage_default,
3080
+ pinMessage_default,
3081
+ unpinMessage_default,
3082
+ listChannels_default,
3083
+ getUserInfo_default,
3084
+ listPins_default,
3085
+ emojiList_default
3086
+ ],
3087
+ providers: [channelStateProvider, workspaceInfoProvider, memberListProvider],
3088
+ init: async (_config, runtime) => {
3089
+ const botToken = runtime.getSetting("SLACK_BOT_TOKEN");
3090
+ const appToken = runtime.getSetting("SLACK_APP_TOKEN");
3091
+ const signingSecret = runtime.getSetting("SLACK_SIGNING_SECRET");
3092
+ const userToken = runtime.getSetting("SLACK_USER_TOKEN");
3093
+ const channelIds = runtime.getSetting("SLACK_CHANNEL_IDS");
3094
+ const ignoreBotMessages = runtime.getSetting("SLACK_SHOULD_IGNORE_BOT_MESSAGES");
3095
+ const respondOnlyToMentions = runtime.getSetting("SLACK_SHOULD_RESPOND_ONLY_TO_MENTIONS");
3096
+ const maskToken = (token) => {
3097
+ if (!token || token.trim() === "")
3098
+ return "[not set]";
3099
+ if (token.length <= 8)
3100
+ return "***";
3101
+ return `${token.slice(0, 4)}...${token.slice(-4)}`;
3102
+ };
3103
+ logger.info({
3104
+ src: "plugin:slack",
3105
+ agentId: runtime.agentId,
3106
+ settings: {
3107
+ botToken: maskToken(botToken),
3108
+ appToken: maskToken(appToken),
3109
+ signingSecret: signingSecret ? "[set]" : "[not set]",
3110
+ userToken: maskToken(userToken),
3111
+ channelIds: channelIds || "[all channels]",
3112
+ ignoreBotMessages: ignoreBotMessages || "false",
3113
+ respondOnlyToMentions: respondOnlyToMentions || "false"
3114
+ }
3115
+ }, "Slack plugin initializing");
3116
+ if (!botToken || botToken.trim() === "") {
3117
+ logger.warn({ src: "plugin:slack", agentId: runtime.agentId }, "SLACK_BOT_TOKEN not provided - Slack plugin is loaded but will not be functional");
3118
+ logger.warn({ src: "plugin:slack", agentId: runtime.agentId }, "To enable Slack functionality, please provide SLACK_BOT_TOKEN in your .env file");
3119
+ return;
3120
+ }
3121
+ if (!appToken || appToken.trim() === "") {
3122
+ logger.warn({ src: "plugin:slack", agentId: runtime.agentId }, "SLACK_APP_TOKEN not provided - Socket Mode will not work");
3123
+ logger.warn({ src: "plugin:slack", agentId: runtime.agentId }, "To enable Socket Mode, please provide SLACK_APP_TOKEN in your .env file");
3124
+ return;
3125
+ }
3126
+ if (!botToken.startsWith("xoxb-")) {
3127
+ logger.warn({ src: "plugin:slack", agentId: runtime.agentId }, "SLACK_BOT_TOKEN should start with 'xoxb-'. Please verify your token.");
3128
+ }
3129
+ if (!appToken.startsWith("xapp-")) {
3130
+ logger.warn({ src: "plugin:slack", agentId: runtime.agentId }, "SLACK_APP_TOKEN should start with 'xapp-'. Please verify your token.");
3131
+ }
3132
+ if (userToken && !userToken.startsWith("xoxp-")) {
3133
+ logger.warn({ src: "plugin:slack", agentId: runtime.agentId }, "SLACK_USER_TOKEN should start with 'xoxp-'. Please verify your token.");
3134
+ }
3135
+ logger.info({ src: "plugin:slack", agentId: runtime.agentId }, "Slack plugin configuration validated successfully");
3136
+ }
3137
+ };
3138
+ var src_default = slackPlugin;
3139
+ export {
3140
+ workspaceInfoProvider,
3141
+ unpinMessage,
3142
+ truncateText,
3143
+ stripSlackFormatting,
3144
+ sendMessage,
3145
+ resolveSlackUserToken,
3146
+ resolveSlackSystemLocation,
3147
+ resolveSlackReplyToMode,
3148
+ resolveSlackBotToken,
3149
+ resolveSlackAppToken,
3150
+ resolveSlackAccount,
3151
+ resolveDefaultSlackAccountId,
3152
+ readChannel,
3153
+ reactToMessage,
3154
+ pinMessage,
3155
+ parseSlackMessagePermalink,
3156
+ parseSlackMessageLink,
3157
+ normalizeAccountId,
3158
+ memberListProvider,
3159
+ markdownToSlackMrkdwnChunks,
3160
+ markdownToSlackMrkdwn,
3161
+ listSlackAccountIds,
3162
+ listPins,
3163
+ listEnabledSlackAccounts,
3164
+ listChannels,
3165
+ isValidUserId,
3166
+ isValidTeamId,
3167
+ isValidMessageTs,
3168
+ isValidChannelId,
3169
+ isPrivateChannel,
3170
+ isMultiAccountEnabled,
3171
+ isGroupDm,
3172
+ isDirectMessage,
3173
+ getUserInfo,
3174
+ getSlackUserDisplayName,
3175
+ getSlackChannelType,
3176
+ getChannelTypeString,
3177
+ formatSlackUserMention,
3178
+ formatSlackUserGroupMention,
3179
+ formatSlackUserDisplayName,
3180
+ formatSlackSpecialMention,
3181
+ formatSlackLink,
3182
+ formatSlackDate,
3183
+ formatSlackChannelMention,
3184
+ formatSlackChannel,
3185
+ formatMessageTsForLink,
3186
+ extractUserIdFromMention,
3187
+ extractUrlFromSlackLink,
3188
+ extractChannelIdFromMention,
3189
+ escapeSlackMrkdwn,
3190
+ emojiList,
3191
+ editMessage,
3192
+ deleteMessage,
3193
+ src_default as default,
3194
+ chunkSlackText,
3195
+ channelStateProvider,
3196
+ buildSlackMessagePermalink,
3197
+ SlackServiceNotInitializedError,
3198
+ SlackService,
3199
+ SlackPluginError,
3200
+ SlackEventTypes,
3201
+ SlackConfigurationError,
3202
+ SlackClientNotAvailableError,
3203
+ SlackApiError,
3204
+ SLACK_SERVICE_NAME,
3205
+ MAX_SLACK_MESSAGE_LENGTH,
3206
+ MAX_SLACK_FILE_SIZE,
3207
+ MAX_SLACK_BLOCKS,
3208
+ DEFAULT_ACCOUNT_ID
3209
+ };
3210
+
3211
+ //# debugId=B812885F9371C56F64756E2164756E21