@openclaw/slack 2026.5.12-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.
Files changed (88) hide show
  1. package/dist/account-inspect-D7AZNs8C.js +77 -0
  2. package/dist/account-inspect-api.js +10 -0
  3. package/dist/accounts-ClAPP5ry.js +139 -0
  4. package/dist/accounts.runtime-DDVcLJUI.js +2 -0
  5. package/dist/action-runtime-e2UhRsNx.js +350 -0
  6. package/dist/action-runtime.runtime-BFcqMbOm.js +2 -0
  7. package/dist/actions-CYLFK-Zy.js +292 -0
  8. package/dist/actions.runtime-CO3OaTLb.js +2 -0
  9. package/dist/allow-list-BPnnlRPL.js +82 -0
  10. package/dist/api.js +21 -0
  11. package/dist/approval-handler.runtime-CmeRr9qA.js +256 -0
  12. package/dist/blocks-input-CwTFVImV.js +29 -0
  13. package/dist/blocks-render-BIDw-Pom.js +161 -0
  14. package/dist/channel-DRjHBTDB.js +1020 -0
  15. package/dist/channel-api-B_nZwosg.js +20 -0
  16. package/dist/channel-config-api.js +2 -0
  17. package/dist/channel-entry.js +22 -0
  18. package/dist/channel-plugin-api.js +2 -0
  19. package/dist/channel.setup-Cayn7afd.js +73 -0
  20. package/dist/client-CPe4GmDR.js +103 -0
  21. package/dist/config-api-B_jq4NJW.js +2 -0
  22. package/dist/config-schema-D9B5LB_L.js +167 -0
  23. package/dist/configured-state.js +11 -0
  24. package/dist/contract-api.js +5 -0
  25. package/dist/directory-config-B3JiHeB7.js +54 -0
  26. package/dist/directory-contract-api.js +2 -0
  27. package/dist/directory-live-Bf16GwDh.js +133 -0
  28. package/dist/doctor-contract-KUjHnkQm.js +147 -0
  29. package/dist/doctor-contract-api.js +2 -0
  30. package/dist/errors-BYFHR24f.js +109 -0
  31. package/dist/exec-approvals-7xUNgLi9.js +58 -0
  32. package/dist/group-policy-CyLUK6My.js +41 -0
  33. package/dist/http-routes-api.js +2 -0
  34. package/dist/inbound-contract-test-api.js +3 -0
  35. package/dist/index.js +33 -0
  36. package/dist/interactive-replies-api.js +2 -0
  37. package/dist/interactive-replies-qAIfuBor.js +173 -0
  38. package/dist/magic-string.es-BMaGRRZ1.js +1011 -0
  39. package/dist/media-D1XCd1uP.js +469 -0
  40. package/dist/message-tool-api-6lowf9zE.js +104 -0
  41. package/dist/message-tool-api.js +2 -0
  42. package/dist/monitor-a97o17G6.js +13 -0
  43. package/dist/mrkdwn-Cax-eSfK.js +6 -0
  44. package/dist/outbound-adapter-B_5sEhCg.js +174 -0
  45. package/dist/outbound-payload-test-api.js +2 -0
  46. package/dist/outbound-payload.test-harness-CVCamg1x.js +13558 -0
  47. package/dist/pipeline.runtime-DT0hLnq2.js +1379 -0
  48. package/dist/plugin-routes-DtTPmga1.js +20 -0
  49. package/dist/prepare-D3YqV8jB.js +1482 -0
  50. package/dist/prepare.test-helpers-DVcjRhfG.js +49 -0
  51. package/dist/probe-3eZf1FjI.js +42 -0
  52. package/dist/provider-D7uAN3Fq.js +3235 -0
  53. package/dist/registry-CeaoNfoP.js +39 -0
  54. package/dist/replies-Xe_jMR6o.js +139 -0
  55. package/dist/reply-blocks-Z5l6_R6H.js +14 -0
  56. package/dist/resolve-allowlist-common-Bk3clYPK.js +43 -0
  57. package/dist/resolve-channels-BRYqyNVJ.js +81 -0
  58. package/dist/resolve-users-Bd_SdP8j.js +113 -0
  59. package/dist/rolldown-runtime-CiIaOW0V.js +13 -0
  60. package/dist/room-context-0vovmZPU.js +787 -0
  61. package/dist/runtime-Bo-KHM-F.js +8 -0
  62. package/dist/runtime-api-Dd1xIV5v.js +9 -0
  63. package/dist/runtime-api.js +14 -0
  64. package/dist/runtime-setter-api.js +2 -0
  65. package/dist/scopes-CDevO8jg.js +74 -0
  66. package/dist/secret-contract-Bo6lbSkh.js +141 -0
  67. package/dist/secret-contract-api.js +2 -0
  68. package/dist/security-audit-BtHGnD3d.js +51 -0
  69. package/dist/security-contract-api.js +2 -0
  70. package/dist/send-D_A9kL-C.js +721 -0
  71. package/dist/send.runtime-BRE_ncCU.js +2 -0
  72. package/dist/send.runtime-_l76lUuL.js +2 -0
  73. package/dist/setup-core-B9NetDkM.js +320 -0
  74. package/dist/setup-entry.js +15 -0
  75. package/dist/setup-plugin-api.js +2 -0
  76. package/dist/setup-surface-D88QBVOW.js +128 -0
  77. package/dist/shared-D8U42xFL.js +208 -0
  78. package/dist/slash-commands.runtime-22kgyst2.js +19 -0
  79. package/dist/slash-dispatch.runtime-BJgT0jwV.js +32 -0
  80. package/dist/slash-plugin-commands.runtime-CF-n3MeP.js +2 -0
  81. package/dist/slash-skill-commands.runtime-BMs0VjTe.js +7 -0
  82. package/dist/streaming-compat-RkZgTmQ2.js +43 -0
  83. package/dist/target-parsing-CQmv-iSm.js +55 -0
  84. package/dist/targets-B1tYCAr6.js +2 -0
  85. package/dist/test-api.js +8 -0
  86. package/dist/thread-ts-C2x7c5PP.js +24 -0
  87. package/openclaw.plugin.json +2405 -0
  88. package/package.json +84 -0
