@elizaos/plugin-slack 2.0.0-beta.1 → 2.0.11-beta.7

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 DELETED
@@ -1,3036 +0,0 @@
1
- // src/index.ts
2
- import {
3
- getConnectorAccountManager,
4
- logger as logger2
5
- } from "@elizaos/core";
6
-
7
- // src/connector-account-provider.ts
8
- import {
9
- logger
10
- } from "@elizaos/core";
11
-
12
- // src/accounts.ts
13
- var DEFAULT_ACCOUNT_ID = "default";
14
- function normalizeAccountId(accountId) {
15
- if (!accountId || typeof accountId !== "string") {
16
- return DEFAULT_ACCOUNT_ID;
17
- }
18
- const trimmed = accountId.trim().toLowerCase();
19
- return trimmed || DEFAULT_ACCOUNT_ID;
20
- }
21
- function normalizeSlackToken(raw, prefix) {
22
- const trimmed = raw?.trim();
23
- return trimmed?.startsWith(prefix) ? trimmed : undefined;
24
- }
25
- function resolveSlackBotToken(raw) {
26
- return normalizeSlackToken(raw, "xoxb-");
27
- }
28
- function resolveSlackAppToken(raw) {
29
- return normalizeSlackToken(raw, "xapp-");
30
- }
31
- function resolveSlackUserToken(raw) {
32
- return normalizeSlackToken(raw, "xoxp-");
33
- }
34
- function getMultiAccountConfig(runtime) {
35
- const characterSlack = runtime.character?.settings?.slack;
36
- return {
37
- enabled: characterSlack?.enabled,
38
- botToken: characterSlack?.botToken,
39
- appToken: characterSlack?.appToken,
40
- accounts: characterSlack?.accounts
41
- };
42
- }
43
- function listSlackAccountIds(runtime) {
44
- const config = getMultiAccountConfig(runtime);
45
- const accounts = config.accounts;
46
- if (!accounts || typeof accounts !== "object") {
47
- return [DEFAULT_ACCOUNT_ID];
48
- }
49
- const ids = Object.keys(accounts).filter(Boolean);
50
- if (ids.length === 0) {
51
- return [DEFAULT_ACCOUNT_ID];
52
- }
53
- return ids.slice().sort((a, b) => a.localeCompare(b));
54
- }
55
- function resolveDefaultSlackAccountId(runtime) {
56
- const ids = listSlackAccountIds(runtime);
57
- if (ids.includes(DEFAULT_ACCOUNT_ID)) {
58
- return DEFAULT_ACCOUNT_ID;
59
- }
60
- return ids[0] ?? DEFAULT_ACCOUNT_ID;
61
- }
62
- function getAccountConfig(runtime, accountId) {
63
- const config = getMultiAccountConfig(runtime);
64
- const accounts = config.accounts;
65
- if (!accounts || typeof accounts !== "object") {
66
- return;
67
- }
68
- return accounts[accountId];
69
- }
70
- function filterDefined(obj) {
71
- return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
72
- }
73
- function mergeSlackAccountConfig(runtime, accountId) {
74
- const multiConfig = getMultiAccountConfig(runtime);
75
- const { accounts: _ignored, ...baseConfig } = multiConfig;
76
- const accountConfig = getAccountConfig(runtime, accountId) ?? {};
77
- const envChannelIds = runtime.getSetting("SLACK_CHANNEL_IDS");
78
- const envConfig = {
79
- shouldIgnoreBotMessages: runtime.getSetting("SLACK_SHOULD_IGNORE_BOT_MESSAGES")?.toLowerCase() === "true",
80
- shouldRespondOnlyToMentions: runtime.getSetting("SLACK_SHOULD_RESPOND_ONLY_TO_MENTIONS")?.toLowerCase() === "true",
81
- allowedChannelIds: envChannelIds ? envChannelIds.split(",").map((s) => s.trim()).filter(Boolean) : undefined
82
- };
83
- return {
84
- ...filterDefined(envConfig),
85
- ...filterDefined(baseConfig),
86
- ...filterDefined(accountConfig)
87
- };
88
- }
89
- function resolveSlackAccount(runtime, accountId) {
90
- const normalizedAccountId = normalizeAccountId(accountId);
91
- const multiConfig = getMultiAccountConfig(runtime);
92
- const baseEnabled = multiConfig.enabled !== false;
93
- const merged = mergeSlackAccountConfig(runtime, normalizedAccountId);
94
- const accountEnabled = merged.enabled !== false;
95
- const enabled = baseEnabled && accountEnabled;
96
- const allowEnv = normalizedAccountId === DEFAULT_ACCOUNT_ID;
97
- const envBotToken = allowEnv ? resolveSlackBotToken(runtime.getSetting("SLACK_BOT_TOKEN")) : undefined;
98
- const configBotToken = resolveSlackBotToken(merged.botToken);
99
- const botToken = configBotToken ?? envBotToken;
100
- const botTokenSource = configBotToken ? "config" : envBotToken ? "env" : "none";
101
- const envAppToken = allowEnv ? resolveSlackAppToken(runtime.getSetting("SLACK_APP_TOKEN")) : undefined;
102
- const configAppToken = resolveSlackAppToken(merged.appToken);
103
- const appToken = configAppToken ?? envAppToken;
104
- const appTokenSource = configAppToken ? "config" : envAppToken ? "env" : "none";
105
- const signingSecret = merged.signingSecret ?? runtime.getSetting("SLACK_SIGNING_SECRET");
106
- const envUserToken = allowEnv ? resolveSlackUserToken(runtime.getSetting("SLACK_USER_TOKEN")) : undefined;
107
- const configUserToken = resolveSlackUserToken(merged.userToken);
108
- const userToken = configUserToken ?? envUserToken;
109
- return {
110
- accountId: normalizedAccountId,
111
- enabled,
112
- name: merged.name?.trim() || undefined,
113
- botToken,
114
- appToken,
115
- signingSecret,
116
- userToken,
117
- botTokenSource,
118
- appTokenSource,
119
- config: merged
120
- };
121
- }
122
- function listEnabledSlackAccounts(runtime) {
123
- return listSlackAccountIds(runtime).map((accountId) => resolveSlackAccount(runtime, accountId)).filter((account) => account.enabled && account.botToken);
124
- }
125
- function isMultiAccountEnabled(runtime) {
126
- const accounts = listEnabledSlackAccounts(runtime);
127
- return accounts.length > 1;
128
- }
129
- function resolveSlackReplyToMode(account, chatType) {
130
- const normalized = chatType?.toLowerCase().trim();
131
- if (normalized && account.config.replyToModeByChatType?.[normalized] !== undefined) {
132
- return account.config.replyToModeByChatType[normalized] ?? "off";
133
- }
134
- if (normalized === "direct" || normalized === "im") {
135
- if (account.config.dm?.replyToMode !== undefined) {
136
- return account.config.dm.replyToMode;
137
- }
138
- }
139
- return account.config.replyToMode ?? "off";
140
- }
141
-
142
- // src/connector-credential-refs.ts
143
- import {
144
- CONNECTOR_ACCOUNT_STORAGE_SERVICE_TYPE
145
- } from "@elizaos/core";
146
- async function persistConnectorCredentialRefs(params) {
147
- const refs = [];
148
- const vaultWriters = resolveVaultWriters(params.runtime, {
149
- provider: params.provider,
150
- accountId: params.accountIdForRef,
151
- caller: params.caller
152
- });
153
- if (vaultWriters.length === 0) {
154
- throw new Error(`No durable connector credential store or vault writer is available for ${params.provider} account ${params.accountIdForRef}. Refusing to mark OAuth account connected without persisted credentials.`);
155
- }
156
- if (!params.storageAccountId) {
157
- throw new Error(`No durable connector account id is available for ${params.provider} account ${params.accountIdForRef}. Refusing to mark OAuth account connected without persisted credential refs.`);
158
- }
159
- const storageWriters = resolveCredentialRefWriters(params.runtime, params.manager, params.storageAccountId);
160
- if (storageWriters.length === 0) {
161
- throw new Error(`No durable connector credential ref writer is available for ${params.provider} account ${params.storageAccountId}. Refusing to mark OAuth account connected without persisted credential refs.`);
162
- }
163
- for (const credential of params.credentials) {
164
- const plannedRef = buildConnectorCredentialVaultRef({
165
- agentId: nonEmptyString(params.runtime.agentId) ?? "agent",
166
- provider: params.provider,
167
- accountId: params.accountIdForRef,
168
- credentialType: credential.credentialType
169
- });
170
- const vaultRef = await writeWithFirstAvailableVault(vaultWriters, plannedRef, credential);
171
- refs.push({
172
- credentialType: credential.credentialType,
173
- vaultRef,
174
- ...credential.expiresAt !== undefined ? { expiresAt: credential.expiresAt } : {},
175
- ...credential.metadata ? { metadata: credential.metadata } : {}
176
- });
177
- }
178
- if (refs.length > 0) {
179
- await writeRefsToStorage(storageWriters, refs);
180
- }
181
- return {
182
- refs,
183
- vaultAvailable: vaultWriters.length > 0,
184
- storageAvailable: storageWriters.length > 0
185
- };
186
- }
187
- function resolveVaultWriters(runtime, context) {
188
- const writers = [];
189
- const credentialStore = getFirstService(runtime, [
190
- "connector_credential_store",
191
- "CONNECTOR_CREDENTIAL_STORE",
192
- "connectorCredentialStore",
193
- "credential_store"
194
- ]);
195
- if (typeof credentialStore?.putSecret === "function") {
196
- writers.push({
197
- name: "connector_credential_store",
198
- write: async (vaultRef, credential) => credentialStore.putSecret?.({
199
- vaultRef,
200
- agentId: nonEmptyString(runtime.agentId) ?? "agent",
201
- provider: context.provider,
202
- accountId: context.accountId,
203
- credentialType: credential.credentialType,
204
- value: credential.value,
205
- caller: context.caller
206
- }) ?? vaultRef
207
- });
208
- }
209
- const vault = getFirstService(runtime, ["vault", "VAULT"]);
210
- if (typeof vault?.set === "function") {
211
- writers.push({
212
- name: "vault",
213
- write: async (vaultRef, credential) => {
214
- await vault.set?.(vaultRef, credential.value, {
215
- sensitive: true,
216
- caller: context.caller
217
- });
218
- return vaultRef;
219
- }
220
- });
221
- }
222
- const secrets = getService(runtime, "SECRETS");
223
- if (typeof secrets?.setGlobal === "function" || typeof secrets?.set === "function") {
224
- writers.push({
225
- name: "SECRETS",
226
- write: async (vaultRef, credential) => {
227
- if (typeof secrets.setGlobal === "function") {
228
- await secrets.setGlobal(vaultRef, credential.value, {
229
- sensitive: true
230
- });
231
- return vaultRef;
232
- }
233
- await secrets.set?.(vaultRef, credential.value, { level: "global", agentId: runtime.agentId }, { sensitive: true });
234
- return vaultRef;
235
- }
236
- });
237
- }
238
- return writers;
239
- }
240
- function resolveCredentialRefWriters(runtime, manager, accountId) {
241
- const candidates = [
242
- manager?.getStorage?.(),
243
- getService(runtime, CONNECTOR_ACCOUNT_STORAGE_SERVICE_TYPE),
244
- runtime.adapter
245
- ].filter(Boolean);
246
- const writers = [];
247
- for (const candidate of candidates) {
248
- const writer = candidate;
249
- if (typeof writer.setConnectorAccountCredentialRef === "function") {
250
- writers.push({
251
- name: "setConnectorAccountCredentialRef",
252
- write: async (ref) => {
253
- await writer.setConnectorAccountCredentialRef?.({
254
- accountId,
255
- credentialType: ref.credentialType,
256
- vaultRef: ref.vaultRef,
257
- ...ref.metadata ? { metadata: ref.metadata } : {},
258
- ...ref.expiresAt !== undefined ? { expiresAt: ref.expiresAt } : {}
259
- });
260
- }
261
- });
262
- } else if (typeof writer.setCredentialRef === "function") {
263
- writers.push({
264
- name: "setCredentialRef",
265
- write: async (ref) => {
266
- await writer.setCredentialRef?.({
267
- accountId,
268
- credentialType: ref.credentialType,
269
- vaultRef: ref.vaultRef,
270
- ...ref.metadata ? { metadata: ref.metadata } : {},
271
- ...ref.expiresAt !== undefined ? { expiresAt: ref.expiresAt } : {}
272
- });
273
- }
274
- });
275
- }
276
- }
277
- return writers;
278
- }
279
- async function writeWithFirstAvailableVault(writers, plannedRef, credential) {
280
- const errors = [];
281
- for (const writer of writers) {
282
- try {
283
- return await writer.write(plannedRef, credential);
284
- } catch (error) {
285
- errors.push(`${writer.name}: ${error instanceof Error ? error.message : String(error)}`);
286
- }
287
- }
288
- throw new Error(`Failed to persist connector credential ref ${plannedRef}: ${errors.join("; ")}`);
289
- }
290
- async function writeRefsToStorage(writers, refs) {
291
- const errors = [];
292
- for (const writer of writers) {
293
- try {
294
- for (const ref of refs) {
295
- await writer.write(ref);
296
- }
297
- return;
298
- } catch (error) {
299
- errors.push(`${writer.name}: ${error instanceof Error ? error.message : String(error)}`);
300
- }
301
- }
302
- throw new Error(`Failed to persist connector credential refs: ${errors.join("; ")}`);
303
- }
304
- function buildConnectorCredentialVaultRef(params) {
305
- return [
306
- "connector",
307
- normalizeVaultSegment(params.agentId),
308
- normalizeVaultSegment(params.provider),
309
- normalizeVaultSegment(params.accountId),
310
- normalizeVaultSegment(params.credentialType)
311
- ].join(".");
312
- }
313
- function normalizeVaultSegment(value) {
314
- const normalized = value.trim().replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
315
- return (normalized || "unknown").slice(0, 64);
316
- }
317
- function getFirstService(runtime, serviceTypes) {
318
- for (const serviceType of serviceTypes) {
319
- const service = getService(runtime, serviceType);
320
- if (service)
321
- return service;
322
- }
323
- return null;
324
- }
325
- function getService(runtime, serviceType) {
326
- try {
327
- return runtime.getService?.(serviceType) ?? null;
328
- } catch {
329
- return null;
330
- }
331
- }
332
- function nonEmptyString(value) {
333
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
334
- }
335
-
336
- // src/types.ts
337
- var SlackEventTypes;
338
- ((SlackEventTypes2) => {
339
- SlackEventTypes2["MESSAGE_RECEIVED"] = "SLACK_MESSAGE_RECEIVED";
340
- SlackEventTypes2["MESSAGE_SENT"] = "SLACK_MESSAGE_SENT";
341
- SlackEventTypes2["REACTION_ADDED"] = "SLACK_REACTION_ADDED";
342
- SlackEventTypes2["REACTION_REMOVED"] = "SLACK_REACTION_REMOVED";
343
- SlackEventTypes2["CHANNEL_JOINED"] = "SLACK_CHANNEL_JOINED";
344
- SlackEventTypes2["CHANNEL_LEFT"] = "SLACK_CHANNEL_LEFT";
345
- SlackEventTypes2["MEMBER_JOINED_CHANNEL"] = "SLACK_MEMBER_JOINED_CHANNEL";
346
- SlackEventTypes2["MEMBER_LEFT_CHANNEL"] = "SLACK_MEMBER_LEFT_CHANNEL";
347
- SlackEventTypes2["APP_MENTION"] = "SLACK_APP_MENTION";
348
- SlackEventTypes2["SLASH_COMMAND"] = "SLACK_SLASH_COMMAND";
349
- SlackEventTypes2["FILE_SHARED"] = "SLACK_FILE_SHARED";
350
- SlackEventTypes2["THREAD_REPLY"] = "SLACK_THREAD_REPLY";
351
- })(SlackEventTypes ||= {});
352
- var SLACK_SERVICE_NAME = "slack";
353
- class SlackPluginError extends Error {
354
- code;
355
- constructor(message, code) {
356
- super(message);
357
- this.code = code;
358
- this.name = "SlackPluginError";
359
- }
360
- }
361
-
362
- class SlackServiceNotInitializedError extends SlackPluginError {
363
- constructor() {
364
- super("Slack service is not initialized", "SERVICE_NOT_INITIALIZED");
365
- this.name = "SlackServiceNotInitializedError";
366
- }
367
- }
368
-
369
- class SlackClientNotAvailableError extends SlackPluginError {
370
- constructor() {
371
- super("Slack client is not available", "CLIENT_NOT_AVAILABLE");
372
- this.name = "SlackClientNotAvailableError";
373
- }
374
- }
375
-
376
- class SlackConfigurationError extends SlackPluginError {
377
- constructor(missingConfig) {
378
- super(`Missing required configuration: ${missingConfig}`, "MISSING_CONFIG");
379
- this.name = "SlackConfigurationError";
380
- }
381
- }
382
-
383
- class SlackApiError extends SlackPluginError {
384
- apiErrorCode;
385
- constructor(message, apiErrorCode) {
386
- super(message, "API_ERROR");
387
- this.apiErrorCode = apiErrorCode;
388
- this.name = "SlackApiError";
389
- }
390
- }
391
- function isValidChannelId(id) {
392
- return /^[CGD][A-Z0-9]{8,}$/i.test(id);
393
- }
394
- function isValidUserId(id) {
395
- return /^[UW][A-Z0-9]{8,}$/i.test(id);
396
- }
397
- function isValidTeamId(id) {
398
- return /^T[A-Z0-9]{8,}$/i.test(id);
399
- }
400
- function isValidMessageTs(ts) {
401
- return /^\d+\.\d{6}$/.test(ts);
402
- }
403
- function parseSlackMessageLink(link) {
404
- const match = link.match(/\/archives\/([CGD][A-Z0-9]+)\/p(\d+)/i);
405
- if (!match)
406
- return null;
407
- const channelId = match[1];
408
- const ts = match[2];
409
- const messageTs = `${ts.slice(0, 10)}.${ts.slice(10)}`;
410
- return { channelId, messageTs };
411
- }
412
- function formatMessageTsForLink(ts) {
413
- return `p${ts.replace(".", "")}`;
414
- }
415
- function getSlackUserDisplayName(user) {
416
- return user.profile.displayName || user.profile.realName || user.name;
417
- }
418
- function getSlackChannelType(channel) {
419
- if (channel.isIm)
420
- return "im";
421
- if (channel.isMpim)
422
- return "mpim";
423
- if (channel.isGroup || channel.isPrivate)
424
- return "group";
425
- return "channel";
426
- }
427
- var MAX_SLACK_MESSAGE_LENGTH = 4000;
428
- var MAX_SLACK_BLOCKS = 50;
429
- var MAX_SLACK_FILE_SIZE = 1024 * 1024 * 1024;
430
-
431
- // src/connector-account-provider.ts
432
- var SLACK_OAUTH_AUTHORIZE_URL = "https://slack.com/oauth/v2/authorize";
433
- var SLACK_OAUTH_TOKEN_URL = "https://slack.com/api/oauth.v2.access";
434
- var DEFAULT_BOT_SCOPES = [
435
- "app_mentions:read",
436
- "channels:history",
437
- "channels:read",
438
- "chat:write",
439
- "groups:history",
440
- "groups:read",
441
- "im:history",
442
- "im:read",
443
- "im:write",
444
- "mpim:history",
445
- "mpim:read",
446
- "reactions:read",
447
- "reactions:write",
448
- "users:read"
449
- ];
450
- var DEFAULT_PURPOSES = [
451
- "messaging",
452
- "posting",
453
- "reading"
454
- ];
455
- function nowMs() {
456
- return Date.now();
457
- }
458
- function nonEmptyString2(value) {
459
- if (typeof value !== "string")
460
- return;
461
- const trimmed = value.trim();
462
- return trimmed.length > 0 ? trimmed : undefined;
463
- }
464
- function readSetting(runtime, key) {
465
- return nonEmptyString2(runtime.getSetting?.(key));
466
- }
467
- function readClientConfig(runtime) {
468
- const clientId = readSetting(runtime, "SLACK_CLIENT_ID");
469
- const clientSecret = readSetting(runtime, "SLACK_CLIENT_SECRET");
470
- const redirectUri = readSetting(runtime, "SLACK_REDIRECT_URI");
471
- if (!clientId || !clientSecret || !redirectUri) {
472
- throw new Error("Slack OAuth requires SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, and SLACK_REDIRECT_URI to be configured.");
473
- }
474
- return { clientId, clientSecret, redirectUri };
475
- }
476
- function synthesizeAccount(accountId, name, isDefault, source) {
477
- return {
478
- id: accountId,
479
- provider: SLACK_SERVICE_NAME,
480
- label: name ?? `Slack (${accountId})`,
481
- role: "OWNER",
482
- purpose: DEFAULT_PURPOSES,
483
- accessGate: "open",
484
- status: "connected",
485
- createdAt: nowMs(),
486
- updatedAt: nowMs(),
487
- metadata: {
488
- synthesized: true,
489
- source,
490
- isDefault
491
- }
492
- };
493
- }
494
- function normalizePurposes(purpose, fallback) {
495
- if (Array.isArray(purpose))
496
- return purpose;
497
- if (typeof purpose === "string" && purpose.trim())
498
- return [purpose];
499
- return fallback;
500
- }
501
- function mergeStoredAccountPatch(account, patch) {
502
- return {
503
- ...account,
504
- ...patch,
505
- provider: SLACK_SERVICE_NAME,
506
- id: account.id,
507
- purpose: normalizePurposes(patch.purpose, account.purpose),
508
- externalId: patch.externalId === undefined ? account.externalId : patch.externalId ?? undefined,
509
- displayHandle: patch.displayHandle === undefined ? account.displayHandle : patch.displayHandle ?? undefined,
510
- ownerBindingId: patch.ownerBindingId === undefined ? account.ownerBindingId : patch.ownerBindingId ?? undefined,
511
- ownerIdentityId: patch.ownerIdentityId === undefined ? account.ownerIdentityId : patch.ownerIdentityId ?? undefined,
512
- metadata: patch.metadata ?? account.metadata,
513
- createdAt: account.createdAt
514
- };
515
- }
516
- function createSlackConnectorAccountProvider(runtime) {
517
- return {
518
- provider: SLACK_SERVICE_NAME,
519
- label: "Slack",
520
- listAccounts: async (manager) => {
521
- const persisted = await manager.getStorage().listAccounts(SLACK_SERVICE_NAME);
522
- const persistedById = new Map(persisted.map((a) => [a.id, a]));
523
- const enabled = listEnabledSlackAccounts(runtime);
524
- const defaultAccountId = resolveDefaultSlackAccountId(runtime);
525
- const synthesized = enabled.filter((account) => !persistedById.has(account.accountId)).map((account) => synthesizeAccount(account.accountId, account.name, account.accountId === defaultAccountId, account.botTokenSource));
526
- if (synthesized.length === 0 && persisted.length === 0) {
527
- const fallback = resolveSlackAccount(runtime, DEFAULT_ACCOUNT_ID);
528
- if (fallback.botToken) {
529
- synthesized.push(synthesizeAccount(DEFAULT_ACCOUNT_ID, fallback.name, true, fallback.botTokenSource));
530
- }
531
- }
532
- return [...persisted, ...synthesized];
533
- },
534
- createAccount: async (input) => {
535
- return {
536
- ...input,
537
- provider: SLACK_SERVICE_NAME,
538
- role: input.role ?? "OWNER",
539
- purpose: input.purpose ?? DEFAULT_PURPOSES,
540
- accessGate: input.accessGate ?? "open",
541
- status: input.status ?? "pending"
542
- };
543
- },
544
- patchAccount: async (accountId, patch, manager) => {
545
- const existing = await manager.getStorage().getAccount(SLACK_SERVICE_NAME, accountId);
546
- if (existing) {
547
- return mergeStoredAccountPatch(existing, patch);
548
- }
549
- return { ...patch, provider: SLACK_SERVICE_NAME };
550
- },
551
- deleteAccount: async () => {},
552
- startOAuth: async (request) => {
553
- const config = readClientConfig(runtime);
554
- const redirectUri = request.redirectUri ?? config.redirectUri;
555
- const requestedScopes = request.scopes && request.scopes.length > 0 ? request.scopes : DEFAULT_BOT_SCOPES;
556
- const params = new URLSearchParams({
557
- client_id: config.clientId,
558
- redirect_uri: redirectUri,
559
- scope: requestedScopes.join(","),
560
- state: request.flow.state
561
- });
562
- return {
563
- authUrl: `${SLACK_OAUTH_AUTHORIZE_URL}?${params.toString()}`,
564
- metadata: {
565
- ...request.metadata,
566
- requestedScopes,
567
- redirectUri
568
- }
569
- };
570
- },
571
- completeOAuth: async (request, manager) => {
572
- const code = nonEmptyString2(request.code);
573
- if (!code) {
574
- throw new Error("Slack OAuth callback is missing an authorization code.");
575
- }
576
- const config = readClientConfig(runtime);
577
- const redirectUri = nonEmptyString2(request.flow.redirectUri) ?? nonEmptyString2(request.flow.metadata?.redirectUri) ?? config.redirectUri;
578
- const tokenParams = new URLSearchParams({
579
- client_id: config.clientId,
580
- client_secret: config.clientSecret,
581
- code,
582
- redirect_uri: redirectUri
583
- });
584
- const response = await fetch(SLACK_OAUTH_TOKEN_URL, {
585
- method: "POST",
586
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
587
- body: tokenParams.toString()
588
- });
589
- if (!response.ok) {
590
- const body = await response.text();
591
- throw new Error(`Slack token exchange failed with ${response.status}: ${body}`);
592
- }
593
- const parsed = await response.json();
594
- if (!parsed.ok || !parsed.access_token) {
595
- throw new Error(`Slack token exchange returned an error: ${parsed.error ?? "unknown"}`);
596
- }
597
- const teamId = parsed.team?.id;
598
- if (!teamId) {
599
- throw new Error("Slack token exchange did not include a team id.");
600
- }
601
- const teamName = parsed.team?.name;
602
- const grantedScopes = parsed.scope ? parsed.scope.split(",") : [];
603
- const expiresAt = typeof parsed.expires_in === "number" ? Date.now() + parsed.expires_in * 1000 : undefined;
604
- const authedUserExpiresAt = typeof parsed.authed_user?.expires_in === "number" ? Date.now() + parsed.authed_user.expires_in * 1000 : undefined;
605
- const oauthCredentialVersion = String(Date.now());
606
- const accountMetadata = {
607
- teamId,
608
- teamName: teamName ?? null,
609
- appId: parsed.app_id ?? null,
610
- botUserId: parsed.bot_user_id ?? null,
611
- enterpriseId: parsed.enterprise?.id ?? null,
612
- authedUserId: parsed.authed_user?.id ?? null,
613
- tokenType: parsed.token_type ?? "bot",
614
- grantedScopes,
615
- hasRefreshToken: Boolean(parsed.refresh_token ?? parsed.authed_user?.refresh_token),
616
- expiresAt,
617
- oauthCredentialVersion
618
- };
619
- const pendingAccount = await manager.upsertAccount(SLACK_SERVICE_NAME, {
620
- provider: SLACK_SERVICE_NAME,
621
- role: "OWNER",
622
- purpose: DEFAULT_PURPOSES,
623
- accessGate: "open",
624
- status: "pending",
625
- externalId: teamId,
626
- displayHandle: teamName,
627
- label: teamName ?? `Slack workspace ${teamId}`,
628
- metadata: accountMetadata
629
- }, request.flow.accountId);
630
- const credentialPersist = await persistConnectorCredentialRefs({
631
- runtime,
632
- manager,
633
- provider: SLACK_SERVICE_NAME,
634
- accountIdForRef: pendingAccount.id,
635
- storageAccountId: pendingAccount.id,
636
- caller: "plugin-slack",
637
- credentials: [
638
- {
639
- credentialType: "oauth.tokens",
640
- value: JSON.stringify({
641
- access_token: parsed.access_token,
642
- ...parsed.refresh_token ? { refresh_token: parsed.refresh_token } : {},
643
- token_type: parsed.token_type ?? "bot",
644
- scope: parsed.scope ?? grantedScopes.join(","),
645
- ...expiresAt !== undefined ? { expires_at: expiresAt } : {},
646
- ...parsed.authed_user?.access_token ? {
647
- authed_user: {
648
- id: parsed.authed_user.id,
649
- access_token: parsed.authed_user.access_token,
650
- ...parsed.authed_user.refresh_token ? { refresh_token: parsed.authed_user.refresh_token } : {},
651
- ...parsed.authed_user.token_type ? { token_type: parsed.authed_user.token_type } : {},
652
- ...parsed.authed_user.scope ? { scope: parsed.authed_user.scope } : {},
653
- ...authedUserExpiresAt !== undefined ? { expires_at: authedUserExpiresAt } : {}
654
- }
655
- } : {}
656
- }),
657
- ...expiresAt !== undefined ? { expiresAt } : {},
658
- metadata: {
659
- provider: SLACK_SERVICE_NAME,
660
- hasRefreshToken: Boolean(parsed.refresh_token ?? parsed.authed_user?.refresh_token),
661
- hasAuthedUserToken: Boolean(parsed.authed_user?.access_token)
662
- }
663
- }
664
- ]
665
- });
666
- const accountPatch = {
667
- ...pendingAccount,
668
- id: pendingAccount.id,
669
- provider: SLACK_SERVICE_NAME,
670
- status: "connected",
671
- metadata: {
672
- ...accountMetadata,
673
- credentialRefs: credentialPersist.refs,
674
- credentialRefStorage: {
675
- vaultAvailable: credentialPersist.vaultAvailable,
676
- storageAvailable: credentialPersist.storageAvailable
677
- }
678
- }
679
- };
680
- logger.info({
681
- src: "plugin:slack:connector",
682
- teamId,
683
- teamName
684
- }, "Slack OAuth completed");
685
- return {
686
- account: accountPatch,
687
- flow: { status: "completed" }
688
- };
689
- }
690
- };
691
- }
692
-
693
- // src/service.ts
694
- import {
695
- ChannelType,
696
- createUniqueUuid,
697
- Service,
698
- stringToUuid
699
- } from "@elizaos/core";
700
- import { App, LogLevel } from "@slack/bolt";
701
-
702
- // src/formatting.ts
703
- function escapeSlackMrkdwnSegment(text) {
704
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
705
- }
706
- var SLACK_ANGLE_TOKEN_RE = /<[^>\n]+>/g;
707
- function isAllowedSlackAngleToken(token) {
708
- if (!token.startsWith("<") || !token.endsWith(">")) {
709
- return false;
710
- }
711
- const inner = token.slice(1, -1);
712
- return inner.startsWith("@") || inner.startsWith("#") || inner.startsWith("!") || inner.startsWith("mailto:") || inner.startsWith("tel:") || inner.startsWith("http://") || inner.startsWith("https://") || inner.startsWith("slack://");
713
- }
714
- function escapeSlackMrkdwnContent(text) {
715
- if (!text.includes("&") && !text.includes("<") && !text.includes(">")) {
716
- return text;
717
- }
718
- SLACK_ANGLE_TOKEN_RE.lastIndex = 0;
719
- const out = [];
720
- let lastIndex = 0;
721
- for (let match = SLACK_ANGLE_TOKEN_RE.exec(text);match; match = SLACK_ANGLE_TOKEN_RE.exec(text)) {
722
- const matchIndex = match.index ?? 0;
723
- out.push(escapeSlackMrkdwnSegment(text.slice(lastIndex, matchIndex)));
724
- const token = match[0] ?? "";
725
- out.push(isAllowedSlackAngleToken(token) ? token : escapeSlackMrkdwnSegment(token));
726
- lastIndex = matchIndex + token.length;
727
- }
728
- out.push(escapeSlackMrkdwnSegment(text.slice(lastIndex)));
729
- return out.join("");
730
- }
731
- function escapeSlackMrkdwn(text) {
732
- if (!text.includes("&") && !text.includes("<") && !text.includes(">")) {
733
- return text;
734
- }
735
- return text.split(`
736
- `).map((line) => {
737
- if (line.startsWith("> ")) {
738
- return `> ${escapeSlackMrkdwnContent(line.slice(2))}`;
739
- }
740
- return escapeSlackMrkdwnContent(line);
741
- }).join(`
742
- `);
743
- }
744
- var BOLD_PLACEHOLDER = "\x00BOLD\x00";
745
- function convertBold(text) {
746
- return text.replace(/\*\*(.+?)\*\*/g, `${BOLD_PLACEHOLDER}$1${BOLD_PLACEHOLDER}`);
747
- }
748
- function convertItalic(text) {
749
- const converted = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "_$1_");
750
- return converted.replace(new RegExp(BOLD_PLACEHOLDER, "g"), "*");
751
- }
752
- function convertStrikethrough(text) {
753
- return text.replace(/~~(.+?)~~/g, "~$1~");
754
- }
755
- function convertCodeBlocks(text) {
756
- return text.replace(/```(\w*)\n?([\s\S]*?)```/g, "```\n$2```");
757
- }
758
- function convertLinks(text) {
759
- return text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, linkText, url) => {
760
- const trimmedUrl = url.trim();
761
- const trimmedText = linkText.trim();
762
- if (trimmedText === trimmedUrl || trimmedText === trimmedUrl.replace(/^mailto:/, "")) {
763
- return `<${escapeSlackMrkdwnSegment(trimmedUrl)}>`;
764
- }
765
- return `<${escapeSlackMrkdwnSegment(trimmedUrl)}|${escapeSlackMrkdwnSegment(trimmedText)}>`;
766
- });
767
- }
768
- function convertHeadings(text) {
769
- return text.replace(/^#{1,6}\s+(.+)$/gm, `${BOLD_PLACEHOLDER}$1${BOLD_PLACEHOLDER}`);
770
- }
771
- function markdownToSlackMrkdwn(markdown) {
772
- if (!markdown) {
773
- return "";
774
- }
775
- let result = convertCodeBlocks(markdown);
776
- result = convertLinks(result);
777
- result = convertHeadings(result);
778
- result = convertBold(result);
779
- result = convertItalic(result);
780
- result = convertStrikethrough(result);
781
- result = escapeSlackMrkdwn(result);
782
- return result;
783
- }
784
- var DEFAULT_MAX_CHARS = 4000;
785
- function chunkSlackText(text, maxChars = DEFAULT_MAX_CHARS) {
786
- if (!text) {
787
- return [];
788
- }
789
- if (text.length <= maxChars) {
790
- return [text];
791
- }
792
- const chunks = [];
793
- let remaining = text;
794
- let inCodeBlock = false;
795
- while (remaining.length > 0) {
796
- if (remaining.length <= maxChars) {
797
- chunks.push(remaining);
798
- break;
799
- }
800
- let breakPoint = maxChars;
801
- const codeBlockCount = (remaining.slice(0, maxChars).match(/```/g) || []).length;
802
- inCodeBlock = codeBlockCount % 2 !== 0;
803
- const newlineIndex = remaining.lastIndexOf(`
804
- `, maxChars);
805
- if (newlineIndex > maxChars * 0.5) {
806
- breakPoint = newlineIndex + 1;
807
- } else {
808
- const spaceIndex = remaining.lastIndexOf(" ", maxChars);
809
- if (spaceIndex > maxChars * 0.5) {
810
- breakPoint = spaceIndex + 1;
811
- }
812
- }
813
- let chunk = remaining.slice(0, breakPoint);
814
- if (inCodeBlock) {
815
- chunk += "\n```";
816
- }
817
- chunks.push(chunk);
818
- remaining = remaining.slice(breakPoint);
819
- if (inCodeBlock) {
820
- remaining = `\`\`\`
821
- ${remaining}`;
822
- }
823
- }
824
- return chunks;
825
- }
826
- function markdownToSlackMrkdwnChunks(markdown, limit) {
827
- return chunkSlackText(markdownToSlackMrkdwn(markdown), limit);
828
- }
829
- function formatSlackUserMention(userId) {
830
- return `<@${userId}>`;
831
- }
832
- function formatSlackChannelMention(channelId) {
833
- return `<#${channelId}>`;
834
- }
835
- function formatSlackUserGroupMention(groupId) {
836
- return `<!subteam^${groupId}>`;
837
- }
838
- function formatSlackSpecialMention(type) {
839
- return `<!${type}>`;
840
- }
841
- function formatSlackLink(url, text) {
842
- const safeUrl = escapeSlackMrkdwnSegment(url);
843
- if (text && text !== url) {
844
- return `<${safeUrl}|${escapeSlackMrkdwnSegment(text)}>`;
845
- }
846
- return `<${safeUrl}>`;
847
- }
848
- function formatSlackDate(timestamp, format = "{date_short_pretty} at {time}", fallbackText) {
849
- const unix = Math.floor((typeof timestamp === "number" ? timestamp : timestamp.getTime()) / 1000);
850
- const fallback = fallbackText || new Date(unix * 1000).toISOString();
851
- return `<!date^${unix}^${format}|${fallback}>`;
852
- }
853
- function extractUserIdFromMention(mention) {
854
- const match = mention.match(/^<@([UW][A-Z0-9]+)(?:\|[^>]*)?>$/i);
855
- return match ? match[1] : null;
856
- }
857
- function extractChannelIdFromMention(mention) {
858
- const match = mention.match(/^<#([CGD][A-Z0-9]+)(?:\|[^>]*)?>$/i);
859
- return match ? match[1] : null;
860
- }
861
- function extractUrlFromSlackLink(link) {
862
- const match = link.match(/^<(https?:\/\/[^|>]+)(?:\|[^>]*)?>$/);
863
- return match ? match[1] : null;
864
- }
865
- function formatSlackUserDisplayName(user) {
866
- return user.profile.displayName || user.profile.realName || user.name;
867
- }
868
- function formatSlackChannel(channel) {
869
- if (channel.isIm) {
870
- return "Direct Message";
871
- }
872
- if (channel.isMpim) {
873
- return `Group DM: ${channel.name}`;
874
- }
875
- return `#${channel.name}`;
876
- }
877
- function getChannelTypeString(channel) {
878
- if (channel.isIm) {
879
- return "DM";
880
- }
881
- if (channel.isMpim) {
882
- return "Group DM";
883
- }
884
- if (channel.isPrivate || channel.isGroup) {
885
- return "Private Channel";
886
- }
887
- return "Channel";
888
- }
889
- function resolveSlackSystemLocation(channel, teamName) {
890
- const channelType = getChannelTypeString(channel);
891
- const channelName = formatSlackChannel(channel);
892
- if (teamName) {
893
- return `${teamName} - ${channelType}: ${channelName}`;
894
- }
895
- return `${channelType}: ${channelName}`;
896
- }
897
- function isDirectMessage(channel) {
898
- return channel.isIm;
899
- }
900
- function isGroupDm(channel) {
901
- return channel.isMpim;
902
- }
903
- function isPrivateChannel(channel) {
904
- return channel.isPrivate || channel.isGroup;
905
- }
906
- function truncateText(text, maxLength, ellipsis = "…") {
907
- if (text.length <= maxLength) {
908
- return text;
909
- }
910
- return text.slice(0, maxLength - ellipsis.length) + ellipsis;
911
- }
912
- function stripSlackFormatting(text) {
913
- return text.replace(/```[\s\S]*?```/g, "").replace(/\*([^*]+)\*/g, "$1").replace(/_([^_]+)_/g, "$1").replace(/~([^~]+)~/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/<@[UW][A-Z0-9]+(?:\|[^>]*)?>/gi, "").replace(/<#[CGD][A-Z0-9]+(?:\|[^>]*)?>/gi, "").replace(/<!subteam\^[A-Z0-9]+(?:\|[^>]*)?>/gi, "").replace(/<!(?:here|channel|everyone)(?:\|[^>]*)?>/gi, "").replace(/<(https?:\/\/[^|>]+)(?:\|([^>]*))?>/, "$2").replace(/<(https?:\/\/[^>]+)>/, "$1").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").trim();
914
- }
915
- function buildSlackMessagePermalink(workspaceDomain, channelId, messageTs) {
916
- const formattedTs = `p${messageTs.replace(".", "")}`;
917
- return `https://${workspaceDomain}.slack.com/archives/${channelId}/${formattedTs}`;
918
- }
919
- function parseSlackMessagePermalink(link) {
920
- const match = link.match(/^https?:\/\/([^.]+)\.slack\.com\/archives\/([CGD][A-Z0-9]+)\/p(\d+)/i);
921
- if (!match) {
922
- return null;
923
- }
924
- const ts = match[3];
925
- const messageTs = `${ts.slice(0, 10)}.${ts.slice(10)}`;
926
- return {
927
- workspaceDomain: match[1],
928
- channelId: match[2],
929
- messageTs
930
- };
931
- }
932
-
933
- // src/service.ts
934
- var SLACK_CONNECTOR_CONTEXTS = ["social", "connectors"];
935
- var SLACK_CONNECTOR_CAPABILITIES = [
936
- "send_message",
937
- "read_messages",
938
- "search_messages",
939
- "resolve_targets",
940
- "list_rooms",
941
- "list_servers",
942
- "chat_context",
943
- "user_context",
944
- "react_message",
945
- "edit_message",
946
- "delete_message",
947
- "pin_message",
948
- "get_user"
949
- ];
950
- var SLACK_USER_ID_PATTERN = /^[UW][A-Z0-9]{2,}$/i;
951
- function normalizeSlackConnectorQuery(value) {
952
- return value.trim().replace(/^<#([A-Z0-9]+)(?:\|[^>]+)?>$/i, "$1").replace(/^<@([A-Z0-9]+)>$/i, "$1").replace(/^#/, "").replace(/^@/, "").toLowerCase();
953
- }
954
- function scoreSlackConnectorMatch(query, id, labels) {
955
- if (!query) {
956
- return 0.45;
957
- }
958
- if (id.toLowerCase() === query) {
959
- return 1;
960
- }
961
- let bestScore = 0;
962
- for (const label of labels) {
963
- const normalized = label?.trim().toLowerCase();
964
- if (!normalized) {
965
- continue;
966
- }
967
- if (normalized === query) {
968
- bestScore = Math.max(bestScore, 0.95);
969
- } else if (normalized.startsWith(query)) {
970
- bestScore = Math.max(bestScore, 0.85);
971
- } else if (normalized.includes(query)) {
972
- bestScore = Math.max(bestScore, 0.7);
973
- }
974
- }
975
- return bestScore;
976
- }
977
- function extractSlackUserIdFromMetadata(metadata, accountId) {
978
- if (!metadata || typeof metadata !== "object") {
979
- return null;
980
- }
981
- const record = metadata;
982
- const slack = record.slack && typeof record.slack === "object" ? record.slack : null;
983
- const metadataAccountId = typeof record.accountId === "string" ? record.accountId : typeof slack?.accountId === "string" ? slack.accountId : undefined;
984
- if (accountId && metadataAccountId && normalizeAccountId(metadataAccountId) !== normalizeAccountId(accountId)) {
985
- return null;
986
- }
987
- const candidates = [
988
- slack?.userId,
989
- slack?.id,
990
- record.slackUserId,
991
- record.originalId
992
- ];
993
- for (const candidate of candidates) {
994
- if (typeof candidate === "string" && SLACK_USER_ID_PATTERN.test(candidate)) {
995
- return candidate;
996
- }
997
- }
998
- return null;
999
- }
1000
- var getMessageService = (runtime) => {
1001
- if ("messageService" in runtime) {
1002
- const withMessageService = runtime;
1003
- return withMessageService.messageService ?? null;
1004
- }
1005
- return null;
1006
- };
1007
-
1008
- class SlackService extends Service {
1009
- static serviceType = SLACK_SERVICE_NAME;
1010
- capabilityDescription = "The agent is able to send and receive messages on Slack";
1011
- app = null;
1012
- client = null;
1013
- character;
1014
- botUserId = null;
1015
- teamId = null;
1016
- settings;
1017
- botToken = null;
1018
- appToken = null;
1019
- signingSecret = null;
1020
- defaultAccountId = DEFAULT_ACCOUNT_ID;
1021
- accountStates = new Map;
1022
- accountStarts = new Map;
1023
- allowedChannelIds = new Set;
1024
- dynamicChannelIds = new Set;
1025
- userCache = new Map;
1026
- channelCache = new Map;
1027
- isConnected = false;
1028
- constructor(runtime) {
1029
- super(runtime);
1030
- this.character = runtime.character;
1031
- this.settings = this.loadSettings();
1032
- this.allowedChannelIds = this.buildAllowedChannelSet();
1033
- if (this.allowedChannelIds.size > 0) {
1034
- this.runtime.logger.debug({
1035
- src: "plugin:slack",
1036
- agentId: this.runtime.agentId,
1037
- allowedChannelIds: Array.from(this.allowedChannelIds)
1038
- }, "Channel restrictions enabled");
1039
- }
1040
- }
1041
- loadSettings(account) {
1042
- const ignoreBotMessages = this.runtime.getSetting("SLACK_SHOULD_IGNORE_BOT_MESSAGES");
1043
- const respondOnlyToMentions = this.runtime.getSetting("SLACK_SHOULD_RESPOND_ONLY_TO_MENTIONS");
1044
- return {
1045
- allowedChannelIds: account?.config.allowedChannelIds,
1046
- shouldIgnoreBotMessages: account?.config.shouldIgnoreBotMessages ?? (ignoreBotMessages === "true" || ignoreBotMessages === true),
1047
- shouldRespondOnlyToMentions: account?.config.shouldRespondOnlyToMentions ?? (respondOnlyToMentions === "true" || respondOnlyToMentions === true)
1048
- };
1049
- }
1050
- buildAllowedChannelSet(account) {
1051
- const allowed = new Set;
1052
- const configuredIds = account?.config.allowedChannelIds;
1053
- const channelIdsRaw = configuredIds && configuredIds.length > 0 ? configuredIds.join(",") : this.runtime.getSetting("SLACK_CHANNEL_IDS");
1054
- if (!channelIdsRaw?.trim()) {
1055
- return allowed;
1056
- }
1057
- channelIdsRaw.split(",").map((s) => s.trim()).filter((s) => s.length > 0 && isValidChannelId(s)).forEach((id) => {
1058
- allowed.add(id);
1059
- });
1060
- return allowed;
1061
- }
1062
- static async start(runtime) {
1063
- const service = new SlackService(runtime);
1064
- const accounts = listEnabledSlackAccounts(runtime);
1065
- service.defaultAccountId = resolveDefaultSlackAccountId(runtime);
1066
- if (accounts.length === 0) {
1067
- runtime.logger.warn({ src: "plugin:slack", agentId: runtime.agentId }, "No enabled Slack accounts configured, Slack service will not start");
1068
- return service;
1069
- }
1070
- let startedAccounts = 0;
1071
- let lastError;
1072
- for (const account of accounts) {
1073
- if (!account.appToken?.trim()) {
1074
- runtime.logger.warn({
1075
- src: "plugin:slack",
1076
- agentId: runtime.agentId,
1077
- accountId: account.accountId
1078
- }, "SLACK_APP_TOKEN not provided for Slack account, Socket Mode will not work");
1079
- continue;
1080
- }
1081
- try {
1082
- await service.initializeAccount(account);
1083
- startedAccounts++;
1084
- } catch (error) {
1085
- lastError = error;
1086
- runtime.logger.error({
1087
- src: "plugin:slack",
1088
- agentId: runtime.agentId,
1089
- accountId: account.accountId,
1090
- error: error instanceof Error ? error.message : String(error)
1091
- }, "Failed to initialize Slack account");
1092
- }
1093
- }
1094
- if (startedAccounts === 0) {
1095
- if (lastError) {
1096
- throw lastError;
1097
- }
1098
- runtime.logger.warn({ src: "plugin:slack", agentId: runtime.agentId }, "Slack service started without connected accounts");
1099
- }
1100
- return service;
1101
- }
1102
- static async stop(runtime) {
1103
- const service = runtime.getService(SLACK_SERVICE_NAME);
1104
- if (service) {
1105
- await service.shutdown();
1106
- }
1107
- }
1108
- static registerSendHandlers(runtime, serviceInstance) {
1109
- if (!serviceInstance) {
1110
- return;
1111
- }
1112
- const registerConnector = (accountId) => {
1113
- const normalizedAccountId = accountId ? normalizeAccountId(accountId) : undefined;
1114
- const state = normalizedAccountId ? serviceInstance.getAccountState(normalizedAccountId) : serviceInstance.getDefaultAccountState();
1115
- const sendHandler = async (handlerRuntime, target, content) => {
1116
- await serviceInstance.handleSendMessage(handlerRuntime, normalizedAccountId && !target.accountId ? { ...target, accountId: normalizedAccountId } : target, content);
1117
- };
1118
- const withContextAccount = (context) => normalizedAccountId && !context.accountId ? {
1119
- ...context,
1120
- accountId: normalizedAccountId,
1121
- account: context.account ?? {
1122
- source: "slack",
1123
- accountId: normalizedAccountId,
1124
- label: state?.account.name ?? normalizedAccountId
1125
- }
1126
- } : context;
1127
- const registration = {
1128
- source: "slack",
1129
- ...normalizedAccountId ? { accountId: normalizedAccountId } : {},
1130
- ...normalizedAccountId ? {
1131
- account: {
1132
- source: "slack",
1133
- accountId: normalizedAccountId,
1134
- label: state?.account.name ?? normalizedAccountId,
1135
- authMethod: "BOT_TOKEN",
1136
- metadata: {
1137
- teamId: state?.teamId
1138
- }
1139
- }
1140
- } : {},
1141
- label: state?.account.name ? `Slack (${state.account.name})` : "Slack",
1142
- description: "Slack connector for sending, reading, searching, reacting to, editing, deleting, and pinning messages in channels, threads, and users.",
1143
- capabilities: [...SLACK_CONNECTOR_CAPABILITIES],
1144
- supportedTargetKinds: ["channel", "thread", "user"],
1145
- contexts: [...SLACK_CONNECTOR_CONTEXTS],
1146
- metadata: {
1147
- service: SLACK_SERVICE_NAME,
1148
- maxMessageLength: MAX_SLACK_MESSAGE_LENGTH,
1149
- ...normalizedAccountId ? { accountId: normalizedAccountId } : {}
1150
- },
1151
- resolveTargets: (query, context) => serviceInstance.resolveConnectorTargets(query, withContextAccount(context)),
1152
- listRecentTargets: (context) => serviceInstance.listRecentConnectorTargets(withContextAccount(context)),
1153
- listRooms: (context) => serviceInstance.listConnectorRooms(withContextAccount(context)),
1154
- listServers: (context) => serviceInstance.listConnectorServers(withContextAccount(context)),
1155
- fetchMessages: (context, params) => serviceInstance.fetchConnectorMessages(withContextAccount(context), {
1156
- ...params,
1157
- ...normalizedAccountId && !params.accountId ? { accountId: normalizedAccountId } : {}
1158
- }),
1159
- searchMessages: (context, params) => serviceInstance.searchConnectorMessages(withContextAccount(context), {
1160
- ...params,
1161
- ...normalizedAccountId && !params.accountId ? { accountId: normalizedAccountId } : {}
1162
- }),
1163
- reactHandler: (handlerRuntime, params) => serviceInstance.reactConnectorMessage(handlerRuntime, {
1164
- ...params,
1165
- ...normalizedAccountId && !params.accountId ? { accountId: normalizedAccountId } : {}
1166
- }),
1167
- editHandler: (handlerRuntime, params) => serviceInstance.editConnectorMessage(handlerRuntime, {
1168
- ...params,
1169
- ...normalizedAccountId && !params.accountId ? { accountId: normalizedAccountId } : {}
1170
- }),
1171
- deleteHandler: (handlerRuntime, params) => serviceInstance.deleteConnectorMessage(handlerRuntime, {
1172
- ...params,
1173
- ...normalizedAccountId && !params.accountId ? { accountId: normalizedAccountId } : {}
1174
- }),
1175
- pinHandler: (handlerRuntime, params) => serviceInstance.pinConnectorMessage(handlerRuntime, {
1176
- ...params,
1177
- ...normalizedAccountId && !params.accountId ? { accountId: normalizedAccountId } : {}
1178
- }),
1179
- getUser: (handlerRuntime, params) => serviceInstance.getConnectorUser(handlerRuntime, {
1180
- ...params,
1181
- ...normalizedAccountId && !params.target ? {
1182
- target: {
1183
- source: "slack",
1184
- accountId: normalizedAccountId
1185
- }
1186
- } : {}
1187
- }),
1188
- getChatContext: (target, context) => serviceInstance.getConnectorChatContext(normalizedAccountId && !target.accountId ? { ...target, accountId: normalizedAccountId } : target, withContextAccount(context)),
1189
- getUserContext: (entityId, context) => serviceInstance.getConnectorUserContext(entityId, withContextAccount(context)),
1190
- sendHandler
1191
- };
1192
- runtime.registerMessageConnector(registration);
1193
- };
1194
- if (typeof runtime.registerMessageConnector === "function") {
1195
- registerConnector();
1196
- for (const accountId of serviceInstance.getRegisteredAccountIds()) {
1197
- registerConnector(accountId);
1198
- }
1199
- runtime.logger.info({ src: "plugin:slack", agentId: runtime.agentId }, "Registered Slack message connector");
1200
- return;
1201
- }
1202
- runtime.registerSendHandler("slack", serviceInstance.handleSendMessage.bind(serviceInstance));
1203
- runtime.logger.info({ src: "plugin:slack", agentId: runtime.agentId }, "Registered Slack send handler");
1204
- }
1205
- async stop() {
1206
- await this.shutdown();
1207
- }
1208
- syncDefaultAccountAliases() {
1209
- const state = this.getDefaultAccountState();
1210
- this.app = state?.app ?? null;
1211
- this.client = state?.client ?? null;
1212
- this.botUserId = state?.botUserId ?? null;
1213
- this.teamId = state?.teamId ?? null;
1214
- this.botToken = state?.account.botToken ?? null;
1215
- this.appToken = state?.account.appToken ?? null;
1216
- this.signingSecret = state?.account.signingSecret ?? null;
1217
- this.isConnected = Array.from(this.accountStates?.values?.() ?? []).some((accountState) => accountState.isConnected);
1218
- }
1219
- async initializeAccount(account) {
1220
- const accountId = normalizeAccountId(account.accountId);
1221
- const cached = this.accountStates.get(accountId);
1222
- if (cached) {
1223
- return cached;
1224
- }
1225
- const starting = this.accountStarts.get(accountId);
1226
- if (starting) {
1227
- return starting;
1228
- }
1229
- const startPromise = (async () => {
1230
- if (!account.botToken || !account.appToken) {
1231
- throw new Error(`Slack account ${accountId} requires bot and app tokens`);
1232
- }
1233
- this.runtime.logger.info({ src: "plugin:slack", agentId: this.runtime.agentId, accountId }, "Initializing Slack service with Socket Mode");
1234
- const app = new App({
1235
- token: account.botToken,
1236
- appToken: account.appToken,
1237
- socketMode: true,
1238
- logLevel: LogLevel.INFO,
1239
- ...account.signingSecret ? { signingSecret: account.signingSecret } : {}
1240
- });
1241
- const state = {
1242
- accountId,
1243
- account,
1244
- app,
1245
- client: app.client,
1246
- botUserId: null,
1247
- teamId: null,
1248
- settings: this.loadSettings(account),
1249
- allowedChannelIds: this.buildAllowedChannelSet(account),
1250
- dynamicChannelIds: new Set,
1251
- userCache: new Map,
1252
- channelCache: new Map,
1253
- isConnected: false
1254
- };
1255
- const authResult = await state.client.auth.test();
1256
- state.botUserId = authResult.user_id;
1257
- state.teamId = authResult.team_id;
1258
- this.accountStates.set(accountId, state);
1259
- this.syncDefaultAccountAliases();
1260
- this.runtime.logger.info({
1261
- src: "plugin:slack",
1262
- agentId: this.runtime.agentId,
1263
- accountId,
1264
- botUserId: state.botUserId,
1265
- teamId: state.teamId
1266
- }, "Slack bot authenticated");
1267
- this.registerEventHandlers(state);
1268
- try {
1269
- await app.start();
1270
- } catch (error) {
1271
- this.accountStates.delete(accountId);
1272
- this.syncDefaultAccountAliases();
1273
- throw error;
1274
- }
1275
- state.isConnected = true;
1276
- this.syncDefaultAccountAliases();
1277
- this.runtime.logger.info({ src: "plugin:slack", agentId: this.runtime.agentId, accountId }, "Slack account started successfully");
1278
- await this.ensureWorkspaceExists(accountId);
1279
- return state;
1280
- })();
1281
- this.accountStarts.set(accountId, startPromise);
1282
- try {
1283
- return await startPromise;
1284
- } finally {
1285
- this.accountStarts.delete(accountId);
1286
- }
1287
- }
1288
- async shutdown() {
1289
- const states = this.accountStates instanceof Map ? Array.from(this.accountStates.values()) : [];
1290
- if (states.length > 0) {
1291
- for (const state of states) {
1292
- await state.app.stop();
1293
- state.isConnected = false;
1294
- this.runtime.logger.info({
1295
- src: "plugin:slack",
1296
- agentId: this.runtime.agentId,
1297
- accountId: state.accountId
1298
- }, "Slack account stopped");
1299
- }
1300
- this.accountStates.clear();
1301
- this.syncDefaultAccountAliases();
1302
- return;
1303
- }
1304
- if (this.app) {
1305
- await this.app.stop();
1306
- this.app = null;
1307
- this.client = null;
1308
- this.isConnected = false;
1309
- this.runtime.logger.info({ src: "plugin:slack", agentId: this.runtime.agentId }, "Slack service stopped");
1310
- }
1311
- }
1312
- registerEventHandlers(state) {
1313
- const app = state?.app ?? this.app;
1314
- const accountId = state?.accountId ?? this.defaultAccountId;
1315
- if (!app)
1316
- return;
1317
- app.message(async ({ message, client }) => {
1318
- await this.handleMessage(message, client, accountId);
1319
- });
1320
- app.event("app_mention", async ({ event, client }) => {
1321
- await this.handleAppMention(event, client, accountId);
1322
- });
1323
- app.event("reaction_added", async ({ event }) => {
1324
- await this.handleReactionAdded(event, accountId);
1325
- });
1326
- app.event("reaction_removed", async ({ event }) => {
1327
- await this.handleReactionRemoved(event, accountId);
1328
- });
1329
- app.event("member_joined_channel", async ({ event }) => {
1330
- await this.handleMemberJoinedChannel(event, accountId);
1331
- });
1332
- app.event("member_left_channel", async ({ event }) => {
1333
- await this.handleMemberLeftChannel(event, accountId);
1334
- });
1335
- app.event("file_shared", async ({ event }) => {
1336
- await this.handleFileShared(event, accountId);
1337
- });
1338
- }
1339
- getDefaultAccountState() {
1340
- const states = this.accountStates;
1341
- if (!(states instanceof Map) || states.size === 0) {
1342
- return null;
1343
- }
1344
- const defaultId = normalizeAccountId(this.defaultAccountId ?? DEFAULT_ACCOUNT_ID);
1345
- return states.get(defaultId) ?? states.values().next().value ?? null;
1346
- }
1347
- getAccountState(accountId) {
1348
- const states = this.accountStates;
1349
- if (!(states instanceof Map) || states.size === 0) {
1350
- return null;
1351
- }
1352
- if (accountId) {
1353
- return states.get(normalizeAccountId(accountId)) ?? null;
1354
- }
1355
- return this.getDefaultAccountState();
1356
- }
1357
- getClientForAccount(accountId) {
1358
- const state = this.getAccountState(accountId);
1359
- if (state?.client) {
1360
- return state.client;
1361
- }
1362
- const requested = accountId ? normalizeAccountId(accountId) : null;
1363
- const defaultId = normalizeAccountId(this.defaultAccountId ?? DEFAULT_ACCOUNT_ID);
1364
- if (!requested || requested === defaultId) {
1365
- return this.client;
1366
- }
1367
- return null;
1368
- }
1369
- getSettingsForAccount(accountId) {
1370
- return this.getAccountState(accountId)?.settings ?? this.settings;
1371
- }
1372
- getAllowedChannelIdsForAccount(accountId) {
1373
- return this.getAccountState(accountId)?.allowedChannelIds ?? this.allowedChannelIds;
1374
- }
1375
- getDynamicChannelIdsForAccount(accountId) {
1376
- return this.getAccountState(accountId)?.dynamicChannelIds ?? this.dynamicChannelIds;
1377
- }
1378
- getUserCacheForAccount(accountId) {
1379
- return this.getAccountState(accountId)?.userCache ?? this.userCache;
1380
- }
1381
- getChannelCacheForAccount(accountId) {
1382
- return this.getAccountState(accountId)?.channelCache ?? this.channelCache;
1383
- }
1384
- getBotUserIdForAccount(accountId) {
1385
- return this.getAccountState(accountId)?.botUserId ?? this.botUserId;
1386
- }
1387
- getTeamIdForAccount(accountId) {
1388
- return this.getAccountState(accountId)?.teamId ?? this.teamId;
1389
- }
1390
- getRegisteredAccountIds() {
1391
- const states = this.accountStates;
1392
- if (states instanceof Map && states.size > 0) {
1393
- return Array.from(states.keys());
1394
- }
1395
- return [normalizeAccountId(this.defaultAccountId ?? DEFAULT_ACCOUNT_ID)];
1396
- }
1397
- resolveAccountIdFromContext(context, target) {
1398
- const scopedTarget = target;
1399
- const scopedContext = context;
1400
- return scopedTarget?.accountId ?? scopedContext?.target?.accountId ?? scopedContext?.accountId ?? scopedContext?.account?.accountId ?? undefined;
1401
- }
1402
- async resolveAccountIdForTarget(runtime, target, fallback) {
1403
- const direct = target?.accountId ?? fallback?.accountId ?? undefined;
1404
- if (direct) {
1405
- return normalizeAccountId(direct);
1406
- }
1407
- const roomId = target?.roomId ?? fallback?.roomId;
1408
- if (roomId && typeof runtime.getRoom === "function") {
1409
- const room = await runtime.getRoom(roomId);
1410
- const metadata = room?.metadata;
1411
- if (typeof metadata?.accountId === "string" && metadata.accountId.trim()) {
1412
- return normalizeAccountId(metadata.accountId);
1413
- }
1414
- const slack = metadata?.slack && typeof metadata.slack === "object" ? metadata.slack : undefined;
1415
- if (typeof slack?.accountId === "string" && slack.accountId.trim()) {
1416
- return normalizeAccountId(slack.accountId);
1417
- }
1418
- }
1419
- return normalizeAccountId(this.defaultAccountId ?? DEFAULT_ACCOUNT_ID);
1420
- }
1421
- getCandidateAccountIds(context, target) {
1422
- const explicit = this.resolveAccountIdFromContext(context, target);
1423
- if (explicit) {
1424
- return [normalizeAccountId(explicit)];
1425
- }
1426
- return this.getRegisteredAccountIds();
1427
- }
1428
- scopedSlackKey(prefix, key, accountId) {
1429
- const normalized = normalizeAccountId(accountId ?? this.defaultAccountId ?? DEFAULT_ACCOUNT_ID);
1430
- return normalized === DEFAULT_ACCOUNT_ID ? `${prefix}-${key}` : `${prefix}-${normalized}-${key}`;
1431
- }
1432
- buildEventPayload(accountId) {
1433
- const normalized = normalizeAccountId(accountId ?? this.defaultAccountId ?? DEFAULT_ACCOUNT_ID);
1434
- return {
1435
- runtime: this.runtime,
1436
- source: "slack",
1437
- accountId: normalized,
1438
- metadata: { accountId: normalized }
1439
- };
1440
- }
1441
- async handleMessage(message, _client, accountId = this.defaultAccountId) {
1442
- const settings = this.getSettingsForAccount(accountId);
1443
- const botUserId = this.getBotUserIdForAccount(accountId);
1444
- if (settings.shouldIgnoreBotMessages && message.bot_id) {
1445
- return;
1446
- }
1447
- if (message.user === botUserId) {
1448
- return;
1449
- }
1450
- if (!this.isChannelAllowed(message.channel, accountId)) {
1451
- this.runtime.logger.debug({
1452
- src: "plugin:slack",
1453
- agentId: this.runtime.agentId,
1454
- accountId,
1455
- channelId: message.channel
1456
- }, "Message received in non-allowed channel, ignoring");
1457
- return;
1458
- }
1459
- const isMentioned = message.text?.includes(`<@${botUserId}>`);
1460
- if (isMentioned && message.channel_type !== "im") {
1461
- return;
1462
- }
1463
- if (settings.shouldRespondOnlyToMentions && !isMentioned) {
1464
- return;
1465
- }
1466
- const _isThreadReply = Boolean(message.thread_ts && message.thread_ts !== message.ts);
1467
- const memory = await this.buildMemoryFromMessage(message, accountId);
1468
- if (!memory)
1469
- return;
1470
- const room = await this.ensureRoomExists(message.channel, message.thread_ts, accountId);
1471
- const existingEntity = await this.runtime.getEntityById(memory.entityId);
1472
- if (!existingEntity) {
1473
- const user = await this.getUser(message.user, accountId);
1474
- const displayName = user ? getSlackUserDisplayName(user) : message.user;
1475
- await this.runtime.createEntity({
1476
- id: memory.entityId,
1477
- names: [displayName],
1478
- metadata: {
1479
- source: "slack",
1480
- accountId,
1481
- slack: {
1482
- accountId,
1483
- id: message.user,
1484
- name: displayName,
1485
- userName: user?.name || message.user
1486
- }
1487
- },
1488
- agentId: this.runtime.agentId
1489
- });
1490
- }
1491
- await this.runtime.createMemory(memory, "messages");
1492
- await this.runtime.emitEvent("SLACK_MESSAGE_RECEIVED" /* MESSAGE_RECEIVED */, this.buildEventPayload(accountId));
1493
- await this.processAgentMessage(memory, room, message.channel, message.thread_ts || message.ts, accountId);
1494
- }
1495
- async handleAppMention(event, _client, accountId = this.defaultAccountId) {
1496
- if (!event.user)
1497
- return;
1498
- const memory = await this.buildMemoryFromMention({
1499
- user: event.user,
1500
- text: event.text,
1501
- channel: event.channel,
1502
- ts: event.ts,
1503
- thread_ts: event.thread_ts
1504
- }, accountId);
1505
- if (!memory)
1506
- return;
1507
- const room = await this.ensureRoomExists(event.channel, event.thread_ts, accountId);
1508
- const existingEntity = await this.runtime.getEntityById(memory.entityId);
1509
- if (!existingEntity) {
1510
- const user = await this.getUser(event.user, accountId);
1511
- const displayName = user ? getSlackUserDisplayName(user) : event.user;
1512
- await this.runtime.createEntity({
1513
- id: memory.entityId,
1514
- names: [displayName],
1515
- metadata: {
1516
- source: "slack",
1517
- accountId,
1518
- slack: {
1519
- accountId,
1520
- id: event.user,
1521
- name: displayName,
1522
- userName: user?.name || event.user
1523
- }
1524
- },
1525
- agentId: this.runtime.agentId
1526
- });
1527
- }
1528
- await this.runtime.createMemory(memory, "messages");
1529
- await this.runtime.emitEvent("SLACK_APP_MENTION" /* APP_MENTION */, this.buildEventPayload(accountId));
1530
- await this.processAgentMessage(memory, room, event.channel, event.thread_ts || event.ts, accountId);
1531
- }
1532
- async handleReactionAdded(_event, accountId = this.defaultAccountId) {
1533
- await this.runtime.emitEvent("SLACK_REACTION_ADDED" /* REACTION_ADDED */, this.buildEventPayload(accountId));
1534
- }
1535
- async handleReactionRemoved(_event, accountId = this.defaultAccountId) {
1536
- await this.runtime.emitEvent("SLACK_REACTION_REMOVED" /* REACTION_REMOVED */, this.buildEventPayload(accountId));
1537
- }
1538
- async handleMemberJoinedChannel(event, accountId = this.defaultAccountId) {
1539
- if (event.user === this.getBotUserIdForAccount(accountId)) {
1540
- this.getDynamicChannelIdsForAccount(accountId).add(event.channel);
1541
- await this.ensureRoomExists(event.channel, undefined, accountId);
1542
- }
1543
- await this.runtime.emitEvent("SLACK_MEMBER_JOINED_CHANNEL" /* MEMBER_JOINED_CHANNEL */, this.buildEventPayload(accountId));
1544
- }
1545
- async handleMemberLeftChannel(event, accountId = this.defaultAccountId) {
1546
- if (event.user === this.getBotUserIdForAccount(accountId)) {
1547
- this.getDynamicChannelIdsForAccount(accountId).delete(event.channel);
1548
- }
1549
- await this.runtime.emitEvent("SLACK_MEMBER_LEFT_CHANNEL" /* MEMBER_LEFT_CHANNEL */, this.buildEventPayload(accountId));
1550
- }
1551
- async handleFileShared(_event, accountId = this.defaultAccountId) {
1552
- await this.runtime.emitEvent("SLACK_FILE_SHARED" /* FILE_SHARED */, this.buildEventPayload(accountId));
1553
- }
1554
- isChannelAllowed(channelId, accountId) {
1555
- const allowedChannelIds = this.getAllowedChannelIdsForAccount(accountId);
1556
- const dynamicChannelIds = this.getDynamicChannelIdsForAccount(accountId);
1557
- if (allowedChannelIds.size === 0 && dynamicChannelIds.size === 0) {
1558
- return true;
1559
- }
1560
- return allowedChannelIds.has(channelId) || dynamicChannelIds.has(channelId);
1561
- }
1562
- async processAgentMessage(memory, room, channelId, threadTs, accountId = this.defaultAccountId) {
1563
- const callback = async (response) => {
1564
- const responseText = response.text || "";
1565
- if (!responseText.trim()) {
1566
- this.runtime.logger.warn({ src: "plugin:slack", channelId, roomId: room.id }, "Empty response from model, skipping sendMessage");
1567
- return [];
1568
- }
1569
- await this.sendMessage(channelId, responseText, {
1570
- threadTs,
1571
- replyBroadcast: undefined,
1572
- unfurlLinks: undefined,
1573
- unfurlMedia: undefined,
1574
- mrkdwn: undefined,
1575
- attachments: undefined,
1576
- blocks: undefined
1577
- }, accountId);
1578
- const responseMemory = {
1579
- id: createUniqueUuid(this.runtime, `slack-response-${Date.now()}`),
1580
- agentId: this.runtime.agentId,
1581
- roomId: room.id,
1582
- entityId: this.runtime.agentId,
1583
- content: {
1584
- text: response.text || "",
1585
- source: "slack",
1586
- inReplyTo: memory.id,
1587
- metadata: { accountId }
1588
- },
1589
- metadata: {
1590
- type: "message",
1591
- source: "slack",
1592
- provider: "slack",
1593
- accountId,
1594
- fromBot: true,
1595
- fromId: this.runtime.agentId,
1596
- sourceId: this.runtime.agentId,
1597
- slack: {
1598
- accountId,
1599
- channelId,
1600
- threadTs
1601
- }
1602
- },
1603
- createdAt: Date.now()
1604
- };
1605
- await this.runtime.createMemory(responseMemory, "messages");
1606
- await this.runtime.emitEvent("SLACK_MESSAGE_SENT" /* MESSAGE_SENT */, this.buildEventPayload(accountId));
1607
- return [responseMemory];
1608
- };
1609
- const messageService = getMessageService(this.runtime);
1610
- if (messageService) {
1611
- await messageService.handleMessage(this.runtime, memory, callback);
1612
- }
1613
- }
1614
- async buildMemoryFromMessage(message, accountId = this.defaultAccountId) {
1615
- if (!message.user)
1616
- return null;
1617
- const roomId = await this.getRoomId(message.channel, message.thread_ts, accountId);
1618
- const entityId = this.getEntityId(message.user, accountId);
1619
- const user = await this.getUser(message.user, accountId);
1620
- const displayName = user ? getSlackUserDisplayName(user) : message.user;
1621
- const media = [];
1622
- if ("files" in message && message.files) {
1623
- for (const file of message.files) {
1624
- media.push({
1625
- id: file.id,
1626
- url: file.urlPrivate,
1627
- title: file.title || file.name,
1628
- source: "slack",
1629
- description: file.name
1630
- });
1631
- }
1632
- }
1633
- const memory = {
1634
- id: createUniqueUuid(this.runtime, this.scopedSlackKey("slack", message.ts, accountId)),
1635
- agentId: this.runtime.agentId,
1636
- roomId,
1637
- entityId,
1638
- content: {
1639
- text: message.text || "",
1640
- source: "slack",
1641
- name: displayName,
1642
- metadata: { accountId },
1643
- ...media.length > 0 ? { attachments: media } : {}
1644
- },
1645
- metadata: {
1646
- type: "message",
1647
- source: "slack",
1648
- provider: "slack",
1649
- accountId,
1650
- timestamp: this.parseSlackTimestamp(message.ts),
1651
- entityName: displayName,
1652
- entityUserName: user?.name ?? message.user,
1653
- fromBot: false,
1654
- fromId: message.user,
1655
- sourceId: entityId,
1656
- chatType: message.channel_type,
1657
- messageIdFull: message.ts,
1658
- sender: {
1659
- id: message.user,
1660
- name: displayName,
1661
- username: user?.name ?? message.user
1662
- },
1663
- slack: {
1664
- accountId,
1665
- teamId: this.getTeamIdForAccount(accountId) ?? undefined,
1666
- channelId: message.channel,
1667
- userId: message.user,
1668
- messageId: message.ts,
1669
- threadTs: message.thread_ts
1670
- },
1671
- slackChannelId: message.channel,
1672
- slackMessageTs: message.ts,
1673
- slackThreadTs: message.thread_ts
1674
- },
1675
- createdAt: this.parseSlackTimestamp(message.ts)
1676
- };
1677
- return memory;
1678
- }
1679
- async buildMemoryFromMention(event, accountId = this.defaultAccountId) {
1680
- const roomId = await this.getRoomId(event.channel, event.thread_ts, accountId);
1681
- const entityId = this.getEntityId(event.user, accountId);
1682
- const user = await this.getUser(event.user, accountId);
1683
- const displayName = user ? getSlackUserDisplayName(user) : event.user;
1684
- const cleanText = event.text.replace(`<@${this.getBotUserIdForAccount(accountId)}>`, "").trim();
1685
- const memory = {
1686
- id: createUniqueUuid(this.runtime, this.scopedSlackKey("slack-mention", event.ts, accountId)),
1687
- agentId: this.runtime.agentId,
1688
- roomId,
1689
- entityId,
1690
- content: {
1691
- text: cleanText,
1692
- source: "slack",
1693
- name: displayName,
1694
- metadata: { accountId },
1695
- mentionContext: { isMention: true, isReply: false, isThread: false }
1696
- },
1697
- metadata: {
1698
- type: "message",
1699
- source: "slack",
1700
- provider: "slack",
1701
- accountId,
1702
- timestamp: this.parseSlackTimestamp(event.ts),
1703
- entityName: displayName,
1704
- entityUserName: user?.name ?? event.user,
1705
- fromBot: false,
1706
- fromId: event.user,
1707
- sourceId: entityId,
1708
- messageIdFull: event.ts,
1709
- slack: {
1710
- accountId,
1711
- teamId: this.getTeamIdForAccount(accountId) ?? undefined,
1712
- channelId: event.channel,
1713
- userId: event.user,
1714
- messageId: event.ts,
1715
- threadTs: event.thread_ts
1716
- },
1717
- slackChannelId: event.channel,
1718
- slackMessageTs: event.ts,
1719
- slackThreadTs: event.thread_ts
1720
- },
1721
- createdAt: this.parseSlackTimestamp(event.ts)
1722
- };
1723
- return memory;
1724
- }
1725
- async getRoomId(channelId, threadTs, accountId) {
1726
- const roomKey = threadTs ? `${channelId}-${threadTs}` : channelId;
1727
- return createUniqueUuid(this.runtime, this.scopedSlackKey("slack-room", roomKey, accountId));
1728
- }
1729
- getEntityId(userId, accountId) {
1730
- return stringToUuid(this.scopedSlackKey("slack-user", userId, accountId));
1731
- }
1732
- parseSlackTimestamp(ts) {
1733
- const [seconds] = ts.split(".");
1734
- return parseInt(seconds, 10) * 1000;
1735
- }
1736
- async ensureWorkspaceExists(accountId = this.defaultAccountId) {
1737
- const teamId = this.getTeamIdForAccount(accountId);
1738
- const client = this.getClientForAccount(accountId);
1739
- if (!teamId || !client)
1740
- return;
1741
- const worldId = createUniqueUuid(this.runtime, this.scopedSlackKey("slack-workspace", teamId, accountId));
1742
- const existingWorld = await this.runtime.getWorld(worldId);
1743
- if (existingWorld)
1744
- return;
1745
- const teamInfo = await client.team.info();
1746
- const team = teamInfo.team;
1747
- const world = {
1748
- id: worldId,
1749
- name: team?.name || `Slack Workspace ${teamId}`,
1750
- agentId: this.runtime.agentId,
1751
- metadata: {
1752
- type: "slack",
1753
- source: "slack",
1754
- accountId,
1755
- extra: {
1756
- accountId,
1757
- teamId,
1758
- domain: team?.domain
1759
- }
1760
- }
1761
- };
1762
- await this.runtime.createWorld(world);
1763
- this.runtime.logger.info({
1764
- src: "plugin:slack",
1765
- agentId: this.runtime.agentId,
1766
- accountId,
1767
- worldId,
1768
- teamId
1769
- }, "Created Slack workspace world");
1770
- }
1771
- async ensureRoomExists(channelId, threadTs, accountId = this.defaultAccountId) {
1772
- const roomId = await this.getRoomId(channelId, threadTs, accountId);
1773
- const existingRoom = await this.runtime.getRoom(roomId);
1774
- if (existingRoom)
1775
- return existingRoom;
1776
- const channel = await this.getChannel(channelId, accountId);
1777
- const channelType = channel ? getSlackChannelType(channel) : "channel";
1778
- const teamId = this.getTeamIdForAccount(accountId);
1779
- const worldId = teamId ? createUniqueUuid(this.runtime, this.scopedSlackKey("slack-workspace", teamId, accountId)) : undefined;
1780
- const elizaChannelType = channelType === "im" ? ChannelType.DM : channelType === "mpim" ? ChannelType.GROUP : ChannelType.GROUP;
1781
- const room = {
1782
- id: roomId,
1783
- name: channel?.name || channelId,
1784
- agentId: this.runtime.agentId,
1785
- source: "slack",
1786
- type: elizaChannelType,
1787
- channelId,
1788
- worldId,
1789
- metadata: {
1790
- source: "slack",
1791
- accountId,
1792
- slackChannelType: channelType,
1793
- threadTs,
1794
- topic: channel?.topic?.value,
1795
- purpose: channel?.purpose?.value,
1796
- serverId: teamId,
1797
- slack: {
1798
- accountId,
1799
- teamId,
1800
- channelId,
1801
- threadTs
1802
- }
1803
- }
1804
- };
1805
- await this.runtime.createRoom(room);
1806
- this.runtime.logger.debug({
1807
- src: "plugin:slack",
1808
- agentId: this.runtime.agentId,
1809
- accountId,
1810
- roomId,
1811
- channelId,
1812
- threadTs
1813
- }, "Created Slack room");
1814
- return room;
1815
- }
1816
- buildConnectorChannelTarget(channel, score = 0.5, accountId = this.defaultAccountId) {
1817
- if (!channel.id || channel.isArchived) {
1818
- return null;
1819
- }
1820
- if (!this.isChannelAllowed(channel.id, accountId)) {
1821
- return null;
1822
- }
1823
- const kind = channel.isIm ? "user" : channel.isMpim ? "group" : "channel";
1824
- const label = channel.name ? `#${channel.name}` : channel.id;
1825
- const teamId = this.getTeamIdForAccount(accountId);
1826
- return {
1827
- target: {
1828
- source: "slack",
1829
- accountId,
1830
- channelId: channel.id,
1831
- serverId: teamId ?? undefined
1832
- },
1833
- label,
1834
- kind,
1835
- description: channel.purpose?.value || channel.topic?.value || label,
1836
- score,
1837
- contexts: ["social", "connectors"],
1838
- metadata: {
1839
- accountId,
1840
- slackChannelId: channel.id,
1841
- slackTeamId: teamId,
1842
- slackChannelType: getSlackChannelType(channel),
1843
- channelName: channel.name,
1844
- isPrivate: channel.isPrivate,
1845
- isMember: channel.isMember,
1846
- topic: channel.topic?.value,
1847
- purpose: channel.purpose?.value
1848
- }
1849
- };
1850
- }
1851
- buildConnectorUserTarget(user, score = 0.5, accountId = this.defaultAccountId) {
1852
- if (!user.id || user.deleted || user.isBot || user.isAppUser) {
1853
- return null;
1854
- }
1855
- const label = getSlackUserDisplayName(user);
1856
- return {
1857
- target: {
1858
- source: "slack",
1859
- accountId,
1860
- entityId: user.id,
1861
- serverId: user.teamId ?? this.getTeamIdForAccount(accountId) ?? undefined
1862
- },
1863
- label: `@${label}`,
1864
- kind: "user",
1865
- description: user.profile.title || "Slack user",
1866
- score,
1867
- contexts: ["social", "connectors"],
1868
- metadata: {
1869
- accountId,
1870
- slackUserId: user.id,
1871
- slackTeamId: user.teamId ?? this.getTeamIdForAccount(accountId),
1872
- slackName: user.name,
1873
- slackRealName: user.realName,
1874
- slackDisplayName: user.profile.displayName,
1875
- email: user.profile.email
1876
- }
1877
- };
1878
- }
1879
- dedupeConnectorTargets(targets) {
1880
- const byKey = new Map;
1881
- for (const target of targets) {
1882
- const key = [
1883
- target.target.accountId ?? "",
1884
- target.kind ?? "target",
1885
- target.target.channelId ?? "",
1886
- target.target.entityId ?? "",
1887
- target.target.threadId ?? ""
1888
- ].join(":");
1889
- const existing = byKey.get(key);
1890
- if (!existing || (target.score ?? 0) > (existing.score ?? 0)) {
1891
- byKey.set(key, target);
1892
- }
1893
- }
1894
- return Array.from(byKey.values()).sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
1895
- }
1896
- async resolveSlackTargetUserId(runtime, entityId, accountId) {
1897
- if (SLACK_USER_ID_PATTERN.test(entityId)) {
1898
- return entityId;
1899
- }
1900
- const entity = typeof runtime.getEntityById === "function" ? await runtime.getEntityById(entityId) : null;
1901
- const metadataUserId = extractSlackUserIdFromMetadata(entity?.metadata, accountId);
1902
- if (metadataUserId) {
1903
- return metadataUserId;
1904
- }
1905
- if (typeof runtime.getRelationships !== "function") {
1906
- return null;
1907
- }
1908
- const relationships = await runtime.getRelationships({
1909
- entityIds: [entityId],
1910
- tags: ["identity_link"]
1911
- });
1912
- for (const relationship of relationships) {
1913
- const linkedEntityId = relationship.sourceEntityId === entityId ? relationship.targetEntityId : relationship.targetEntityId === entityId ? relationship.sourceEntityId : null;
1914
- if (!linkedEntityId || linkedEntityId === entityId) {
1915
- continue;
1916
- }
1917
- const linkedEntity = typeof runtime.getEntityById === "function" ? await runtime.getEntityById(linkedEntityId) : null;
1918
- const linkedUserId = extractSlackUserIdFromMetadata(linkedEntity?.metadata, accountId);
1919
- if (linkedUserId) {
1920
- return linkedUserId;
1921
- }
1922
- }
1923
- return null;
1924
- }
1925
- async openDirectMessageChannel(userId, accountId) {
1926
- const client = this.getClientForAccount(accountId);
1927
- if (!client) {
1928
- throw new Error("Slack client not initialized");
1929
- }
1930
- const result = await client.conversations.open({ users: userId });
1931
- const channel = result.channel;
1932
- if (!channel?.id) {
1933
- throw new Error(`Could not open Slack DM channel for user ${userId}`);
1934
- }
1935
- return channel.id;
1936
- }
1937
- async handleSendMessage(runtime, target, content) {
1938
- const accountId = await this.resolveAccountIdForTarget(runtime, target);
1939
- if (!this.getClientForAccount(accountId)) {
1940
- throw new Error("Slack client not initialized");
1941
- }
1942
- const text = typeof content.text === "string" ? content.text.trim() : "";
1943
- if (!text) {
1944
- throw new Error("Slack SendHandler requires non-empty text content.");
1945
- }
1946
- let channelId = target.channelId;
1947
- let threadTs = target.threadId;
1948
- if (target.roomId && (!channelId || !threadTs)) {
1949
- const room = await runtime.getRoom(target.roomId);
1950
- channelId = channelId ?? room?.channelId;
1951
- const metadata = room?.metadata;
1952
- const metadataThreadTs = typeof metadata?.threadTs === "string" ? metadata.threadTs : undefined;
1953
- threadTs = threadTs ?? metadataThreadTs;
1954
- }
1955
- if (channelId && SLACK_USER_ID_PATTERN.test(channelId)) {
1956
- channelId = await this.openDirectMessageChannel(channelId, accountId);
1957
- }
1958
- if (!channelId && target.entityId) {
1959
- const slackUserId = await this.resolveSlackTargetUserId(runtime, String(target.entityId), accountId);
1960
- if (!slackUserId) {
1961
- throw new Error(`Could not resolve Slack user ID for entity ${target.entityId}`);
1962
- }
1963
- channelId = await this.openDirectMessageChannel(slackUserId, accountId);
1964
- }
1965
- if (!channelId) {
1966
- throw new Error("Slack SendHandler requires channelId, roomId, or entityId.");
1967
- }
1968
- await this.sendMessage(channelId, text, {
1969
- threadTs,
1970
- replyBroadcast: undefined,
1971
- unfurlLinks: undefined,
1972
- unfurlMedia: undefined,
1973
- mrkdwn: undefined,
1974
- attachments: undefined,
1975
- blocks: undefined
1976
- }, accountId);
1977
- }
1978
- async resolveConnectorTargets(query, context) {
1979
- const normalizedQuery = normalizeSlackConnectorQuery(query);
1980
- const targets = [];
1981
- const accountIds = this.getCandidateAccountIds(context, context.target);
1982
- for (const accountId of accountIds) {
1983
- const client = this.getClientForAccount(accountId);
1984
- if (!client) {
1985
- continue;
1986
- }
1987
- try {
1988
- const channels = await this.listChannels({
1989
- types: "public_channel,private_channel,mpim,im",
1990
- limit: 1000
1991
- }, accountId);
1992
- for (const channel of channels) {
1993
- const score = scoreSlackConnectorMatch(normalizedQuery, channel.id, [
1994
- channel.name,
1995
- channel.topic?.value,
1996
- channel.purpose?.value
1997
- ]);
1998
- if (score <= 0) {
1999
- continue;
2000
- }
2001
- const target = this.buildConnectorChannelTarget(channel, score, accountId);
2002
- if (target) {
2003
- targets.push(target);
2004
- }
2005
- }
2006
- } catch (error) {
2007
- this.runtime.logger.debug({
2008
- src: "plugin:slack",
2009
- agentId: this.runtime.agentId,
2010
- accountId,
2011
- error: error instanceof Error ? error.message : String(error)
2012
- }, "Slack connector channel query failed");
2013
- }
2014
- try {
2015
- const usersResult = await client.users.list({ limit: 200 });
2016
- const members = usersResult.members ?? [];
2017
- for (const member of members) {
2018
- const user = {
2019
- id: member.id ?? "",
2020
- teamId: member.team_id,
2021
- name: member.name ?? "",
2022
- deleted: Boolean(member.deleted),
2023
- realName: member.real_name,
2024
- tz: member.tz,
2025
- tzLabel: member.tz_label,
2026
- tzOffset: member.tz_offset,
2027
- profile: {
2028
- title: member.profile?.title,
2029
- phone: member.profile?.phone,
2030
- skype: member.profile?.skype,
2031
- realName: member.profile?.real_name,
2032
- realNameNormalized: member.profile?.real_name_normalized,
2033
- displayName: member.profile?.display_name,
2034
- displayNameNormalized: member.profile?.display_name_normalized,
2035
- statusText: member.profile?.status_text,
2036
- statusEmoji: member.profile?.status_emoji,
2037
- statusExpiration: member.profile?.status_expiration,
2038
- avatarHash: member.profile?.avatar_hash,
2039
- email: member.profile?.email,
2040
- image24: member.profile?.image_24,
2041
- image32: member.profile?.image_32,
2042
- image48: member.profile?.image_48,
2043
- image72: member.profile?.image_72,
2044
- image192: member.profile?.image_192,
2045
- image512: member.profile?.image_512,
2046
- image1024: member.profile?.image_1024,
2047
- imageOriginal: member.profile?.image_original,
2048
- team: member.profile?.team
2049
- },
2050
- isAdmin: Boolean(member.is_admin),
2051
- isOwner: Boolean(member.is_owner),
2052
- isPrimaryOwner: Boolean(member.is_primary_owner),
2053
- isRestricted: Boolean(member.is_restricted),
2054
- isUltraRestricted: Boolean(member.is_ultra_restricted),
2055
- isBot: Boolean(member.is_bot),
2056
- isAppUser: Boolean(member.is_app_user),
2057
- updated: member.updated ?? 0
2058
- };
2059
- const score = scoreSlackConnectorMatch(normalizedQuery, user.id, [
2060
- user.name,
2061
- user.realName,
2062
- user.profile.displayName,
2063
- user.profile.realName,
2064
- user.profile.email
2065
- ]);
2066
- if (score <= 0) {
2067
- continue;
2068
- }
2069
- const target = this.buildConnectorUserTarget(user, score, accountId);
2070
- if (target) {
2071
- targets.push(target);
2072
- }
2073
- }
2074
- } catch (error) {
2075
- this.runtime.logger.debug({
2076
- src: "plugin:slack",
2077
- agentId: this.runtime.agentId,
2078
- accountId,
2079
- error: error instanceof Error ? error.message : String(error)
2080
- }, "Slack connector user query failed");
2081
- }
2082
- if (context.target?.channelId) {
2083
- const channel = await this.getChannel(context.target.channelId, accountId);
2084
- if (channel) {
2085
- const target = this.buildConnectorChannelTarget(channel, 0.6, accountId);
2086
- if (target) {
2087
- targets.push(target);
2088
- }
2089
- }
2090
- }
2091
- }
2092
- return this.dedupeConnectorTargets(targets).slice(0, 25);
2093
- }
2094
- async listConnectorRooms(context) {
2095
- const targets = [];
2096
- for (const accountId of this.getCandidateAccountIds(context)) {
2097
- if (!this.getClientForAccount(accountId)) {
2098
- continue;
2099
- }
2100
- const channels = await this.listChannels({
2101
- types: "public_channel,private_channel,mpim,im",
2102
- limit: 1000
2103
- }, accountId);
2104
- targets.push(...channels.map((channel) => this.buildConnectorChannelTarget(channel, 0.5, accountId)).filter((target) => Boolean(target)));
2105
- }
2106
- return this.dedupeConnectorTargets(targets).slice(0, 50);
2107
- }
2108
- async listRecentConnectorTargets(context) {
2109
- const targets = [];
2110
- const room = context.roomId && typeof context.runtime.getRoom === "function" ? await context.runtime.getRoom(context.roomId) : null;
2111
- const accountId = await this.resolveAccountIdForTarget(context.runtime, context.target, {
2112
- accountId: context.accountId,
2113
- roomId: context.roomId
2114
- });
2115
- const channelId = context.target?.channelId ?? (room?.source === "slack" ? room.channelId : undefined);
2116
- const roomMetadata = room?.metadata;
2117
- const threadTs = context.target?.threadId ?? (typeof roomMetadata?.threadTs === "string" ? roomMetadata.threadTs : undefined);
2118
- if (channelId) {
2119
- const channel = await this.getChannel(channelId, accountId);
2120
- if (channel) {
2121
- const target = this.buildConnectorChannelTarget(channel, 0.95, accountId);
2122
- if (target) {
2123
- if (threadTs) {
2124
- target.kind = "thread";
2125
- target.target.threadId = threadTs;
2126
- target.label = `${target.label ?? channelId} thread`;
2127
- target.metadata = {
2128
- ...target.metadata ?? {},
2129
- slackThreadTs: threadTs
2130
- };
2131
- }
2132
- targets.push(target);
2133
- }
2134
- }
2135
- }
2136
- targets.push(...await this.listConnectorRooms({
2137
- ...context,
2138
- accountId
2139
- }));
2140
- return this.dedupeConnectorTargets(targets).slice(0, 25);
2141
- }
2142
- async getConnectorChatContext(target, context) {
2143
- const accountId = await this.resolveAccountIdForTarget(context.runtime, target, {
2144
- accountId: context.accountId,
2145
- roomId: context.roomId
2146
- });
2147
- const client = this.getClientForAccount(accountId);
2148
- if (!client) {
2149
- return null;
2150
- }
2151
- const room = target.roomId && typeof context.runtime.getRoom === "function" ? await context.runtime.getRoom(target.roomId) : null;
2152
- const channelId = target.channelId ?? room?.channelId;
2153
- if (!channelId) {
2154
- return null;
2155
- }
2156
- const metadata = room?.metadata;
2157
- const threadTs = target.threadId ?? (typeof metadata?.threadTs === "string" ? metadata.threadTs : undefined);
2158
- const channel = await this.getChannel(channelId, accountId);
2159
- const messages = threadTs ? await client.conversations.replies({
2160
- channel: channelId,
2161
- ts: threadTs,
2162
- limit: 10
2163
- }) : {
2164
- messages: await this.readHistory(channelId, { limit: 10 }, accountId)
2165
- };
2166
- const rawMessages = messages.messages ?? [];
2167
- const recentMessages = [];
2168
- for (const rawMessage of rawMessages.slice().reverse()) {
2169
- const text = String(rawMessage.text ?? "");
2170
- if (!text.trim()) {
2171
- continue;
2172
- }
2173
- const userId = rawMessage.user ?? rawMessage.user;
2174
- const user = typeof userId === "string" ? await this.getUser(userId, accountId) : null;
2175
- recentMessages.push({
2176
- entityId: userId ? userId : undefined,
2177
- name: user ? getSlackUserDisplayName(user) : userId,
2178
- text,
2179
- timestamp: Number(rawMessage.ts) * 1000 || undefined,
2180
- metadata: {
2181
- accountId,
2182
- slackMessageTs: rawMessage.ts,
2183
- slackUserId: userId
2184
- }
2185
- });
2186
- }
2187
- return {
2188
- target: {
2189
- source: "slack",
2190
- accountId,
2191
- roomId: target.roomId ?? room?.id,
2192
- channelId,
2193
- serverId: target.serverId ?? this.getTeamIdForAccount(accountId) ?? undefined,
2194
- threadId: threadTs
2195
- },
2196
- label: channel?.name ? `#${channel.name}` : channelId,
2197
- summary: channel?.purpose?.value || channel?.topic?.value,
2198
- recentMessages,
2199
- metadata: {
2200
- accountId,
2201
- slackChannelId: channelId,
2202
- slackTeamId: this.getTeamIdForAccount(accountId),
2203
- slackThreadTs: threadTs,
2204
- channelName: channel?.name
2205
- }
2206
- };
2207
- }
2208
- async resolveConnectorMessageLocation(target, fallback) {
2209
- const accountId = await this.resolveAccountIdForTarget(this.runtime, target, fallback);
2210
- let channelId = target?.channelId ?? fallback?.channelId;
2211
- let threadTs = target?.threadId ?? fallback?.threadId;
2212
- const roomId = target?.roomId ?? fallback?.roomId;
2213
- if (roomId && (!channelId || !threadTs)) {
2214
- const room = await this.runtime.getRoom(roomId);
2215
- channelId = channelId ?? room?.channelId;
2216
- const metadata = room?.metadata;
2217
- const roomThreadTs = typeof metadata?.threadTs === "string" ? metadata.threadTs : undefined;
2218
- threadTs = threadTs ?? roomThreadTs;
2219
- }
2220
- if (channelId && SLACK_USER_ID_PATTERN.test(channelId)) {
2221
- channelId = await this.openDirectMessageChannel(channelId, accountId);
2222
- }
2223
- if (!channelId && target?.entityId) {
2224
- const slackUserId = await this.resolveSlackTargetUserId(this.runtime, String(target.entityId), accountId);
2225
- if (slackUserId) {
2226
- channelId = await this.openDirectMessageChannel(slackUserId, accountId);
2227
- }
2228
- }
2229
- if (!channelId) {
2230
- throw new Error("Slack message operation requires channelId, roomId, or entityId.");
2231
- }
2232
- return { accountId, channelId, threadTs };
2233
- }
2234
- async slackMessageToMemory(message, channelId, threadTs, accountId = this.defaultAccountId) {
2235
- const effectiveThreadTs = threadTs ?? message.threadTs;
2236
- const roomId = await this.getRoomId(channelId, effectiveThreadTs, accountId);
2237
- const botUserId = this.getBotUserIdForAccount(accountId);
2238
- const slackUserId = message.user ?? botUserId ?? "unknown";
2239
- const entityId = slackUserId === botUserId ? this.runtime.agentId : this.getEntityId(slackUserId, accountId);
2240
- const user = message.user ? await this.getUser(message.user, accountId) : null;
2241
- const displayName = user ? getSlackUserDisplayName(user) : slackUserId;
2242
- const channel = await this.getChannel(channelId, accountId).catch(() => null);
2243
- const channelType = effectiveThreadTs ? ChannelType.THREAD : channel?.isIm ? ChannelType.DM : ChannelType.GROUP;
2244
- const attachments = (message.files ?? []).map((file) => ({
2245
- id: file.id,
2246
- url: file.urlPrivate,
2247
- title: file.title || file.name,
2248
- source: "slack",
2249
- description: file.name
2250
- }));
2251
- return {
2252
- id: createUniqueUuid(this.runtime, this.scopedSlackKey("slack", `${channelId}-${message.ts}`, accountId)),
2253
- agentId: this.runtime.agentId,
2254
- roomId,
2255
- entityId,
2256
- content: {
2257
- text: message.text || "",
2258
- source: "slack",
2259
- name: displayName,
2260
- channelType,
2261
- metadata: { accountId },
2262
- ...effectiveThreadTs && effectiveThreadTs !== message.ts ? {
2263
- inReplyTo: createUniqueUuid(this.runtime, this.scopedSlackKey("slack", `${channelId}-${effectiveThreadTs}`, accountId))
2264
- } : {},
2265
- ...attachments.length > 0 ? { attachments } : {}
2266
- },
2267
- metadata: {
2268
- type: "message",
2269
- source: "slack",
2270
- provider: "slack",
2271
- accountId,
2272
- timestamp: this.parseSlackTimestamp(message.ts),
2273
- entityName: displayName,
2274
- entityUserName: user?.name ?? slackUserId,
2275
- fromBot: slackUserId === botUserId,
2276
- fromId: slackUserId,
2277
- sourceId: entityId,
2278
- chatType: channelType,
2279
- messageIdFull: message.ts,
2280
- sender: {
2281
- id: slackUserId,
2282
- name: displayName,
2283
- username: user?.name ?? slackUserId
2284
- },
2285
- slack: {
2286
- accountId,
2287
- teamId: this.getTeamIdForAccount(accountId) ?? undefined,
2288
- channelId,
2289
- userId: slackUserId,
2290
- messageId: message.ts,
2291
- threadTs: effectiveThreadTs
2292
- },
2293
- slackChannelId: channelId,
2294
- slackMessageTs: message.ts,
2295
- slackThreadTs: effectiveThreadTs,
2296
- reactions: message.reactions
2297
- },
2298
- createdAt: this.parseSlackTimestamp(message.ts)
2299
- };
2300
- }
2301
- async listConnectorServers(context) {
2302
- const worlds = [];
2303
- for (const accountId of this.getCandidateAccountIds(context)) {
2304
- const teamId = this.getTeamIdForAccount(accountId);
2305
- if (!teamId) {
2306
- continue;
2307
- }
2308
- let name = `Slack Workspace ${teamId}`;
2309
- try {
2310
- const client = this.getClientForAccount(accountId);
2311
- if (client) {
2312
- const teamInfo = await client.team.info();
2313
- const team = teamInfo.team;
2314
- name = team?.name || name;
2315
- }
2316
- } catch {}
2317
- worlds.push({
2318
- id: createUniqueUuid(this.runtime, this.scopedSlackKey("slack-workspace", teamId, accountId)),
2319
- agentId: this.runtime.agentId,
2320
- name,
2321
- metadata: {
2322
- source: "slack",
2323
- accountId,
2324
- teamId
2325
- }
2326
- });
2327
- }
2328
- return worlds;
2329
- }
2330
- async fetchConnectorMessages(_context, params) {
2331
- const accountId = await this.resolveAccountIdForTarget(_context.runtime, params.target, { accountId: params.accountId, roomId: params.roomId });
2332
- const client = this.getClientForAccount(accountId);
2333
- if (!client) {
2334
- return [];
2335
- }
2336
- const { channelId, threadTs } = await this.resolveConnectorMessageLocation(params.target, { ...params, accountId });
2337
- const limit = Number.isFinite(params.limit) ? Math.max(1, Math.min(Number(params.limit), 100)) : 25;
2338
- const rawMessages = threadTs ? (await client.conversations.replies({
2339
- channel: channelId,
2340
- ts: threadTs,
2341
- limit,
2342
- latest: params.before,
2343
- oldest: params.after,
2344
- cursor: params.cursor
2345
- })).messages ?? [] : await this.readHistory(channelId, {
2346
- limit,
2347
- before: params.before,
2348
- after: params.after
2349
- }, accountId);
2350
- const memories = [];
2351
- for (const rawMessage of rawMessages) {
2352
- const message = rawMessage;
2353
- if (!message.ts) {
2354
- continue;
2355
- }
2356
- memories.push(await this.slackMessageToMemory(message, channelId, threadTs, accountId));
2357
- }
2358
- return memories.sort((left, right) => Number(right.createdAt ?? 0) - Number(left.createdAt ?? 0));
2359
- }
2360
- async searchConnectorMessages(context, params) {
2361
- const query = params.query?.trim().toLowerCase();
2362
- if (!query) {
2363
- return [];
2364
- }
2365
- const memories = await this.fetchConnectorMessages(context, {
2366
- ...params,
2367
- limit: Math.max(params.limit ?? 100, 100)
2368
- });
2369
- return memories.filter((memory) => {
2370
- const text = String(memory.content?.text ?? "").toLowerCase();
2371
- const name = String(memory.content?.name ?? "").toLowerCase();
2372
- return text.includes(query) || name.includes(query);
2373
- }).slice(0, params.limit ?? 25);
2374
- }
2375
- async reactConnectorMessage(_runtime, params) {
2376
- const { accountId, channelId } = await this.resolveConnectorMessageLocation(params.target, params);
2377
- const messageTs = params.messageTs ?? params.messageId;
2378
- const emoji = params.emoji?.trim();
2379
- if (!messageTs || !emoji) {
2380
- throw new Error("Slack reaction requires messageId/messageTs and emoji.");
2381
- }
2382
- if (params.remove) {
2383
- await this.removeReaction(channelId, messageTs, emoji, accountId);
2384
- return;
2385
- }
2386
- await this.sendReaction(channelId, messageTs, emoji, accountId);
2387
- }
2388
- async editConnectorMessage(_runtime, params) {
2389
- const { accountId, channelId, threadTs } = await this.resolveConnectorMessageLocation(params.target, params);
2390
- const messageTs = params.messageTs ?? params.messageId;
2391
- const text = params.content?.text ?? params.text;
2392
- if (!messageTs || !text?.trim()) {
2393
- throw new Error("Slack edit requires messageId/messageTs and text.");
2394
- }
2395
- await this.editMessage(channelId, messageTs, text, accountId);
2396
- return this.slackMessageToMemory({
2397
- type: "message",
2398
- ts: messageTs,
2399
- user: this.getBotUserIdForAccount(accountId) ?? undefined,
2400
- text,
2401
- threadTs,
2402
- subtype: undefined,
2403
- replyCount: undefined,
2404
- replyUsersCount: undefined,
2405
- latestReply: undefined,
2406
- reactions: undefined,
2407
- files: undefined,
2408
- attachments: undefined,
2409
- blocks: undefined
2410
- }, channelId, threadTs, accountId);
2411
- }
2412
- async deleteConnectorMessage(_runtime, params) {
2413
- const { accountId, channelId } = await this.resolveConnectorMessageLocation(params.target, params);
2414
- const messageTs = params.messageTs ?? params.messageId;
2415
- if (!messageTs) {
2416
- throw new Error("Slack delete requires messageId/messageTs.");
2417
- }
2418
- await this.deleteMessage(channelId, messageTs, accountId);
2419
- }
2420
- async pinConnectorMessage(_runtime, params) {
2421
- const { accountId, channelId } = await this.resolveConnectorMessageLocation(params.target, params);
2422
- const messageTs = params.messageTs ?? params.messageId;
2423
- if (!messageTs) {
2424
- throw new Error("Slack pin requires messageId/messageTs.");
2425
- }
2426
- if (params.pin === false) {
2427
- await this.unpinMessage(channelId, messageTs, accountId);
2428
- return;
2429
- }
2430
- await this.pinMessage(channelId, messageTs, accountId);
2431
- }
2432
- async getConnectorUser(runtime, params) {
2433
- const accountId = normalizeAccountId(params.target?.accountId ?? this.defaultAccountId ?? DEFAULT_ACCOUNT_ID);
2434
- const lookup = params.userId ?? params.handle ?? params.username ?? params.query;
2435
- if (!lookup) {
2436
- return null;
2437
- }
2438
- let slackUserId = SLACK_USER_ID_PATTERN.test(lookup) ? lookup : await this.resolveSlackTargetUserId(runtime, lookup, accountId);
2439
- const client = this.getClientForAccount(accountId);
2440
- if (!slackUserId && client) {
2441
- const usersResult = await client.users.list({ limit: 200 });
2442
- const members = usersResult.members ?? [];
2443
- const normalized = normalizeSlackConnectorQuery(lookup);
2444
- const match = members.find((member) => [
2445
- member.id,
2446
- member.name,
2447
- member.real_name,
2448
- member.profile?.display_name,
2449
- member.profile?.email
2450
- ].filter((value) => Boolean(value)).some((value) => normalizeSlackConnectorQuery(value).includes(normalized)));
2451
- slackUserId = match?.id ?? null;
2452
- }
2453
- if (!slackUserId) {
2454
- return null;
2455
- }
2456
- const user = await this.getUser(slackUserId, accountId);
2457
- if (!user) {
2458
- return null;
2459
- }
2460
- return {
2461
- id: this.getEntityId(user.id, accountId),
2462
- agentId: this.runtime.agentId,
2463
- names: [getSlackUserDisplayName(user), user.name, user.realName].filter((value) => Boolean(value)),
2464
- metadata: {
2465
- source: "slack",
2466
- accountId,
2467
- slack: {
2468
- accountId,
2469
- id: user.id,
2470
- teamId: user.teamId ?? this.getTeamIdForAccount(accountId),
2471
- name: user.name,
2472
- realName: user.realName,
2473
- profile: { ...user.profile }
2474
- }
2475
- }
2476
- };
2477
- }
2478
- async getConnectorUserContext(entityId, context) {
2479
- const accountId = normalizeAccountId(context.target?.accountId ?? context.accountId ?? context.account?.accountId ?? this.defaultAccountId ?? DEFAULT_ACCOUNT_ID);
2480
- const slackUserId = await this.resolveSlackTargetUserId(context.runtime, String(entityId), accountId);
2481
- if (!slackUserId) {
2482
- return null;
2483
- }
2484
- const user = await this.getUser(slackUserId, accountId);
2485
- if (!user) {
2486
- return null;
2487
- }
2488
- return {
2489
- entityId,
2490
- label: getSlackUserDisplayName(user),
2491
- aliases: [
2492
- user.name,
2493
- user.realName,
2494
- user.profile.displayName,
2495
- user.profile.realName,
2496
- user.profile.email
2497
- ].filter((value) => Boolean(value)),
2498
- handles: {
2499
- slack: user.id,
2500
- ...user.profile.email ? { email: user.profile.email } : {}
2501
- },
2502
- metadata: {
2503
- accountId,
2504
- slackUserId: user.id,
2505
- slackTeamId: user.teamId ?? this.getTeamIdForAccount(accountId),
2506
- profile: { ...user.profile }
2507
- }
2508
- };
2509
- }
2510
- async getUser(userId, accountId) {
2511
- const userCache = this.getUserCacheForAccount(accountId);
2512
- const cachedUser = userCache.get(userId);
2513
- if (cachedUser) {
2514
- return cachedUser;
2515
- }
2516
- const client = this.getClientForAccount(accountId);
2517
- if (!client)
2518
- return null;
2519
- const result = await client.users.info({ user: userId });
2520
- if (!result.user)
2521
- return null;
2522
- const user = {
2523
- id: result.user.id ?? userId,
2524
- teamId: result.user.team_id,
2525
- name: result.user.name ?? "",
2526
- deleted: result.user.deleted || false,
2527
- realName: result.user.real_name,
2528
- tz: result.user.tz,
2529
- tzLabel: result.user.tz_label,
2530
- tzOffset: result.user.tz_offset,
2531
- profile: {
2532
- title: result.user.profile?.title,
2533
- phone: result.user.profile?.phone,
2534
- skype: result.user.profile?.skype,
2535
- realName: result.user.profile?.real_name,
2536
- realNameNormalized: result.user.profile?.real_name_normalized,
2537
- displayName: result.user.profile?.display_name,
2538
- displayNameNormalized: result.user.profile?.display_name_normalized,
2539
- statusText: result.user.profile?.status_text,
2540
- statusEmoji: result.user.profile?.status_emoji,
2541
- statusExpiration: result.user.profile?.status_expiration,
2542
- avatarHash: result.user.profile?.avatar_hash,
2543
- email: result.user.profile?.email,
2544
- image24: result.user.profile?.image_24,
2545
- image32: result.user.profile?.image_32,
2546
- image48: result.user.profile?.image_48,
2547
- image72: result.user.profile?.image_72,
2548
- image192: result.user.profile?.image_192,
2549
- image512: result.user.profile?.image_512,
2550
- image1024: result.user.profile?.image_1024,
2551
- imageOriginal: result.user.profile?.image_original,
2552
- team: result.user.profile?.team
2553
- },
2554
- isAdmin: result.user.is_admin || false,
2555
- isOwner: result.user.is_owner || false,
2556
- isPrimaryOwner: result.user.is_primary_owner || false,
2557
- isRestricted: result.user.is_restricted || false,
2558
- isUltraRestricted: result.user.is_ultra_restricted || false,
2559
- isBot: result.user.is_bot || false,
2560
- isAppUser: result.user.is_app_user || false,
2561
- updated: result.user.updated || 0
2562
- };
2563
- userCache.set(userId, user);
2564
- return user;
2565
- }
2566
- async getChannel(channelId, accountId) {
2567
- const channelCache = this.getChannelCacheForAccount(accountId);
2568
- const cachedChannel = channelCache.get(channelId);
2569
- if (cachedChannel) {
2570
- return cachedChannel;
2571
- }
2572
- const client = this.getClientForAccount(accountId);
2573
- if (!client)
2574
- return null;
2575
- const result = await client.conversations.info({ channel: channelId });
2576
- if (!result.channel)
2577
- return null;
2578
- const channel = {
2579
- id: result.channel.id,
2580
- name: result.channel.name || "",
2581
- isChannel: result.channel.is_channel || false,
2582
- isGroup: result.channel.is_group || false,
2583
- isIm: result.channel.is_im || false,
2584
- isMpim: result.channel.is_mpim || false,
2585
- isPrivate: result.channel.is_private || false,
2586
- isArchived: result.channel.is_archived || false,
2587
- isGeneral: result.channel.is_general || false,
2588
- isShared: result.channel.is_shared || false,
2589
- isOrgShared: result.channel.is_org_shared || false,
2590
- isMember: result.channel.is_member || false,
2591
- topic: result.channel.topic ? {
2592
- value: result.channel.topic.value,
2593
- creator: result.channel.topic.creator,
2594
- lastSet: result.channel.topic.last_set
2595
- } : undefined,
2596
- purpose: result.channel.purpose ? {
2597
- value: result.channel.purpose.value,
2598
- creator: result.channel.purpose.creator,
2599
- lastSet: result.channel.purpose.last_set
2600
- } : undefined,
2601
- numMembers: result.channel.num_members,
2602
- created: result.channel.created,
2603
- creator: result.channel.creator
2604
- };
2605
- channelCache.set(channelId, channel);
2606
- return channel;
2607
- }
2608
- async sendMessage(channelId, text, options, accountId) {
2609
- const client = this.getClientForAccount(accountId);
2610
- if (!client) {
2611
- throw new Error("Slack client not initialized");
2612
- }
2613
- const convertedText = markdownToSlackMrkdwn(text);
2614
- const messages = this.splitMessage(convertedText);
2615
- let lastTs = "";
2616
- for (const msg of messages) {
2617
- const result = await client.chat.postMessage({
2618
- channel: channelId,
2619
- text: msg,
2620
- thread_ts: options?.threadTs,
2621
- reply_broadcast: options?.replyBroadcast,
2622
- unfurl_links: options?.unfurlLinks,
2623
- unfurl_media: options?.unfurlMedia,
2624
- mrkdwn: options?.mrkdwn ?? true,
2625
- attachments: options?.attachments,
2626
- blocks: options?.blocks
2627
- });
2628
- lastTs = result.ts;
2629
- }
2630
- return { ts: lastTs, channelId };
2631
- }
2632
- async sendReaction(channelId, messageTs, emoji, accountId) {
2633
- const client = this.getClientForAccount(accountId);
2634
- if (!client) {
2635
- throw new Error("Slack client not initialized");
2636
- }
2637
- const cleanEmoji = emoji.replace(/^:/, "").replace(/:$/, "");
2638
- await client.reactions.add({
2639
- channel: channelId,
2640
- timestamp: messageTs,
2641
- name: cleanEmoji
2642
- });
2643
- }
2644
- async removeReaction(channelId, messageTs, emoji, accountId) {
2645
- const client = this.getClientForAccount(accountId);
2646
- if (!client) {
2647
- throw new Error("Slack client not initialized");
2648
- }
2649
- const cleanEmoji = emoji.replace(/^:/, "").replace(/:$/, "");
2650
- await client.reactions.remove({
2651
- channel: channelId,
2652
- timestamp: messageTs,
2653
- name: cleanEmoji
2654
- });
2655
- }
2656
- async editMessage(channelId, messageTs, text, accountId) {
2657
- const client = this.getClientForAccount(accountId);
2658
- if (!client) {
2659
- throw new Error("Slack client not initialized");
2660
- }
2661
- await client.chat.update({
2662
- channel: channelId,
2663
- ts: messageTs,
2664
- text
2665
- });
2666
- }
2667
- async deleteMessage(channelId, messageTs, accountId) {
2668
- const client = this.getClientForAccount(accountId);
2669
- if (!client) {
2670
- throw new Error("Slack client not initialized");
2671
- }
2672
- await client.chat.delete({
2673
- channel: channelId,
2674
- ts: messageTs
2675
- });
2676
- }
2677
- async pinMessage(channelId, messageTs, accountId) {
2678
- const client = this.getClientForAccount(accountId);
2679
- if (!client) {
2680
- throw new Error("Slack client not initialized");
2681
- }
2682
- await client.pins.add({
2683
- channel: channelId,
2684
- timestamp: messageTs
2685
- });
2686
- }
2687
- async unpinMessage(channelId, messageTs, accountId) {
2688
- const client = this.getClientForAccount(accountId);
2689
- if (!client) {
2690
- throw new Error("Slack client not initialized");
2691
- }
2692
- await client.pins.remove({
2693
- channel: channelId,
2694
- timestamp: messageTs
2695
- });
2696
- }
2697
- async listPins(channelId, accountId) {
2698
- const client = this.getClientForAccount(accountId);
2699
- if (!client) {
2700
- throw new Error("Slack client not initialized");
2701
- }
2702
- const result = await client.pins.list({ channel: channelId });
2703
- return (result.items || []).filter((item) => item.type === "message" && ("message" in item) && !!item.message).map((item) => ({
2704
- type: item.message.type,
2705
- subtype: item.message.subtype,
2706
- ts: item.message.ts,
2707
- user: item.message.user,
2708
- text: item.message.text,
2709
- threadTs: item.message.thread_ts,
2710
- replyCount: item.message.reply_count,
2711
- replyUsersCount: item.message.reply_users_count,
2712
- latestReply: item.message.latest_reply,
2713
- reactions: item.message.reactions,
2714
- files: item.message.files,
2715
- attachments: item.message.attachments,
2716
- blocks: item.message.blocks
2717
- }));
2718
- }
2719
- async readHistory(channelId, options, accountId) {
2720
- const client = this.getClientForAccount(accountId);
2721
- if (!client) {
2722
- throw new Error("Slack client not initialized");
2723
- }
2724
- const result = await client.conversations.history({
2725
- channel: channelId,
2726
- limit: options?.limit || 100,
2727
- latest: options?.before,
2728
- oldest: options?.after
2729
- });
2730
- return (result.messages || []).map((msg) => ({
2731
- type: msg.type,
2732
- subtype: msg.subtype,
2733
- ts: msg.ts,
2734
- user: msg.user,
2735
- text: msg.text,
2736
- threadTs: msg.thread_ts,
2737
- replyCount: msg.reply_count,
2738
- replyUsersCount: msg.reply_users_count,
2739
- latestReply: msg.latest_reply,
2740
- reactions: msg.reactions,
2741
- files: msg.files,
2742
- attachments: msg.attachments,
2743
- blocks: msg.blocks
2744
- }));
2745
- }
2746
- async listChannels(options, accountId) {
2747
- const client = this.getClientForAccount(accountId);
2748
- if (!client) {
2749
- throw new Error("Slack client not initialized");
2750
- }
2751
- const result = await client.conversations.list({
2752
- types: options?.types || "public_channel,private_channel",
2753
- limit: options?.limit || 1000
2754
- });
2755
- return (result.channels || []).map((ch) => ({
2756
- id: ch.id ?? "",
2757
- name: ch.name || "",
2758
- isChannel: ch.is_channel || false,
2759
- isGroup: ch.is_group || false,
2760
- isIm: ch.is_im || false,
2761
- isMpim: ch.is_mpim || false,
2762
- isPrivate: ch.is_private || false,
2763
- isArchived: ch.is_archived || false,
2764
- isGeneral: ch.is_general || false,
2765
- isShared: ch.is_shared || false,
2766
- isOrgShared: ch.is_org_shared || false,
2767
- isMember: ch.is_member || false,
2768
- topic: ch.topic ? {
2769
- value: ch.topic.value ?? "",
2770
- creator: ch.topic.creator ?? "",
2771
- lastSet: ch.topic.last_set ?? 0
2772
- } : undefined,
2773
- purpose: ch.purpose ? {
2774
- value: ch.purpose.value ?? "",
2775
- creator: ch.purpose.creator ?? "",
2776
- lastSet: ch.purpose.last_set ?? 0
2777
- } : undefined,
2778
- numMembers: ch.num_members,
2779
- created: ch.created || 0,
2780
- creator: ch.creator || ""
2781
- }));
2782
- }
2783
- async getEmojiList(accountId) {
2784
- const client = this.getClientForAccount(accountId);
2785
- if (!client) {
2786
- throw new Error("Slack client not initialized");
2787
- }
2788
- const result = await client.emoji.list();
2789
- return result.emoji || {};
2790
- }
2791
- async uploadFile(channelId, content, filename, options, accountId) {
2792
- const client = this.getClientForAccount(accountId);
2793
- if (!client) {
2794
- throw new Error("Slack client not initialized");
2795
- }
2796
- const result = await client.files.uploadV2({
2797
- channel_id: channelId,
2798
- content: typeof content === "string" ? content : undefined,
2799
- file: typeof content !== "string" ? content : undefined,
2800
- filename,
2801
- title: options?.title,
2802
- initial_comment: options?.initialComment,
2803
- thread_ts: options?.threadTs
2804
- });
2805
- const resultWithFile = result;
2806
- const file = resultWithFile.file;
2807
- return {
2808
- fileId: file?.id || "",
2809
- permalink: file?.permalink || ""
2810
- };
2811
- }
2812
- splitMessage(text) {
2813
- if (text.length <= MAX_SLACK_MESSAGE_LENGTH) {
2814
- return [text];
2815
- }
2816
- const messages = [];
2817
- let remaining = text;
2818
- while (remaining.length > 0) {
2819
- if (remaining.length <= MAX_SLACK_MESSAGE_LENGTH) {
2820
- messages.push(remaining);
2821
- break;
2822
- }
2823
- let splitIndex = MAX_SLACK_MESSAGE_LENGTH;
2824
- const lastNewline = remaining.lastIndexOf(`
2825
- `, MAX_SLACK_MESSAGE_LENGTH);
2826
- if (lastNewline > MAX_SLACK_MESSAGE_LENGTH / 2) {
2827
- splitIndex = lastNewline + 1;
2828
- } else {
2829
- const lastSpace = remaining.lastIndexOf(" ", MAX_SLACK_MESSAGE_LENGTH);
2830
- if (lastSpace > MAX_SLACK_MESSAGE_LENGTH / 2) {
2831
- splitIndex = lastSpace + 1;
2832
- }
2833
- }
2834
- messages.push(remaining.slice(0, splitIndex));
2835
- remaining = remaining.slice(splitIndex);
2836
- }
2837
- return messages;
2838
- }
2839
- addAllowedChannel(channelId, accountId) {
2840
- if (isValidChannelId(channelId)) {
2841
- this.getDynamicChannelIdsForAccount(accountId).add(channelId);
2842
- }
2843
- }
2844
- removeAllowedChannel(channelId, accountId) {
2845
- this.getDynamicChannelIdsForAccount(accountId).delete(channelId);
2846
- }
2847
- getAllowedChannelIds(accountId) {
2848
- return [
2849
- ...this.getAllowedChannelIdsForAccount(accountId),
2850
- ...this.getDynamicChannelIdsForAccount(accountId)
2851
- ];
2852
- }
2853
- isServiceConnected() {
2854
- return this.isConnected && this.app !== null;
2855
- }
2856
- getBotUserId() {
2857
- return this.botUserId;
2858
- }
2859
- getTeamId() {
2860
- return this.teamId;
2861
- }
2862
- clearUserCache() {
2863
- this.userCache.clear();
2864
- }
2865
- clearChannelCache() {
2866
- this.channelCache.clear();
2867
- }
2868
- }
2869
-
2870
- // src/workflow-credential-provider.ts
2871
- import { Service as Service2 } from "@elizaos/core";
2872
- var WORKFLOW_CREDENTIAL_PROVIDER_TYPE = "workflow_credential_provider";
2873
- var SUPPORTED = ["slackApi", "slackOAuth2Api"];
2874
-
2875
- class SlackWorkflowCredentialProvider extends Service2 {
2876
- static serviceType = WORKFLOW_CREDENTIAL_PROVIDER_TYPE;
2877
- capabilityDescription = "Supplies Slack credentials to the workflow plugin.";
2878
- static async start(runtime) {
2879
- return new SlackWorkflowCredentialProvider(runtime);
2880
- }
2881
- async stop() {}
2882
- async resolve(_userId, credType) {
2883
- const botToken = this.runtime.getSetting("SLACK_BOT_TOKEN");
2884
- const userToken = this.runtime.getSetting("SLACK_USER_TOKEN");
2885
- if (credType === "slackApi" && botToken?.trim()) {
2886
- return {
2887
- status: "credential_data",
2888
- data: { accessToken: botToken.trim() }
2889
- };
2890
- }
2891
- if (credType === "slackOAuth2Api" && userToken?.trim()) {
2892
- return {
2893
- status: "credential_data",
2894
- data: { accessToken: userToken.trim() }
2895
- };
2896
- }
2897
- return null;
2898
- }
2899
- checkCredentialTypes(credTypes) {
2900
- return {
2901
- supported: credTypes.filter((t) => SUPPORTED.includes(t)),
2902
- unsupported: credTypes.filter((t) => !SUPPORTED.includes(t))
2903
- };
2904
- }
2905
- }
2906
-
2907
- // src/index.ts
2908
- var slackPlugin = {
2909
- name: "slack",
2910
- description: "Slack integration plugin for ElizaOS with Socket Mode support",
2911
- services: [SlackService, SlackWorkflowCredentialProvider],
2912
- actions: [],
2913
- providers: [],
2914
- autoEnable: {
2915
- connectorKeys: ["slack"]
2916
- },
2917
- init: async (_config, runtime) => {
2918
- try {
2919
- const manager = getConnectorAccountManager(runtime);
2920
- manager.registerProvider(createSlackConnectorAccountProvider(runtime));
2921
- } catch (err) {
2922
- logger2.warn({
2923
- src: "plugin:slack",
2924
- err: err instanceof Error ? err.message : String(err)
2925
- }, "Failed to register Slack provider with ConnectorAccountManager");
2926
- }
2927
- const botToken = runtime.getSetting("SLACK_BOT_TOKEN");
2928
- const appToken = runtime.getSetting("SLACK_APP_TOKEN");
2929
- const signingSecret = runtime.getSetting("SLACK_SIGNING_SECRET");
2930
- const userToken = runtime.getSetting("SLACK_USER_TOKEN");
2931
- const channelIds = runtime.getSetting("SLACK_CHANNEL_IDS");
2932
- const ignoreBotMessages = runtime.getSetting("SLACK_SHOULD_IGNORE_BOT_MESSAGES");
2933
- const respondOnlyToMentions = runtime.getSetting("SLACK_SHOULD_RESPOND_ONLY_TO_MENTIONS");
2934
- const maskToken = (token) => {
2935
- if (!token || token.trim() === "")
2936
- return "[not set]";
2937
- if (token.length <= 8)
2938
- return "***";
2939
- return `${token.slice(0, 4)}...${token.slice(-4)}`;
2940
- };
2941
- logger2.info({
2942
- src: "plugin:slack",
2943
- agentId: runtime.agentId,
2944
- settings: {
2945
- botToken: maskToken(botToken),
2946
- appToken: maskToken(appToken),
2947
- signingSecret: signingSecret ? "[set]" : "[not set]",
2948
- userToken: maskToken(userToken),
2949
- channelIds: channelIds || "[all channels]",
2950
- ignoreBotMessages: ignoreBotMessages || "false",
2951
- respondOnlyToMentions: respondOnlyToMentions || "false"
2952
- }
2953
- }, "Slack plugin initializing");
2954
- if (!botToken || botToken.trim() === "") {
2955
- logger2.warn({ src: "plugin:slack", agentId: runtime.agentId }, "SLACK_BOT_TOKEN not provided - Slack plugin is loaded but will not be functional");
2956
- logger2.warn({ src: "plugin:slack", agentId: runtime.agentId }, "To enable Slack functionality, please provide SLACK_BOT_TOKEN in your .env file");
2957
- return;
2958
- }
2959
- if (!appToken || appToken.trim() === "") {
2960
- logger2.warn({ src: "plugin:slack", agentId: runtime.agentId }, "SLACK_APP_TOKEN not provided - Socket Mode will not work");
2961
- logger2.warn({ src: "plugin:slack", agentId: runtime.agentId }, "To enable Socket Mode, please provide SLACK_APP_TOKEN in your .env file");
2962
- return;
2963
- }
2964
- if (!botToken.startsWith("xoxb-")) {
2965
- logger2.warn({ src: "plugin:slack", agentId: runtime.agentId }, "SLACK_BOT_TOKEN should start with 'xoxb-'. Please verify your token.");
2966
- }
2967
- if (!appToken.startsWith("xapp-")) {
2968
- logger2.warn({ src: "plugin:slack", agentId: runtime.agentId }, "SLACK_APP_TOKEN should start with 'xapp-'. Please verify your token.");
2969
- }
2970
- if (userToken && !userToken.startsWith("xoxp-")) {
2971
- logger2.warn({ src: "plugin:slack", agentId: runtime.agentId }, "SLACK_USER_TOKEN should start with 'xoxp-'. Please verify your token.");
2972
- }
2973
- logger2.info({ src: "plugin:slack", agentId: runtime.agentId }, "Slack plugin configuration validated successfully");
2974
- }
2975
- };
2976
- var src_default = slackPlugin;
2977
- export {
2978
- truncateText,
2979
- stripSlackFormatting,
2980
- resolveSlackUserToken,
2981
- resolveSlackSystemLocation,
2982
- resolveSlackReplyToMode,
2983
- resolveSlackBotToken,
2984
- resolveSlackAppToken,
2985
- resolveSlackAccount,
2986
- resolveDefaultSlackAccountId,
2987
- parseSlackMessagePermalink,
2988
- parseSlackMessageLink,
2989
- normalizeAccountId,
2990
- markdownToSlackMrkdwnChunks,
2991
- markdownToSlackMrkdwn,
2992
- listSlackAccountIds,
2993
- listEnabledSlackAccounts,
2994
- isValidUserId,
2995
- isValidTeamId,
2996
- isValidMessageTs,
2997
- isValidChannelId,
2998
- isPrivateChannel,
2999
- isMultiAccountEnabled,
3000
- isGroupDm,
3001
- isDirectMessage,
3002
- getSlackUserDisplayName,
3003
- getSlackChannelType,
3004
- getChannelTypeString,
3005
- formatSlackUserMention,
3006
- formatSlackUserGroupMention,
3007
- formatSlackUserDisplayName,
3008
- formatSlackSpecialMention,
3009
- formatSlackLink,
3010
- formatSlackDate,
3011
- formatSlackChannelMention,
3012
- formatSlackChannel,
3013
- formatMessageTsForLink,
3014
- extractUserIdFromMention,
3015
- extractUrlFromSlackLink,
3016
- extractChannelIdFromMention,
3017
- escapeSlackMrkdwn,
3018
- src_default as default,
3019
- createSlackConnectorAccountProvider,
3020
- chunkSlackText,
3021
- buildSlackMessagePermalink,
3022
- SlackServiceNotInitializedError,
3023
- SlackService,
3024
- SlackPluginError,
3025
- SlackEventTypes,
3026
- SlackConfigurationError,
3027
- SlackClientNotAvailableError,
3028
- SlackApiError,
3029
- SLACK_SERVICE_NAME,
3030
- MAX_SLACK_MESSAGE_LENGTH,
3031
- MAX_SLACK_FILE_SIZE,
3032
- MAX_SLACK_BLOCKS,
3033
- DEFAULT_ACCOUNT_ID
3034
- };
3035
-
3036
- //# debugId=3501C22F83015CAC64756E2164756E21