@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.
- package/dist/account-inspect-D7AZNs8C.js +77 -0
- package/dist/account-inspect-api.js +10 -0
- package/dist/accounts-ClAPP5ry.js +139 -0
- package/dist/accounts.runtime-DDVcLJUI.js +2 -0
- package/dist/action-runtime-e2UhRsNx.js +350 -0
- package/dist/action-runtime.runtime-BFcqMbOm.js +2 -0
- package/dist/actions-CYLFK-Zy.js +292 -0
- package/dist/actions.runtime-CO3OaTLb.js +2 -0
- package/dist/allow-list-BPnnlRPL.js +82 -0
- package/dist/api.js +21 -0
- package/dist/approval-handler.runtime-CmeRr9qA.js +256 -0
- package/dist/blocks-input-CwTFVImV.js +29 -0
- package/dist/blocks-render-BIDw-Pom.js +161 -0
- package/dist/channel-DRjHBTDB.js +1020 -0
- package/dist/channel-api-B_nZwosg.js +20 -0
- package/dist/channel-config-api.js +2 -0
- package/dist/channel-entry.js +22 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.setup-Cayn7afd.js +73 -0
- package/dist/client-CPe4GmDR.js +103 -0
- package/dist/config-api-B_jq4NJW.js +2 -0
- package/dist/config-schema-D9B5LB_L.js +167 -0
- package/dist/configured-state.js +11 -0
- package/dist/contract-api.js +5 -0
- package/dist/directory-config-B3JiHeB7.js +54 -0
- package/dist/directory-contract-api.js +2 -0
- package/dist/directory-live-Bf16GwDh.js +133 -0
- package/dist/doctor-contract-KUjHnkQm.js +147 -0
- package/dist/doctor-contract-api.js +2 -0
- package/dist/errors-BYFHR24f.js +109 -0
- package/dist/exec-approvals-7xUNgLi9.js +58 -0
- package/dist/group-policy-CyLUK6My.js +41 -0
- package/dist/http-routes-api.js +2 -0
- package/dist/inbound-contract-test-api.js +3 -0
- package/dist/index.js +33 -0
- package/dist/interactive-replies-api.js +2 -0
- package/dist/interactive-replies-qAIfuBor.js +173 -0
- package/dist/magic-string.es-BMaGRRZ1.js +1011 -0
- package/dist/media-D1XCd1uP.js +469 -0
- package/dist/message-tool-api-6lowf9zE.js +104 -0
- package/dist/message-tool-api.js +2 -0
- package/dist/monitor-a97o17G6.js +13 -0
- package/dist/mrkdwn-Cax-eSfK.js +6 -0
- package/dist/outbound-adapter-B_5sEhCg.js +174 -0
- package/dist/outbound-payload-test-api.js +2 -0
- package/dist/outbound-payload.test-harness-CVCamg1x.js +13558 -0
- package/dist/pipeline.runtime-DT0hLnq2.js +1379 -0
- package/dist/plugin-routes-DtTPmga1.js +20 -0
- package/dist/prepare-D3YqV8jB.js +1482 -0
- package/dist/prepare.test-helpers-DVcjRhfG.js +49 -0
- package/dist/probe-3eZf1FjI.js +42 -0
- package/dist/provider-D7uAN3Fq.js +3235 -0
- package/dist/registry-CeaoNfoP.js +39 -0
- package/dist/replies-Xe_jMR6o.js +139 -0
- package/dist/reply-blocks-Z5l6_R6H.js +14 -0
- package/dist/resolve-allowlist-common-Bk3clYPK.js +43 -0
- package/dist/resolve-channels-BRYqyNVJ.js +81 -0
- package/dist/resolve-users-Bd_SdP8j.js +113 -0
- package/dist/rolldown-runtime-CiIaOW0V.js +13 -0
- package/dist/room-context-0vovmZPU.js +787 -0
- package/dist/runtime-Bo-KHM-F.js +8 -0
- package/dist/runtime-api-Dd1xIV5v.js +9 -0
- package/dist/runtime-api.js +14 -0
- package/dist/runtime-setter-api.js +2 -0
- package/dist/scopes-CDevO8jg.js +74 -0
- package/dist/secret-contract-Bo6lbSkh.js +141 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/security-audit-BtHGnD3d.js +51 -0
- package/dist/security-contract-api.js +2 -0
- package/dist/send-D_A9kL-C.js +721 -0
- package/dist/send.runtime-BRE_ncCU.js +2 -0
- package/dist/send.runtime-_l76lUuL.js +2 -0
- package/dist/setup-core-B9NetDkM.js +320 -0
- package/dist/setup-entry.js +15 -0
- package/dist/setup-plugin-api.js +2 -0
- package/dist/setup-surface-D88QBVOW.js +128 -0
- package/dist/shared-D8U42xFL.js +208 -0
- package/dist/slash-commands.runtime-22kgyst2.js +19 -0
- package/dist/slash-dispatch.runtime-BJgT0jwV.js +32 -0
- package/dist/slash-plugin-commands.runtime-CF-n3MeP.js +2 -0
- package/dist/slash-skill-commands.runtime-BMs0VjTe.js +7 -0
- package/dist/streaming-compat-RkZgTmQ2.js +43 -0
- package/dist/target-parsing-CQmv-iSm.js +55 -0
- package/dist/targets-B1tYCAr6.js +2 -0
- package/dist/test-api.js +8 -0
- package/dist/thread-ts-C2x7c5PP.js +24 -0
- package/openclaw.plugin.json +2405 -0
- 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 };
|