@@ -0,0 +1,1482 @@
1
+ import { l as resolveSlackReplyToMode } from "./accounts-ClAPP5ry.js";
2
+ import { r as parseSlackTarget } from "./target-parsing-CQmv-iSm.js";
3
+ import "./targets-B1tYCAr6.js";
4
+ import { i as normalizeSlackAllowOwnerEntry, o as resolveSlackAllowListMatch, r as normalizeAllowListLower } from "./allow-list-BPnnlRPL.js";
5
+ import { i as hasSlackThreadParticipationWithPersistence, t as sendMessageSlack } from "./send-D_A9kL-C.js";
6
+ import { l as reactSlackMessage } from "./actions-CYLFK-Zy.js";
7
+ import { i as resolveSlackThreadStarter, o as formatSlackFileReference, r as resolveSlackThreadHistory } from "./media-D1XCd1uP.js";
8
+ import { t as formatSlackError } from "./errors-BYFHR24f.js";
9
+ import { b as readSessionUpdatedAt, c as authorizeSlackBotRoomMessage, d as resolveSlackEffectiveAllowFrom, g as resolveSlackChannelConfig, h as resolveSlackChatType, k as stripSlackMentionsForCommandDetection, m as normalizeSlackChannelType, n as authorizeSlackDirectMessage, o as resolveConversationLabel$1, t as resolveSlackRoomContextHints, u as resolveSlackCommandIngress, w as resolveStorePath, x as resolveChannelContextVisibilityMode } from "./room-context-0vovmZPU.js";
10
+ import "./send.runtime-BRE_ncCU.js";
11
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } 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-message";
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 { buildPendingHistoryContextFromMap, recordPendingHistoryEntryIfEnabled } from "openclaw/plugin-sdk/reply-history";
18
+ import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
19
+ import { buildMentionRegexes, formatInboundEnvelope, implicitMentionKindWhen, logInboundDrop, matchesMentionWithExplicit, resolveEnvelopeFormatOptions } 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 { shouldHandleTextCommands } from "openclaw/plugin-sdk/command-surface";
24
+ import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
25
+ import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
26
+ //#region extensions/slack/src/monitor/message-handler/prepare-content.ts
27
+ const SLACK_MENTION_RESOLUTION_CONCURRENCY = 4;
28
+ const SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE = 20;
29
+ const SLACK_USER_MENTION_RE$1 = /<@([A-Z0-9]+)(?:\|[^>]+)?>/gi;
30
+ let slackMediaModulePromise$1;
31
+ function loadSlackMediaModule$1() {
32
+ slackMediaModulePromise$1 ??= import("./media-D1XCd1uP.js").then((n) => n.t);
33
+ return slackMediaModulePromise$1;
34
+ }
35
+ function collectUniqueSlackMentionIds$1(texts) {
36
+ const seen = /* @__PURE__ */ new Set();
37
+ const mentionIds = [];
38
+ for (const text of texts) {
39
+ if (!text) continue;
40
+ SLACK_USER_MENTION_RE$1.lastIndex = 0;
41
+ for (const match of text.matchAll(SLACK_USER_MENTION_RE$1)) {
42
+ const userId = match[1];
43
+ if (!userId || seen.has(userId)) continue;
44
+ seen.add(userId);
45
+ mentionIds.push(userId);
46
+ }
47
+ }
48
+ return mentionIds;
49
+ }
50
+ function renderSlackUserMentions(text, renderedMentions) {
51
+ if (!text || renderedMentions.size === 0) return text;
52
+ SLACK_USER_MENTION_RE$1.lastIndex = 0;
53
+ return text.replace(SLACK_USER_MENTION_RE$1, (full, userId) => {
54
+ return renderedMentions.get(userId) ?? full;
55
+ });
56
+ }
57
+ function readString(value) {
58
+ return typeof value === "string" ? value : void 0;
59
+ }
60
+ function readTextObject(value) {
61
+ if (!value || typeof value !== "object") return;
62
+ return normalizeOptionalString(readString(value.text));
63
+ }
64
+ function renderSlackRichTextLeaf(element) {
65
+ switch (element.type) {
66
+ case "text": return readString(element.text) ?? "";
67
+ case "link": return readString(element.text) ?? readString(element.url) ?? "";
68
+ case "user": {
69
+ const userId = readString(element.user_id);
70
+ return userId ? `<@${userId}>` : "";
71
+ }
72
+ case "channel": {
73
+ const channelId = readString(element.channel_id);
74
+ return channelId ? `<#${channelId}>` : "";
75
+ }
76
+ case "usergroup": {
77
+ const usergroupId = readString(element.usergroup_id);
78
+ return usergroupId ? `<!subteam^${usergroupId}>` : "";
79
+ }
80
+ case "broadcast": {
81
+ const range = readString(element.range);
82
+ return range ? `<!${range}>` : "";
83
+ }
84
+ case "emoji": {
85
+ const name = readString(element.name);
86
+ return name ? `:${name}:` : "";
87
+ }
88
+ default: return "";
89
+ }
90
+ }
91
+ function renderSlackRichTextElements(elements) {
92
+ if (!Array.isArray(elements)) return "";
93
+ const parts = [];
94
+ for (const rawElement of elements) {
95
+ if (!rawElement || typeof rawElement !== "object") continue;
96
+ const element = rawElement;
97
+ switch (element.type) {
98
+ case "rich_text_section":
99
+ case "rich_text_preformatted":
100
+ case "rich_text_quote":
101
+ parts.push(renderSlackRichTextElements(element.elements));
102
+ break;
103
+ case "rich_text_list": {
104
+ const listParts = [];
105
+ if (Array.isArray(element.elements)) for (const child of element.elements) {
106
+ if (!child || typeof child !== "object") continue;
107
+ const rendered = renderSlackRichTextElements(child.elements);
108
+ if (rendered) listParts.push(rendered);
109
+ }
110
+ const listText = listParts.join("\n");
111
+ parts.push(listText);
112
+ break;
113
+ }
114
+ default:
115
+ parts.push(renderSlackRichTextLeaf(element));
116
+ break;
117
+ }
118
+ }
119
+ return parts.join("");
120
+ }
121
+ function readSlackBlockText(block) {
122
+ if (!block || typeof block !== "object") return;
123
+ const blockLike = block;
124
+ switch (blockLike.type) {
125
+ case "rich_text": return normalizeOptionalString(renderSlackRichTextElements(blockLike.elements));
126
+ case "section": {
127
+ const text = readTextObject(blockLike.text);
128
+ if (text) return text;
129
+ if (Array.isArray(blockLike.fields)) {
130
+ const fields = [];
131
+ for (const field of blockLike.fields) {
132
+ const fieldText = readTextObject(field);
133
+ if (fieldText) fields.push(fieldText);
134
+ }
135
+ return fields.length > 0 ? fields.join("\n") : void 0;
136
+ }
137
+ return;
138
+ }
139
+ case "header": return readTextObject(blockLike.text);
140
+ case "context": {
141
+ if (!Array.isArray(blockLike.elements)) return;
142
+ const parts = [];
143
+ for (const element of blockLike.elements) {
144
+ const text = readTextObject(element);
145
+ if (text) parts.push(text);
146
+ }
147
+ return parts.length > 0 ? parts.join(" ") : void 0;
148
+ }
149
+ case "image": return normalizeOptionalString(readString(blockLike.alt_text)) ?? readTextObject(blockLike.title);
150
+ case "video": return readTextObject(blockLike.title) ?? normalizeOptionalString(readString(blockLike.alt_text));
151
+ default: return;
152
+ }
153
+ }
154
+ function resolveSlackBlocksText(blocks) {
155
+ if (!blocks?.length) return;
156
+ const parts = [];
157
+ let hasRichText = false;
158
+ for (const block of blocks) {
159
+ if (block && typeof block === "object" && block.type === "rich_text") hasRichText = true;
160
+ const text = readSlackBlockText(block);
161
+ if (text) parts.push(text);
162
+ }
163
+ return parts.length > 0 ? {
164
+ text: parts.join("\n"),
165
+ hasRichText
166
+ } : void 0;
167
+ }
168
+ function chooseSlackPrimaryText(params) {
169
+ const { messageText, blocksText } = params;
170
+ if (!blocksText) return messageText;
171
+ if (!messageText) return blocksText.text;
172
+ if (blocksText.hasRichText && blocksText.text.length > messageText.length) return blocksText.text;
173
+ return blocksText.text.length > messageText.length && blocksText.text.startsWith(messageText) ? blocksText.text : messageText;
174
+ }
175
+ function filterInheritedParentFiles(params) {
176
+ const { files, isThreadReply, threadStarter } = params;
177
+ if (!isThreadReply || !files?.length) return files;
178
+ if (!threadStarter?.files?.length) return files;
179
+ const starterFileIds = new Set(threadStarter.files.map((file) => file.id));
180
+ const filtered = files.filter((file) => !file.id || !starterFileIds.has(file.id));
181
+ if (filtered.length < files.length) logVerbose(`slack: filtered ${files.length - filtered.length} inherited parent file(s) from thread reply`);
182
+ return filtered.length > 0 ? filtered : void 0;
183
+ }
184
+ async function resolveSlackMessageContent(params) {
185
+ const ownFiles = filterInheritedParentFiles({
186
+ files: params.message.files,
187
+ isThreadReply: params.isThreadReply,
188
+ threadStarter: params.threadStarter
189
+ });
190
+ const mediaPromise = ownFiles && ownFiles.length > 0 ? loadSlackMediaModule$1().then(({ resolveSlackMedia }) => resolveSlackMedia({
191
+ files: ownFiles,
192
+ client: params.client,
193
+ token: params.botToken,
194
+ maxBytes: params.mediaMaxBytes
195
+ })) : Promise.resolve(null);
196
+ const attachmentContentPromise = params.message.attachments && params.message.attachments.length > 0 ? loadSlackMediaModule$1().then(({ resolveSlackAttachmentContent }) => resolveSlackAttachmentContent({
197
+ attachments: params.message.attachments,
198
+ client: params.client,
199
+ token: params.botToken,
200
+ maxBytes: params.mediaMaxBytes
201
+ })) : Promise.resolve(null);
202
+ const [media, attachmentContent] = await Promise.all([mediaPromise, attachmentContentPromise]);
203
+ const mergedMedia = [...media ?? [], ...attachmentContent?.media ?? []];
204
+ const effectiveDirectMedia = mergedMedia.length > 0 ? mergedMedia : null;
205
+ const mediaPlaceholder = effectiveDirectMedia ? effectiveDirectMedia.map((item) => item.placeholder).join(" ") : void 0;
206
+ const fallbackFiles = ownFiles ?? [];
207
+ const fileOnlyFallback = !mediaPlaceholder && fallbackFiles.length > 0 ? fallbackFiles.slice(0, 8).map((file) => formatSlackFileReference(file)).join(", ") : void 0;
208
+ const fileOnlyPlaceholder = fileOnlyFallback ? `[Slack file: ${fileOnlyFallback}]` : void 0;
209
+ let botAttachmentText;
210
+ if (params.isBotMessage && !attachmentContent?.text) {
211
+ const botAttachmentTextParts = [];
212
+ for (const attachment of params.message.attachments ?? []) {
213
+ const text = normalizeOptionalString(attachment.text) ?? normalizeOptionalString(attachment.fallback);
214
+ if (text) botAttachmentTextParts.push(text);
215
+ }
216
+ botAttachmentText = botAttachmentTextParts.length > 0 ? botAttachmentTextParts.join("\n") : void 0;
217
+ }
218
+ const blocksText = resolveSlackBlocksText(params.message.blocks);
219
+ const textParts = [
220
+ chooseSlackPrimaryText({
221
+ messageText: normalizeOptionalString(params.message.text),
222
+ blocksText
223
+ }),
224
+ attachmentContent?.text,
225
+ botAttachmentText
226
+ ];
227
+ const renderedMentions = /* @__PURE__ */ new Map();
228
+ const resolveUserName = params.resolveUserName;
229
+ if (resolveUserName) {
230
+ const mentionIds = collectUniqueSlackMentionIds$1(textParts);
231
+ const lookupIds = mentionIds.slice(0, SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE);
232
+ const skippedLookups = mentionIds.length - lookupIds.length;
233
+ if (skippedLookups > 0) logVerbose(`slack: skipping ${skippedLookups} mention lookup(s) beyond per-message cap (${SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE})`);
234
+ const { results } = await runTasksWithConcurrency({
235
+ tasks: lookupIds.map((userId) => async () => {
236
+ const renderedName = normalizeOptionalString((await resolveUserName(userId))?.name);
237
+ return {
238
+ userId,
239
+ rendered: renderedName ? `<@${userId}> (${renderedName})` : null
240
+ };
241
+ }),
242
+ limit: SLACK_MENTION_RESOLUTION_CONCURRENCY
243
+ });
244
+ for (const result of results) {
245
+ if (!result) continue;
246
+ renderedMentions.set(result.userId, result.rendered);
247
+ }
248
+ }
249
+ const rawBody = [
250
+ renderSlackUserMentions(textParts[0], renderedMentions),
251
+ renderSlackUserMentions(textParts[1], renderedMentions),
252
+ renderSlackUserMentions(textParts[2], renderedMentions),
253
+ mediaPlaceholder,
254
+ fileOnlyPlaceholder
255
+ ].filter(Boolean).join("\n") || "";
256
+ if (!rawBody) return null;
257
+ return {
258
+ rawBody,
259
+ effectiveDirectMedia
260
+ };
261
+ }
262
+ //#endregion
263
+ //#region extensions/slack/src/monitor/message-handler/prepare-dm-history.ts
264
+ function resolveSlackDmHistoryLimit(params) {
265
+ const override = params.userId && params.account.config.dms?.[params.userId]?.historyLimit !== void 0 ? params.account.config.dms[params.userId]?.historyLimit : void 0;
266
+ return Math.max(0, override ?? params.defaultLimit);
267
+ }
268
+ async function resolveSlackDmHistoryContext(params) {
269
+ const maxMessages = Math.max(0, Math.floor(params.limit));
270
+ if (maxMessages <= 0) return {
271
+ body: void 0,
272
+ inboundHistory: void 0
273
+ };
274
+ try {
275
+ const messages = ((await params.ctx.app.client.conversations.history({
276
+ token: params.ctx.botToken,
277
+ channel: params.channelId,
278
+ ...params.currentMessageTs ? {
279
+ latest: params.currentMessageTs,
280
+ inclusive: true
281
+ } : {},
282
+ limit: maxMessages + 1
283
+ })).messages ?? []).filter((message) => {
284
+ if (params.currentMessageTs && message.ts === params.currentMessageTs) return false;
285
+ return Boolean(normalizeOptionalString(message.text));
286
+ }).slice(0, maxMessages).toReversed();
287
+ if (messages.length === 0) return {
288
+ body: void 0,
289
+ inboundHistory: void 0
290
+ };
291
+ const userNames = /* @__PURE__ */ new Map();
292
+ const resolveUserLabel = async (userId) => {
293
+ const cached = userNames.get(userId);
294
+ if (cached) return cached;
295
+ const label = normalizeOptionalString((await params.ctx.resolveUserName(userId)).name) ?? userId;
296
+ userNames.set(userId, label);
297
+ return label;
298
+ };
299
+ const entries = [];
300
+ const formatted = [];
301
+ for (const message of messages) {
302
+ const body = normalizeOptionalString(message.text);
303
+ if (!body) continue;
304
+ const isCurrentBot = params.ctx.botUserId && message.user === params.ctx.botUserId || params.ctx.botId && message.bot_id === params.ctx.botId;
305
+ const role = isCurrentBot || message.bot_id ? "assistant" : "user";
306
+ const sender = `${isCurrentBot ? "Assistant" : message.user ? await resolveUserLabel(message.user) : normalizeOptionalString(message.username) ?? (message.bot_id ? "Bot" : "Unknown")} (${role})`;
307
+ const timestamp = message.ts ? Math.round(Number(message.ts) * 1e3) : void 0;
308
+ entries.push({
309
+ sender,
310
+ body,
311
+ timestamp
312
+ });
313
+ formatted.push(formatInboundEnvelope({
314
+ channel: "Slack",
315
+ from: sender,
316
+ timestamp,
317
+ body: `${body}\n[slack message id: ${message.ts ?? "unknown"} channel: ${params.channelId}]`,
318
+ chatType: "direct",
319
+ envelope: params.envelopeOptions
320
+ }));
321
+ }
322
+ return {
323
+ body: formatted.length > 0 ? formatted.join("\n\n") : void 0,
324
+ inboundHistory: entries.length > 0 ? entries : void 0
325
+ };
326
+ } catch (err) {
327
+ logVerbose(`slack: failed to fetch DM history for channel ${params.channelId}: ${formatErrorMessage(err)}`);
328
+ return {
329
+ body: void 0,
330
+ inboundHistory: void 0
331
+ };
332
+ }
333
+ }
334
+ //#endregion
335
+ //#region extensions/slack/src/threading.ts
336
+ function resolveSlackThreadContext(params) {
337
+ const incomingThreadTs = params.message.thread_ts;
338
+ const eventTs = params.message.event_ts;
339
+ const messageTs = params.message.ts ?? eventTs;
340
+ const isThreadReply = typeof incomingThreadTs === "string" && incomingThreadTs.length > 0 && (incomingThreadTs !== messageTs || Boolean(params.message.parent_user_id));
341
+ return {
342
+ incomingThreadTs,
343
+ messageTs,
344
+ isThreadReply,
345
+ replyToId: incomingThreadTs ?? messageTs,
346
+ messageThreadId: isThreadReply ? incomingThreadTs : params.replyToMode === "all" ? messageTs : void 0
347
+ };
348
+ }
349
+ /**
350
+ * Resolves Slack thread targeting for replies and status indicators.
351
+ *
352
+ * @returns replyThreadTs - Thread timestamp for reply messages
353
+ * @returns statusThreadTs - Thread timestamp for status indicators (typing, etc.)
354
+ * @returns isThreadReply - true if this is a genuine user reply in a thread,
355
+ * false if thread_ts comes from a bot status message (e.g. typing indicator)
356
+ */
357
+ function resolveSlackThreadTargets(params) {
358
+ const { incomingThreadTs, messageTs, isThreadReply } = resolveSlackThreadContext(params);
359
+ const replyThreadTs = isThreadReply ? incomingThreadTs : params.replyToMode === "all" ? messageTs : void 0;
360
+ return {
361
+ replyThreadTs,
362
+ statusThreadTs: replyThreadTs,
363
+ isThreadReply
364
+ };
365
+ }
366
+ //#endregion
367
+ //#region extensions/slack/src/monitor/message-handler/prepare-routing.ts
368
+ const slackRouteBindingConfigCache = /* @__PURE__ */ new WeakMap();
369
+ function slackTargetDefaultKindForPeer(kind) {
370
+ return kind === "direct" ? "user" : "channel";
371
+ }
372
+ function slackTargetKindMatchesPeer(peerKind, targetKind) {
373
+ if (targetKind === "user") return peerKind === "direct";
374
+ return peerKind === "channel" || peerKind === "group";
375
+ }
376
+ function normalizeSlackRouteBindingPeer(peer) {
377
+ const rawId = peer.id.trim();
378
+ if (!rawId || rawId === "*") return peer;
379
+ const target = (() => {
380
+ try {
381
+ return parseSlackTarget(rawId, { defaultKind: slackTargetDefaultKindForPeer(peer.kind) });
382
+ } catch {
383
+ return;
384
+ }
385
+ })();
386
+ if (!target || !slackTargetKindMatchesPeer(peer.kind, target.kind) || target.id === peer.id) return peer;
387
+ return {
388
+ ...peer,
389
+ id: target.id
390
+ };
391
+ }
392
+ function normalizeSlackRouteBindingConfig(cfg) {
393
+ const bindings = cfg.bindings;
394
+ const cached = slackRouteBindingConfigCache.get(cfg);
395
+ if (cached && cached.bindingsRef === bindings) return cached.normalizedCfg;
396
+ if (!Array.isArray(bindings)) return cfg;
397
+ let changed = false;
398
+ const normalizedBindings = bindings.map((binding) => {
399
+ if (binding.type === "acp" || binding.match.channel.trim().toLowerCase() !== "slack") return binding;
400
+ const peer = binding.match.peer;
401
+ if (!peer) return binding;
402
+ const normalizedPeer = normalizeSlackRouteBindingPeer(peer);
403
+ if (normalizedPeer === peer) return binding;
404
+ changed = true;
405
+ return {
406
+ ...binding,
407
+ match: {
408
+ ...binding.match,
409
+ peer: normalizedPeer
410
+ }
411
+ };
412
+ });
413
+ const normalizedCfg = changed ? {
414
+ ...cfg,
415
+ bindings: normalizedBindings
416
+ } : cfg;
417
+ slackRouteBindingConfigCache.set(cfg, {
418
+ bindingsRef: bindings,
419
+ normalizedCfg
420
+ });
421
+ return normalizedCfg;
422
+ }
423
+ function resolveSlackBaseConversationId(params) {
424
+ return params.isDirectMessage ? `user:${params.message.user ?? "unknown"}` : params.message.channel;
425
+ }
426
+ function resolveSlackInitialAgentRoute(params) {
427
+ return resolveAgentRoute({
428
+ cfg: normalizeSlackRouteBindingConfig(params.ctx.cfg),
429
+ channel: "slack",
430
+ accountId: params.account.accountId,
431
+ teamId: params.ctx.teamId || void 0,
432
+ peer: {
433
+ kind: params.isDirectMessage ? "direct" : params.isRoom ? "channel" : "group",
434
+ id: params.isDirectMessage ? params.message.user ?? "unknown" : params.message.channel
435
+ }
436
+ });
437
+ }
438
+ function resolveSlackRoutingContext(params) {
439
+ const { ctx, account, message, isDirectMessage, isGroupDm, isRoom, isRoomish, seedTopLevelRoomThread } = params;
440
+ let route = resolveSlackInitialAgentRoute({
441
+ ctx,
442
+ account,
443
+ message,
444
+ isDirectMessage,
445
+ isRoom
446
+ });
447
+ const chatType = isDirectMessage ? "direct" : isGroupDm ? "group" : "channel";
448
+ const replyToMode = resolveSlackReplyToMode(account, chatType);
449
+ const threadContext = resolveSlackThreadContext({
450
+ message,
451
+ replyToMode
452
+ });
453
+ const threadTs = threadContext.incomingThreadTs;
454
+ const isThreadReply = threadContext.isThreadReply;
455
+ const autoThreadId = !isThreadReply && replyToMode === "all" && threadContext.messageTs ? threadContext.messageTs : void 0;
456
+ const seedCandidateThreadId = threadContext.incomingThreadTs ?? threadContext.messageTs;
457
+ const routedThreadId = (isDirectMessage ? isThreadReply ? threadTs : void 0 : isRoomish ? isThreadReply && threadTs ? threadTs : void 0 : isThreadReply ? threadTs : autoThreadId) ?? (isRoomish ? !isThreadReply && isRoom && seedTopLevelRoomThread && replyToMode !== "off" && seedCandidateThreadId ? seedCandidateThreadId : void 0 : void 0);
458
+ const baseConversationId = resolveSlackBaseConversationId({
459
+ message,
460
+ isDirectMessage
461
+ });
462
+ const boundThreadRoute = routedThreadId ? resolveRuntimeConversationBindingRoute({
463
+ route,
464
+ conversation: {
465
+ channel: "slack",
466
+ accountId: account.accountId,
467
+ conversationId: routedThreadId,
468
+ parentConversationId: baseConversationId
469
+ }
470
+ }) : null;
471
+ const runtimeRoute = boundThreadRoute?.boundSessionKey || boundThreadRoute?.bindingRecord ? boundThreadRoute : resolveRuntimeConversationBindingRoute({
472
+ route,
473
+ conversation: {
474
+ channel: "slack",
475
+ accountId: account.accountId,
476
+ conversationId: baseConversationId
477
+ }
478
+ });
479
+ let configuredBinding = null;
480
+ let configuredBindingSessionKey = "";
481
+ if (runtimeRoute.boundSessionKey || runtimeRoute.bindingRecord) route = runtimeRoute.route;
482
+ else {
483
+ const configuredRoute = resolveConfiguredBindingRoute({
484
+ cfg: ctx.cfg,
485
+ route,
486
+ conversation: {
487
+ channel: "slack",
488
+ accountId: account.accountId,
489
+ conversationId: baseConversationId
490
+ }
491
+ });
492
+ configuredBinding = configuredRoute.bindingResolution;
493
+ configuredBindingSessionKey = configuredRoute.boundSessionKey ?? "";
494
+ route = configuredRoute.route;
495
+ }
496
+ const threadKeys = runtimeRoute.boundSessionKey || configuredBindingSessionKey ? {
497
+ sessionKey: route.sessionKey,
498
+ parentSessionKey: void 0
499
+ } : resolveThreadSessionKeys({
500
+ baseSessionKey: route.sessionKey,
501
+ threadId: routedThreadId,
502
+ parentSessionKey: routedThreadId && ctx.threadInheritParent ? route.sessionKey : void 0
503
+ });
504
+ const sessionKey = threadKeys.sessionKey;
505
+ const historyKey = isThreadReply && ctx.threadHistoryScope === "thread" ? sessionKey : message.channel;
506
+ return {
507
+ route,
508
+ runtimeBinding: runtimeRoute.bindingRecord,
509
+ runtimeBoundSessionKey: runtimeRoute.boundSessionKey,
510
+ configuredBinding,
511
+ configuredBindingSessionKey,
512
+ chatType,
513
+ replyToMode,
514
+ threadContext,
515
+ threadTs,
516
+ isThreadReply,
517
+ threadKeys,
518
+ sessionKey,
519
+ historyKey
520
+ };
521
+ }
522
+ //#endregion
523
+ //#region extensions/slack/src/monitor/message-handler/prepare-thread-context-root.ts
524
+ function isSlackThreadAuthorCurrentBot(params) {
525
+ const { identity, author } = params;
526
+ if (identity.botUserId && author.userId && author.userId === identity.botUserId) return true;
527
+ if (identity.botId && author.botId && author.botId === identity.botId) return true;
528
+ return false;
529
+ }
530
+ function resolveSlackThreadHistoryFilterPolicy(params) {
531
+ if (!params.includeBotStarterAsRootContext || !params.starterTs) return {};
532
+ return { retainCurrentBotRootTs: params.starterTs };
533
+ }
534
+ function applySlackThreadHistoryFilterPolicy(params) {
535
+ const kept = [];
536
+ let omittedCurrentBot = 0;
537
+ for (const entry of params.history) {
538
+ if (!isSlackThreadAuthorCurrentBot({
539
+ identity: params.identity,
540
+ author: entry
541
+ })) {
542
+ kept.push(entry);
543
+ continue;
544
+ }
545
+ if (params.policy.retainCurrentBotRootTs && entry.ts === params.policy.retainCurrentBotRootTs) kept.push(entry);
546
+ else omittedCurrentBot += 1;
547
+ }
548
+ return {
549
+ kept,
550
+ omittedCurrentBot
551
+ };
552
+ }
553
+ function shouldIncludeBotThreadStarterContext(params) {
554
+ if (!params.hasStarterText) return false;
555
+ return params.starterIsCurrentBot && params.isNewThreadSession;
556
+ }
557
+ function ensureSlackThreadHistoryHasBotRoot(params) {
558
+ if (!params.includeBotStarterAsRootContext || !params.threadStarter?.text) return params.history;
559
+ if (params.history.some((entry) => entry.ts === params.threadStarter?.ts)) return params.history;
560
+ return [params.threadStarter, ...params.history];
561
+ }
562
+ function formatSlackBotStarterThreadLabel(params) {
563
+ const base = `Slack thread ${params.roomLabel}`;
564
+ if (!params.starterText) return base;
565
+ const snippet = params.starterText.replace(/\s+/g, " ").slice(0, 80).trim();
566
+ if (!snippet) return base;
567
+ return `${base} (assistant root): ${snippet}`;
568
+ }
569
+ //#endregion
570
+ //#region extensions/slack/src/monitor/message-handler/prepare-thread-context.ts
571
+ let slackMediaModulePromise;
572
+ function loadSlackMediaModule() {
573
+ slackMediaModulePromise ??= import("./media-D1XCd1uP.js").then((n) => n.t);
574
+ return slackMediaModulePromise;
575
+ }
576
+ const SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY = 4;
577
+ function isSlackThreadContextSenderAllowed(params) {
578
+ if (params.allowFromLower.length === 0 || params.botId) return true;
579
+ if (!params.userId) return false;
580
+ return resolveSlackAllowListMatch({
581
+ allowList: params.allowFromLower,
582
+ id: params.userId,
583
+ name: params.userName,
584
+ allowNameMatching: params.allowNameMatching
585
+ }).allowed;
586
+ }
587
+ async function resolveSlackThreadUserMap(params) {
588
+ const uniqueUserIds = [];
589
+ const seen = /* @__PURE__ */ new Set();
590
+ for (const item of params.messages) {
591
+ if (!item.userId || seen.has(item.userId)) continue;
592
+ seen.add(item.userId);
593
+ uniqueUserIds.push(item.userId);
594
+ }
595
+ const userMap = /* @__PURE__ */ new Map();
596
+ if (uniqueUserIds.length === 0) return userMap;
597
+ const { results } = await runTasksWithConcurrency({
598
+ tasks: uniqueUserIds.map((id) => async () => {
599
+ const user = await params.ctx.resolveUserName(id);
600
+ return user ? {
601
+ id,
602
+ user
603
+ } : null;
604
+ }),
605
+ limit: SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY
606
+ });
607
+ for (const result of results) if (result) userMap.set(result.id, result.user);
608
+ return userMap;
609
+ }
610
+ async function resolveSlackThreadContextData(params) {
611
+ const botIdentity = {
612
+ botUserId: params.ctx.botUserId,
613
+ botId: params.ctx.botId
614
+ };
615
+ const isCurrentBotAuthor = (author) => isSlackThreadAuthorCurrentBot({
616
+ identity: botIdentity,
617
+ author
618
+ });
619
+ let threadStarterBody;
620
+ let threadHistoryBody;
621
+ let threadSessionPreviousTimestamp;
622
+ let threadLabel;
623
+ let threadStarterMedia = null;
624
+ if (!params.isThreadReply || !params.threadTs) return {
625
+ threadStarterBody,
626
+ threadHistoryBody,
627
+ threadSessionPreviousTimestamp,
628
+ threadLabel,
629
+ threadStarterMedia
630
+ };
631
+ const starter = params.threadStarter;
632
+ const starterSenderName = params.allowNameMatching && params.allowFromLower.length > 0 && starter?.userId ? (await params.ctx.resolveUserName(starter.userId))?.name : void 0;
633
+ const starterIsCurrentBot = Boolean(starter && isCurrentBotAuthor({
634
+ userId: starter.userId,
635
+ botId: starter.botId
636
+ }));
637
+ const starterAllowed = !starter || !starterIsCurrentBot && isSlackThreadContextSenderAllowed({
638
+ allowFromLower: params.allowFromLower,
639
+ allowNameMatching: params.allowNameMatching,
640
+ userId: starter.userId,
641
+ userName: starterSenderName,
642
+ botId: starter.botId
643
+ });
644
+ const includeStarterContext = !starter || !starterIsCurrentBot && shouldIncludeSupplementalContext({
645
+ mode: params.contextVisibilityMode,
646
+ kind: "thread",
647
+ senderAllowed: starterAllowed
648
+ });
649
+ if (starter?.text && includeStarterContext) {
650
+ threadStarterBody = starter.text;
651
+ const snippet = starter.text.replace(/\s+/g, " ").slice(0, 80);
652
+ threadLabel = `Slack thread ${params.roomLabel}${snippet ? `: ${snippet}` : ""}`;
653
+ if (!params.effectiveDirectMedia && starter.files && starter.files.length > 0) {
654
+ const { resolveSlackMedia } = await loadSlackMediaModule();
655
+ threadStarterMedia = await resolveSlackMedia({
656
+ files: starter.files,
657
+ client: params.ctx.app.client,
658
+ token: params.ctx.botToken,
659
+ maxBytes: params.ctx.mediaMaxBytes
660
+ });
661
+ if (threadStarterMedia) logVerbose(`slack: hydrated thread starter file ${threadStarterMedia.map((item) => item.placeholder).join(", ")} from root message`);
662
+ }
663
+ } else threadLabel = `Slack thread ${params.roomLabel}`;
664
+ threadSessionPreviousTimestamp = readSessionUpdatedAt({
665
+ storePath: params.storePath,
666
+ sessionKey: params.sessionKey
667
+ });
668
+ const includeBotStarterAsRootContext = shouldIncludeBotThreadStarterContext({
669
+ starterIsCurrentBot,
670
+ isNewThreadSession: !threadSessionPreviousTimestamp,
671
+ hasStarterText: Boolean(starter?.text)
672
+ });
673
+ if (starter?.text && starterIsCurrentBot && !includeBotStarterAsRootContext) logVerbose("slack: omitted current-bot thread starter from context");
674
+ else if (starter?.text && !includeStarterContext && !starterIsCurrentBot) logVerbose(`slack: omitted thread starter from context (mode=${params.contextVisibilityMode}, sender_allowed=${starterAllowed ? "yes" : "no"})`);
675
+ else if (includeBotStarterAsRootContext) {
676
+ threadLabel = formatSlackBotStarterThreadLabel({
677
+ roomLabel: params.roomLabel,
678
+ starterText: starter?.text
679
+ });
680
+ logVerbose("slack: retained current-bot thread starter as assistant root context");
681
+ }
682
+ const threadInitialHistoryLimit = params.account.config?.thread?.initialHistoryLimit ?? 20;
683
+ if (threadInitialHistoryLimit > 0 && !threadSessionPreviousTimestamp) {
684
+ const currentBotRootTs = starter?.ts ?? params.threadTs;
685
+ const threadHistoryWithBotRoot = ensureSlackThreadHistoryHasBotRoot({
686
+ history: await resolveSlackThreadHistory({
687
+ channelId: params.message.channel,
688
+ threadTs: params.threadTs,
689
+ client: params.ctx.app.client,
690
+ currentMessageTs: params.message.ts,
691
+ limit: threadInitialHistoryLimit
692
+ }),
693
+ includeBotStarterAsRootContext,
694
+ threadStarter: starter ? {
695
+ ...starter,
696
+ ts: currentBotRootTs
697
+ } : null
698
+ });
699
+ if (threadHistoryWithBotRoot.length > 0) {
700
+ const { kept: threadHistoryWithoutCurrentBot, omittedCurrentBot: omittedCurrentBotHistoryCount } = applySlackThreadHistoryFilterPolicy({
701
+ history: threadHistoryWithBotRoot,
702
+ policy: resolveSlackThreadHistoryFilterPolicy({
703
+ includeBotStarterAsRootContext,
704
+ starterTs: currentBotRootTs
705
+ }),
706
+ identity: botIdentity
707
+ });
708
+ const userMapForFilter = params.contextVisibilityMode !== "all" && params.allowNameMatching && params.allowFromLower.length > 0 ? await resolveSlackThreadUserMap({
709
+ ctx: params.ctx,
710
+ messages: threadHistoryWithoutCurrentBot
711
+ }) : /* @__PURE__ */ new Map();
712
+ const { items: filteredThreadHistory, omitted: omittedHistoryCount } = params.contextVisibilityMode === "all" ? {
713
+ items: threadHistoryWithoutCurrentBot,
714
+ omitted: 0
715
+ } : filterSupplementalContextItems({
716
+ items: threadHistoryWithoutCurrentBot,
717
+ mode: params.contextVisibilityMode,
718
+ kind: "thread",
719
+ isSenderAllowed: (historyMsg) => {
720
+ if (isCurrentBotAuthor({
721
+ userId: historyMsg.userId,
722
+ botId: historyMsg.botId
723
+ })) return true;
724
+ const msgUser = historyMsg.userId ? userMapForFilter.get(historyMsg.userId) : null;
725
+ return isSlackThreadContextSenderAllowed({
726
+ allowFromLower: params.allowFromLower,
727
+ allowNameMatching: params.allowNameMatching,
728
+ userId: historyMsg.userId,
729
+ userName: msgUser?.name,
730
+ botId: historyMsg.botId
731
+ });
732
+ }
733
+ });
734
+ const userMap = await resolveSlackThreadUserMap({
735
+ ctx: params.ctx,
736
+ messages: filteredThreadHistory
737
+ });
738
+ if (omittedHistoryCount > 0 || omittedCurrentBotHistoryCount > 0) logVerbose(`slack: omitted ${omittedHistoryCount + omittedCurrentBotHistoryCount} thread message(s) from context (mode=${params.contextVisibilityMode})`);
739
+ const historyParts = [];
740
+ for (const historyMsg of filteredThreadHistory) {
741
+ const msgUser = historyMsg.userId ? userMap.get(historyMsg.userId) : null;
742
+ const isOtherBot = Boolean(historyMsg.botId) && historyMsg.botId !== params.ctx.botId;
743
+ const isCurrentBot = isCurrentBotAuthor({
744
+ userId: historyMsg.userId,
745
+ botId: historyMsg.botId
746
+ });
747
+ const role = isCurrentBot || isOtherBot || Boolean(historyMsg.botId) ? "assistant" : "user";
748
+ const msgSenderName = isCurrentBot ? "Bot (this assistant)" : msgUser?.name ?? (historyMsg.botId ? `Bot (${historyMsg.botId})` : "Unknown");
749
+ const msgWithId = `${historyMsg.text}\n[slack message id: ${historyMsg.ts ?? "unknown"} channel: ${params.message.channel}]`;
750
+ historyParts.push(formatInboundEnvelope({
751
+ channel: "Slack",
752
+ from: `${msgSenderName} (${role})`,
753
+ timestamp: historyMsg.ts ? Math.round(Number(historyMsg.ts) * 1e3) : void 0,
754
+ body: msgWithId,
755
+ chatType: "channel",
756
+ envelope: params.envelopeOptions
757
+ }));
758
+ }
759
+ if (historyParts.length > 0) {
760
+ threadHistoryBody = historyParts.join("\n\n");
761
+ logVerbose(`slack: populated thread history with ${filteredThreadHistory.length} messages for new session`);
762
+ }
763
+ }
764
+ }
765
+ return {
766
+ threadStarterBody,
767
+ threadHistoryBody,
768
+ threadSessionPreviousTimestamp,
769
+ threadLabel,
770
+ threadStarterMedia
771
+ };
772
+ }
773
+ //#endregion
774
+ //#region extensions/slack/src/monitor/message-handler/subteam-mentions.ts
775
+ const SUBTEAM_MENTION_RE = /<!subteam\^([A-Z0-9]+)(?:\|[^>]*)?>/gi;
776
+ const SUBTEAM_MEMBER_CACHE_TTL_MS = 300 * 1e3;
777
+ let subteamMemberCache = /* @__PURE__ */ new WeakMap();
778
+ function normalizeSlackId(value) {
779
+ return typeof value === "string" && value.trim() ? value.trim().toUpperCase() : void 0;
780
+ }
781
+ function extractSlackSubteamMentionIds(text) {
782
+ if (!text) return [];
783
+ const ids = /* @__PURE__ */ new Set();
784
+ for (const match of text.matchAll(SUBTEAM_MENTION_RE)) {
785
+ const id = normalizeSlackId(match[1]);
786
+ if (id) ids.add(id);
787
+ }
788
+ return [...ids];
789
+ }
790
+ async function readSlackSubteamUsers(params) {
791
+ let bySubteam = subteamMemberCache.get(params.client);
792
+ if (!bySubteam) {
793
+ bySubteam = /* @__PURE__ */ new Map();
794
+ subteamMemberCache.set(params.client, bySubteam);
795
+ }
796
+ const cacheKey = `${normalizeSlackId(params.teamId) ?? ""}:${params.subteamId}`;
797
+ const cached = bySubteam.get(cacheKey);
798
+ if (cached && cached.expiresAt > params.now) return cached.users;
799
+ try {
800
+ const response = await params.client.usergroups.users.list({
801
+ usergroup: params.subteamId,
802
+ ...params.teamId ? { team_id: params.teamId } : {}
803
+ });
804
+ if (!response.ok) {
805
+ params.log?.(`slack: failed to resolve user-group mention ${params.subteamId}: ${response.error ?? "unknown_error"}`);
806
+ return /* @__PURE__ */ new Set();
807
+ }
808
+ const users = new Set((response.users ?? []).map((userId) => normalizeSlackId(userId)).filter(Boolean));
809
+ bySubteam.set(cacheKey, {
810
+ expiresAt: params.now + SUBTEAM_MEMBER_CACHE_TTL_MS,
811
+ users
812
+ });
813
+ return users;
814
+ } catch (err) {
815
+ params.log?.(`slack: failed to resolve user-group mention ${params.subteamId}: ${formatErrorMessage(err)}`);
816
+ return /* @__PURE__ */ new Set();
817
+ }
818
+ }
819
+ async function isSlackSubteamMentionForBot(params) {
820
+ const botUserId = normalizeSlackId(params.botUserId);
821
+ if (!botUserId) return false;
822
+ const subteamIds = extractSlackSubteamMentionIds(params.text);
823
+ if (subteamIds.length === 0) return false;
824
+ const now = params.now ?? Date.now();
825
+ for (const subteamId of subteamIds) if ((await readSlackSubteamUsers({
826
+ client: params.client,
827
+ subteamId,
828
+ teamId: normalizeOptionalString(params.teamId),
829
+ now,
830
+ log: params.log
831
+ })).has(botUserId)) return true;
832
+ return false;
833
+ }
834
+ //#endregion
835
+ //#region extensions/slack/src/monitor/message-handler/prepare.ts
836
+ const mentionRegexCache = /* @__PURE__ */ new WeakMap();
837
+ const SLACK_ANY_MENTION_RE = /<@[^>]+>|<!subteam\^[^>]+>/;
838
+ const SLACK_USER_MENTION_RE = /<@([^>|]+)(?:\|[^>]+)?>/g;
839
+ const SLACK_SUBTEAM_MENTION_RE = /<!subteam\^([^>|]+)(?:\|[^>]+)?>/g;
840
+ const SLACK_SUBTEAM_MENTION_MARKER = "<!subteam^";
841
+ function resolveCachedMentionRegexes(ctx, agentId) {
842
+ const key = normalizeOptionalString(agentId) ?? "__default__";
843
+ let byAgent = mentionRegexCache.get(ctx);
844
+ if (!byAgent) {
845
+ byAgent = /* @__PURE__ */ new Map();
846
+ mentionRegexCache.set(ctx, byAgent);
847
+ }
848
+ const cached = byAgent.get(key);
849
+ if (cached) return cached;
850
+ const built = buildMentionRegexes(ctx.cfg, agentId);
851
+ byAgent.set(key, built);
852
+ return built;
853
+ }
854
+ function collectUniqueSlackMentionIds(text, regex) {
855
+ const ids = [];
856
+ regex.lastIndex = 0;
857
+ for (const match of text.matchAll(regex)) {
858
+ const id = normalizeOptionalString(match[1]);
859
+ if (id && !ids.includes(id)) ids.push(id);
860
+ }
861
+ return ids;
862
+ }
863
+ function collectSlackMentionMetadata(text) {
864
+ return {
865
+ mentionedUserIds: collectUniqueSlackMentionIds(text, SLACK_USER_MENTION_RE),
866
+ mentionedSubteamIds: collectUniqueSlackMentionIds(text, SLACK_SUBTEAM_MENTION_RE),
867
+ hasAnyMention: SLACK_ANY_MENTION_RE.test(text),
868
+ hasSubteamMention: text.includes(SLACK_SUBTEAM_MENTION_MARKER)
869
+ };
870
+ }
871
+ async function resolveSlackExplicitMentionState(params) {
872
+ const explicitlyMentionedBotUser = Boolean(params.ctx.botUserId && params.mentionedUserIds.includes(params.ctx.botUserId));
873
+ const explicitlyMentionedBotSubteam = Boolean(params.ctx.botUserId && params.hasSubteamMention) && await isSlackSubteamMentionForBot({
874
+ client: params.ctx.app.client,
875
+ text: params.messageText,
876
+ botUserId: params.ctx.botUserId,
877
+ teamId: params.ctx.teamId,
878
+ log: logVerbose
879
+ });
880
+ return {
881
+ explicitlyMentionedBotUser,
882
+ explicitlyMentionedBotSubteam,
883
+ explicitlyMentioned: explicitlyMentionedBotUser || explicitlyMentionedBotSubteam || params.source === "app_mention"
884
+ };
885
+ }
886
+ function resolveSlackMentionSource(params) {
887
+ if (params.explicitBotMention) return "explicit_bot";
888
+ if (params.explicitSubteamMention) return "subteam";
889
+ if (params.shouldBypassMention) return "command_bypass";
890
+ if (params.wasMentioned) return "mention_pattern";
891
+ if (params.matchedImplicitMentionKinds.length > 0) return "implicit_thread";
892
+ return "none";
893
+ }
894
+ function buildSlackMentionContextPayload(params) {
895
+ if (!params.isRoomish) return {};
896
+ return {
897
+ WasMentioned: params.effectiveWasMentioned,
898
+ ExplicitlyMentionedBot: params.explicitlyMentioned,
899
+ MentionedUserIds: params.mentionedUserIds.length > 0 ? [...params.mentionedUserIds] : void 0,
900
+ MentionedSubteamIds: params.mentionedSubteamIds.length > 0 ? [...params.mentionedSubteamIds] : void 0,
901
+ ImplicitMentionKinds: params.matchedImplicitMentionKinds.length > 0 ? [...params.matchedImplicitMentionKinds] : void 0,
902
+ MentionSource: params.mentionSource
903
+ };
904
+ }
905
+ async function resolveSlackConversationContext(params) {
906
+ const { ctx, account, message } = params;
907
+ const cfg = ctx.cfg;
908
+ let channelInfo = {};
909
+ let resolvedChannelType = normalizeSlackChannelType(message.channel_type, message.channel);
910
+ if (resolvedChannelType !== "im" && (!message.channel_type || message.channel_type !== "im")) {
911
+ channelInfo = await ctx.resolveChannelName(message.channel);
912
+ resolvedChannelType = normalizeSlackChannelType(message.channel_type ?? channelInfo.type, message.channel);
913
+ }
914
+ const channelName = channelInfo?.name;
915
+ const isDirectMessage = resolvedChannelType === "im";
916
+ const isGroupDm = resolvedChannelType === "mpim";
917
+ const isRoom = resolvedChannelType === "channel" || resolvedChannelType === "group";
918
+ const isRoomish = isRoom || isGroupDm;
919
+ const channelConfig = isRoom ? resolveSlackChannelConfig({
920
+ channelId: message.channel,
921
+ channelName,
922
+ channels: ctx.channelsConfig,
923
+ channelKeys: ctx.channelsConfigKeys,
924
+ defaultRequireMention: ctx.defaultRequireMention,
925
+ allowNameMatching: ctx.allowNameMatching
926
+ }) : null;
927
+ const allowBotsSetting = channelConfig?.allowBots ?? account.config?.allowBots ?? cfg.channels?.slack?.allowBots ?? false;
928
+ return {
929
+ channelInfo,
930
+ channelName,
931
+ resolvedChannelType,
932
+ isDirectMessage,
933
+ isGroupDm,
934
+ isRoom,
935
+ isRoomish,
936
+ channelConfig,
937
+ allowBotsMode: allowBotsSetting === "mentions" ? "mentions" : allowBotsSetting ? "all" : "off",
938
+ isBotMessage: Boolean(message.bot_id)
939
+ };
940
+ }
941
+ async function authorizeSlackInboundMessage(params) {
942
+ const { ctx, account, message, conversation } = params;
943
+ const { isDirectMessage, channelName, resolvedChannelType, isBotMessage, allowBotsMode } = conversation;
944
+ if (isBotMessage) {
945
+ if (message.user && ctx.botUserId && message.user === ctx.botUserId) return null;
946
+ if (allowBotsMode === "off") {
947
+ logVerbose(`slack: drop bot message ${message.bot_id ?? "unknown"} (allowBots=false)`);
948
+ return null;
949
+ }
950
+ }
951
+ if (isDirectMessage && !message.user) {
952
+ logVerbose("slack: drop dm message (missing user id)");
953
+ return null;
954
+ }
955
+ const senderId = message.user ?? (isBotMessage ? message.bot_id : void 0);
956
+ if (!senderId) {
957
+ logVerbose("slack: drop message (missing sender id)");
958
+ return null;
959
+ }
960
+ if (!ctx.isChannelAllowed({
961
+ channelId: message.channel,
962
+ channelName,
963
+ channelType: resolvedChannelType
964
+ })) {
965
+ logVerbose("slack: drop message (channel not allowed)");
966
+ return null;
967
+ }
968
+ const allowFromLower = await resolveSlackEffectiveAllowFrom(ctx, { includePairingStore: isDirectMessage });
969
+ if (isDirectMessage) {
970
+ const directUserId = message.user;
971
+ if (!directUserId) {
972
+ logVerbose("slack: drop dm message (missing user id)");
973
+ return null;
974
+ }
975
+ if (!await authorizeSlackDirectMessage({
976
+ ctx,
977
+ accountId: account.accountId,
978
+ senderId: directUserId,
979
+ allowFromLower,
980
+ resolveSenderName: ctx.resolveUserName,
981
+ sendPairingReply: async (text) => {
982
+ await sendMessageSlack(message.channel, text, {
983
+ cfg: ctx.cfg,
984
+ token: ctx.botToken,
985
+ client: ctx.app.client,
986
+ accountId: account.accountId
987
+ });
988
+ },
989
+ onDisabled: () => {
990
+ logVerbose("slack: drop dm (dms disabled)");
991
+ },
992
+ onUnauthorized: ({ allowMatchMeta }) => {
993
+ logVerbose(`Blocked unauthorized slack sender ${message.user} (dmPolicy=${ctx.dmPolicy}, ${allowMatchMeta})`);
994
+ },
995
+ log: logVerbose
996
+ })) return null;
997
+ }
998
+ return {
999
+ senderId,
1000
+ allowFromLower
1001
+ };
1002
+ }
1003
+ async function prepareSlackMessage(params) {
1004
+ const { ctx, account, message, opts } = params;
1005
+ const cfg = ctx.cfg;
1006
+ const conversation = await resolveSlackConversationContext({
1007
+ ctx,
1008
+ account,
1009
+ message
1010
+ });
1011
+ const { channelInfo, channelName, isDirectMessage, isGroupDm, isRoom, isRoomish, channelConfig, allowBotsMode, isBotMessage } = conversation;
1012
+ const authorization = await authorizeSlackInboundMessage({
1013
+ ctx,
1014
+ account,
1015
+ message,
1016
+ conversation
1017
+ });
1018
+ if (!authorization) return null;
1019
+ const { senderId, allowFromLower } = authorization;
1020
+ const messageText = message.text ?? "";
1021
+ const mentionMetadata = collectSlackMentionMetadata(messageText);
1022
+ const { mentionedUserIds, mentionedSubteamIds, hasAnyMention } = mentionMetadata;
1023
+ const { explicitlyMentionedBotUser, explicitlyMentionedBotSubteam, explicitlyMentioned } = await resolveSlackExplicitMentionState({
1024
+ ctx,
1025
+ messageText,
1026
+ mentionedUserIds,
1027
+ hasSubteamMention: mentionMetadata.hasSubteamMention,
1028
+ source: opts.source
1029
+ });
1030
+ const channelRequireMention = channelConfig?.requireMention ?? ctx.defaultRequireMention ?? true;
1031
+ const willImplicitlyThreadReply = isRoom && !channelRequireMention && resolveSlackReplyToMode(account, isDirectMessage ? "direct" : isGroupDm ? "group" : "channel") !== "off";
1032
+ const seedTopLevelRoomThreadBySource = opts.source === "app_mention" || opts.wasMentioned === true || explicitlyMentioned || willImplicitlyThreadReply;
1033
+ let routing = resolveSlackRoutingContext({
1034
+ ctx,
1035
+ account,
1036
+ message,
1037
+ isDirectMessage,
1038
+ isGroupDm,
1039
+ isRoom,
1040
+ isRoomish,
1041
+ seedTopLevelRoomThread: seedTopLevelRoomThreadBySource
1042
+ });
1043
+ const resolveWasMentioned = (mentionRegexes) => opts.wasMentioned ?? (!isDirectMessage && matchesMentionWithExplicit({
1044
+ text: messageText,
1045
+ mentionRegexes,
1046
+ explicit: {
1047
+ hasAnyMention,
1048
+ isExplicitlyMentioned: explicitlyMentioned,
1049
+ canResolveExplicit: Boolean(ctx.botUserId)
1050
+ }
1051
+ }));
1052
+ let mentionRegexes = resolveCachedMentionRegexes(ctx, routing.route.agentId);
1053
+ let wasMentioned = resolveWasMentioned(mentionRegexes);
1054
+ const hasBoundSession = Boolean(routing.runtimeBoundSessionKey || routing.configuredBindingSessionKey);
1055
+ if (!seedTopLevelRoomThreadBySource && wasMentioned && isRoom && !routing.isThreadReply && !hasBoundSession) {
1056
+ routing = resolveSlackRoutingContext({
1057
+ ctx,
1058
+ account,
1059
+ message,
1060
+ isDirectMessage,
1061
+ isGroupDm,
1062
+ isRoom,
1063
+ isRoomish,
1064
+ seedTopLevelRoomThread: true
1065
+ });
1066
+ mentionRegexes = resolveCachedMentionRegexes(ctx, routing.route.agentId);
1067
+ wasMentioned = resolveWasMentioned(mentionRegexes);
1068
+ }
1069
+ const { route, runtimeBinding, configuredBinding, configuredBindingSessionKey, replyToMode, threadContext, threadTs, isThreadReply, threadKeys, sessionKey, historyKey } = routing;
1070
+ if (runtimeBinding && shouldLogVerbose()) logVerbose(`slack: routed via bound conversation ${runtimeBinding.conversation.conversationId} -> ${runtimeBinding.targetSessionKey}`);
1071
+ if (configuredBinding) {
1072
+ const ensured = await ensureConfiguredBindingRouteReady({
1073
+ cfg,
1074
+ bindingResolution: configuredBinding
1075
+ });
1076
+ if (ensured.ok) {
1077
+ if (shouldLogVerbose()) logVerbose(`slack: using configured ACP binding for ${configuredBinding.record.conversation.conversationId} -> ${configuredBindingSessionKey}`);
1078
+ } else {
1079
+ if (shouldLogVerbose()) logVerbose(`slack: configured ACP binding unavailable for ${configuredBinding.record.conversation.conversationId}: ${ensured.error}`);
1080
+ logInboundDrop({
1081
+ log: logVerbose,
1082
+ channel: "slack",
1083
+ reason: "configured ACP binding unavailable",
1084
+ target: configuredBinding.record.conversation.conversationId
1085
+ });
1086
+ return null;
1087
+ }
1088
+ }
1089
+ let implicitMentionKinds = [];
1090
+ if (!isDirectMessage && ctx.botUserId && message.thread_ts && !ctx.threadRequireExplicitMention && !wasMentioned) {
1091
+ const replyToBotKinds = implicitMentionKindWhen("reply_to_bot", message.parent_user_id === ctx.botUserId);
1092
+ implicitMentionKinds = replyToBotKinds.length > 0 ? replyToBotKinds : implicitMentionKindWhen("bot_thread_participant", await hasSlackThreadParticipationWithPersistence({
1093
+ accountId: account.accountId,
1094
+ channelId: message.channel,
1095
+ threadTs: message.thread_ts
1096
+ }));
1097
+ }
1098
+ let resolvedSenderName = normalizeOptionalString(message.username);
1099
+ const resolveSenderName = async () => {
1100
+ if (resolvedSenderName) return resolvedSenderName;
1101
+ if (message.user) {
1102
+ const normalized = normalizeOptionalString((await ctx.resolveUserName(message.user))?.name);
1103
+ if (normalized) {
1104
+ resolvedSenderName = normalized;
1105
+ return resolvedSenderName;
1106
+ }
1107
+ }
1108
+ resolvedSenderName = message.user ?? message.bot_id ?? "unknown";
1109
+ return resolvedSenderName;
1110
+ };
1111
+ const senderNameForAuth = ctx.allowNameMatching ? await resolveSenderName() : void 0;
1112
+ const allowTextCommands = shouldHandleTextCommands({
1113
+ cfg,
1114
+ surface: "slack"
1115
+ });
1116
+ const shouldRequireMention = isRoom ? channelConfig?.requireMention ?? ctx.defaultRequireMention : false;
1117
+ if (message._ambiguousThreadReply) {
1118
+ ctx.logger.info({
1119
+ channel: message.channel,
1120
+ ts: message.ts,
1121
+ parentUserId: message.parent_user_id
1122
+ }, "skipping ambiguous slack thread reply");
1123
+ return null;
1124
+ }
1125
+ const canDetectMention = Boolean(ctx.botUserId) || mentionRegexes.length > 0;
1126
+ const textForCommandDetection = stripSlackMentionsForCommandDetection(message.text ?? "");
1127
+ const hasControlCommandInMessage = hasControlCommand(textForCommandDetection, cfg);
1128
+ const channelUsersAllowlistConfigured = isRoom && Array.isArray(channelConfig?.users) && channelConfig.users.length > 0;
1129
+ const messageIngress = await resolveSlackCommandIngress({
1130
+ ctx,
1131
+ senderId,
1132
+ senderName: senderNameForAuth,
1133
+ channelType: conversation.resolvedChannelType ?? "channel",
1134
+ channelId: message.channel,
1135
+ ownerAllowFromLower: allowFromLower,
1136
+ channelUsers: isRoom ? channelConfig?.users : void 0,
1137
+ allowTextCommands,
1138
+ hasControlCommand: hasControlCommandInMessage,
1139
+ mentionFacts: {
1140
+ canDetectMention,
1141
+ wasMentioned,
1142
+ hasAnyMention,
1143
+ implicitMentionKinds
1144
+ },
1145
+ activation: {
1146
+ requireMention: shouldRequireMention,
1147
+ allowTextCommands,
1148
+ ...ctx.threadRequireExplicitMention ? { allowedImplicitMentionKinds: [] } : {}
1149
+ }
1150
+ });
1151
+ const effectiveWasMentioned = messageIngress.activationAccess.effectiveWasMentioned ?? false;
1152
+ const shouldBypassMention = messageIngress.activationAccess.shouldBypassMention ?? false;
1153
+ const matchedImplicitMentionKinds = implicitMentionKinds;
1154
+ const mentionSource = resolveSlackMentionSource({
1155
+ explicitBotMention: explicitlyMentionedBotUser || opts.source === "app_mention",
1156
+ explicitSubteamMention: explicitlyMentionedBotSubteam,
1157
+ matchedImplicitMentionKinds,
1158
+ shouldBypassMention,
1159
+ wasMentioned
1160
+ });
1161
+ const senderGate = messageIngress.senderAccess.gate;
1162
+ if (isRoom && senderGate?.allowed === false) {
1163
+ logVerbose(`Blocked unauthorized slack sender ${senderId} (not in channel users)`);
1164
+ return null;
1165
+ }
1166
+ if (isRoom && isBotMessage && allowBotsMode !== "off" && !await authorizeSlackBotRoomMessage({
1167
+ ctx,
1168
+ channelId: message.channel,
1169
+ senderId,
1170
+ senderName: senderNameForAuth,
1171
+ channelUsers: channelConfig?.users,
1172
+ allowFromLower
1173
+ })) return null;
1174
+ if (isBotMessage && allowBotsMode === "mentions") {
1175
+ if (!(isDirectMessage || effectiveWasMentioned || shouldBypassMention)) {
1176
+ logVerbose("slack: drop bot message (allowBots=mentions, missing mention)");
1177
+ return null;
1178
+ }
1179
+ }
1180
+ const threadContextAllowFromLower = isRoom ? channelUsersAllowlistConfigured ? normalizeAllowListLower(channelConfig?.users) : [] : isDirectMessage ? allowFromLower : [];
1181
+ const contextVisibilityMode = resolveChannelContextVisibilityMode({
1182
+ cfg: ctx.cfg,
1183
+ channel: "slack",
1184
+ accountId: account.accountId
1185
+ });
1186
+ const commandAuthorized = messageIngress.commandAccess.authorized;
1187
+ if (isRoomish && messageIngress.commandAccess.shouldBlockControlCommand) {
1188
+ logInboundDrop({
1189
+ log: logVerbose,
1190
+ channel: "slack",
1191
+ reason: "control command (unauthorized)",
1192
+ target: senderId
1193
+ });
1194
+ return null;
1195
+ }
1196
+ if (isRoom && shouldRequireMention && messageIngress.activationAccess.shouldSkip) {
1197
+ ctx.logger.info({
1198
+ channel: message.channel,
1199
+ reason: "no-mention"
1200
+ }, "skipping channel message");
1201
+ const pendingText = (message.text ?? "").trim();
1202
+ const fallbackFile = message.files?.length ? `[Slack file: ${formatSlackFileReference(message.files[0])}]` : "";
1203
+ const pendingBody = pendingText || fallbackFile;
1204
+ recordPendingHistoryEntryIfEnabled({
1205
+ historyMap: ctx.channelHistories,
1206
+ historyKey,
1207
+ limit: ctx.historyLimit,
1208
+ entry: pendingBody ? {
1209
+ sender: await resolveSenderName(),
1210
+ body: pendingBody,
1211
+ timestamp: message.ts ? Math.round(Number(message.ts) * 1e3) : void 0,
1212
+ messageId: message.ts
1213
+ } : null
1214
+ });
1215
+ return null;
1216
+ }
1217
+ const threadStarter = isThreadReply && threadTs ? await resolveSlackThreadStarter({
1218
+ channelId: message.channel,
1219
+ threadTs,
1220
+ client: ctx.app.client
1221
+ }) : null;
1222
+ const resolvedMessageContent = await resolveSlackMessageContent({
1223
+ message,
1224
+ isThreadReply,
1225
+ threadStarter,
1226
+ isBotMessage,
1227
+ botToken: ctx.botToken,
1228
+ client: ctx.app.client,
1229
+ mediaMaxBytes: ctx.mediaMaxBytes,
1230
+ resolveUserName: ctx.resolveUserName
1231
+ });
1232
+ if (!resolvedMessageContent) return null;
1233
+ const { rawBody, effectiveDirectMedia } = resolvedMessageContent;
1234
+ const chatType = resolveSlackChatType(conversation.resolvedChannelType);
1235
+ const ackReaction = resolveAckReaction(cfg, route.agentId, {
1236
+ channel: "slack",
1237
+ accountId: account.accountId
1238
+ });
1239
+ const ackReactionValue = ackReaction ?? "";
1240
+ const sourceRepliesAreToolOnly = resolveChannelMessageSourceReplyDeliveryMode({
1241
+ cfg,
1242
+ ctx: { ChatType: chatType }
1243
+ }) === "message_tool_only";
1244
+ const statusReactionsExplicitlyEnabled = cfg.messages?.statusReactions?.enabled === true;
1245
+ const shouldAckReaction$1 = () => Boolean(ackReaction && shouldAckReaction({
1246
+ scope: ctx.ackReactionScope,
1247
+ isDirect: isDirectMessage,
1248
+ isGroup: isRoomish,
1249
+ isMentionableGroup: isRoom,
1250
+ requireMention: shouldRequireMention,
1251
+ canDetectMention,
1252
+ effectiveWasMentioned,
1253
+ shouldBypassMention
1254
+ }));
1255
+ const ackReactionMessageTs = message.ts;
1256
+ const allowToolOnlyStatusReaction = statusReactionsExplicitlyEnabled && (effectiveWasMentioned || shouldBypassMention);
1257
+ const shouldSendAckReaction = shouldAckReaction$1() && (!sourceRepliesAreToolOnly || allowToolOnlyStatusReaction);
1258
+ const statusReactionsWillHandle = Boolean(ackReactionMessageTs) && cfg.messages?.statusReactions?.enabled !== false && shouldSendAckReaction;
1259
+ const ackReactionPromise = !statusReactionsWillHandle && shouldSendAckReaction && ackReactionMessageTs && ackReactionValue ? reactSlackMessage(message.channel, ackReactionMessageTs, ackReactionValue, {
1260
+ token: ctx.botToken,
1261
+ client: ctx.app.client
1262
+ }).then(() => true, (err) => {
1263
+ logVerbose(`slack react failed for channel ${message.channel}: ${formatSlackError(err)}`);
1264
+ return false;
1265
+ }) : statusReactionsWillHandle ? Promise.resolve(true) : null;
1266
+ const roomLabel = channelName ? `#${channelName}` : `#${message.channel}`;
1267
+ const senderName = await resolveSenderName();
1268
+ const preview = rawBody.replace(/\s+/g, " ").slice(0, 160);
1269
+ const inboundLabel = isDirectMessage ? `Slack DM from ${senderName}` : `Slack message in ${roomLabel} from ${senderName}`;
1270
+ const slackFrom = isDirectMessage ? `slack:${message.user}` : isRoom ? `slack:channel:${message.channel}` : `slack:group:${message.channel}`;
1271
+ enqueueSystemEvent(`${inboundLabel}: ${preview}`, {
1272
+ sessionKey,
1273
+ contextKey: `slack:message:${message.channel}:${message.ts ?? "unknown"}`,
1274
+ trusted: false
1275
+ });
1276
+ const envelopeFrom = resolveConversationLabel$1({
1277
+ ChatType: chatType,
1278
+ SenderName: senderName,
1279
+ GroupSubject: isRoomish ? roomLabel : void 0,
1280
+ From: slackFrom
1281
+ }) ?? (isDirectMessage ? senderName : roomLabel);
1282
+ const threadInfo = isThreadReply && threadTs ? ` thread_ts: ${threadTs}${message.parent_user_id ? ` parent_user_id: ${message.parent_user_id}` : ""}` : "";
1283
+ const textWithId = `${rawBody}\n[slack message id: ${message.ts} channel: ${message.channel}${threadInfo}]`;
1284
+ const storePath = resolveStorePath(ctx.cfg.session?.store, { agentId: route.agentId });
1285
+ const envelopeOptions = resolveEnvelopeFormatOptions(ctx.cfg);
1286
+ const previousTimestamp = readSessionUpdatedAt({
1287
+ storePath,
1288
+ sessionKey
1289
+ });
1290
+ const dmHistoryLimit = isDirectMessage ? resolveSlackDmHistoryLimit({
1291
+ account,
1292
+ userId: message.user,
1293
+ defaultLimit: ctx.dmHistoryLimit
1294
+ }) : 0;
1295
+ let combinedBody = formatInboundEnvelope({
1296
+ channel: "Slack",
1297
+ from: envelopeFrom,
1298
+ timestamp: message.ts ? Math.round(Number(message.ts) * 1e3) : void 0,
1299
+ body: textWithId,
1300
+ chatType,
1301
+ sender: {
1302
+ name: senderName,
1303
+ id: senderId
1304
+ },
1305
+ previousTimestamp,
1306
+ envelope: envelopeOptions
1307
+ });
1308
+ const dmHistoryContext = isDirectMessage && !isThreadReply && dmHistoryLimit > 0 && !previousTimestamp ? await resolveSlackDmHistoryContext({
1309
+ ctx,
1310
+ channelId: message.channel,
1311
+ currentMessageTs: message.ts,
1312
+ limit: dmHistoryLimit,
1313
+ envelopeOptions
1314
+ }) : {
1315
+ body: void 0,
1316
+ inboundHistory: void 0
1317
+ };
1318
+ if (dmHistoryContext.body) combinedBody = `${dmHistoryContext.body}\n\n${combinedBody}`;
1319
+ if (isRoomish && ctx.historyLimit > 0) combinedBody = buildPendingHistoryContextFromMap({
1320
+ historyMap: ctx.channelHistories,
1321
+ historyKey,
1322
+ limit: ctx.historyLimit,
1323
+ currentMessage: combinedBody,
1324
+ formatEntry: (entry) => formatInboundEnvelope({
1325
+ channel: "Slack",
1326
+ from: roomLabel,
1327
+ timestamp: entry.timestamp,
1328
+ body: `${entry.body}${entry.messageId ? ` [id:${entry.messageId} channel:${message.channel}]` : ""}`,
1329
+ chatType: "channel",
1330
+ senderLabel: entry.sender,
1331
+ envelope: envelopeOptions
1332
+ })
1333
+ });
1334
+ const slackTo = isDirectMessage ? `user:${message.user}` : `channel:${message.channel}`;
1335
+ const { untrustedChannelMetadata, groupSystemPrompt } = resolveSlackRoomContextHints({
1336
+ isRoomish,
1337
+ channelInfo,
1338
+ channelConfig
1339
+ });
1340
+ const { threadStarterBody, threadHistoryBody, threadSessionPreviousTimestamp, threadLabel, threadStarterMedia } = await resolveSlackThreadContextData({
1341
+ ctx,
1342
+ account,
1343
+ message,
1344
+ isThreadReply,
1345
+ threadTs,
1346
+ threadStarter,
1347
+ roomLabel,
1348
+ storePath,
1349
+ sessionKey,
1350
+ allowFromLower: threadContextAllowFromLower,
1351
+ allowNameMatching: ctx.allowNameMatching,
1352
+ contextVisibilityMode,
1353
+ envelopeOptions,
1354
+ effectiveDirectMedia
1355
+ });
1356
+ const effectiveMedia = effectiveDirectMedia ?? threadStarterMedia;
1357
+ const firstMedia = effectiveMedia?.[0];
1358
+ const inboundHistory = isRoomish && ctx.historyLimit > 0 ? (ctx.channelHistories.get(historyKey) ?? []).map((entry) => ({
1359
+ sender: entry.sender,
1360
+ body: entry.body,
1361
+ timestamp: entry.timestamp
1362
+ })) : dmHistoryContext.inboundHistory;
1363
+ const commandBody = textForCommandDetection.trim();
1364
+ const ctxPayload = finalizeInboundContext({
1365
+ Body: combinedBody,
1366
+ BodyForAgent: rawBody,
1367
+ InboundHistory: inboundHistory,
1368
+ RawBody: rawBody,
1369
+ CommandBody: commandBody,
1370
+ BodyForCommands: commandBody,
1371
+ From: slackFrom,
1372
+ To: slackTo,
1373
+ SessionKey: sessionKey,
1374
+ AccountId: route.accountId,
1375
+ ChatType: chatType,
1376
+ ConversationLabel: envelopeFrom,
1377
+ GroupSubject: isRoomish ? roomLabel : void 0,
1378
+ GroupSpace: ctx.teamId || void 0,
1379
+ GroupSystemPrompt: groupSystemPrompt,
1380
+ UntrustedContext: untrustedChannelMetadata ? [untrustedChannelMetadata] : void 0,
1381
+ SenderName: senderName,
1382
+ SenderId: senderId,
1383
+ Provider: "slack",
1384
+ Surface: "slack",
1385
+ MessageSid: message.ts,
1386
+ ReplyToId: threadContext.replyToId,
1387
+ MessageThreadId: threadContext.messageThreadId,
1388
+ ParentSessionKey: threadKeys.parentSessionKey,
1389
+ ThreadStarterBody: !threadSessionPreviousTimestamp ? threadStarterBody : void 0,
1390
+ ThreadHistoryBody: threadHistoryBody,
1391
+ IsFirstThreadTurn: isThreadReply && threadTs && !threadSessionPreviousTimestamp ? true : void 0,
1392
+ ThreadLabel: threadLabel,
1393
+ Timestamp: message.ts ? Math.round(Number(message.ts) * 1e3) : void 0,
1394
+ ...buildSlackMentionContextPayload({
1395
+ isRoomish,
1396
+ effectiveWasMentioned,
1397
+ explicitlyMentioned,
1398
+ mentionedUserIds,
1399
+ mentionedSubteamIds,
1400
+ matchedImplicitMentionKinds,
1401
+ mentionSource
1402
+ }),
1403
+ MediaPath: firstMedia?.path,
1404
+ MediaType: firstMedia?.contentType,
1405
+ MediaUrl: firstMedia?.path,
1406
+ MediaPaths: effectiveMedia && effectiveMedia.length > 0 ? effectiveMedia.map((m) => m.path) : void 0,
1407
+ MediaUrls: effectiveMedia && effectiveMedia.length > 0 ? effectiveMedia.map((m) => m.path) : void 0,
1408
+ MediaTypes: effectiveMedia && effectiveMedia.length > 0 ? effectiveMedia.map((m) => m.contentType ?? "") : void 0,
1409
+ CommandAuthorized: commandAuthorized,
1410
+ OriginatingChannel: "slack",
1411
+ OriginatingTo: slackTo,
1412
+ NativeChannelId: message.channel
1413
+ });
1414
+ if (isRoomish && !shouldRequireMention) recordPendingHistoryEntryIfEnabled({
1415
+ historyMap: ctx.channelHistories,
1416
+ historyKey,
1417
+ limit: ctx.historyLimit,
1418
+ entry: {
1419
+ sender: senderName,
1420
+ body: rawBody,
1421
+ timestamp: message.ts ? Math.round(Number(message.ts) * 1e3) : void 0,
1422
+ messageId: message.ts
1423
+ }
1424
+ });
1425
+ const pinnedMainDmOwner = isDirectMessage ? resolvePinnedMainDmOwnerFromAllowlist({
1426
+ dmScope: cfg.session?.dmScope,
1427
+ allowFrom: ctx.allowFrom,
1428
+ normalizeEntry: normalizeSlackAllowOwnerEntry
1429
+ }) : null;
1430
+ const replyTarget = isDirectMessage ? `channel:${message.channel}` : ctxPayload.To ?? void 0;
1431
+ if (!replyTarget) return null;
1432
+ if (shouldLogVerbose()) logVerbose(`slack inbound: channel=${message.channel} from=${slackFrom} preview="${preview}"`);
1433
+ return {
1434
+ ctx,
1435
+ account,
1436
+ message,
1437
+ route,
1438
+ channelConfig,
1439
+ replyTarget,
1440
+ ctxPayload,
1441
+ turn: {
1442
+ storePath,
1443
+ record: {
1444
+ updateLastRoute: isDirectMessage ? {
1445
+ sessionKey: resolveInboundLastRouteSessionKey({
1446
+ route,
1447
+ sessionKey
1448
+ }),
1449
+ channel: "slack",
1450
+ to: `user:${message.user}`,
1451
+ accountId: route.accountId,
1452
+ threadId: threadContext.messageThreadId,
1453
+ mainDmOwnerPin: pinnedMainDmOwner && message.user ? {
1454
+ ownerRecipient: pinnedMainDmOwner,
1455
+ senderRecipient: normalizeLowercaseStringOrEmpty(message.user),
1456
+ onSkip: ({ ownerRecipient, senderRecipient }) => {
1457
+ logVerbose(`slack: skip main-session last route for ${senderRecipient} (pinned owner ${ownerRecipient})`);
1458
+ }
1459
+ } : void 0
1460
+ } : void 0,
1461
+ onRecordError: (err) => {
1462
+ ctx.logger.warn({
1463
+ error: formatErrorMessage(err),
1464
+ storePath,
1465
+ sessionKey
1466
+ }, "failed updating session meta");
1467
+ }
1468
+ }
1469
+ },
1470
+ replyToMode,
1471
+ requireMention: shouldRequireMention,
1472
+ isDirectMessage,
1473
+ isRoomish,
1474
+ historyKey,
1475
+ preview,
1476
+ ackReactionMessageTs,
1477
+ ackReactionValue,
1478
+ ackReactionPromise
1479
+ };
1480
+ }
1481
+ //#endregion
1482
+ export { resolveSlackThreadTargets as n, prepareSlackMessage as t };