@openclaw/slack 2026.5.27 → 2026.5.28-beta.2

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.
Files changed (94) hide show
  1. package/dist/{account-inspect-vVg3pT03.js → account-inspect-CdGk6R7l.js} +1 -1
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/{accounts-BnLQ3fe2.js → accounts-f6Xcv9Vi.js} +42 -1
  4. package/dist/accounts.runtime-BJt1IxOS.js +2 -0
  5. package/dist/{action-runtime-JVb7KyQs.js → action-runtime-BOEgcnv6.js} +7 -8
  6. package/dist/action-runtime.runtime-BXQYV0yA.js +2 -0
  7. package/dist/{actions-1o9nMIY8.js → actions-zfVWcIY6.js} +4 -4
  8. package/dist/{actions.runtime-qQtdNww-.js → actions.runtime-CoijPN8g.js} +1 -1
  9. package/dist/api.js +14 -17
  10. package/dist/{approval-handler.runtime-Ba8nwnYi.js → approval-handler.runtime-CWz3XLfN.js} +12 -69
  11. package/dist/{channel-Bd7eept6.js → channel-D8p_1twn.js} +25 -28
  12. package/dist/channel-config-api.js +1 -1
  13. package/dist/channel-plugin-api.js +1 -1
  14. package/dist/{channel.setup-WNNVmCm0.js → channel.setup-oGp4gSTP.js} +4 -4
  15. package/dist/{client-Dc2Ji2YN.js → client-DowBk5k0.js} +6 -24
  16. package/dist/{config-schema-CUiDK8HV.js → config-schema-C0RewpJQ.js} +4 -0
  17. package/dist/contract-api.js +1 -1
  18. package/dist/{directory-config-9_djLTOK.js → directory-config-8UPAEyNg.js} +1 -1
  19. package/dist/directory-contract-api.js +1 -1
  20. package/dist/{directory-live-Cl83cGLZ.js → directory-live-BFB1pSax.js} +2 -2
  21. package/dist/http-routes-api.js +1 -1
  22. package/dist/index.js +3 -7
  23. package/dist/{interactive-replies-DtYu4Sf5.js → interactive-replies-DrBq4Mld.js} +1 -1
  24. package/dist/interactive-replies-api.js +1 -1
  25. package/dist/{message-tool-api-BieXX5lk.js → message-tool-api-B9M0zzlQ.js} +2 -2
  26. package/dist/message-tool-api.js +1 -1
  27. package/dist/{monitor-Cn7LUDFL.js → monitor-D7jGKmQk.js} +3 -4
  28. package/dist/{outbound-adapter-ChuR4_0v.js → outbound-adapter-BHZMgblN.js} +4 -7
  29. package/dist/pipeline.runtime-xM6ppqQZ.js +3492 -0
  30. package/dist/plugin-routes-B9PvcDQJ.js +22 -0
  31. package/dist/{policy-pu8tsF8_.js → policy-BBDU-PQK.js} +1 -1
  32. package/dist/{probe-cBVPhCKZ.js → probe-Djes9Fy6.js} +1 -1
  33. package/dist/{provider-Bccng2Wp.js → provider-CxMP_s2o.js} +828 -28
  34. package/dist/{replies-jiEDV6lH.js → replies-DkmWK7JW.js} +2 -4
  35. package/dist/{resolve-channels-CT4oiz_9.js → resolve-channels-zXt5f47h.js} +1 -1
  36. package/dist/{resolve-users-o5S-Ijks.js → resolve-users-BLfGAz1v.js} +1 -1
  37. package/dist/{runtime-api-CyPE-EvY.js → runtime-api-DvpUD2hw.js} +2 -2
  38. package/dist/runtime-api.js +12 -12
  39. package/dist/{scopes-CnyhLIPT.js → scopes-DiiHsqh1.js} +1 -1
  40. package/dist/{send-DvfE8LVm.js → send-BURYyCXI.js} +4 -4
  41. package/dist/send.runtime-CKaMG3s-.js +2 -0
  42. package/dist/{setup-core-DKe7Ug3V.js → setup-core-POfI_bgP.js} +2 -2
  43. package/dist/setup-entry.js +8 -1
  44. package/dist/setup-plugin-api.js +1 -1
  45. package/dist/{setup-surface-gjRthuZA.js → setup-surface-DJTHAguz.js} +5 -5
  46. package/dist/{shared-D8A7iVVH.js → shared-D9WMYymo.js} +5 -5
  47. package/dist/{slash-dispatch.runtime-BBhdJHb8.js → slash-dispatch.runtime-lsyTm_q5.js} +1 -1
  48. package/dist/thread-ts-NSVqWybn.js +646 -0
  49. package/node_modules/semver/classes/range.js +7 -0
  50. package/node_modules/semver/package.json +1 -1
  51. package/node_modules/semver/ranges/subset.js +2 -2
  52. package/npm-shrinkwrap.json +6 -29
  53. package/openclaw.plugin.json +10 -0
  54. package/package.json +8 -7
  55. package/dist/accounts.runtime-BVdtQeuq.js +0 -2
  56. package/dist/action-runtime.runtime-BZa5VDjs.js +0 -2
  57. package/dist/allow-list-B1lkGjwl.js +0 -82
  58. package/dist/blocks-render-CNC4vQnd.js +0 -232
  59. package/dist/inbound-contract-test-api.js +0 -3
  60. package/dist/magic-string.es-BPXBBMwL.js +0 -1011
  61. package/dist/outbound-payload-test-api.js +0 -2
  62. package/dist/outbound-payload.test-harness-8MnHFgR1.js +0 -13551
  63. package/dist/pipeline.runtime-BVK8yXw_.js +0 -1473
  64. package/dist/plugin-routes-DRR3ijKM.js +0 -20
  65. package/dist/prepare-Bl5WcC3f.js +0 -1713
  66. package/dist/prepare.test-helpers-BcAo4KMw.js +0 -49
  67. package/dist/reply-blocks-BlOURkUm.js +0 -290
  68. package/dist/room-context-BmNTBiw5.js +0 -816
  69. package/dist/send.runtime-Dv6ajTGK.js +0 -2
  70. package/dist/send.runtime-v3TSw9xY.js +0 -2
  71. package/dist/test-api.js +0 -8
  72. package/dist/thread-ts-ks-O8cEG.js +0 -52
  73. package/node_modules/agent-base/LICENSE +0 -22
  74. package/node_modules/agent-base/README.md +0 -69
  75. package/node_modules/agent-base/dist/helpers.d.ts +0 -10
  76. package/node_modules/agent-base/dist/helpers.d.ts.map +0 -1
  77. package/node_modules/agent-base/dist/helpers.js +0 -37
  78. package/node_modules/agent-base/dist/helpers.js.map +0 -1
  79. package/node_modules/agent-base/dist/index.d.ts +0 -37
  80. package/node_modules/agent-base/dist/index.d.ts.map +0 -1
  81. package/node_modules/agent-base/dist/index.js +0 -146
  82. package/node_modules/agent-base/dist/index.js.map +0 -1
  83. package/node_modules/agent-base/package.json +0 -46
  84. package/node_modules/https-proxy-agent/LICENSE +0 -22
  85. package/node_modules/https-proxy-agent/README.md +0 -70
  86. package/node_modules/https-proxy-agent/dist/index.d.ts +0 -43
  87. package/node_modules/https-proxy-agent/dist/index.d.ts.map +0 -1
  88. package/node_modules/https-proxy-agent/dist/index.js +0 -150
  89. package/node_modules/https-proxy-agent/dist/index.js.map +0 -1
  90. package/node_modules/https-proxy-agent/dist/parse-proxy-response.d.ts +0 -12
  91. package/node_modules/https-proxy-agent/dist/parse-proxy-response.d.ts.map +0 -1
  92. package/node_modules/https-proxy-agent/dist/parse-proxy-response.js +0 -94
  93. package/node_modules/https-proxy-agent/dist/parse-proxy-response.js.map +0 -1
  94. package/node_modules/https-proxy-agent/package.json +0 -50
