@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 +3211 -0
- package/dist/index.js.map +28 -0
- package/package.json +119 -0
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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(/&/g, "&").replace(/</g, "<").replace(/>/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
|