@@ -1,1713 +0,0 @@
1
- import { l as resolveSlackReplyToMode } from "./accounts-BnLQ3fe2.js";
2
- import { r as parseSlackTarget } from "./target-parsing-C7eeWg7M.js";
3
- import "./targets-nUqxHGgg.js";
4
- import { i as normalizeSlackAllowOwnerEntry, o as resolveSlackAllowListMatch, r as normalizeAllowListLower } from "./allow-list-B1lkGjwl.js";
5
- import { i as hasSlackThreadParticipationWithPersistence, t as sendMessageSlack } from "./send-DvfE8LVm.js";
6
- import { _ as resolveSlackThreadStarter, g as resolveSlackThreadHistory, l as reactSlackMessage, y as formatSlackFileReference } from "./actions-1o9nMIY8.js";
7
- import { t as formatSlackError } from "./errors-CZtmv-h0.js";
8
- import { C as resolveStorePath, O as stripSlackMentionsForCommandDetection, b as resolveChannelContextVisibilityMode, c as authorizeSlackBotRoomMessage, d as resolveSlackEffectiveAllowFrom, f as buildSlackAssistantThreadMetadata, g as resolveSlackChatType, h as normalizeSlackChannelType, m as parseSlackAssistantThreadMetadata, n as authorizeSlackDirectMessage, o as resolveConversationLabel$1, t as resolveSlackRoomContextHints, u as resolveSlackCommandIngress, y as readSessionUpdatedAt } from "./room-context-BmNTBiw5.js";
9
- import { n as resolveSlackChannelConfig } from "./policy-pu8tsF8_.js";
10
- import "./send.runtime-Dv6ajTGK.js";
11
- import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
12
- import { resolveAgentRoute, resolveInboundLastRouteSessionKey, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
13
- import { resolveChannelMessageSourceReplyDeliveryMode } from "openclaw/plugin-sdk/channel-outbound";
14
- import { logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
15
- import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
16
- import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
17
- import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
18
- import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
19
- import { buildChannelInboundEventContext, buildMentionRegexes, classifyChannelInboundEvent, formatInboundEnvelope, implicitMentionKindWhen, logInboundDrop, matchesMentionWithExplicit, recordDroppedChannelInboundHistory, resolveEnvelopeFormatOptions, resolveUnmentionedGroupInboundPolicy, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
20
- import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist, shouldIncludeSupplementalContext } from "openclaw/plugin-sdk/security-runtime";
21
- import { resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
22
- import { hasControlCommand } from "openclaw/plugin-sdk/command-detection";
23
- import { isAbortRequestText } from "openclaw/plugin-sdk/command-primitives-runtime";
24
- import { shouldHandleTextCommands } from "openclaw/plugin-sdk/command-surface";
25
- import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
26
- import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
27
- //#region extensions/slack/src/monitor/message-handler/prepare-content.ts
28
- const SLACK_MENTION_RESOLUTION_CONCURRENCY = 4;
29
- const SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE = 20;
30
- const SLACK_USER_MENTION_RE$1 = /<@([A-Z0-9]+)(?:\|[^>]+)?>/gi;
31
- let slackMediaModulePromise$1;
32
- function loadSlackMediaModule$1() {
33
- slackMediaModulePromise$1 ??= import("./actions-1o9nMIY8.js").then((n) => n.h);
34
- return slackMediaModulePromise$1;
35
- }
36
- function collectUniqueSlackMentionIds$1(texts) {
37
- const seen = /* @__PURE__ */ new Set();
38
- const mentionIds = [];
39
- for (const text of texts) {
40
- if (!text) continue;
41
- SLACK_USER_MENTION_RE$1.lastIndex = 0;
42
- for (const match of text.matchAll(SLACK_USER_MENTION_RE$1)) {
43
- const userId = match[1];
44
- if (!userId || seen.has(userId)) continue;
45
- seen.add(userId);
46
- mentionIds.push(userId);
47
- }
48
- }
49
- return mentionIds;
50
- }
51
- function renderSlackUserMentions(text, renderedMentions) {
52
- if (!text || renderedMentions.size === 0) return text;
53
- SLACK_USER_MENTION_RE$1.lastIndex = 0;
54
- return text.replace(SLACK_USER_MENTION_RE$1, (full, userId) => {
55
- return renderedMentions.get(userId) ?? full;
56
- });
57
- }
58
- function readTextObject(value) {
59
- if (!value || typeof value !== "object") return;
60
- return normalizeOptionalString(readStringValue(value.text));
61
- }
62
- function renderSlackRichTextLeaf(element) {
63
- switch (element.type) {
64
- case "text": return readStringValue(element.text) ?? "";
65
- case "link": return readStringValue(element.text) ?? readStringValue(element.url) ?? "";
66
- case "user": {
67
- const userId = readStringValue(element.user_id);
68
- return userId ? `<@${userId}>` : "";
69
- }
70
- case "channel": {
71
- const channelId = readStringValue(element.channel_id);
72
- return channelId ? `<#${channelId}>` : "";
73
- }
74
- case "usergroup": {
75
- const usergroupId = readStringValue(element.usergroup_id);
76
- return usergroupId ? `<!subteam^${usergroupId}>` : "";
77
- }
78
- case "broadcast": {
79
- const range = readStringValue(element.range);
80
- return range ? `<!${range}>` : "";
81
- }
82
- case "emoji": {
83
- const name = readStringValue(element.name);
84
- return name ? `:${name}:` : "";
85
- }
86
- default: return "";
87
- }
88
- }
89
- function renderSlackRichTextElements(elements) {
90
- if (!Array.isArray(elements)) return "";
91
- const parts = [];
92
- for (const rawElement of elements) {
93
- if (!rawElement || typeof rawElement !== "object") continue;
94
- const element = rawElement;
95
- switch (element.type) {
96
- case "rich_text_section":
97
- case "rich_text_preformatted":
98
- case "rich_text_quote":
99
- parts.push(renderSlackRichTextElements(element.elements));
100
- break;
101
- case "rich_text_list": {
102
- const listParts = [];
103
- if (Array.isArray(element.elements)) for (const child of element.elements) {
104
- if (!child || typeof child !== "object") continue;
105
- const rendered = renderSlackRichTextElements(child.elements);
106
- if (rendered) listParts.push(rendered);
107
- }
108
- const listText = listParts.join("\n");
109
- parts.push(listText);
110
- break;
111
- }
112
- default:
113
- parts.push(renderSlackRichTextLeaf(element));
114
- break;
115
- }
116
- }
117
- return parts.join("");
118
- }
119
- function readSlackBlockText(block) {
120
- if (!block || typeof block !== "object") return;
121
- const blockLike = block;
122
- switch (blockLike.type) {
123
- case "rich_text": return normalizeOptionalString(renderSlackRichTextElements(blockLike.elements));
124
- case "section": {
125
- const text = readTextObject(blockLike.text);
126
- if (text) return text;
127
- if (Array.isArray(blockLike.fields)) {
128
- const fields = [];
129
- for (const field of blockLike.fields) {
130
- const fieldText = readTextObject(field);
131
- if (fieldText) fields.push(fieldText);
132
- }
133
- return fields.length > 0 ? fields.join("\n") : void 0;
134
- }
135
- return;
136
- }
137
- case "header": return readTextObject(blockLike.text);
138
- case "context": {
139
- if (!Array.isArray(blockLike.elements)) return;
140
- const parts = [];
141
- for (const element of blockLike.elements) {
142
- const text = readTextObject(element);
143
- if (text) parts.push(text);
144
- }
145
- return parts.length > 0 ? parts.join(" ") : void 0;
146
- }
147
- case "image": return normalizeOptionalString(readStringValue(blockLike.alt_text)) ?? readTextObject(blockLike.title);
148
- case "video": return readTextObject(blockLike.title) ?? normalizeOptionalString(readStringValue(blockLike.alt_text));
149
- default: return;
150
- }
151
- }
152
- function resolveSlackBlocksText(blocks) {
153
- if (!blocks?.length) return;
154
- const parts = [];
155
- let hasRichText = false;
156
- for (const block of blocks) {
157
- if (block && typeof block === "object" && block.type === "rich_text") hasRichText = true;
158
- const text = readSlackBlockText(block);
159
- if (text) parts.push(text);
160
- }
161
- return parts.length > 0 ? {
162
- text: parts.join("\n"),
163
- hasRichText
164
- } : void 0;
165
- }
166
- function chooseSlackPrimaryText(params) {
167
- const { messageText, blocksText } = params;
168
- if (!blocksText) return messageText;
169
- if (!messageText) return blocksText.text;
170
- if (blocksText.hasRichText && blocksText.text.length > messageText.length) return blocksText.text;
171
- return blocksText.text.length > messageText.length && blocksText.text.startsWith(messageText) ? blocksText.text : messageText;
172
- }
173
- function filterInheritedParentFiles(params) {
174
- const { files, isThreadReply, threadStarter } = params;
175
- if (!isThreadReply || !files?.length) return files;
176
- if (!threadStarter?.files?.length) return files;
177
- const starterFileIds = new Set(threadStarter.files.map((file) => file.id));
178
- const filtered = files.filter((file) => !file.id || !starterFileIds.has(file.id));
179
- if (filtered.length < files.length) logVerbose(`slack: filtered ${files.length - filtered.length} inherited parent file(s) from thread reply`);
180
- return filtered.length > 0 ? filtered : void 0;
181
- }
182
- async function resolveSlackMessageContent(params) {
183
- const ownFiles = filterInheritedParentFiles({
184
- files: params.message.files,
185
- isThreadReply: params.isThreadReply,
186
- threadStarter: params.threadStarter
187
- });
188
- const mediaPromise = ownFiles && ownFiles.length > 0 ? loadSlackMediaModule$1().then(({ resolveSlackMedia }) => resolveSlackMedia({
189
- files: ownFiles,
190
- client: params.client,
191
- token: params.botToken,
192
- maxBytes: params.mediaMaxBytes,
193
- readIdleTimeoutMs: params.mediaReadIdleTimeoutMs,
194
- totalTimeoutMs: params.mediaTotalTimeoutMs,
195
- abortSignal: params.abortSignal
196
- })) : Promise.resolve(null);
197
- const attachmentContentPromise = params.message.attachments && params.message.attachments.length > 0 ? loadSlackMediaModule$1().then(({ resolveSlackAttachmentContent }) => resolveSlackAttachmentContent({
198
- attachments: params.message.attachments,
199
- client: params.client,
200
- token: params.botToken,
201
- maxBytes: params.mediaMaxBytes,
202
- readIdleTimeoutMs: params.mediaReadIdleTimeoutMs,
203
- totalTimeoutMs: params.mediaTotalTimeoutMs,
204
- abortSignal: params.abortSignal
205
- })) : Promise.resolve(null);
206
- const [media, attachmentContent] = await Promise.all([mediaPromise, attachmentContentPromise]);
207
- const mergedMedia = [...media ?? [], ...attachmentContent?.media ?? []];
208
- const effectiveDirectMedia = mergedMedia.length > 0 ? mergedMedia : null;
209
- const mediaPlaceholder = effectiveDirectMedia ? effectiveDirectMedia.map((item) => item.placeholder).join(" ") : void 0;
210
- const fallbackFiles = ownFiles ?? [];
211
- const fileOnlyFallback = !mediaPlaceholder && fallbackFiles.length > 0 ? fallbackFiles.slice(0, 8).map((file) => formatSlackFileReference(file)).join(", ") : void 0;
212
- const fileOnlyPlaceholder = fileOnlyFallback ? `[Slack file: ${fileOnlyFallback}]` : void 0;
213
- let botAttachmentText;
214
- if (params.isBotMessage && !attachmentContent?.text) {
215
- const botAttachmentTextParts = [];
216
- for (const attachment of params.message.attachments ?? []) {
217
- const text = normalizeOptionalString(attachment.text) ?? normalizeOptionalString(attachment.fallback);
218
- if (text) botAttachmentTextParts.push(text);
219
- }
220
- botAttachmentText = botAttachmentTextParts.length > 0 ? botAttachmentTextParts.join("\n") : void 0;
221
- }
222
- const blocksText = resolveSlackBlocksText(params.message.blocks);
223
- const textParts = [
224
- chooseSlackPrimaryText({
225
- messageText: normalizeOptionalString(params.message.text),
226
- blocksText
227
- }),
228
- attachmentContent?.text,
229
- botAttachmentText
230
- ];
231
- const renderedMentions = /* @__PURE__ */ new Map();
232
- const resolveUserName = params.resolveUserName;
233
- if (resolveUserName) {
234
- const mentionIds = collectUniqueSlackMentionIds$1(textParts);
235
- const lookupIds = mentionIds.slice(0, SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE);
236
- const skippedLookups = mentionIds.length - lookupIds.length;
237
- if (skippedLookups > 0) logVerbose(`slack: skipping ${skippedLookups} mention lookup(s) beyond per-message cap (${SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE})`);
238
- const { results } = await runTasksWithConcurrency({
239
- tasks: lookupIds.map((userId) => async () => {
240
- const renderedName = normalizeOptionalString((await resolveUserName(userId))?.name);
241
- return {
242
- userId,
243
- rendered: renderedName ? `<@${userId}> (${renderedName})` : null
244
- };
245
- }),
246
- limit: SLACK_MENTION_RESOLUTION_CONCURRENCY
247
- });
248
- for (const result of results) {
249
- if (!result) continue;
250
- renderedMentions.set(result.userId, result.rendered);
251
- }
252
- }
253
- const rawBody = [
254
- renderSlackUserMentions(textParts[0], renderedMentions),
255
- renderSlackUserMentions(textParts[1], renderedMentions),
256
- renderSlackUserMentions(textParts[2], renderedMentions),
257
- mediaPlaceholder,
258
- fileOnlyPlaceholder
259
- ].filter(Boolean).join("\n") || "";
260
- if (!rawBody) return null;
261
- return {
262
- rawBody,
263
- effectiveDirectMedia
264
- };
265
- }
266
- //#endregion
267
- //#region extensions/slack/src/monitor/message-handler/prepare-dm-history.ts
268
- function resolveSlackDmHistoryLimit(params) {
269
- const override = params.userId && params.account.config.dms?.[params.userId]?.historyLimit !== void 0 ? params.account.config.dms[params.userId]?.historyLimit : void 0;
270
- return Math.max(0, override ?? params.defaultLimit);
271
- }
272
- async function resolveSlackDmHistoryContext(params) {
273
- const maxMessages = Math.max(0, Math.floor(params.limit));
274
- if (maxMessages <= 0) return {
275
- body: void 0,
276
- inboundHistory: void 0
277
- };
278
- try {
279
- const messages = ((await params.ctx.app.client.conversations.history({
280
- token: params.ctx.botToken,
281
- channel: params.channelId,
282
- ...params.currentMessageTs ? {
283
- latest: params.currentMessageTs,
284
- inclusive: true
285
- } : {},
286
- limit: maxMessages + 1
287
- })).messages ?? []).filter((message) => {
288
- if (params.currentMessageTs && message.ts === params.currentMessageTs) return false;
289
- return Boolean(normalizeOptionalString(message.text));
290
- }).slice(0, maxMessages).toReversed();
291
- if (messages.length === 0) return {
292
- body: void 0,
293
- inboundHistory: void 0
294
- };
295
- const userNames = /* @__PURE__ */ new Map();
296
- const resolveUserLabel = async (userId) => {
297
- const cached = userNames.get(userId);
298
- if (cached) return cached;
299
- const label = normalizeOptionalString((await params.ctx.resolveUserName(userId)).name) ?? userId;
300
- userNames.set(userId, label);
301
- return label;
302
- };
303
- const entries = [];
304
- const formatted = [];
305
- for (const message of messages) {
306
- const body = normalizeOptionalString(message.text);
307
- if (!body) continue;
308
- const isCurrentBot = params.ctx.botUserId && message.user === params.ctx.botUserId || params.ctx.botId && message.bot_id === params.ctx.botId;
309
- const role = isCurrentBot || message.bot_id ? "assistant" : "user";
310
- const sender = `${isCurrentBot ? "Assistant" : message.user ? await resolveUserLabel(message.user) : normalizeOptionalString(message.username) ?? (message.bot_id ? "Bot" : "Unknown")} (${role})`;
311
- const timestamp = message.ts ? Math.round(Number(message.ts) * 1e3) : void 0;
312
- entries.push({
313
- sender,
314
- body,
315
- timestamp
316
- });
317
- formatted.push(formatInboundEnvelope({
318
- channel: "Slack",
319
- from: sender,
320
- timestamp,
321
- body: `${body}\n[slack message id: ${message.ts ?? "unknown"} channel: ${params.channelId}]`,
322
- chatType: "direct",
323
- envelope: params.envelopeOptions
324
- }));
325
- }
326
- return {
327
- body: formatted.length > 0 ? formatted.join("\n\n") : void 0,
328
- inboundHistory: entries.length > 0 ? entries : void 0
329
- };
330
- } catch (err) {
331
- logVerbose(`slack: failed to fetch DM history for channel ${params.channelId}: ${formatErrorMessage(err)}`);
332
- return {
333
- body: void 0,
334
- inboundHistory: void 0
335
- };
336
- }
337
- }
338
- //#endregion
339
- //#region extensions/slack/src/threading.ts
340
- function resolveSlackThreadContext(params) {
341
- const incomingThreadTs = params.message.thread_ts;
342
- const eventTs = params.message.event_ts;
343
- const messageTs = params.message.ts ?? eventTs;
344
- const isThreadReply = typeof incomingThreadTs === "string" && incomingThreadTs.length > 0 && (incomingThreadTs !== messageTs || Boolean(params.message.parent_user_id));
345
- return {
346
- incomingThreadTs,
347
- messageTs,
348
- isThreadReply,
349
- replyToId: incomingThreadTs ?? messageTs,
350
- messageThreadId: isThreadReply ? incomingThreadTs : params.replyToMode === "all" ? messageTs : void 0
351
- };
352
- }
353
- /**
354
- * Resolves Slack thread targeting for replies and status indicators.
355
- *
356
- * @returns replyThreadTs - Thread timestamp for reply messages
357
- * @returns statusThreadTs - Thread timestamp for status indicators (typing, etc.)
358
- * @returns isThreadReply - true if this is a genuine user reply in a thread,
359
- * false if thread_ts comes from a bot status message (e.g. typing indicator)
360
- */
361
- function resolveSlackThreadTargets(params) {
362
- const { incomingThreadTs, messageTs, isThreadReply } = resolveSlackThreadContext(params);
363
- const replyThreadTs = isThreadReply ? incomingThreadTs : params.replyToMode === "all" ? messageTs : void 0;
364
- return {
365
- replyThreadTs,
366
- statusThreadTs: replyThreadTs,
367
- isThreadReply
368
- };
369
- }
370
- //#endregion
371
- //#region extensions/slack/src/monitor/message-handler/prepare-routing.ts
372
- const slackRouteBindingConfigCache = /* @__PURE__ */ new WeakMap();
373
- function slackTargetDefaultKindForPeer(kind) {
374
- return kind === "direct" ? "user" : "channel";
375
- }
376
- function slackTargetKindMatchesPeer(peerKind, targetKind) {
377
- if (targetKind === "user") return peerKind === "direct";
378
- return peerKind === "channel" || peerKind === "group";
379
- }
380
- function normalizeSlackRouteBindingPeer(peer) {
381
- const rawId = peer.id.trim();
382
- if (!rawId || rawId === "*") return peer;
383
- const target = (() => {
384
- try {
385
- return parseSlackTarget(rawId, { defaultKind: slackTargetDefaultKindForPeer(peer.kind) });
386
- } catch {
387
- return;
388
- }
389
- })();
390
- if (!target || !slackTargetKindMatchesPeer(peer.kind, target.kind) || target.id === peer.id) return peer;
391
- return {
392
- ...peer,
393
- id: target.id
394
- };
395
- }
396
- function normalizeSlackRouteBindingConfig(cfg) {
397
- const bindings = cfg.bindings;
398
- const cached = slackRouteBindingConfigCache.get(cfg);
399
- if (cached && cached.bindingsRef === bindings) return cached.normalizedCfg;
400
- if (!Array.isArray(bindings)) return cfg;
401
- let changed = false;
402
- const normalizedBindings = bindings.map((binding) => {
403
- if (binding.type === "acp" || binding.match.channel.trim().toLowerCase() !== "slack") return binding;
404
- const peer = binding.match.peer;
405
- if (!peer) return binding;
406
- const normalizedPeer = normalizeSlackRouteBindingPeer(peer);
407
- if (normalizedPeer === peer) return binding;
408
- changed = true;
409
- return {
410
- ...binding,
411
- match: {
412
- ...binding.match,
413
- peer: normalizedPeer
414
- }
415
- };
416
- });
417
- const normalizedCfg = changed ? {
418
- ...cfg,
419
- bindings: normalizedBindings
420
- } : cfg;
421
- slackRouteBindingConfigCache.set(cfg, {
422
- bindingsRef: bindings,
423
- normalizedCfg
424
- });
425
- return normalizedCfg;
426
- }
427
- function resolveSlackBaseConversationId(params) {
428
- return params.isDirectMessage ? `user:${params.message.user ?? "unknown"}` : params.message.channel;
429
- }
430
- function resolveSlackInitialAgentRoute(params) {
431
- return resolveAgentRoute({
432
- cfg: normalizeSlackRouteBindingConfig(params.ctx.cfg),
433
- channel: "slack",
434
- accountId: params.account.accountId,
435
- teamId: params.ctx.teamId || void 0,
436
- peer: {
437
- kind: params.isDirectMessage ? "direct" : params.isRoom ? "channel" : "group",
438
- id: params.isDirectMessage ? params.message.user ?? "unknown" : params.message.channel
439
- }
440
- });
441
- }
442
- function resolveSlackRoutingContext(params) {
443
- const { ctx, account, message, isDirectMessage, isGroupDm, isRoom, isRoomish, seedTopLevelRoomThread, assistantThreadTs } = params;
444
- let route = resolveSlackInitialAgentRoute({
445
- ctx,
446
- account,
447
- message,
448
- isDirectMessage,
449
- isRoom
450
- });
451
- const chatType = isDirectMessage ? "direct" : isGroupDm ? "group" : "channel";
452
- const replyToMode = resolveSlackReplyToMode(account, chatType);
453
- const threadContext = resolveSlackThreadContext({
454
- message,
455
- replyToMode
456
- });
457
- const threadTs = threadContext.incomingThreadTs;
458
- const isThreadReply = threadContext.isThreadReply;
459
- const autoThreadId = !isThreadReply && replyToMode === "all" && threadContext.messageTs ? threadContext.messageTs : void 0;
460
- const seedCandidateThreadId = threadContext.incomingThreadTs ?? threadContext.messageTs;
461
- const routedThreadId = (isDirectMessage ? assistantThreadTs : isRoomish ? isThreadReply && threadTs ? threadTs : void 0 : isThreadReply ? threadTs : autoThreadId) ?? (isRoomish ? !isThreadReply && isRoom && seedTopLevelRoomThread && replyToMode !== "off" && seedCandidateThreadId ? seedCandidateThreadId : void 0 : void 0);
462
- const baseConversationId = resolveSlackBaseConversationId({
463
- message,
464
- isDirectMessage
465
- });
466
- const runtimeBindingThreadId = routedThreadId ?? (isDirectMessage && isThreadReply ? threadTs : void 0);
467
- const boundThreadRoute = runtimeBindingThreadId ? resolveRuntimeConversationBindingRoute({
468
- route,
469
- conversation: {
470
- channel: "slack",
471
- accountId: account.accountId,
472
- conversationId: runtimeBindingThreadId,
473
- parentConversationId: baseConversationId
474
- }
475
- }) : null;
476
- const runtimeRoute = boundThreadRoute?.boundSessionKey || boundThreadRoute?.bindingRecord ? boundThreadRoute : resolveRuntimeConversationBindingRoute({
477
- route,
478
- conversation: {
479
- channel: "slack",
480
- accountId: account.accountId,
481
- conversationId: baseConversationId
482
- }
483
- });
484
- let configuredBinding = null;
485
- let configuredBindingSessionKey = "";
486
- if (runtimeRoute.boundSessionKey || runtimeRoute.bindingRecord) route = runtimeRoute.route;
487
- else {
488
- const configuredRoute = resolveConfiguredBindingRoute({
489
- cfg: ctx.cfg,
490
- route,
491
- conversation: {
492
- channel: "slack",
493
- accountId: account.accountId,
494
- conversationId: baseConversationId
495
- }
496
- });
497
- configuredBinding = configuredRoute.bindingResolution;
498
- configuredBindingSessionKey = configuredRoute.boundSessionKey ?? "";
499
- route = configuredRoute.route;
500
- }
501
- const threadKeys = runtimeRoute.boundSessionKey || configuredBindingSessionKey ? {
502
- sessionKey: route.sessionKey,
503
- parentSessionKey: void 0
504
- } : resolveThreadSessionKeys({
505
- baseSessionKey: route.sessionKey,
506
- threadId: routedThreadId,
507
- parentSessionKey: routedThreadId && ctx.threadInheritParent ? route.sessionKey : void 0
508
- });
509
- const sessionKey = threadKeys.sessionKey;
510
- const historyKey = isThreadReply && ctx.threadHistoryScope === "thread" ? sessionKey : message.channel;
511
- return {
512
- route,
513
- runtimeBinding: runtimeRoute.bindingRecord,
514
- runtimeBoundSessionKey: runtimeRoute.boundSessionKey,
515
- configuredBinding,
516
- configuredBindingSessionKey,
517
- chatType,
518
- replyToMode,
519
- threadContext,
520
- threadTs,
521
- isThreadReply,
522
- threadKeys,
523
- sessionKey,
524
- historyKey
525
- };
526
- }
527
- //#endregion
528
- //#region extensions/slack/src/monitor/message-handler/prepare-thread-context-root.ts
529
- function isSlackThreadAuthorCurrentBot(params) {
530
- const { identity, author } = params;
531
- if (identity.botUserId && author.userId && author.userId === identity.botUserId) return true;
532
- if (identity.botId && author.botId && author.botId === identity.botId) return true;
533
- return false;
534
- }
535
- function resolveSlackThreadHistoryFilterPolicy(params) {
536
- if (!params.includeBotStarterAsRootContext || !params.starterTs) return {};
537
- return { retainCurrentBotRootTs: params.starterTs };
538
- }
539
- function applySlackThreadHistoryFilterPolicy(params) {
540
- const kept = [];
541
- let omittedCurrentBot = 0;
542
- for (const entry of params.history) {
543
- if (!isSlackThreadAuthorCurrentBot({
544
- identity: params.identity,
545
- author: entry
546
- })) {
547
- kept.push(entry);
548
- continue;
549
- }
550
- if (params.policy.retainCurrentBotRootTs && entry.ts === params.policy.retainCurrentBotRootTs) kept.push(entry);
551
- else omittedCurrentBot += 1;
552
- }
553
- return {
554
- kept,
555
- omittedCurrentBot
556
- };
557
- }
558
- function shouldIncludeBotThreadStarterContext(params) {
559
- if (!params.hasStarterText) return false;
560
- return params.starterIsCurrentBot && params.isNewThreadSession;
561
- }
562
- function ensureSlackThreadHistoryHasBotRoot(params) {
563
- if (!params.includeBotStarterAsRootContext || !params.threadStarter?.text) return params.history;
564
- if (params.history.some((entry) => entry.ts === params.threadStarter?.ts)) return params.history;
565
- return [params.threadStarter, ...params.history];
566
- }
567
- function formatSlackBotStarterThreadLabel(params) {
568
- const base = `Slack thread ${params.roomLabel}`;
569
- if (!params.starterText) return base;
570
- const snippet = params.starterText.replace(/\s+/g, " ").slice(0, 80).trim();
571
- if (!snippet) return base;
572
- return `${base} (assistant root): ${snippet}`;
573
- }
574
- //#endregion
575
- //#region extensions/slack/src/monitor/message-handler/prepare-thread-context.ts
576
- let slackMediaModulePromise;
577
- function loadSlackMediaModule() {
578
- slackMediaModulePromise ??= import("./actions-1o9nMIY8.js").then((n) => n.h);
579
- return slackMediaModulePromise;
580
- }
581
- const SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY = 4;
582
- function isSlackThreadContextSenderAllowed(params) {
583
- if (params.allowFromLower.length === 0 || params.botId) return true;
584
- if (!params.userId) return false;
585
- return resolveSlackAllowListMatch({
586
- allowList: params.allowFromLower,
587
- id: params.userId,
588
- name: params.userName,
589
- allowNameMatching: params.allowNameMatching
590
- }).allowed;
591
- }
592
- async function resolveSlackThreadUserMap(params) {
593
- const uniqueUserIds = [];
594
- const seen = /* @__PURE__ */ new Set();
595
- for (const item of params.messages) {
596
- if (!item.userId || seen.has(item.userId)) continue;
597
- seen.add(item.userId);
598
- uniqueUserIds.push(item.userId);
599
- }
600
- const userMap = /* @__PURE__ */ new Map();
601
- if (uniqueUserIds.length === 0) return userMap;
602
- const { results } = await runTasksWithConcurrency({
603
- tasks: uniqueUserIds.map((id) => async () => {
604
- const user = await params.ctx.resolveUserName(id);
605
- return user ? {
606
- id,
607
- user
608
- } : null;
609
- }),
610
- limit: SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY
611
- });
612
- for (const result of results) if (result) userMap.set(result.id, result.user);
613
- return userMap;
614
- }
615
- async function resolveSlackThreadContextData(params) {
616
- const botIdentity = {
617
- botUserId: params.ctx.botUserId,
618
- botId: params.ctx.botId
619
- };
620
- const isCurrentBotAuthor = (author) => isSlackThreadAuthorCurrentBot({
621
- identity: botIdentity,
622
- author
623
- });
624
- let threadStarterBody;
625
- let threadHistoryBody;
626
- let threadSessionPreviousTimestamp;
627
- let threadLabel;
628
- let threadStarterMedia = null;
629
- if (!params.isThreadReply || !params.threadTs) return {
630
- threadStarterBody,
631
- threadHistoryBody,
632
- threadSessionPreviousTimestamp,
633
- threadLabel,
634
- threadStarterMedia
635
- };
636
- const starter = params.threadStarter;
637
- const starterSenderName = params.allowNameMatching && params.allowFromLower.length > 0 && starter?.userId ? (await params.ctx.resolveUserName(starter.userId))?.name : void 0;
638
- const starterIsCurrentBot = Boolean(starter && isCurrentBotAuthor({
639
- userId: starter.userId,
640
- botId: starter.botId
641
- }));
642
- const starterAllowed = !starter || !starterIsCurrentBot && isSlackThreadContextSenderAllowed({
643
- allowFromLower: params.allowFromLower,
644
- allowNameMatching: params.allowNameMatching,
645
- userId: starter.userId,
646
- userName: starterSenderName,
647
- botId: starter.botId
648
- });
649
- const includeStarterContext = !starter || !starterIsCurrentBot && shouldIncludeSupplementalContext({
650
- mode: params.contextVisibilityMode,
651
- kind: "thread",
652
- senderAllowed: starterAllowed
653
- });
654
- if (starter?.text && includeStarterContext) {
655
- threadStarterBody = starter.text;
656
- const snippet = starter.text.replace(/\s+/g, " ").slice(0, 80);
657
- threadLabel = `Slack thread ${params.roomLabel}${snippet ? `: ${snippet}` : ""}`;
658
- if (!params.effectiveDirectMedia && starter.files && starter.files.length > 0) {
659
- const { resolveSlackMedia } = await loadSlackMediaModule();
660
- threadStarterMedia = await resolveSlackMedia({
661
- files: starter.files,
662
- client: params.ctx.app.client,
663
- token: params.ctx.botToken,
664
- maxBytes: params.ctx.mediaMaxBytes
665
- });
666
- if (threadStarterMedia) logVerbose(`slack: hydrated thread starter file ${threadStarterMedia.map((item) => item.placeholder).join(", ")} from root message`);
667
- }
668
- } else threadLabel = `Slack thread ${params.roomLabel}`;
669
- threadSessionPreviousTimestamp = readSessionUpdatedAt({
670
- storePath: params.storePath,
671
- sessionKey: params.sessionKey
672
- });
673
- const includeBotStarterAsRootContext = shouldIncludeBotThreadStarterContext({
674
- starterIsCurrentBot,
675
- isNewThreadSession: !threadSessionPreviousTimestamp,
676
- hasStarterText: Boolean(starter?.text)
677
- });
678
- if (starter?.text && starterIsCurrentBot && !includeBotStarterAsRootContext) logVerbose("slack: omitted current-bot thread starter from context");
679
- else if (starter?.text && !includeStarterContext && !starterIsCurrentBot) logVerbose(`slack: omitted thread starter from context (mode=${params.contextVisibilityMode}, sender_allowed=${starterAllowed ? "yes" : "no"})`);
680
- else if (includeBotStarterAsRootContext) {
681
- threadLabel = formatSlackBotStarterThreadLabel({
682
- roomLabel: params.roomLabel,
683
- starterText: starter?.text
684
- });
685
- logVerbose("slack: retained current-bot thread starter as assistant root context");
686
- }
687
- const threadInitialHistoryLimit = params.account.config?.thread?.initialHistoryLimit ?? 20;
688
- if (threadInitialHistoryLimit > 0 && (!threadSessionPreviousTimestamp || params.forceInitialHistory)) {
689
- const currentBotRootTs = starter?.ts ?? params.threadTs;
690
- const threadHistoryWithBotRoot = ensureSlackThreadHistoryHasBotRoot({
691
- history: await resolveSlackThreadHistory({
692
- channelId: params.message.channel,
693
- threadTs: params.threadTs,
694
- client: params.ctx.app.client,
695
- currentMessageTs: params.message.ts,
696
- limit: threadInitialHistoryLimit
697
- }),
698
- includeBotStarterAsRootContext,
699
- threadStarter: starter ? {
700
- ...starter,
701
- ts: currentBotRootTs
702
- } : null
703
- });
704
- if (threadHistoryWithBotRoot.length > 0) {
705
- const { kept: threadHistoryWithoutCurrentBot, omittedCurrentBot: omittedCurrentBotHistoryCount } = applySlackThreadHistoryFilterPolicy({
706
- history: threadHistoryWithBotRoot,
707
- policy: resolveSlackThreadHistoryFilterPolicy({
708
- includeBotStarterAsRootContext,
709
- starterTs: currentBotRootTs
710
- }),
711
- identity: botIdentity
712
- });
713
- const userMapForFilter = params.contextVisibilityMode !== "all" && params.allowNameMatching && params.allowFromLower.length > 0 ? await resolveSlackThreadUserMap({
714
- ctx: params.ctx,
715
- messages: threadHistoryWithoutCurrentBot
716
- }) : /* @__PURE__ */ new Map();
717
- const { items: filteredThreadHistory, omitted: omittedHistoryCount } = params.contextVisibilityMode === "all" ? {
718
- items: threadHistoryWithoutCurrentBot,
719
- omitted: 0
720
- } : filterSupplementalContextItems({
721
- items: threadHistoryWithoutCurrentBot,
722
- mode: params.contextVisibilityMode,
723
- kind: "thread",
724
- isSenderAllowed: (historyMsg) => {
725
- if (isCurrentBotAuthor({
726
- userId: historyMsg.userId,
727
- botId: historyMsg.botId
728
- })) return true;
729
- const msgUser = historyMsg.userId ? userMapForFilter.get(historyMsg.userId) : null;
730
- return isSlackThreadContextSenderAllowed({
731
- allowFromLower: params.allowFromLower,
732
- allowNameMatching: params.allowNameMatching,
733
- userId: historyMsg.userId,
734
- userName: msgUser?.name,
735
- botId: historyMsg.botId
736
- });
737
- }
738
- });
739
- const userMap = await resolveSlackThreadUserMap({
740
- ctx: params.ctx,
741
- messages: filteredThreadHistory
742
- });
743
- if (omittedHistoryCount > 0 || omittedCurrentBotHistoryCount > 0) logVerbose(`slack: omitted ${omittedHistoryCount + omittedCurrentBotHistoryCount} thread message(s) from context (mode=${params.contextVisibilityMode})`);
744
- const historyParts = [];
745
- for (const historyMsg of filteredThreadHistory) {
746
- const msgUser = historyMsg.userId ? userMap.get(historyMsg.userId) : null;
747
- const isOtherBot = Boolean(historyMsg.botId) && historyMsg.botId !== params.ctx.botId;
748
- const isCurrentBot = isCurrentBotAuthor({
749
- userId: historyMsg.userId,
750
- botId: historyMsg.botId
751
- });
752
- const role = isCurrentBot || isOtherBot || Boolean(historyMsg.botId) ? "assistant" : "user";
753
- const msgSenderName = isCurrentBot ? "Bot (this assistant)" : msgUser?.name ?? (historyMsg.botId ? `Bot (${historyMsg.botId})` : "Unknown");
754
- const msgWithId = `${historyMsg.text}\n[slack message id: ${historyMsg.ts ?? "unknown"} channel: ${params.message.channel}]`;
755
- historyParts.push(formatInboundEnvelope({
756
- channel: "Slack",
757
- from: `${msgSenderName} (${role})`,
758
- timestamp: historyMsg.ts ? Math.round(Number(historyMsg.ts) * 1e3) : void 0,
759
- body: msgWithId,
760
- chatType: "channel",
761
- envelope: params.envelopeOptions
762
- }));
763
- }
764
- if (historyParts.length > 0) {
765
- threadHistoryBody = historyParts.join("\n\n");
766
- logVerbose(`slack: populated thread history with ${filteredThreadHistory.length} messages for new session`);
767
- }
768
- }
769
- }
770
- return {
771
- threadStarterBody,
772
- threadHistoryBody,
773
- threadSessionPreviousTimestamp,
774
- threadLabel,
775
- threadStarterMedia
776
- };
777
- }
778
- //#endregion
779
- //#region extensions/slack/src/monitor/message-handler/subteam-mentions.ts
780
- const SUBTEAM_MENTION_RE = /<!subteam\^([A-Z0-9]+)(?:\|[^>]*)?>/gi;
781
- const SUBTEAM_MEMBER_CACHE_TTL_MS = 300 * 1e3;
782
- let subteamMemberCache = /* @__PURE__ */ new WeakMap();
783
- function normalizeSlackId(value) {
784
- return typeof value === "string" && value.trim() ? value.trim().toUpperCase() : void 0;
785
- }
786
- function extractSlackSubteamMentionIds(text) {
787
- if (!text) return [];
788
- const ids = /* @__PURE__ */ new Set();
789
- for (const match of text.matchAll(SUBTEAM_MENTION_RE)) {
790
- const id = normalizeSlackId(match[1]);
791
- if (id) ids.add(id);
792
- }
793
- return [...ids];
794
- }
795
- async function readSlackSubteamUsers(params) {
796
- let bySubteam = subteamMemberCache.get(params.client);
797
- if (!bySubteam) {
798
- bySubteam = /* @__PURE__ */ new Map();
799
- subteamMemberCache.set(params.client, bySubteam);
800
- }
801
- const cacheKey = `${normalizeSlackId(params.teamId) ?? ""}:${params.subteamId}`;
802
- const cached = bySubteam.get(cacheKey);
803
- if (cached && cached.expiresAt > params.now) return cached.users;
804
- try {
805
- const response = await params.client.usergroups.users.list({
806
- usergroup: params.subteamId,
807
- ...params.teamId ? { team_id: params.teamId } : {}
808
- });
809
- if (!response.ok) {
810
- params.log?.(`slack: failed to resolve user-group mention ${params.subteamId}: ${response.error ?? "unknown_error"}`);
811
- return /* @__PURE__ */ new Set();
812
- }
813
- const users = new Set((response.users ?? []).map((userId) => normalizeSlackId(userId)).filter(Boolean));
814
- bySubteam.set(cacheKey, {
815
- expiresAt: params.now + SUBTEAM_MEMBER_CACHE_TTL_MS,
816
- users
817
- });
818
- return users;
819
- } catch (err) {
820
- params.log?.(`slack: failed to resolve user-group mention ${params.subteamId}: ${formatErrorMessage(err)}`);
821
- return /* @__PURE__ */ new Set();
822
- }
823
- }
824
- async function isSlackSubteamMentionForBot(params) {
825
- const botUserId = normalizeSlackId(params.botUserId);
826
- if (!botUserId) return false;
827
- const subteamIds = extractSlackSubteamMentionIds(params.text);
828
- if (subteamIds.length === 0) return false;
829
- const now = params.now ?? Date.now();
830
- for (const subteamId of subteamIds) if ((await readSlackSubteamUsers({
831
- client: params.client,
832
- subteamId,
833
- teamId: normalizeOptionalString(params.teamId),
834
- now,
835
- log: params.log
836
- })).has(botUserId)) return true;
837
- return false;
838
- }
839
- //#endregion
840
- //#region extensions/slack/src/monitor/message-handler/prepare.ts
841
- const mentionRegexCache = /* @__PURE__ */ new WeakMap();
842
- const SLACK_ANY_MENTION_RE = /<@[^>]+>|<!subteam\^[^>]+>/;
843
- const SLACK_USER_MENTION_RE = /<@([^>|]+)(?:\|[^>]+)?>/g;
844
- const SLACK_SUBTEAM_MENTION_RE = /<!subteam\^([^>|]+)(?:\|[^>]+)?>/g;
845
- const SLACK_SUBTEAM_MENTION_MARKER = "<!subteam^";
846
- const SLACK_HISTORY_MEDIA_MAX_ATTACHMENTS = 4;
847
- const SLACK_HISTORY_MEDIA_MAX_BYTES = 10 * 1024 * 1024;
848
- const SLACK_HISTORY_MEDIA_IDLE_TIMEOUT_MS = 1e3;
849
- const SLACK_HISTORY_MEDIA_TOTAL_TIMEOUT_MS = 3e3;
850
- function recordString(record, key) {
851
- return normalizeOptionalString(record?.[key]);
852
- }
853
- function recordNullableString(record, key) {
854
- if (!record || !(key in record)) return;
855
- if (record[key] === null) return null;
856
- return normalizeOptionalString(record[key]);
857
- }
858
- function mergeSlackAssistantThreadContext(primary, fallback) {
859
- if (!primary) return fallback;
860
- if (!fallback) return primary;
861
- return {
862
- assistantChannelId: primary.assistantChannelId || fallback.assistantChannelId,
863
- threadTs: primary.threadTs || fallback.threadTs,
864
- userId: primary.userId ?? fallback.userId,
865
- channelId: primary.channelId ?? fallback.channelId,
866
- teamId: primary.teamId ?? fallback.teamId,
867
- enterpriseId: primary.enterpriseId !== void 0 ? primary.enterpriseId : fallback.enterpriseId
868
- };
869
- }
870
- function hasSlackAssistantThreadMetadata(context) {
871
- return Boolean(context?.channelId || context?.teamId || context?.enterpriseId !== void 0);
872
- }
873
- function resolveSlackMessageAssistantThreadContext(message) {
874
- const thread = asOptionalRecord(message.assistant_thread);
875
- if (!thread) return;
876
- const context = asOptionalRecord(thread.context);
877
- const assistantChannelId = recordString(thread, "channel_id") ?? message.channel;
878
- const threadTs = recordString(thread, "thread_ts") ?? message.thread_ts ?? message.ts;
879
- if (!assistantChannelId || !threadTs) return;
880
- return {
881
- assistantChannelId,
882
- threadTs,
883
- userId: recordString(thread, "user_id") ?? message.user,
884
- channelId: recordString(context, "channel_id"),
885
- teamId: recordString(context, "team_id"),
886
- enterpriseId: recordNullableString(context, "enterprise_id")
887
- };
888
- }
889
- async function restoreSlackAssistantThreadContextFromMetadata(params) {
890
- const threadTs = params.message.thread_ts;
891
- const parentUserId = params.message.parent_user_id?.trim();
892
- if (!params.message.channel || !threadTs || !parentUserId || parentUserId !== params.ctx.botUserId && parentUserId !== params.ctx.botId) return;
893
- try {
894
- const response = await params.ctx.app.client.conversations.replies({
895
- channel: params.message.channel,
896
- ts: threadTs,
897
- oldest: threadTs,
898
- include_all_metadata: true,
899
- limit: 4
900
- });
901
- for (const message of response.messages ?? []) {
902
- const context = parseSlackAssistantThreadMetadata(message.metadata);
903
- if (!context) continue;
904
- return {
905
- assistantChannelId: params.message.channel,
906
- threadTs,
907
- userId: params.message.user,
908
- channelId: context.channelId,
909
- teamId: context.teamId,
910
- enterpriseId: context.enterpriseId
911
- };
912
- }
913
- } catch (err) {
914
- logVerbose(`slack assistant context restore failed channel=${params.message.channel} ts=${threadTs}: ${formatErrorMessage(err)}`);
915
- }
916
- }
917
- function resolveCachedMentionRegexes(ctx, agentId) {
918
- const key = normalizeOptionalString(agentId) ?? "__default__";
919
- let byAgent = mentionRegexCache.get(ctx);
920
- if (!byAgent) {
921
- byAgent = /* @__PURE__ */ new Map();
922
- mentionRegexCache.set(ctx, byAgent);
923
- }
924
- const cached = byAgent.get(key);
925
- if (cached) return cached;
926
- const built = buildMentionRegexes(ctx.cfg, agentId);
927
- byAgent.set(key, built);
928
- return built;
929
- }
930
- function isSlackImageFileCandidate(file) {
931
- if ((file.mimetype?.split(";")[0]?.trim().toLowerCase())?.startsWith("image/")) return true;
932
- return Boolean(mimeTypeFromFilePath(file.name)?.startsWith("image/"));
933
- }
934
- function sliceSlackImageFileCandidates(files, limit) {
935
- if (limit <= 0 || !files?.length) return [];
936
- return files.filter(isSlackImageFileCandidate).slice(0, limit);
937
- }
938
- function sliceSlackHistoryAttachmentCandidates(attachments, limit) {
939
- if (limit <= 0 || !attachments?.length) return [];
940
- const out = [];
941
- let remaining = limit;
942
- for (const attachment of attachments) {
943
- if (attachment.is_share !== true) continue;
944
- const hasImageUrl = Boolean(normalizeOptionalString(attachment.image_url));
945
- const files = sliceSlackImageFileCandidates(attachment.files, remaining - (hasImageUrl ? 1 : 0));
946
- if (!hasImageUrl && files.length === 0) continue;
947
- out.push({
948
- ...attachment,
949
- files
950
- });
951
- remaining -= (hasImageUrl ? 1 : 0) + files.length;
952
- if (remaining <= 0) break;
953
- }
954
- return out;
955
- }
956
- function buildSlackHistoryMediaCandidateMessage(message) {
957
- const files = sliceSlackImageFileCandidates(message.files, SLACK_HISTORY_MEDIA_MAX_ATTACHMENTS);
958
- const attachments = sliceSlackHistoryAttachmentCandidates(message.attachments, Math.max(0, SLACK_HISTORY_MEDIA_MAX_ATTACHMENTS - files.length));
959
- if (files.length === 0 && attachments.length === 0) return null;
960
- return {
961
- ...message,
962
- files,
963
- attachments
964
- };
965
- }
966
- async function resolveSlackHistoryMediaForPendingRecord(params) {
967
- const mediaMessage = buildSlackHistoryMediaCandidateMessage(params.message);
968
- if (!mediaMessage) return [];
969
- return toInboundMediaFacts((await resolveSlackMessageContent({
970
- message: mediaMessage,
971
- isThreadReply: params.isThreadReply,
972
- threadStarter: params.threadStarter,
973
- isBotMessage: params.isBotMessage,
974
- client: params.ctx.app.client,
975
- botToken: params.ctx.botToken,
976
- mediaMaxBytes: Math.min(params.ctx.mediaMaxBytes, SLACK_HISTORY_MEDIA_MAX_BYTES),
977
- mediaReadIdleTimeoutMs: SLACK_HISTORY_MEDIA_IDLE_TIMEOUT_MS,
978
- mediaTotalTimeoutMs: SLACK_HISTORY_MEDIA_TOTAL_TIMEOUT_MS
979
- }))?.effectiveDirectMedia, {
980
- kind: "image",
981
- messageId: params.message.ts
982
- });
983
- }
984
- function collectUniqueSlackMentionIds(text, regex) {
985
- const ids = [];
986
- regex.lastIndex = 0;
987
- for (const match of text.matchAll(regex)) {
988
- const id = normalizeSlackId(match[1]);
989
- if (id && !ids.includes(id)) ids.push(id);
990
- }
991
- return ids;
992
- }
993
- function collectSlackMentionMetadata(text) {
994
- return {
995
- mentionedUserIds: collectUniqueSlackMentionIds(text, SLACK_USER_MENTION_RE),
996
- mentionedSubteamIds: collectUniqueSlackMentionIds(text, SLACK_SUBTEAM_MENTION_RE),
997
- hasAnyMention: SLACK_ANY_MENTION_RE.test(text),
998
- hasSubteamMention: text.includes(SLACK_SUBTEAM_MENTION_MARKER)
999
- };
1000
- }
1001
- async function resolveSlackExplicitMentionState(params) {
1002
- const normalizedBotUserId = normalizeSlackId(params.ctx.botUserId);
1003
- const explicitlyMentionedBotUser = Boolean(normalizedBotUserId && params.mentionedUserIds.includes(normalizedBotUserId));
1004
- const explicitlyMentionedBotSubteam = Boolean(params.ctx.botUserId && params.hasSubteamMention) && await isSlackSubteamMentionForBot({
1005
- client: params.ctx.app.client,
1006
- text: params.messageText,
1007
- botUserId: params.ctx.botUserId,
1008
- teamId: params.ctx.teamId,
1009
- log: logVerbose
1010
- });
1011
- return {
1012
- explicitlyMentionedBotUser,
1013
- explicitlyMentionedBotSubteam,
1014
- explicitlyMentioned: explicitlyMentionedBotUser || explicitlyMentionedBotSubteam || params.source === "app_mention"
1015
- };
1016
- }
1017
- function resolveSlackMentionSource(params) {
1018
- if (params.explicitBotMention) return "explicit_bot";
1019
- if (params.explicitSubteamMention) return "subteam";
1020
- if (params.shouldBypassMention) return "command_bypass";
1021
- if (params.wasMentioned) return "mention_pattern";
1022
- if (params.matchedImplicitMentionKinds.length > 0) return "implicit_thread";
1023
- return "none";
1024
- }
1025
- function buildSlackMentionContextPayload(params) {
1026
- if (!params.isRoomish) return {};
1027
- return {
1028
- WasMentioned: params.effectiveWasMentioned,
1029
- ExplicitlyMentionedBot: params.explicitlyMentioned,
1030
- MentionedUserIds: params.mentionedUserIds.length > 0 ? [...params.mentionedUserIds] : void 0,
1031
- MentionedSubteamIds: params.mentionedSubteamIds.length > 0 ? [...params.mentionedSubteamIds] : void 0,
1032
- ImplicitMentionKinds: params.matchedImplicitMentionKinds.length > 0 ? [...params.matchedImplicitMentionKinds] : void 0,
1033
- MentionSource: params.mentionSource
1034
- };
1035
- }
1036
- async function resolveSlackConversationContext(params) {
1037
- const { ctx, account, message } = params;
1038
- const cfg = ctx.cfg;
1039
- let channelInfo = {};
1040
- let resolvedChannelType = normalizeSlackChannelType(message.channel_type, message.channel);
1041
- if (resolvedChannelType !== "im" && (!message.channel_type || message.channel_type !== "im")) {
1042
- channelInfo = await ctx.resolveChannelName(message.channel);
1043
- resolvedChannelType = normalizeSlackChannelType(message.channel_type ?? channelInfo.type, message.channel);
1044
- }
1045
- const channelName = channelInfo?.name;
1046
- const isDirectMessage = resolvedChannelType === "im";
1047
- const isGroupDm = resolvedChannelType === "mpim";
1048
- const isRoom = resolvedChannelType === "channel" || resolvedChannelType === "group";
1049
- const isRoomish = isRoom || isGroupDm;
1050
- const channelConfig = isRoom ? resolveSlackChannelConfig({
1051
- channelId: message.channel,
1052
- channelName,
1053
- channels: ctx.channelsConfig,
1054
- channelKeys: ctx.channelsConfigKeys,
1055
- defaultRequireMention: ctx.defaultRequireMention,
1056
- allowNameMatching: ctx.allowNameMatching
1057
- }) : null;
1058
- const allowBotsSetting = channelConfig?.allowBots ?? account.config?.allowBots ?? cfg.channels?.slack?.allowBots ?? false;
1059
- return {
1060
- channelInfo,
1061
- channelName,
1062
- resolvedChannelType,
1063
- isDirectMessage,
1064
- isGroupDm,
1065
- isRoom,
1066
- isRoomish,
1067
- channelConfig,
1068
- allowBotsMode: allowBotsSetting === "mentions" ? "mentions" : allowBotsSetting ? "all" : "off",
1069
- isBotMessage: Boolean(message.bot_id)
1070
- };
1071
- }
1072
- async function authorizeSlackInboundMessage(params) {
1073
- const { ctx, account, message, conversation } = params;
1074
- const { isDirectMessage, channelName, resolvedChannelType, isBotMessage, allowBotsMode } = conversation;
1075
- if (isBotMessage) {
1076
- if (message.user && ctx.botUserId && message.user === ctx.botUserId) return null;
1077
- if (allowBotsMode === "off") {
1078
- logVerbose(`slack: drop bot message ${message.bot_id ?? "unknown"} (allowBots=false)`);
1079
- return null;
1080
- }
1081
- }
1082
- if (isDirectMessage && !message.user) {
1083
- logVerbose("slack: drop dm message (missing user id)");
1084
- return null;
1085
- }
1086
- const senderId = message.user ?? (isBotMessage ? message.bot_id : void 0);
1087
- if (!senderId) {
1088
- logVerbose("slack: drop message (missing sender id)");
1089
- return null;
1090
- }
1091
- if (!ctx.isChannelAllowed({
1092
- channelId: message.channel,
1093
- channelName,
1094
- channelType: resolvedChannelType
1095
- })) {
1096
- logVerbose("slack: drop message (channel not allowed)");
1097
- return null;
1098
- }
1099
- const allowFromLower = await resolveSlackEffectiveAllowFrom(ctx, { includePairingStore: isDirectMessage });
1100
- if (isDirectMessage) {
1101
- const directUserId = message.user;
1102
- if (!directUserId) {
1103
- logVerbose("slack: drop dm message (missing user id)");
1104
- return null;
1105
- }
1106
- if (!await authorizeSlackDirectMessage({
1107
- ctx,
1108
- accountId: account.accountId,
1109
- senderId: directUserId,
1110
- allowFromLower,
1111
- resolveSenderName: ctx.resolveUserName,
1112
- sendPairingReply: async (text) => {
1113
- await sendMessageSlack(message.channel, text, {
1114
- cfg: ctx.cfg,
1115
- token: ctx.botToken,
1116
- client: ctx.app.client,
1117
- accountId: account.accountId
1118
- });
1119
- },
1120
- onDisabled: () => {
1121
- logVerbose("slack: drop dm (dms disabled)");
1122
- },
1123
- onUnauthorized: ({ allowMatchMeta }) => {
1124
- logVerbose(`Blocked unauthorized slack sender ${message.user} (dmPolicy=${ctx.dmPolicy}, ${allowMatchMeta})`);
1125
- },
1126
- log: logVerbose
1127
- })) return null;
1128
- }
1129
- return {
1130
- senderId,
1131
- allowFromLower
1132
- };
1133
- }
1134
- async function prepareSlackMessage(params) {
1135
- const { ctx, account, message, opts } = params;
1136
- const cfg = ctx.cfg;
1137
- const conversation = await resolveSlackConversationContext({
1138
- ctx,
1139
- account,
1140
- message
1141
- });
1142
- const { channelInfo, channelName, isDirectMessage, isGroupDm, isRoom, isRoomish, channelConfig, allowBotsMode, isBotMessage } = conversation;
1143
- const authorization = await authorizeSlackInboundMessage({
1144
- ctx,
1145
- account,
1146
- message,
1147
- conversation
1148
- });
1149
- if (!authorization) return null;
1150
- const { senderId, allowFromLower } = authorization;
1151
- const messageText = message.text ?? "";
1152
- const mentionMetadata = collectSlackMentionMetadata(messageText);
1153
- const { mentionedUserIds, mentionedSubteamIds, hasAnyMention } = mentionMetadata;
1154
- const { explicitlyMentionedBotUser, explicitlyMentionedBotSubteam, explicitlyMentioned } = await resolveSlackExplicitMentionState({
1155
- ctx,
1156
- messageText,
1157
- mentionedUserIds,
1158
- hasSubteamMention: mentionMetadata.hasSubteamMention,
1159
- source: opts.source
1160
- });
1161
- const channelRequireMention = channelConfig?.requireMention ?? ctx.defaultRequireMention ?? true;
1162
- const channelChatType = isDirectMessage ? "direct" : isGroupDm ? "group" : "channel";
1163
- const messageAssistantThreadContext = resolveSlackMessageAssistantThreadContext(message);
1164
- const assistantContextLookupChannelId = messageAssistantThreadContext?.assistantChannelId ?? message.channel;
1165
- const assistantContextLookupThreadTs = messageAssistantThreadContext?.threadTs ?? message.thread_ts ?? message.ts;
1166
- const cachedAssistantThreadContext = isDirectMessage ? ctx.getSlackAssistantThreadContext(assistantContextLookupChannelId, assistantContextLookupThreadTs) : void 0;
1167
- const restoredAssistantThreadContext = isDirectMessage && !cachedAssistantThreadContext && !hasSlackAssistantThreadMetadata(messageAssistantThreadContext) ? await restoreSlackAssistantThreadContextFromMetadata({
1168
- ctx,
1169
- message
1170
- }) : void 0;
1171
- const assistantThreadContext = mergeSlackAssistantThreadContext(messageAssistantThreadContext, cachedAssistantThreadContext ?? restoredAssistantThreadContext);
1172
- const assistantThreadContextToCache = messageAssistantThreadContext || restoredAssistantThreadContext ? assistantThreadContext : void 0;
1173
- if (assistantThreadContextToCache) ctx.saveSlackAssistantThreadContext(assistantThreadContextToCache);
1174
- const willImplicitlyThreadReply = isRoom && !channelRequireMention && resolveSlackReplyToMode(account, channelChatType) !== "off";
1175
- const seedTopLevelRoomThreadBySource = opts.source === "app_mention" || opts.wasMentioned === true || explicitlyMentioned || willImplicitlyThreadReply;
1176
- let routing = resolveSlackRoutingContext({
1177
- ctx,
1178
- account,
1179
- message,
1180
- isDirectMessage,
1181
- isGroupDm,
1182
- isRoom,
1183
- isRoomish,
1184
- seedTopLevelRoomThread: seedTopLevelRoomThreadBySource,
1185
- assistantThreadTs: assistantThreadContext?.threadTs
1186
- });
1187
- const resolveWasMentioned = (mentionRegexes) => opts.wasMentioned ?? (!isDirectMessage && matchesMentionWithExplicit({
1188
- text: messageText,
1189
- mentionRegexes,
1190
- explicit: {
1191
- hasAnyMention,
1192
- isExplicitlyMentioned: explicitlyMentioned,
1193
- canResolveExplicit: Boolean(ctx.botUserId)
1194
- }
1195
- }));
1196
- let mentionRegexes = resolveCachedMentionRegexes(ctx, routing.route.agentId);
1197
- let wasMentioned = resolveWasMentioned(mentionRegexes);
1198
- const hasBoundSession = Boolean(routing.runtimeBoundSessionKey || routing.configuredBindingSessionKey);
1199
- if (!seedTopLevelRoomThreadBySource && wasMentioned && isRoom && !routing.isThreadReply && !hasBoundSession) {
1200
- routing = resolveSlackRoutingContext({
1201
- ctx,
1202
- account,
1203
- message,
1204
- isDirectMessage,
1205
- isGroupDm,
1206
- isRoom,
1207
- isRoomish,
1208
- seedTopLevelRoomThread: true,
1209
- assistantThreadTs: assistantThreadContext?.threadTs
1210
- });
1211
- mentionRegexes = resolveCachedMentionRegexes(ctx, routing.route.agentId);
1212
- wasMentioned = resolveWasMentioned(mentionRegexes);
1213
- }
1214
- const { route, runtimeBinding, configuredBinding, configuredBindingSessionKey, replyToMode, threadContext, threadTs, isThreadReply, threadKeys, sessionKey, historyKey } = routing;
1215
- if (runtimeBinding && shouldLogVerbose()) logVerbose(`slack: routed via bound conversation ${runtimeBinding.conversation.conversationId} -> ${runtimeBinding.targetSessionKey}`);
1216
- if (configuredBinding) {
1217
- const ensured = await ensureConfiguredBindingRouteReady({
1218
- cfg,
1219
- bindingResolution: configuredBinding
1220
- });
1221
- if (ensured.ok) {
1222
- if (shouldLogVerbose()) logVerbose(`slack: using configured ACP binding for ${configuredBinding.record.conversation.conversationId} -> ${configuredBindingSessionKey}`);
1223
- } else {
1224
- if (shouldLogVerbose()) logVerbose(`slack: configured ACP binding unavailable for ${configuredBinding.record.conversation.conversationId}: ${ensured.error}`);
1225
- logInboundDrop({
1226
- log: logVerbose,
1227
- channel: "slack",
1228
- reason: "configured ACP binding unavailable",
1229
- target: configuredBinding.record.conversation.conversationId
1230
- });
1231
- return null;
1232
- }
1233
- }
1234
- const directThreadRoutedToDmSession = !assistantThreadContext && isDirectMessage && isThreadReply && threadTs && runtimeBinding?.conversation.conversationId !== threadTs;
1235
- let implicitMentionKinds = [];
1236
- if (!isDirectMessage && ctx.botUserId && message.thread_ts && !ctx.threadRequireExplicitMention && !wasMentioned) {
1237
- const replyToBotKinds = implicitMentionKindWhen("reply_to_bot", message.parent_user_id === ctx.botUserId);
1238
- implicitMentionKinds = replyToBotKinds.length > 0 ? replyToBotKinds : implicitMentionKindWhen("bot_thread_participant", await hasSlackThreadParticipationWithPersistence({
1239
- accountId: account.accountId,
1240
- channelId: message.channel,
1241
- threadTs: message.thread_ts
1242
- }));
1243
- }
1244
- let resolvedSenderName = normalizeOptionalString(message.username);
1245
- const resolveSenderName = async () => {
1246
- if (resolvedSenderName) return resolvedSenderName;
1247
- if (message.user) {
1248
- const normalized = normalizeOptionalString((await ctx.resolveUserName(message.user))?.name);
1249
- if (normalized) {
1250
- resolvedSenderName = normalized;
1251
- return resolvedSenderName;
1252
- }
1253
- }
1254
- resolvedSenderName = message.user ?? message.bot_id ?? "unknown";
1255
- return resolvedSenderName;
1256
- };
1257
- const senderNameForAuth = ctx.allowNameMatching ? await resolveSenderName() : void 0;
1258
- const allowTextCommands = shouldHandleTextCommands({
1259
- cfg,
1260
- surface: "slack"
1261
- });
1262
- const shouldRequireMention = isRoom ? channelConfig?.requireMention ?? ctx.defaultRequireMention : false;
1263
- if (message["_ambiguousThreadReply"]) {
1264
- ctx.logger.info({
1265
- channel: message.channel,
1266
- ts: message.ts,
1267
- parentUserId: message.parent_user_id
1268
- }, "skipping ambiguous slack thread reply");
1269
- return null;
1270
- }
1271
- const canDetectMention = Boolean(ctx.botUserId) || mentionRegexes.length > 0;
1272
- const textForCommandDetection = stripSlackMentionsForCommandDetection(message.text ?? "");
1273
- const hasControlCommandInMessage = hasControlCommand(textForCommandDetection, cfg);
1274
- const hasAbortRequest = isAbortRequestText(textForCommandDetection);
1275
- const channelUsersAllowlistConfigured = isRoom && Array.isArray(channelConfig?.users) && channelConfig.users.length > 0;
1276
- const messageIngress = await resolveSlackCommandIngress({
1277
- ctx,
1278
- senderId,
1279
- senderName: senderNameForAuth,
1280
- channelType: conversation.resolvedChannelType ?? "channel",
1281
- channelId: message.channel,
1282
- ownerAllowFromLower: allowFromLower,
1283
- channelUsers: isRoom ? channelConfig?.users : void 0,
1284
- allowTextCommands,
1285
- hasControlCommand: hasControlCommandInMessage,
1286
- mentionFacts: {
1287
- canDetectMention,
1288
- wasMentioned,
1289
- hasAnyMention,
1290
- implicitMentionKinds
1291
- },
1292
- activation: {
1293
- requireMention: shouldRequireMention,
1294
- allowTextCommands,
1295
- ...ctx.threadRequireExplicitMention ? { allowedImplicitMentionKinds: [] } : {}
1296
- }
1297
- });
1298
- const effectiveWasMentioned = messageIngress.activationAccess.effectiveWasMentioned ?? false;
1299
- const shouldBypassMention = messageIngress.activationAccess.shouldBypassMention ?? false;
1300
- const matchedImplicitMentionKinds = implicitMentionKinds;
1301
- const mentionSource = resolveSlackMentionSource({
1302
- explicitBotMention: explicitlyMentionedBotUser || opts.source === "app_mention",
1303
- explicitSubteamMention: explicitlyMentionedBotSubteam,
1304
- matchedImplicitMentionKinds,
1305
- shouldBypassMention,
1306
- wasMentioned
1307
- });
1308
- const senderGate = messageIngress.senderAccess.gate;
1309
- if (isRoom && senderGate?.allowed === false) {
1310
- logVerbose(`Blocked unauthorized slack sender ${senderId} (not in channel users)`);
1311
- return null;
1312
- }
1313
- if (isRoom && isBotMessage && allowBotsMode !== "off" && !await authorizeSlackBotRoomMessage({
1314
- ctx,
1315
- channelId: message.channel,
1316
- senderId,
1317
- senderName: senderNameForAuth,
1318
- channelUsers: channelConfig?.users,
1319
- allowFromLower
1320
- })) return null;
1321
- if (isBotMessage && allowBotsMode === "mentions") {
1322
- if (!(isDirectMessage || effectiveWasMentioned || shouldBypassMention)) {
1323
- logVerbose("slack: drop bot message (allowBots=mentions, missing mention)");
1324
- return null;
1325
- }
1326
- }
1327
- const threadContextAllowFromLower = isRoom ? channelUsersAllowlistConfigured ? normalizeAllowListLower(channelConfig?.users) : [] : isDirectMessage ? allowFromLower : [];
1328
- const contextVisibilityMode = resolveChannelContextVisibilityMode({
1329
- cfg: ctx.cfg,
1330
- channel: "slack",
1331
- accountId: account.accountId
1332
- });
1333
- const commandAuthorized = messageIngress.commandAccess.authorized;
1334
- if (isRoomish && messageIngress.commandAccess.shouldBlockControlCommand) {
1335
- logInboundDrop({
1336
- log: logVerbose,
1337
- channel: "slack",
1338
- reason: "control command (unauthorized)",
1339
- target: senderId
1340
- });
1341
- return null;
1342
- }
1343
- if (isRoom && shouldRequireMention && messageIngress.activationAccess.shouldSkip) {
1344
- ctx.logger.info({
1345
- channel: message.channel,
1346
- reason: "no-mention"
1347
- }, "skipping channel message");
1348
- const pendingText = (message.text ?? "").trim();
1349
- const historyMediaCandidate = buildSlackHistoryMediaCandidateMessage(message);
1350
- const fallbackFile = message.files?.length ? `[Slack file: ${formatSlackFileReference(message.files[0])}]` : "";
1351
- const pendingBody = pendingText || fallbackFile || (!fallbackFile && historyMediaCandidate ? "[Slack media attachment]" : "");
1352
- const skippedThreadStarter = historyMediaCandidate && isThreadReply && threadTs ? await resolveSlackThreadStarter({
1353
- channelId: message.channel,
1354
- threadTs,
1355
- client: ctx.app.client
1356
- }) : null;
1357
- const timestamp = message.ts ? Math.round(Number(message.ts) * 1e3) : void 0;
1358
- const senderName = pendingBody ? await resolveSenderName() : void 0;
1359
- await recordDroppedChannelInboundHistory({
1360
- input: {
1361
- id: message.ts ?? `${message.channel}:${Date.now()}`,
1362
- timestamp,
1363
- rawText: pendingBody,
1364
- textForAgent: pendingBody,
1365
- raw: message
1366
- },
1367
- admission: {
1368
- kind: "drop",
1369
- reason: "slack-no-mention",
1370
- recordHistory: true
1371
- },
1372
- preflight: {
1373
- message: pendingBody ? {
1374
- rawBody: pendingBody,
1375
- body: pendingBody,
1376
- bodyForAgent: pendingBody,
1377
- senderLabel: senderName,
1378
- envelopeFrom: senderName
1379
- } : void 0,
1380
- history: {
1381
- key: historyKey,
1382
- historyMap: ctx.channelHistories,
1383
- limit: ctx.historyLimit,
1384
- recordOnDrop: true,
1385
- mediaLimit: SLACK_HISTORY_MEDIA_MAX_ATTACHMENTS
1386
- },
1387
- media: () => resolveSlackHistoryMediaForPendingRecord({
1388
- ctx,
1389
- message,
1390
- isThreadReply,
1391
- threadStarter: skippedThreadStarter,
1392
- isBotMessage
1393
- })
1394
- }
1395
- });
1396
- return null;
1397
- }
1398
- const threadStarter = isThreadReply && threadTs ? await resolveSlackThreadStarter({
1399
- channelId: message.channel,
1400
- threadTs,
1401
- client: ctx.app.client
1402
- }) : null;
1403
- const resolvedMessageContent = await resolveSlackMessageContent({
1404
- message,
1405
- isThreadReply,
1406
- threadStarter,
1407
- isBotMessage,
1408
- botToken: ctx.botToken,
1409
- client: ctx.app.client,
1410
- mediaMaxBytes: ctx.mediaMaxBytes,
1411
- resolveUserName: ctx.resolveUserName
1412
- });
1413
- if (!resolvedMessageContent) return null;
1414
- const { rawBody, effectiveDirectMedia } = resolvedMessageContent;
1415
- const chatType = resolveSlackChatType(conversation.resolvedChannelType);
1416
- const inboundEventKind = classifyChannelInboundEvent({
1417
- conversation: { kind: chatType },
1418
- unmentionedGroupPolicy: resolveUnmentionedGroupInboundPolicy({
1419
- cfg,
1420
- agentId: route.agentId
1421
- }),
1422
- wasMentioned: effectiveWasMentioned,
1423
- hasControlCommand: hasControlCommandInMessage,
1424
- hasAbortRequest
1425
- });
1426
- const ackReaction = resolveAckReaction(cfg, route.agentId, {
1427
- channel: "slack",
1428
- accountId: account.accountId
1429
- });
1430
- const ackReactionValue = ackReaction ?? "";
1431
- const sourceRepliesAreToolOnly = resolveChannelMessageSourceReplyDeliveryMode({
1432
- cfg,
1433
- ctx: {
1434
- ChatType: chatType,
1435
- InboundEventKind: inboundEventKind
1436
- }
1437
- }) === "message_tool_only";
1438
- const statusReactionsExplicitlyEnabled = cfg.messages?.statusReactions?.enabled === true;
1439
- const shouldAckReaction$1 = () => Boolean(ackReaction && shouldAckReaction({
1440
- scope: ctx.ackReactionScope,
1441
- isDirect: isDirectMessage,
1442
- isGroup: isRoomish,
1443
- isMentionableGroup: isRoom,
1444
- requireMention: shouldRequireMention,
1445
- canDetectMention,
1446
- effectiveWasMentioned,
1447
- shouldBypassMention
1448
- }));
1449
- const ackReactionMessageTs = message.ts;
1450
- const allowToolOnlyStatusReaction = statusReactionsExplicitlyEnabled && (effectiveWasMentioned || shouldBypassMention);
1451
- const shouldSendAckReaction = shouldAckReaction$1() && (!sourceRepliesAreToolOnly || allowToolOnlyStatusReaction);
1452
- const statusReactionsWillHandle = Boolean(ackReactionMessageTs) && cfg.messages?.statusReactions?.enabled !== false && shouldSendAckReaction;
1453
- const ackReactionPromise = !statusReactionsWillHandle && shouldSendAckReaction && ackReactionMessageTs && ackReactionValue ? reactSlackMessage(message.channel, ackReactionMessageTs, ackReactionValue, {
1454
- token: ctx.botToken,
1455
- client: ctx.app.client
1456
- }).then(() => true, (err) => {
1457
- logVerbose(`slack react failed for channel ${message.channel}: ${formatSlackError(err)}`);
1458
- return false;
1459
- }) : statusReactionsWillHandle ? Promise.resolve(true) : null;
1460
- const roomLabel = channelName ? `#${channelName}` : `#${message.channel}`;
1461
- const senderName = await resolveSenderName();
1462
- const preview = rawBody.replace(/\s+/g, " ").slice(0, 160);
1463
- const inboundLabel = isDirectMessage ? `Slack DM from ${senderName}` : `Slack message in ${roomLabel} from ${senderName}`;
1464
- const slackFrom = isDirectMessage ? `slack:${message.user}` : isRoom ? `slack:channel:${message.channel}` : `slack:group:${message.channel}`;
1465
- enqueueSystemEvent(`${inboundLabel}: ${preview}`, {
1466
- sessionKey,
1467
- contextKey: `slack:message:${message.channel}:${message.ts ?? "unknown"}`
1468
- });
1469
- const envelopeFrom = resolveConversationLabel$1({
1470
- ChatType: chatType,
1471
- SenderName: senderName,
1472
- GroupSubject: isRoomish ? roomLabel : void 0,
1473
- From: slackFrom
1474
- }) ?? (isDirectMessage ? senderName : roomLabel);
1475
- const threadInfo = isThreadReply && threadTs ? ` thread_ts: ${threadTs}${message.parent_user_id ? ` parent_user_id: ${message.parent_user_id}` : ""}` : "";
1476
- const textWithId = `${rawBody}\n[slack message id: ${message.ts} channel: ${message.channel}${threadInfo}]`;
1477
- const storePath = resolveStorePath(ctx.cfg.session?.store, { agentId: route.agentId });
1478
- const envelopeOptions = resolveEnvelopeFormatOptions(ctx.cfg);
1479
- const previousTimestamp = readSessionUpdatedAt({
1480
- storePath,
1481
- sessionKey
1482
- });
1483
- const channelHistory = createChannelHistoryWindow({ historyMap: ctx.channelHistories });
1484
- const dmHistoryLimit = isDirectMessage ? resolveSlackDmHistoryLimit({
1485
- account,
1486
- userId: message.user,
1487
- defaultLimit: ctx.dmHistoryLimit
1488
- }) : 0;
1489
- let combinedBody = formatInboundEnvelope({
1490
- channel: "Slack",
1491
- from: envelopeFrom,
1492
- timestamp: message.ts ? Math.round(Number(message.ts) * 1e3) : void 0,
1493
- body: textWithId,
1494
- chatType,
1495
- sender: {
1496
- name: senderName,
1497
- id: senderId
1498
- },
1499
- previousTimestamp,
1500
- envelope: envelopeOptions
1501
- });
1502
- const dmHistoryContext = isDirectMessage && !isThreadReply && dmHistoryLimit > 0 && !previousTimestamp ? await resolveSlackDmHistoryContext({
1503
- ctx,
1504
- channelId: message.channel,
1505
- currentMessageTs: message.ts,
1506
- limit: dmHistoryLimit,
1507
- envelopeOptions
1508
- }) : {
1509
- body: void 0,
1510
- inboundHistory: void 0
1511
- };
1512
- if (dmHistoryContext.body) combinedBody = `${dmHistoryContext.body}\n\n${combinedBody}`;
1513
- if (isRoomish && ctx.historyLimit > 0) combinedBody = channelHistory.buildPendingContext({
1514
- historyKey,
1515
- limit: ctx.historyLimit,
1516
- currentMessage: combinedBody,
1517
- formatEntry: (entry) => formatInboundEnvelope({
1518
- channel: "Slack",
1519
- from: roomLabel,
1520
- timestamp: entry.timestamp,
1521
- body: `${entry.body}${entry.messageId ? ` [id:${entry.messageId} channel:${message.channel}]` : ""}`,
1522
- chatType: "channel",
1523
- senderLabel: entry.sender,
1524
- envelope: envelopeOptions
1525
- })
1526
- });
1527
- const slackTo = isDirectMessage ? `user:${message.user}` : `channel:${message.channel}`;
1528
- const { untrustedChannelMetadata, groupSystemPrompt } = resolveSlackRoomContextHints({
1529
- isRoomish,
1530
- channelInfo,
1531
- channelConfig
1532
- });
1533
- const { threadStarterBody, threadHistoryBody, threadSessionPreviousTimestamp, threadLabel, threadStarterMedia } = await resolveSlackThreadContextData({
1534
- ctx,
1535
- account,
1536
- message,
1537
- isThreadReply,
1538
- threadTs,
1539
- threadStarter,
1540
- roomLabel,
1541
- storePath,
1542
- sessionKey,
1543
- forceInitialHistory: Boolean(directThreadRoutedToDmSession),
1544
- allowFromLower: threadContextAllowFromLower,
1545
- allowNameMatching: ctx.allowNameMatching,
1546
- contextVisibilityMode,
1547
- envelopeOptions,
1548
- effectiveDirectMedia
1549
- });
1550
- const effectiveMedia = effectiveDirectMedia ?? threadStarterMedia;
1551
- const inboundHistory = isRoomish && ctx.historyLimit > 0 ? channelHistory.buildInboundHistory({
1552
- historyKey,
1553
- limit: ctx.historyLimit
1554
- }) : dmHistoryContext.inboundHistory;
1555
- const commandBody = textForCommandDetection.trim();
1556
- const supplementalThreadHistoryBody = directThreadRoutedToDmSession && !threadHistoryBody ? threadStarterBody : threadHistoryBody;
1557
- const effectiveMessageThreadId = assistantThreadContext?.threadTs ?? threadContext.messageThreadId;
1558
- const ctxPayload = buildChannelInboundEventContext({
1559
- channel: "slack",
1560
- accountId: route.accountId,
1561
- messageId: message.ts,
1562
- timestamp: message.ts ? Math.round(Number(message.ts) * 1e3) : void 0,
1563
- from: slackFrom,
1564
- sender: {
1565
- id: senderId,
1566
- name: senderName,
1567
- displayLabel: senderName
1568
- },
1569
- conversation: {
1570
- kind: chatType,
1571
- id: message.channel,
1572
- label: envelopeFrom,
1573
- spaceId: ctx.teamId || void 0,
1574
- threadId: directThreadRoutedToDmSession ? void 0 : effectiveMessageThreadId,
1575
- nativeChannelId: message.channel
1576
- },
1577
- route: {
1578
- agentId: route.agentId,
1579
- accountId: route.accountId,
1580
- routeSessionKey: sessionKey,
1581
- parentSessionKey: threadKeys.parentSessionKey
1582
- },
1583
- reply: {
1584
- to: slackTo,
1585
- replyToId: threadContext.replyToId,
1586
- messageThreadId: directThreadRoutedToDmSession ? void 0 : effectiveMessageThreadId,
1587
- nativeChannelId: message.channel
1588
- },
1589
- message: {
1590
- inboundEventKind,
1591
- body: combinedBody,
1592
- bodyForAgent: rawBody,
1593
- rawBody,
1594
- commandBody,
1595
- inboundHistory
1596
- },
1597
- access: {
1598
- mentions: {
1599
- canDetectMention: isRoomish,
1600
- wasMentioned: effectiveWasMentioned,
1601
- hasAnyMention: explicitlyMentioned || mentionedSubteamIds.length > 0,
1602
- implicitMentionKinds: matchedImplicitMentionKinds,
1603
- requireMention: shouldRequireMention,
1604
- effectiveWasMentioned
1605
- },
1606
- commands: { authorized: commandAuthorized }
1607
- },
1608
- media: toInboundMediaFacts(effectiveMedia),
1609
- supplemental: {
1610
- thread: {
1611
- starterBody: !directThreadRoutedToDmSession && !threadSessionPreviousTimestamp ? threadStarterBody : void 0,
1612
- historyBody: supplementalThreadHistoryBody,
1613
- label: directThreadRoutedToDmSession ? void 0 : threadLabel
1614
- },
1615
- groupSystemPrompt
1616
- },
1617
- extra: {
1618
- GroupSubject: isRoomish ? roomLabel : void 0,
1619
- UntrustedContext: untrustedChannelMetadata ? [untrustedChannelMetadata] : void 0,
1620
- TransportThreadId: directThreadRoutedToDmSession ? threadContext.messageThreadId : void 0,
1621
- SlackAssistantThread: assistantThreadContext ? true : void 0,
1622
- SlackAssistantThreadContextChannelId: assistantThreadContext?.channelId,
1623
- SlackAssistantThreadContextTeamId: assistantThreadContext?.teamId,
1624
- SlackAssistantThreadContextEnterpriseId: assistantThreadContext?.enterpriseId ?? void 0,
1625
- IsFirstThreadTurn: isThreadReply && threadTs && !directThreadRoutedToDmSession && !threadSessionPreviousTimestamp ? true : void 0,
1626
- ...buildSlackMentionContextPayload({
1627
- isRoomish,
1628
- effectiveWasMentioned,
1629
- explicitlyMentioned,
1630
- mentionedUserIds,
1631
- mentionedSubteamIds,
1632
- matchedImplicitMentionKinds,
1633
- mentionSource
1634
- })
1635
- }
1636
- });
1637
- if (isRoomish && !shouldRequireMention) channelHistory.record({
1638
- historyKey,
1639
- limit: ctx.historyLimit,
1640
- entry: {
1641
- sender: senderName,
1642
- body: rawBody,
1643
- timestamp: message.ts ? Math.round(Number(message.ts) * 1e3) : void 0,
1644
- messageId: message.ts
1645
- }
1646
- });
1647
- const pinnedMainDmOwner = isDirectMessage ? resolvePinnedMainDmOwnerFromAllowlist({
1648
- dmScope: cfg.session?.dmScope,
1649
- allowFrom: ctx.allowFrom,
1650
- normalizeEntry: normalizeSlackAllowOwnerEntry
1651
- }) : null;
1652
- const replyTarget = isDirectMessage ? `channel:${message.channel}` : ctxPayload.To ?? void 0;
1653
- if (!replyTarget) return null;
1654
- if (shouldLogVerbose()) logVerbose(`slack inbound: channel=${message.channel} from=${slackFrom} preview="${preview}"`);
1655
- const updateLastRouteSessionKey = resolveInboundLastRouteSessionKey({
1656
- route,
1657
- sessionKey
1658
- });
1659
- return {
1660
- ctx,
1661
- account,
1662
- message,
1663
- route,
1664
- channelConfig,
1665
- replyTarget,
1666
- ctxPayload,
1667
- turn: {
1668
- storePath,
1669
- record: {
1670
- updateLastRoute: isDirectMessage ? {
1671
- sessionKey: updateLastRouteSessionKey,
1672
- channel: "slack",
1673
- to: `user:${message.user}`,
1674
- accountId: route.accountId,
1675
- threadId: effectiveMessageThreadId,
1676
- mainDmOwnerPin: updateLastRouteSessionKey === route.mainSessionKey && pinnedMainDmOwner && message.user ? {
1677
- ownerRecipient: pinnedMainDmOwner,
1678
- senderRecipient: normalizeLowercaseStringOrEmpty(message.user),
1679
- onSkip: ({ ownerRecipient, senderRecipient }) => {
1680
- logVerbose(`slack: skip main-session last route for ${senderRecipient} (pinned owner ${ownerRecipient})`);
1681
- }
1682
- } : void 0
1683
- } : void 0,
1684
- onRecordError: (err) => {
1685
- ctx.logger.warn({
1686
- error: formatErrorMessage(err),
1687
- storePath,
1688
- sessionKey
1689
- }, "failed updating session meta");
1690
- }
1691
- },
1692
- history: isRoomish && shouldRequireMention ? {
1693
- isGroup: true,
1694
- historyKey,
1695
- historyMap: ctx.channelHistories,
1696
- limit: ctx.historyLimit
1697
- } : void 0
1698
- },
1699
- replyToMode,
1700
- ...assistantThreadContext?.threadTs ? { forcedReplyThreadTs: assistantThreadContext.threadTs } : {},
1701
- ...assistantThreadContext ? { slackMessageMetadata: buildSlackAssistantThreadMetadata(assistantThreadContext) } : {},
1702
- requireMention: shouldRequireMention,
1703
- isDirectMessage,
1704
- isRoomish,
1705
- historyKey,
1706
- preview,
1707
- ackReactionMessageTs,
1708
- ackReactionValue,
1709
- ackReactionPromise
1710
- };
1711
- }
1712
- //#endregion
1713
- export { resolveSlackThreadTargets as n, prepareSlackMessage as t };