@openclaw/slack 2026.7.1 → 2026.7.2-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accounts.runtime-KvftRRek.js +2 -0
- package/dist/{action-runtime-DoQGk_8D.js → action-runtime-Cee2ih0s.js} +209 -30
- package/dist/action-runtime.runtime-DZfd7IFr.js +2 -0
- package/dist/{actions-DMpDmKEA.js → actions-BZiuSTIq.js} +88 -143
- package/dist/{actions.runtime-C103_BPW.js → actions.runtime-DoD0-gxw.js} +1 -1
- package/dist/api.js +14 -13
- package/dist/{approval-handler.runtime-CIUbOt5T.js → approval-handler.runtime-CUPtIM4k.js} +3 -3
- package/dist/{channel-BjlsaGHn.js → channel-DaIiGUOp.js} +119 -141
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/channel-type-Cfm6_6n1.js +109 -0
- package/dist/{channel.setup-Dq6syTNU.js → channel.setup-C-mLz50O.js} +3 -3
- package/dist/{client-DGxwSL7f.js → client-WIPAS3Yr.js} +46 -2
- package/dist/config-api-Df9a1B8C.js +3 -0
- package/dist/{config-schema-BzqzFW__.js → config-schema-xPSnK2VK.js} +32 -37
- package/dist/cursor-pages-DSmTg0su.js +13 -0
- package/dist/{directory-config-BZuS8141.js → directory-config-DRkFujwG.js} +2 -2
- package/dist/directory-contract-api.js +1 -1
- package/dist/{directory-live-BNsxCgqP.js → directory-live-D_yZhjgp.js} +2 -2
- package/dist/{doctor-contract-XL6HeNCb.js → doctor-contract-DddfkMlF.js} +25 -38
- package/dist/doctor-contract-api.js +1 -1
- package/dist/group-policy-85UWiriS.js +2345 -0
- package/dist/{interactive-replies-CXpT-vA_.js → interactive-replies-BilWUdcI.js} +5 -1
- package/dist/interactive-replies-api.js +1 -1
- package/dist/{message-tool-api-BgWKyu2r.js → message-tool-api-CA9jZrCY.js} +1 -1
- package/dist/message-tool-api.js +1 -1
- package/dist/{monitor-Ux-wzUWI.js → monitor-CalGciTs.js} +3 -3
- package/dist/outbound-adapter-BiLKcnyH.js +228 -0
- package/dist/{pipeline.runtime-rpVpay59.js → pipeline.runtime-DofAvTtb.js} +657 -221
- package/dist/{policy-Dj-puVPb.js → policy-BVs4LiSa.js} +7 -7
- package/dist/preflight-audio.runtime-D5Bgags0.js +3 -0
- package/dist/{probe-CuwRDE5j.js → probe-CN0ssfLN.js} +2 -2
- package/dist/{provider-C1-DFSpw.js → provider-CmeoUiIM.js} +907 -206
- package/dist/{relay-source-D2jv1iGg.js → relay-source-DMmgtvg4.js} +6 -17
- package/dist/replies-CJJCp6qz.js +420 -0
- package/dist/{resolve-channels-CstkChBE.js → resolve-channels-Rfts6POK.js} +17 -6
- package/dist/{resolve-users-C3NP2c-Q.js → resolve-users-y6wAxR8E.js} +11 -6
- package/dist/runtime-JSVZSWAj.js +8 -0
- package/dist/{runtime-api-CEmqYxR-.js → runtime-api-BGDyOt1D.js} +3 -3
- package/dist/runtime-api.js +11 -11
- package/dist/runtime-setter-api.js +1 -1
- package/dist/{scopes-DCWVD5-8.js → scopes-CuxNQZ6v.js} +2 -2
- package/dist/{secret-contract-C3ChHo5A.js → secret-contract-CMohXFWS.js} +18 -113
- package/dist/secret-contract-api.js +1 -1
- package/dist/{send-DKDXoeoV.js → send-DYp8ca-l.js} +662 -325
- package/dist/{send.runtime-GPRa63l1.js → send.runtime-CJLxAt26.js} +1 -1
- package/dist/{setup-core-fGd6Gquy.js → setup-core-CkwVLzFO.js} +15 -21
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{setup-surface-Dv2fIJNB.js → setup-surface-oSd3NX9J.js} +4 -4
- package/dist/{shared-CosfcFri.js → shared-D9yz5Otg.js} +17 -38
- package/dist/{slash-dispatch.runtime-CcMbbT3_.js → slash-dispatch.runtime-CSja9VuD.js} +1 -1
- package/dist/{streaming-compat-DjlgH-Be.js → streaming-compat-3_X-21wW.js} +1 -0
- package/dist/{target-parsing-3jYH8LfB.js → target-parsing-N6lHogYZ.js} +16 -3
- package/dist/{targets-Dll4NJgs.js → targets-KmjP2E8s.js} +1 -1
- package/node_modules/p-map/index.d.ts +155 -0
- package/node_modules/p-map/index.js +284 -0
- package/node_modules/p-map/license +9 -0
- package/node_modules/p-map/package.json +57 -0
- package/node_modules/p-map/readme.md +190 -0
- package/npm-shrinkwrap.json +16 -3
- package/openclaw.plugin.json +84 -0
- package/package.json +7 -7
- package/dist/accounts.runtime-DWvaCb-X.js +0 -2
- package/dist/action-runtime.runtime-BABj3aM-.js +0 -2
- package/dist/config-api-DnT9ohtE.js +0 -2
- package/dist/configured-state.js +0 -11
- package/dist/format-YCNoNkM2.js +0 -107
- package/dist/outbound-adapter-DVoBfBW5.js +0 -272
- package/dist/replies-DpF38e1y.js +0 -287
- package/dist/reply-blocks-BGQ6zSqM.js +0 -877
- package/dist/resolve-allowlist-common-CP8SH0Zf.js +0 -43
- package/dist/runtime-BOk7xkOl.js +0 -8
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
import { u as resolveSlackReplyToMode } from "./accounts-BOJJiHSr.js";
|
|
2
|
-
import {
|
|
3
|
-
import "./targets-
|
|
4
|
-
import {
|
|
5
|
-
import { n as isSlackInteractiveRepliesEnabled, t as compileSlackInteractiveReplies } from "./interactive-replies-
|
|
6
|
-
import { n as resolveSlackNativeStreaming, r as resolveSlackStreamingMode, t as mapStreamingModeToSlackLegacyDraftStreamMode } from "./streaming-compat-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { n as
|
|
12
|
-
import { a as resolveDeliveredSlackReplyThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as resolveSlackThreadTs, s as emitSlackMessageSentHooks, t as createSlackReplyDeliveryPlan } from "./replies-DpF38e1y.js";
|
|
2
|
+
import { i as parseSlackTarget } from "./target-parsing-N6lHogYZ.js";
|
|
3
|
+
import "./targets-KmjP2E8s.js";
|
|
4
|
+
import { A as buildSlackBlocksFallbackText, B as truncateSlackText, I as SLACK_TEXT_LIMIT, P as SLACK_EDIT_TEXT_LIMIT, c as normalizeAllowListLower, d as resolveSlackAllowListMatch, i as formatSlackError, k as normalizeSlackOutboundText, l as normalizeSlackAllowOwnerEntry, q as escapeSlackMrkdwn, v as resolveSlackReplyRenderPlan, w as hasSlackNativeDataBlock } from "./group-policy-85UWiriS.js";
|
|
5
|
+
import { n as isSlackInteractiveRepliesEnabled, t as compileSlackInteractiveReplies } from "./interactive-replies-BilWUdcI.js";
|
|
6
|
+
import { n as resolveSlackNativeStreaming, r as resolveSlackStreamingMode, t as mapStreamingModeToSlackLegacyDraftStreamMode } from "./streaming-compat-3_X-21wW.js";
|
|
7
|
+
import { S as buildSlackEditTextPayload, _ as resolveSlackThreadHistory, b as resolveSlackBlocksText, f as removeSlackReaction, l as reactSlackMessage, r as editSlackMessage, t as deleteSlackMessage, v as resolveSlackThreadStarter, x as formatSlackFileReference, y as chooseSlackPrimaryText } from "./actions-BZiuSTIq.js";
|
|
8
|
+
import { c as recordSlackThreadParticipation, l as buildSlackNativeDataDeliveryPlan, r as sendMessageSlack, s as hasSlackThreadParticipationWithPersistence } from "./send-DYp8ca-l.js";
|
|
9
|
+
import { S as updateLastRoute, _ as resolveSlackChatType, b as resolveChannelResetConfig, d as authorizeSlackBotRoomMessage, f as resolveSlackCommandIngress, g as normalizeSlackChannelType, h as parseSlackAssistantThreadMetadata, l as recordInboundSession, m as buildSlackAssistantThreadMetadata, o as authorizeSlackDirectMessage, p as resolveSlackEffectiveAllowFrom, r as resolveSlackRoomContextHints, u as resolveConversationLabel$1, v as readSessionUpdatedAt, w as stripSlackMentionsForCommandDetection, x as resolveStorePath, y as resolveChannelContextVisibilityMode } from "./provider-CmeoUiIM.js";
|
|
10
|
+
import { n as resolveSlackChannelConfig } from "./policy-BVs4LiSa.js";
|
|
11
|
+
import { a as resolveDeliveredSlackReplyThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as resolveSlackThreadTs, s as emitSlackMessageSentHooks, t as createSlackReplyDeliveryPlan } from "./replies-CJJCp6qz.js";
|
|
13
12
|
import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
14
13
|
import { resolveAgentRoute, resolveInboundLastRouteSessionKey, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
|
|
15
|
-
import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelMessageReplyPipeline, createChannelProgressDraftGate, createDraftStreamLoop, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, formatChannelProgressDraftText, isChannelProgressDraftWorkToolName, mergeChannelProgressDraftLine, resolveAgentOutboundIdentity, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelProgressDraftConfig, resolveChannelProgressDraftMaxLineChars, resolveChannelProgressDraftMaxLines, resolveChannelProgressDraftRender, resolveChannelStreamingBlockEnabled, resolveChannelStreamingNativeTransport, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingSuppressDefaultToolProgressMessages } from "openclaw/plugin-sdk/channel-outbound";
|
|
14
|
+
import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelMessageReplyPipeline, createChannelProgressDraftGate, createDraftStreamLoop, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, formatChannelProgressDraftText, formatPlanChecklistLines, isChannelProgressDraftWorkToolName, mergeChannelProgressDraftLine, resolveAgentOutboundIdentity, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelProgressDraftConfig, resolveChannelProgressDraftMaxLineChars, resolveChannelProgressDraftMaxLines, resolveChannelProgressDraftRender, resolveChannelStreamingBlockEnabled, resolveChannelStreamingNativeTransport, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingProgressCommentary, resolveChannelStreamingSuppressDefaultToolProgressMessages } from "openclaw/plugin-sdk/channel-outbound";
|
|
16
15
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
17
16
|
import { danger, logVerbose, shouldLogVerbose, sleep } from "openclaw/plugin-sdk/runtime-env";
|
|
18
17
|
import { stripReasoningTagsFromText } from "openclaw/plugin-sdk/text-chunking";
|
|
19
18
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
20
19
|
import { createHash } from "node:crypto";
|
|
20
|
+
import fs from "node:fs/promises";
|
|
21
21
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
22
22
|
import { asDateTimestampMs, asFiniteNumberInRange, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
23
23
|
import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
|
24
|
+
import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist, shouldIncludeSupplementalContext } from "openclaw/plugin-sdk/security-runtime";
|
|
24
25
|
import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
|
|
25
26
|
import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
26
27
|
import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
27
28
|
import { mergePairLoopGuardConfig } from "openclaw/plugin-sdk/pair-loop-guard-runtime";
|
|
28
29
|
import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
|
|
29
|
-
import { buildChannelInboundEventContext, buildMentionRegexes, classifyChannelInboundEvent, dispatchChannelInboundReply, formatInboundEnvelope, hasVisibleInboundReplyDispatch, implicitMentionKindWhen, logInboundDrop, matchesMentionWithExplicit, recordDroppedChannelInboundHistory, resolveEnvelopeFormatOptions, resolveUnmentionedGroupInboundPolicy, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
|
|
30
|
-
import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist, shouldIncludeSupplementalContext } from "openclaw/plugin-sdk/security-runtime";
|
|
30
|
+
import { buildChannelInboundEventContext, buildMentionRegexes, classifyChannelInboundEvent, dispatchChannelInboundReply, formatInboundEnvelope, hasVisibleInboundReplyDispatch, implicitMentionKindWhen, logInboundDrop, matchesMentionWithExplicit, recordDroppedChannelInboundHistory, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, resolveUnmentionedGroupInboundPolicy, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
|
|
31
31
|
import { DEFAULT_TIMING, createStatusReactionController, logAckFailure, logTypingFailure, removeAckReactionAfterReply, resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
|
|
32
32
|
import { dispatchReplyWithBufferedBlockDispatcher } from "openclaw/plugin-sdk/reply-runtime";
|
|
33
33
|
import { hasControlCommand } from "openclaw/plugin-sdk/command-detection";
|
|
@@ -69,6 +69,7 @@ function createSlackDraftStream(params) {
|
|
|
69
69
|
cfg: params.cfg,
|
|
70
70
|
token: params.token,
|
|
71
71
|
accountId: params.accountId,
|
|
72
|
+
...params.eventScope ? { client: params.eventScope.client } : {},
|
|
72
73
|
...blocks ? { blocks } : {}
|
|
73
74
|
});
|
|
74
75
|
return;
|
|
@@ -79,6 +80,10 @@ function createSlackDraftStream(params) {
|
|
|
79
80
|
accountId: params.accountId,
|
|
80
81
|
threadTs: params.resolveThreadTs?.(),
|
|
81
82
|
identity: params.identity,
|
|
83
|
+
...params.eventScope ? {
|
|
84
|
+
client: params.eventScope.client,
|
|
85
|
+
enterpriseEventScope: params.eventScope
|
|
86
|
+
} : {},
|
|
82
87
|
...params.metadata ? { metadata: params.metadata } : {},
|
|
83
88
|
...blocks ? { blocks } : {}
|
|
84
89
|
});
|
|
@@ -120,7 +125,8 @@ function createSlackDraftStream(params) {
|
|
|
120
125
|
try {
|
|
121
126
|
await remove(channelId, messageId, {
|
|
122
127
|
token: params.token,
|
|
123
|
-
accountId: params.accountId
|
|
128
|
+
accountId: params.accountId,
|
|
129
|
+
...params.eventScope ? { client: params.eventScope.client } : {}
|
|
124
130
|
});
|
|
125
131
|
} catch (err) {
|
|
126
132
|
params.warn?.(`slack stream preview cleanup failed: ${formatSlackError(err)}`);
|
|
@@ -215,6 +221,11 @@ function stableTaskIdPart(value) {
|
|
|
215
221
|
return `${slugTaskIdPart(value)}_${suffix}`;
|
|
216
222
|
}
|
|
217
223
|
function buildPlanTasks(params) {
|
|
224
|
+
if (params.plan) return params.plan.slice(-50).map((entry, index) => ({
|
|
225
|
+
id: `plan_step_${index + 1}`,
|
|
226
|
+
title: compactTitle(entry.step),
|
|
227
|
+
status: entry.status === "completed" ? "complete" : entry.status
|
|
228
|
+
}));
|
|
218
229
|
const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_TASK_DETAIL_MAX_CHARS);
|
|
219
230
|
return params.lines.slice(-50).map((line, index) => ({
|
|
220
231
|
id: line.id ? stableTaskIdPart(line.id) : `${slugTaskIdPart(line.toolName ?? line.kind ?? line.label)}_${index + 1}`,
|
|
@@ -228,6 +239,7 @@ function resolvePlanTitle(params) {
|
|
|
228
239
|
function buildSlackProgressStreamChunks(params) {
|
|
229
240
|
const tasks = buildPlanTasks({
|
|
230
241
|
lines: params.lines,
|
|
242
|
+
plan: params.plan,
|
|
231
243
|
maxLineChars: params.maxLineChars
|
|
232
244
|
});
|
|
233
245
|
if (tasks.length === 0) return;
|
|
@@ -248,15 +260,63 @@ function buildSlackProgressStreamChunks(params) {
|
|
|
248
260
|
function buildSlackProgressDraftBlocks(params) {
|
|
249
261
|
const label = params.label?.trim() || params.title?.trim();
|
|
250
262
|
const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS);
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
263
|
+
const planLines = formatPlanChecklistLines(params.plan ?? [], {
|
|
264
|
+
maxLines: 50,
|
|
265
|
+
maxLineChars
|
|
266
|
+
});
|
|
267
|
+
const narration = params.narration?.replace(/\s+/g, " ").trim();
|
|
268
|
+
const headBlocks = [
|
|
269
|
+
...label ? [{
|
|
270
|
+
type: "section",
|
|
271
|
+
text: field(`*${escapeSlackMrkdwn(label)}*`)
|
|
272
|
+
}] : [],
|
|
273
|
+
...narration ? [{
|
|
274
|
+
type: "section",
|
|
275
|
+
text: field(`_${escapeSlackMrkdwn(narration)}_`)
|
|
276
|
+
}] : [],
|
|
277
|
+
...planLines.length > 0 ? [{
|
|
278
|
+
type: "section",
|
|
279
|
+
text: field(planLines.map((line) => escapeSlackMrkdwn(line)).join("\n"))
|
|
280
|
+
}] : []
|
|
281
|
+
].slice(0, 50);
|
|
282
|
+
const lineBudget = Math.max(0, 50 - headBlocks.length);
|
|
283
|
+
const renderedBlocks = [...headBlocks, ...params.lines.slice(-lineBudget).map((line) => ({
|
|
255
284
|
type: "section",
|
|
256
285
|
fields: [field(legacyLineTitle(line)), field(legacyLineDetail(line, maxLineChars))]
|
|
257
|
-
}))]
|
|
286
|
+
}))];
|
|
258
287
|
return renderedBlocks.length ? renderedBlocks : void 0;
|
|
259
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Slack native streams key task rows by persistent id with no removal chunk.
|
|
291
|
+
* When the task source switches representation (tool lines <-> typed plan) or
|
|
292
|
+
* a snapshot drops ids, previously emitted non-terminal rows must receive a
|
|
293
|
+
* final update or they linger in_progress forever.
|
|
294
|
+
*/
|
|
295
|
+
function reconcileSlackNativeTaskChunks(params) {
|
|
296
|
+
const nextTasks = /* @__PURE__ */ new Map();
|
|
297
|
+
for (const chunk of params.chunks ?? []) if (chunk.type === "task_update") nextTasks.set(chunk.id, {
|
|
298
|
+
title: chunk.title,
|
|
299
|
+
status: chunk.status
|
|
300
|
+
});
|
|
301
|
+
const terminalized = [...params.previousTasks].filter(([id, task]) => !nextTasks.has(id) && task.status !== "complete" && task.status !== "error").map(([id, task]) => {
|
|
302
|
+
const entry = {
|
|
303
|
+
title: task.title,
|
|
304
|
+
status: "complete"
|
|
305
|
+
};
|
|
306
|
+
nextTasks.set(id, entry);
|
|
307
|
+
return {
|
|
308
|
+
type: "task_update",
|
|
309
|
+
id,
|
|
310
|
+
title: task.title,
|
|
311
|
+
status: "complete"
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
for (const [id, task] of params.previousTasks) if (!nextTasks.has(id)) nextTasks.set(id, task);
|
|
315
|
+
return {
|
|
316
|
+
chunks: params.chunks?.length ? [...params.chunks, ...terminalized] : terminalized.length ? terminalized : params.chunks,
|
|
317
|
+
tasks: nextTasks
|
|
318
|
+
};
|
|
319
|
+
}
|
|
260
320
|
function buildSlackProgressStreamStartChunks(params) {
|
|
261
321
|
return buildSlackProgressStreamChunks(params);
|
|
262
322
|
}
|
|
@@ -503,7 +563,7 @@ function resolveSlackThreadContext(params) {
|
|
|
503
563
|
const messageTs = params.message.ts ?? eventTs;
|
|
504
564
|
const hasThreadTs = typeof incomingThreadTs === "string" && incomingThreadTs.length > 0;
|
|
505
565
|
const isThreadReply = hasThreadTs && (incomingThreadTs !== messageTs || Boolean(params.message.parent_user_id));
|
|
506
|
-
const replyToId = incomingThreadTs
|
|
566
|
+
const replyToId = isThreadReply ? incomingThreadTs : void 0;
|
|
507
567
|
const isAssistantDmThreadRoot = hasThreadTs && !isThreadReply && params.isDirectMessage === true;
|
|
508
568
|
return {
|
|
509
569
|
incomingThreadTs,
|
|
@@ -533,12 +593,44 @@ function resolveSlackThreadTargets(params) {
|
|
|
533
593
|
//#endregion
|
|
534
594
|
//#region extensions/slack/src/monitor/message-handler/preview-finalize.ts
|
|
535
595
|
function buildExpectedSlackEditText(params) {
|
|
536
|
-
|
|
596
|
+
const trimmedText = params.text.trim();
|
|
597
|
+
if (trimmedText) return normalizeSlackOutboundText(trimmedText);
|
|
598
|
+
if (params.blocks?.length) return normalizeSlackOutboundText(buildSlackBlocksFallbackText(params.blocks));
|
|
599
|
+
return " ";
|
|
600
|
+
}
|
|
601
|
+
function buildAcceptedSlackEditTexts(params) {
|
|
602
|
+
const expected = buildExpectedSlackEditText(params);
|
|
603
|
+
const texts = /* @__PURE__ */ new Set([
|
|
604
|
+
expected,
|
|
605
|
+
normalizeSlackOutboundText(truncateSlackText(expected, SLACK_EDIT_TEXT_LIMIT)),
|
|
606
|
+
normalizeSlackOutboundText(buildSlackEditTextPayload(params.text, params.blocks))
|
|
607
|
+
]);
|
|
608
|
+
if (params.blocks?.length && hasSlackNativeDataBlock(params.blocks)) {
|
|
609
|
+
const fallbackPlan = buildSlackNativeDataDeliveryPlan({
|
|
610
|
+
baseText: params.text,
|
|
611
|
+
blocks: params.blocks
|
|
612
|
+
});
|
|
613
|
+
for (const message of fallbackPlan.fallbackMessages) texts.add(normalizeSlackOutboundText(message.text));
|
|
614
|
+
}
|
|
615
|
+
return texts;
|
|
537
616
|
}
|
|
538
617
|
function blocksMatch(expected, actual) {
|
|
539
618
|
if (!expected?.length) return !actual?.length;
|
|
540
|
-
if (!actual?.length)
|
|
541
|
-
|
|
619
|
+
if (!actual?.length) {
|
|
620
|
+
if (!hasSlackNativeDataBlock(expected)) return false;
|
|
621
|
+
return buildSlackNativeDataDeliveryPlan({ blocks: expected }).fallbackMessages.every((message) => !message.blocks?.length);
|
|
622
|
+
}
|
|
623
|
+
if (JSON.stringify(expected) === JSON.stringify(actual)) return true;
|
|
624
|
+
if (!hasSlackNativeDataBlock(expected)) return false;
|
|
625
|
+
try {
|
|
626
|
+
const fallbackPlan = buildSlackNativeDataDeliveryPlan({ blocks: expected });
|
|
627
|
+
const fallbackBlocks = fallbackPlan.fallbackMessages.flatMap((message) => message.blocks ?? []);
|
|
628
|
+
if (JSON.stringify(fallbackBlocks) === JSON.stringify(actual)) return true;
|
|
629
|
+
const fallbackText = fallbackPlan.fallbackMessages.map((message) => message.text).filter(Boolean).join("\n\n");
|
|
630
|
+
return normalizeSlackOutboundText(buildSlackBlocksFallbackText(actual)) === normalizeSlackOutboundText(fallbackText);
|
|
631
|
+
} catch {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
542
634
|
}
|
|
543
635
|
async function readSlackMessageAfterEditError(params) {
|
|
544
636
|
if (params.threadTs) return ((await params.client.conversations.replies({
|
|
@@ -567,8 +659,12 @@ async function didSlackPreviewEditApplyAfterError(params) {
|
|
|
567
659
|
text: params.text,
|
|
568
660
|
blocks: params.blocks
|
|
569
661
|
});
|
|
662
|
+
const acceptedTexts = buildAcceptedSlackEditTexts({
|
|
663
|
+
text: params.text,
|
|
664
|
+
blocks: params.blocks
|
|
665
|
+
});
|
|
570
666
|
const actualText = normalizeSlackOutboundText((readback.text ?? "").trim());
|
|
571
|
-
if (params.blocks?.length) return actualText
|
|
667
|
+
if (params.blocks?.length) return acceptedTexts.has(actualText) && blocksMatch(params.blocks, readback.blocks);
|
|
572
668
|
return actualText === expectedText;
|
|
573
669
|
}
|
|
574
670
|
async function finalizeSlackPreviewEdit(params) {
|
|
@@ -679,16 +775,26 @@ function resolveSlackStreamingThreadHint(params) {
|
|
|
679
775
|
});
|
|
680
776
|
}
|
|
681
777
|
const SLACK_STREAM_RECIPIENT_TEAM_CACHE_MAX = 2e3;
|
|
682
|
-
const
|
|
778
|
+
const slackStreamRecipientTeamCaches = /* @__PURE__ */ new WeakMap();
|
|
779
|
+
function getSlackStreamRecipientTeamCache(client) {
|
|
780
|
+
const existing = slackStreamRecipientTeamCaches.get(client);
|
|
781
|
+
if (existing) return existing;
|
|
782
|
+
const cache = /* @__PURE__ */ new Map();
|
|
783
|
+
slackStreamRecipientTeamCaches.set(client, cache);
|
|
784
|
+
return cache;
|
|
785
|
+
}
|
|
683
786
|
function buildSlackEventDeliveryKey(params) {
|
|
684
787
|
const reply = resolveSendableOutboundReplyParts(params.payload, { text: params.textOverride });
|
|
685
|
-
const
|
|
686
|
-
|
|
788
|
+
const renderPlan = resolveSlackReplyRenderPlan(params.payload, params.textOverride ?? params.payload.text);
|
|
789
|
+
const plannedBlocks = renderPlan.mode === "single" ? renderPlan.blocks : renderPlan.blockPart?.blocks;
|
|
790
|
+
const slackBlocks = readSlackReplyBlocks(params.payload) ?? plannedBlocks;
|
|
791
|
+
const renderedText = renderPlan.mode === "single" ? renderPlan.text : renderPlan.fallbackText;
|
|
792
|
+
if (!reply.hasContent && !slackBlocks?.length && !renderedText.trim()) return null;
|
|
687
793
|
return JSON.stringify({
|
|
688
794
|
kind: params.kind,
|
|
689
795
|
threadTs: params.threadTs ?? "",
|
|
690
796
|
replyToId: params.payload.replyToId ?? null,
|
|
691
|
-
text: reply.trimmedText,
|
|
797
|
+
text: renderedText || reply.trimmedText,
|
|
692
798
|
mediaUrls: reply.mediaUrls,
|
|
693
799
|
blocks: slackBlocks ?? null
|
|
694
800
|
});
|
|
@@ -696,20 +802,22 @@ function buildSlackEventDeliveryKey(params) {
|
|
|
696
802
|
function readSlackStreamRecipientTeamCache(params) {
|
|
697
803
|
if (!params.fallbackTeamId || !params.userId) return;
|
|
698
804
|
const cacheKey = `${params.fallbackTeamId}:${params.userId}`;
|
|
699
|
-
const
|
|
805
|
+
const cache = getSlackStreamRecipientTeamCache(params.client);
|
|
806
|
+
const cached = cache.get(cacheKey);
|
|
700
807
|
if (!cached) return;
|
|
701
|
-
|
|
702
|
-
|
|
808
|
+
cache.delete(cacheKey);
|
|
809
|
+
cache.set(cacheKey, cached);
|
|
703
810
|
return cached;
|
|
704
811
|
}
|
|
705
812
|
function rememberSlackStreamRecipientTeam(params) {
|
|
706
813
|
if (!params.fallbackTeamId || !params.userId) return;
|
|
707
814
|
const cacheKey = `${params.fallbackTeamId}:${params.userId}`;
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
815
|
+
const cache = getSlackStreamRecipientTeamCache(params.client);
|
|
816
|
+
if (cache.has(cacheKey)) cache.delete(cacheKey);
|
|
817
|
+
cache.set(cacheKey, params.teamId);
|
|
818
|
+
if (cache.size > SLACK_STREAM_RECIPIENT_TEAM_CACHE_MAX) {
|
|
819
|
+
const oldest = cache.keys().next().value;
|
|
820
|
+
if (oldest) cache.delete(oldest);
|
|
713
821
|
}
|
|
714
822
|
}
|
|
715
823
|
function normalizeSlackReasoningProgressLine(text) {
|
|
@@ -793,6 +901,8 @@ async function resolveSlackStreamRecipientTeamId(params) {
|
|
|
793
901
|
}
|
|
794
902
|
async function dispatchPreparedSlackMessage(prepared) {
|
|
795
903
|
const { ctx, account, message, route } = prepared;
|
|
904
|
+
const slackClient = prepared.eventScope?.client ?? ctx.app.client;
|
|
905
|
+
const slackStreamFallbackTeamId = prepared.eventScope?.teamId ?? ctx.teamId;
|
|
796
906
|
const cfg = ctx.cfg;
|
|
797
907
|
const runtime = ctx.runtime;
|
|
798
908
|
const outboundIdentity = resolveAgentOutboundIdentity(cfg, route.agentId);
|
|
@@ -841,6 +951,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
841
951
|
ctx: prepared.ctxPayload
|
|
842
952
|
});
|
|
843
953
|
const sourceRepliesAreToolOnly = sourceReplyDeliveryMode === "message_tool_only";
|
|
954
|
+
const suppressRoomEventTyping = prepared.ctxPayload.InboundEventKind === "room_event";
|
|
844
955
|
const messageSentHookTarget = prepared.ctxPayload.OriginatingTo ?? prepared.ctxPayload.To ?? prepared.replyTarget;
|
|
845
956
|
const messageSentHookContext = {
|
|
846
957
|
sessionKeyForInternalHooks: prepared.ctxPayload.SessionKey ?? route.sessionKey,
|
|
@@ -860,7 +971,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
860
971
|
setReaction: async (emoji) => {
|
|
861
972
|
await reactSlackMessage(message.channel, reactionMessageTs ?? "", emoji, {
|
|
862
973
|
token: ctx.botToken,
|
|
863
|
-
client:
|
|
974
|
+
client: slackClient
|
|
864
975
|
}).catch((err) => {
|
|
865
976
|
if (formatErrorMessage(err).includes("already_reacted")) return;
|
|
866
977
|
throw err;
|
|
@@ -869,7 +980,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
869
980
|
removeReaction: async (emoji) => {
|
|
870
981
|
await removeSlackReaction(message.channel, reactionMessageTs ?? "", emoji, {
|
|
871
982
|
token: ctx.botToken,
|
|
872
|
-
client:
|
|
983
|
+
client: slackClient
|
|
873
984
|
}).catch((err) => {
|
|
874
985
|
if (formatErrorMessage(err).includes("no_reaction")) return;
|
|
875
986
|
throw err;
|
|
@@ -925,12 +1036,15 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
925
1036
|
channelId: message.channel,
|
|
926
1037
|
threadTs: statusThreadTs,
|
|
927
1038
|
status: "is typing...",
|
|
928
|
-
loadingMessages: SLACK_THREAD_LOADING_MESSAGES
|
|
1039
|
+
loadingMessages: SLACK_THREAD_LOADING_MESSAGES,
|
|
1040
|
+
eventScope: prepared.eventScope
|
|
929
1041
|
});
|
|
930
1042
|
if (typingReaction && message.ts) await reactSlackMessage(message.channel, message.ts, typingReaction, {
|
|
931
1043
|
token: ctx.botToken,
|
|
932
|
-
client:
|
|
933
|
-
}).catch(() => {
|
|
1044
|
+
client: slackClient
|
|
1045
|
+
}).catch((err) => {
|
|
1046
|
+
logVerbose(`slack send: typing reaction failed: ${formatSlackError(err)}`);
|
|
1047
|
+
});
|
|
934
1048
|
},
|
|
935
1049
|
stop: async () => {
|
|
936
1050
|
if (!didSetStatus) return;
|
|
@@ -938,12 +1052,15 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
938
1052
|
await ctx.setSlackThreadStatus({
|
|
939
1053
|
channelId: message.channel,
|
|
940
1054
|
threadTs: statusThreadTs,
|
|
941
|
-
status: ""
|
|
1055
|
+
status: "",
|
|
1056
|
+
eventScope: prepared.eventScope
|
|
942
1057
|
});
|
|
943
1058
|
if (typingReaction && message.ts) await removeSlackReaction(message.channel, message.ts, typingReaction, {
|
|
944
1059
|
token: ctx.botToken,
|
|
945
|
-
client:
|
|
946
|
-
}).catch(() => {
|
|
1060
|
+
client: slackClient
|
|
1061
|
+
}).catch((err) => {
|
|
1062
|
+
logVerbose(`slack send: typing reaction removal failed: ${formatSlackError(err)}`);
|
|
1063
|
+
});
|
|
947
1064
|
},
|
|
948
1065
|
onStartError: (err) => {
|
|
949
1066
|
logTypingFailure({
|
|
@@ -1067,7 +1184,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1067
1184
|
delivery.outcome = "success";
|
|
1068
1185
|
}
|
|
1069
1186
|
};
|
|
1070
|
-
|
|
1187
|
+
let deliveryTracker = createSlackEventDeliveryTracker();
|
|
1071
1188
|
const markPreviewPayloadDelivered = (params) => {
|
|
1072
1189
|
deliveryTracker.markDelivered(params);
|
|
1073
1190
|
const nextThreadTs = replyPlan.peekThreadTs();
|
|
@@ -1113,12 +1230,14 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1113
1230
|
accountId: account.accountId,
|
|
1114
1231
|
runtime,
|
|
1115
1232
|
textLimit: ctx.textLimit,
|
|
1233
|
+
mediaMaxBytes: ctx.mediaMaxBytes,
|
|
1116
1234
|
replyThreadTs: session.threadTs,
|
|
1117
1235
|
replyToMode: replyDeliveryMode,
|
|
1118
1236
|
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1119
1237
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
|
|
1120
1238
|
...messageSentDeliveryHookContext,
|
|
1121
|
-
deferMessageSentHooks: true
|
|
1239
|
+
deferMessageSentHooks: true,
|
|
1240
|
+
...prepared.eventScope ? { eventScope: prepared.eventScope } : {}
|
|
1122
1241
|
});
|
|
1123
1242
|
markSlackStreamFallbackDelivered(session);
|
|
1124
1243
|
if (!session.stopped) try {
|
|
@@ -1160,11 +1279,13 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1160
1279
|
accountId: account.accountId,
|
|
1161
1280
|
runtime,
|
|
1162
1281
|
textLimit: ctx.textLimit,
|
|
1282
|
+
mediaMaxBytes: ctx.mediaMaxBytes,
|
|
1163
1283
|
replyThreadTs: deliveryReplyThreadTs,
|
|
1164
1284
|
replyToMode: replyDeliveryMode,
|
|
1165
1285
|
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1166
1286
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
|
|
1167
|
-
...messageSentDeliveryHookContext
|
|
1287
|
+
...messageSentDeliveryHookContext,
|
|
1288
|
+
...prepared.eventScope ? { eventScope: prepared.eventScope } : {}
|
|
1168
1289
|
});
|
|
1169
1290
|
observedReplyDelivery = true;
|
|
1170
1291
|
if (params.kind === "final") observedFinalReplyDelivery = true;
|
|
@@ -1201,7 +1322,9 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1201
1322
|
const deliverWithStreaming = async (params) => {
|
|
1202
1323
|
if (params.payload.isReasoning === true) return;
|
|
1203
1324
|
const reply = resolveSendableOutboundReplyParts(params.payload);
|
|
1204
|
-
|
|
1325
|
+
const renderPlan = resolveSlackReplyRenderPlan(params.payload);
|
|
1326
|
+
const plannedBlocks = renderPlan.mode === "single" ? renderPlan.blocks : renderPlan.blockPart?.blocks;
|
|
1327
|
+
if (streamFailed || reply.hasMedia || renderPlan.mode === "split" || Boolean(plannedBlocks?.length) || readSlackReplyBlocks(params.payload)?.length || !reply.hasText) {
|
|
1205
1328
|
await deliverNormally({
|
|
1206
1329
|
payload: params.payload,
|
|
1207
1330
|
kind: params.kind,
|
|
@@ -1250,16 +1373,16 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1250
1373
|
return;
|
|
1251
1374
|
}
|
|
1252
1375
|
streamSession = await startSlackStream({
|
|
1253
|
-
client:
|
|
1376
|
+
client: slackClient,
|
|
1254
1377
|
channel: message.channel,
|
|
1255
1378
|
threadTs: streamThreadTs,
|
|
1256
1379
|
text,
|
|
1257
1380
|
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1258
1381
|
teamId: await resolveSlackStreamRecipientTeamId({
|
|
1259
|
-
client:
|
|
1382
|
+
client: slackClient,
|
|
1260
1383
|
token: ctx.botToken,
|
|
1261
1384
|
userId: message.user,
|
|
1262
|
-
fallbackTeamId:
|
|
1385
|
+
fallbackTeamId: slackStreamFallbackTeamId
|
|
1263
1386
|
}),
|
|
1264
1387
|
userId: message.user
|
|
1265
1388
|
});
|
|
@@ -1288,12 +1411,16 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1288
1411
|
logVerbose("slack-stream: suppressed duplicate append payload");
|
|
1289
1412
|
return;
|
|
1290
1413
|
}
|
|
1291
|
-
const completionChunks = useNativeProgressStreaming && !nativeProgressCompletionSent && previewToolProgressLines.length > 0 ?
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1414
|
+
const completionChunks = useNativeProgressStreaming && !nativeProgressCompletionSent && (previewToolProgressLines.length > 0 || Boolean(latestPlan?.length) || Boolean(latestPlanExplanation) || [...nativeTaskState.values()].some((task) => task.status !== "complete" && task.status !== "error")) ? reconcileSlackNativeTaskChunks({
|
|
1415
|
+
previousTasks: nativeTaskState,
|
|
1416
|
+
chunks: buildSlackProgressStreamCompletionChunks({
|
|
1417
|
+
title: resolveNativeProgressTitle(),
|
|
1418
|
+
lines: resolveNativeProgressLines(),
|
|
1419
|
+
plan: resolveNativeProgressPlan(),
|
|
1420
|
+
maxLineChars: progressDraftMaxLineChars,
|
|
1421
|
+
finalInProgressStatus: params.payload.isError ? "error" : "complete"
|
|
1422
|
+
})
|
|
1423
|
+
}).chunks : void 0;
|
|
1297
1424
|
if (useNativeProgressStreaming) {
|
|
1298
1425
|
if (completionChunks?.length) {
|
|
1299
1426
|
await appendSlackStream({
|
|
@@ -1379,11 +1506,15 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1379
1506
|
return;
|
|
1380
1507
|
}
|
|
1381
1508
|
const reply = resolveSendableOutboundReplyParts(payload);
|
|
1382
|
-
const slackBlocks = readSlackReplyBlocks(payload);
|
|
1383
1509
|
const ttsSupplement = getReplyPayloadTtsSupplement(payload);
|
|
1384
|
-
const
|
|
1510
|
+
const replySourceText = payload.text ?? ttsSupplement?.spokenText;
|
|
1511
|
+
const replyRenderPlan = resolveSlackReplyRenderPlan(payload, replySourceText);
|
|
1512
|
+
const slackBlocks = replyRenderPlan.mode === "single" ? replyRenderPlan.blocks : replyRenderPlan.blockPart?.blocks;
|
|
1513
|
+
const requiresSeparateFallbackDelivery = replyRenderPlan.mode === "split";
|
|
1514
|
+
const trimmedFinalText = replyRenderPlan.mode === "single" ? replyRenderPlan.text.trim() : replyRenderPlan.fallbackText.trim();
|
|
1515
|
+
const previewFinalText = replyRenderPlan.mode === "single" && replyRenderPlan.textIsSlackMrkdwn ? trimmedFinalText : normalizeSlackOutboundText((replySourceText ?? "").trim());
|
|
1385
1516
|
const shouldRestoreTtsSupplementTextForPreviewFallback = Boolean(ttsSupplement) && ttsSupplement?.visibleTextAlreadyDelivered !== true && Boolean(draftStream) && !draftPreviewCommitted && !observedFinalReplyDelivery && previewStreamingEnabled && !payload.text?.trim();
|
|
1386
|
-
if (info.kind === "final" && ttsSupplement && draftStream && !draftPreviewCommitted && !observedFinalReplyDelivery && previewStreamingEnabled && !payload.isError && trimmedFinalText.length > 0) {
|
|
1517
|
+
if (info.kind === "final" && ttsSupplement && draftStream && !draftPreviewCommitted && !observedFinalReplyDelivery && previewStreamingEnabled && !payload.isError && !requiresSeparateFallbackDelivery && trimmedFinalText.length > 0) {
|
|
1387
1518
|
const channelId = draftStream.channelId();
|
|
1388
1519
|
const messageId = draftStream.messageId();
|
|
1389
1520
|
if (channelId && messageId) {
|
|
@@ -1392,12 +1523,12 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1392
1523
|
await draftStream.seal();
|
|
1393
1524
|
try {
|
|
1394
1525
|
await finalizeSlackPreviewEdit({
|
|
1395
|
-
client:
|
|
1526
|
+
client: slackClient,
|
|
1396
1527
|
token: ctx.botToken,
|
|
1397
1528
|
accountId: account.accountId,
|
|
1398
1529
|
channelId,
|
|
1399
1530
|
messageId,
|
|
1400
|
-
text:
|
|
1531
|
+
text: previewFinalText,
|
|
1401
1532
|
...slackBlocks?.length ? { blocks: slackBlocks } : {},
|
|
1402
1533
|
threadTs: finalThreadTs
|
|
1403
1534
|
});
|
|
@@ -1409,7 +1540,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1409
1540
|
await deliverNormally({
|
|
1410
1541
|
payload: payload.text?.trim() ? payload : {
|
|
1411
1542
|
...payload,
|
|
1412
|
-
text:
|
|
1543
|
+
text: ttsSupplement.spokenText
|
|
1413
1544
|
},
|
|
1414
1545
|
kind: info.kind,
|
|
1415
1546
|
forcedThreadTs: finalThreadTs
|
|
@@ -1456,9 +1587,9 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1456
1587
|
}
|
|
1457
1588
|
} : void 0,
|
|
1458
1589
|
buildFinalEdit: () => {
|
|
1459
|
-
if (!previewStreamingEnabled || reply.hasMedia && !ttsSupplement || payload.isError || trimmedFinalText.length === 0 && !slackBlocks?.length) return;
|
|
1590
|
+
if (!previewStreamingEnabled || reply.hasMedia && !ttsSupplement || payload.isError || requiresSeparateFallbackDelivery || trimmedFinalText.length === 0 && !slackBlocks?.length) return;
|
|
1460
1591
|
return {
|
|
1461
|
-
text:
|
|
1592
|
+
text: previewFinalText,
|
|
1462
1593
|
blocks: slackBlocks,
|
|
1463
1594
|
threadTs: usedReplyThreadTs ?? statusThreadTs
|
|
1464
1595
|
};
|
|
@@ -1470,7 +1601,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1470
1601
|
threadTs: edit.threadTs
|
|
1471
1602
|
})) return;
|
|
1472
1603
|
await finalizeSlackPreviewEdit({
|
|
1473
|
-
client:
|
|
1604
|
+
client: slackClient,
|
|
1474
1605
|
token: ctx.botToken,
|
|
1475
1606
|
accountId: account.accountId,
|
|
1476
1607
|
channelId: preview.channelId,
|
|
@@ -1540,6 +1671,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1540
1671
|
cfg,
|
|
1541
1672
|
token: ctx.botToken,
|
|
1542
1673
|
accountId: account.accountId,
|
|
1674
|
+
...prepared.eventScope ? { eventScope: prepared.eventScope } : {},
|
|
1543
1675
|
identity: slackIdentity,
|
|
1544
1676
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
|
|
1545
1677
|
maxChars: Math.min(ctx.textLimit, SLACK_TEXT_LIMIT),
|
|
@@ -1554,7 +1686,11 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1554
1686
|
let hasStreamedMessage = false;
|
|
1555
1687
|
const streamMode = slackStreaming.draftMode;
|
|
1556
1688
|
const useNativeProgressStreaming = useStreaming && slackStreaming.mode === "progress";
|
|
1557
|
-
const
|
|
1689
|
+
const progressDraftActive = Boolean(draftStream) || useNativeProgressStreaming;
|
|
1690
|
+
const previewToolProgressEnabled = progressDraftActive && resolveChannelStreamingPreviewToolProgress(account.config);
|
|
1691
|
+
const commentaryProgressEnabled = progressDraftActive && resolveChannelStreamingProgressCommentary(account.config);
|
|
1692
|
+
const commentaryDraftEnabled = progressDraftActive && (slackStreaming.mode === "progress" ? resolveChannelStreamingProgressCommentary(account.config, previewToolProgressEnabled) : previewToolProgressEnabled);
|
|
1693
|
+
let shouldYieldDraftProgress = () => false;
|
|
1558
1694
|
const suppressDefaultToolProgressMessages = resolveChannelStreamingSuppressDefaultToolProgressMessages(account.config, {
|
|
1559
1695
|
draftStreamActive: Boolean(draftStream) || useNativeProgressStreaming,
|
|
1560
1696
|
previewToolProgressEnabled,
|
|
@@ -1563,6 +1699,9 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1563
1699
|
let previewToolProgressSuppressed = false;
|
|
1564
1700
|
let previewToolProgressLines = [];
|
|
1565
1701
|
let lastNonEmptyPreviewToolProgressLines = [];
|
|
1702
|
+
let latestPlan;
|
|
1703
|
+
let latestPlanExplanation;
|
|
1704
|
+
let nativeTaskState = /* @__PURE__ */ new Map();
|
|
1566
1705
|
let appendRenderedText = "";
|
|
1567
1706
|
let appendSourceText = "";
|
|
1568
1707
|
let reasoningProgressRawText = "";
|
|
@@ -1580,12 +1719,16 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1580
1719
|
entry: account.config,
|
|
1581
1720
|
lines: progressLines,
|
|
1582
1721
|
seed: progressSeed,
|
|
1583
|
-
formatLine: escapeSlackMrkdwn
|
|
1722
|
+
formatLine: escapeSlackMrkdwn,
|
|
1723
|
+
narration: latestPlanExplanation,
|
|
1724
|
+
plan: latestPlan
|
|
1584
1725
|
});
|
|
1585
1726
|
if (!previewText) return;
|
|
1586
1727
|
const richProgressBlocks = useRichProgressDraft ? buildSlackProgressDraftBlocks({
|
|
1587
1728
|
title: explicitProgressTitle,
|
|
1588
1729
|
lines: progressLines,
|
|
1730
|
+
plan: latestPlan,
|
|
1731
|
+
narration: latestPlanExplanation,
|
|
1589
1732
|
maxLineChars: resolveChannelProgressDraftMaxLineChars(account.config)
|
|
1590
1733
|
}) : void 0;
|
|
1591
1734
|
draftStream.update(useRichProgressDraft && richProgressBlocks ? {
|
|
@@ -1604,13 +1747,26 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1604
1747
|
}
|
|
1605
1748
|
return !streamFailed;
|
|
1606
1749
|
};
|
|
1750
|
+
const resolveNativeProgressPlan = () => latestPlan?.length ? latestPlan : void 0;
|
|
1751
|
+
const resolveNativeProgressLines = () => {
|
|
1752
|
+
if (latestPlan?.length || !latestPlanExplanation) return previewToolProgressLines;
|
|
1753
|
+
const explanationLine = buildChannelProgressDraftLine({
|
|
1754
|
+
event: "plan",
|
|
1755
|
+
phase: "update",
|
|
1756
|
+
explanation: latestPlanExplanation
|
|
1757
|
+
});
|
|
1758
|
+
return explanationLine ? [...previewToolProgressLines, explanationLine] : previewToolProgressLines;
|
|
1759
|
+
};
|
|
1760
|
+
const resolveNativeProgressTitle = () => explicitProgressTitle && latestPlanExplanation ? `${explicitProgressTitle} — ${latestPlanExplanation}` : explicitProgressTitle ?? latestPlanExplanation;
|
|
1607
1761
|
const buildNativeProgressChunks = () => streamSession ? buildSlackProgressStreamUpdateChunks({
|
|
1608
|
-
title:
|
|
1609
|
-
lines:
|
|
1762
|
+
title: resolveNativeProgressTitle(),
|
|
1763
|
+
lines: resolveNativeProgressLines(),
|
|
1764
|
+
plan: resolveNativeProgressPlan(),
|
|
1610
1765
|
maxLineChars: progressDraftMaxLineChars
|
|
1611
1766
|
}) : buildSlackProgressStreamStartChunks({
|
|
1612
|
-
title:
|
|
1613
|
-
lines:
|
|
1767
|
+
title: resolveNativeProgressTitle(),
|
|
1768
|
+
lines: resolveNativeProgressLines(),
|
|
1769
|
+
plan: resolveNativeProgressPlan(),
|
|
1614
1770
|
maxLineChars: progressDraftMaxLineChars
|
|
1615
1771
|
});
|
|
1616
1772
|
const markNativeProgressDelivered = (session, threadTs) => {
|
|
@@ -1630,17 +1786,17 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1630
1786
|
nativeProgressStreamThreadTs = streamThreadTs;
|
|
1631
1787
|
const startPromise = (async () => {
|
|
1632
1788
|
const session = await startSlackStream({
|
|
1633
|
-
client:
|
|
1789
|
+
client: slackClient,
|
|
1634
1790
|
channel: message.channel,
|
|
1635
1791
|
threadTs: streamThreadTs,
|
|
1636
1792
|
chunks,
|
|
1637
1793
|
taskDisplayMode: "plan",
|
|
1638
1794
|
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1639
1795
|
teamId: await resolveSlackStreamRecipientTeamId({
|
|
1640
|
-
client:
|
|
1796
|
+
client: slackClient,
|
|
1641
1797
|
token: ctx.botToken,
|
|
1642
1798
|
userId: message.user,
|
|
1643
|
-
fallbackTeamId:
|
|
1799
|
+
fallbackTeamId: slackStreamFallbackTeamId
|
|
1644
1800
|
}),
|
|
1645
1801
|
userId: message.user
|
|
1646
1802
|
});
|
|
@@ -1668,30 +1824,60 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1668
1824
|
nativeProgressChunkKey = chunkKey;
|
|
1669
1825
|
};
|
|
1670
1826
|
const updateNativeProgressStream = async () => {
|
|
1671
|
-
|
|
1827
|
+
const hasRetirableNativeTasks = [...nativeTaskState.values()].some((task) => task.status !== "complete" && task.status !== "error");
|
|
1828
|
+
if (!useNativeProgressStreaming || streamFailed || previewToolProgressLines.length === 0 && !latestPlan?.length && !latestPlanExplanation && !hasRetirableNativeTasks) return;
|
|
1672
1829
|
if (!await waitForNativeProgressStreamStart()) return;
|
|
1673
|
-
const
|
|
1830
|
+
const reconciled = reconcileSlackNativeTaskChunks({
|
|
1831
|
+
previousTasks: nativeTaskState,
|
|
1832
|
+
chunks: buildNativeProgressChunks()
|
|
1833
|
+
});
|
|
1834
|
+
const chunks = reconciled.chunks;
|
|
1674
1835
|
if (!chunks?.length) return;
|
|
1675
1836
|
const chunkKey = JSON.stringify(chunks);
|
|
1676
1837
|
if (chunkKey === nativeProgressChunkKey) return;
|
|
1677
1838
|
try {
|
|
1678
|
-
if (!streamSession)
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
}
|
|
1682
|
-
await appendNativeProgressStream(chunks, chunkKey);
|
|
1839
|
+
if (!streamSession) await startNativeProgressStream(chunks, chunkKey);
|
|
1840
|
+
else await appendNativeProgressStream(chunks, chunkKey);
|
|
1841
|
+
nativeTaskState = reconciled.tasks;
|
|
1683
1842
|
} catch (err) {
|
|
1684
1843
|
runtime.error?.(danger(`slack-stream: native progress stream failed: ${formatSlackError(err)}, falling back`));
|
|
1685
1844
|
streamFailed = true;
|
|
1686
1845
|
}
|
|
1687
1846
|
};
|
|
1688
1847
|
const progressDraftGate = createChannelProgressDraftGate({ onStart: useNativeProgressStreaming ? updateNativeProgressStream : renderProgressDraft });
|
|
1848
|
+
const pushPlanProgress = async (steps, explanation) => {
|
|
1849
|
+
if (previewToolProgressSuppressed) return;
|
|
1850
|
+
latestPlan = steps ? steps.map((entry) => ({ ...entry })) : void 0;
|
|
1851
|
+
latestPlanExplanation = explanation?.replace(/\s+/g, " ").trim() || void 0;
|
|
1852
|
+
if (!draftStream && !useNativeProgressStreaming) return;
|
|
1853
|
+
if (streamMode !== "status_final" && !useNativeProgressStreaming) {
|
|
1854
|
+
const text = formatChannelProgressDraftText({
|
|
1855
|
+
entry: account.config,
|
|
1856
|
+
lines: previewToolProgressLines,
|
|
1857
|
+
seed: progressSeed,
|
|
1858
|
+
formatLine: escapeSlackMrkdwn,
|
|
1859
|
+
narration: latestPlanExplanation,
|
|
1860
|
+
plan: latestPlan
|
|
1861
|
+
});
|
|
1862
|
+
if (text) {
|
|
1863
|
+
draftStream?.update(text);
|
|
1864
|
+
hasStreamedMessage = true;
|
|
1865
|
+
}
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
const alreadyStarted = progressDraftGate.hasStarted;
|
|
1869
|
+
await progressDraftGate.startNow();
|
|
1870
|
+
if (alreadyStarted && progressDraftGate.hasStarted) await refreshStartedProgressDraft();
|
|
1871
|
+
};
|
|
1689
1872
|
const refreshStartedProgressDraft = async () => {
|
|
1690
1873
|
if (useNativeProgressStreaming) await updateNativeProgressStream();
|
|
1691
1874
|
else renderProgressDraft();
|
|
1692
1875
|
};
|
|
1693
|
-
const
|
|
1876
|
+
const pushPreviewProgress = async (line, options) => {
|
|
1877
|
+
const lane = options?.lane ?? "tool";
|
|
1878
|
+
const progressEnabled = lane === "commentary" ? commentaryDraftEnabled : previewToolProgressEnabled;
|
|
1694
1879
|
if (!draftStream && !useNativeProgressStreaming) return;
|
|
1880
|
+
if (lane === "commentary" && commentaryProgressEnabled && shouldYieldDraftProgress()) return;
|
|
1695
1881
|
if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return;
|
|
1696
1882
|
const normalized = line?.text.replace(/\s+/g, " ").trim();
|
|
1697
1883
|
if (!line || !normalized) {
|
|
@@ -1702,7 +1888,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1702
1888
|
return;
|
|
1703
1889
|
}
|
|
1704
1890
|
if (streamMode !== "status_final") {
|
|
1705
|
-
if (!
|
|
1891
|
+
if (!progressEnabled || previewToolProgressSuppressed) return;
|
|
1706
1892
|
const nextLines = mergeChannelProgressDraftLine(previewToolProgressLines, line, { maxLines: resolveChannelProgressDraftMaxLines(account.config) });
|
|
1707
1893
|
if (nextLines === previewToolProgressLines) return;
|
|
1708
1894
|
previewToolProgressLines = nextLines;
|
|
@@ -1715,7 +1901,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1715
1901
|
hasStreamedMessage = true;
|
|
1716
1902
|
return;
|
|
1717
1903
|
}
|
|
1718
|
-
if (
|
|
1904
|
+
if (progressEnabled && !previewToolProgressSuppressed) {
|
|
1719
1905
|
previewToolProgressLines = mergeChannelProgressDraftLine(previewToolProgressLines, line, { maxLines: resolveChannelProgressDraftMaxLines(account.config) });
|
|
1720
1906
|
if (previewToolProgressLines.length > 0) lastNonEmptyPreviewToolProgressLines = previewToolProgressLines;
|
|
1721
1907
|
}
|
|
@@ -1766,24 +1952,42 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1766
1952
|
reasoningProgressRawText = mergeSlackReasoningProgressText(reasoningProgressRawText, payload.text, { snapshot: payload.isReasoningSnapshot === true });
|
|
1767
1953
|
const normalized = normalizeSlackReasoningProgressLine(reasoningProgressRawText);
|
|
1768
1954
|
if (!normalized) return;
|
|
1769
|
-
await
|
|
1955
|
+
await pushPreviewProgress({
|
|
1770
1956
|
id: "reasoning",
|
|
1771
1957
|
kind: "item",
|
|
1772
1958
|
text: normalized,
|
|
1773
1959
|
label: "Reasoning"
|
|
1774
1960
|
});
|
|
1775
1961
|
};
|
|
1962
|
+
const resetDraftDeliveryState = () => {
|
|
1963
|
+
hasStreamedMessage = false;
|
|
1964
|
+
appendRenderedText = "";
|
|
1965
|
+
appendSourceText = "";
|
|
1966
|
+
statusUpdateCount = 0;
|
|
1967
|
+
};
|
|
1968
|
+
const resetDraftProgressState = () => {
|
|
1969
|
+
reasoningProgressRawText = "";
|
|
1970
|
+
previewToolProgressSuppressed = false;
|
|
1971
|
+
previewToolProgressLines = [];
|
|
1972
|
+
};
|
|
1776
1973
|
const onDraftBoundary = !shouldUseDraftStream ? void 0 : async () => {
|
|
1777
1974
|
if (hasStreamedMessage && streamMode !== "status_final") {
|
|
1778
1975
|
draftStream?.forceNewMessage();
|
|
1779
|
-
|
|
1780
|
-
appendRenderedText = "";
|
|
1781
|
-
appendSourceText = "";
|
|
1782
|
-
statusUpdateCount = 0;
|
|
1976
|
+
resetDraftDeliveryState();
|
|
1783
1977
|
}
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1978
|
+
resetDraftProgressState();
|
|
1979
|
+
};
|
|
1980
|
+
const onQueuedFollowupAdmitted = !shouldUseDraftStream ? void 0 : async () => {
|
|
1981
|
+
await draftStream?.flush();
|
|
1982
|
+
draftStream?.forceNewMessage();
|
|
1983
|
+
draftPreviewCommitted = false;
|
|
1984
|
+
observedFinalReplyDelivery = false;
|
|
1985
|
+
deliveryTracker = createSlackEventDeliveryTracker();
|
|
1986
|
+
resetDraftDeliveryState();
|
|
1987
|
+
resetDraftProgressState();
|
|
1988
|
+
latestPlan = void 0;
|
|
1989
|
+
latestPlanExplanation = void 0;
|
|
1990
|
+
nativeTaskState = /* @__PURE__ */ new Map();
|
|
1787
1991
|
};
|
|
1788
1992
|
let dispatchError;
|
|
1789
1993
|
let queuedFinal = false;
|
|
@@ -1813,10 +2017,15 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1813
2017
|
replyOptions: {
|
|
1814
2018
|
skillFilter: prepared.channelConfig?.skills,
|
|
1815
2019
|
sourceReplyDeliveryMode,
|
|
2020
|
+
suppressTyping: suppressRoomEventTyping ? true : void 0,
|
|
1816
2021
|
hasRepliedRef,
|
|
1817
2022
|
disableBlockStreaming,
|
|
1818
2023
|
onModelSelected,
|
|
1819
2024
|
suppressDefaultToolProgressMessages: suppressDefaultToolProgressMessages ? true : void 0,
|
|
2025
|
+
commentaryProgressEnabled: commentaryProgressEnabled ? true : void 0,
|
|
2026
|
+
onVerboseProgressVisibility: commentaryProgressEnabled ? (isActive) => {
|
|
2027
|
+
shouldYieldDraftProgress = isActive;
|
|
2028
|
+
} : void 0,
|
|
1820
2029
|
allowProgressCallbacksWhenSourceDeliverySuppressed: sourceReplyDeliveryMode === "message_tool_only" && statusReactionsEnabled ? true : void 0,
|
|
1821
2030
|
allowToolLifecycleWhenProgressHidden: statusReactionsEnabled ? true : void 0,
|
|
1822
2031
|
onPartialReply: useStreaming ? void 0 : !previewStreamingEnabled ? void 0 : async (payload) => {
|
|
@@ -1824,6 +2033,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1824
2033
|
},
|
|
1825
2034
|
onAssistantMessageStart: onDraftBoundary,
|
|
1826
2035
|
onReasoningEnd: onDraftBoundary,
|
|
2036
|
+
onQueuedFollowupAdmitted,
|
|
1827
2037
|
onReasoningStream: statusReactionsEnabled || previewToolProgressEnabled ? async (payload) => {
|
|
1828
2038
|
await pushReasoningProgress(payload);
|
|
1829
2039
|
if (!statusReactionsEnabled) return;
|
|
@@ -1831,7 +2041,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1831
2041
|
} : void 0,
|
|
1832
2042
|
onToolStart: async (payload) => {
|
|
1833
2043
|
if (statusReactionsEnabled) await statusReactions.setTool(payload.name);
|
|
1834
|
-
await
|
|
2044
|
+
await pushPreviewProgress(buildChannelProgressDraftLineForEntry(account.config, {
|
|
1835
2045
|
event: "tool",
|
|
1836
2046
|
itemId: payload.itemId,
|
|
1837
2047
|
toolCallId: payload.toolCallId,
|
|
@@ -1841,7 +2051,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1841
2051
|
}, payload.detailMode ? { detailMode: payload.detailMode } : void 0), { toolName: payload.name });
|
|
1842
2052
|
},
|
|
1843
2053
|
onItemEvent: async (payload) => {
|
|
1844
|
-
await
|
|
2054
|
+
await pushPreviewProgress(buildChannelProgressDraftLineForEntry(account.config, {
|
|
1845
2055
|
event: "item",
|
|
1846
2056
|
itemId: payload.itemId,
|
|
1847
2057
|
toolCallId: payload.toolCallId,
|
|
@@ -1853,21 +2063,15 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1853
2063
|
summary: payload.summary,
|
|
1854
2064
|
progressText: payload.progressText,
|
|
1855
2065
|
meta: payload.meta
|
|
1856
|
-
}));
|
|
2066
|
+
}), payload.kind === "preamble" ? { lane: "commentary" } : void 0);
|
|
1857
2067
|
},
|
|
1858
2068
|
onPlanUpdate: async (payload) => {
|
|
1859
2069
|
if (payload.phase !== "update") return;
|
|
1860
|
-
await
|
|
1861
|
-
event: "plan",
|
|
1862
|
-
phase: payload.phase,
|
|
1863
|
-
title: payload.title,
|
|
1864
|
-
explanation: payload.explanation,
|
|
1865
|
-
steps: payload.steps
|
|
1866
|
-
}));
|
|
2070
|
+
await pushPlanProgress(payload.planSteps, payload.explanation);
|
|
1867
2071
|
},
|
|
1868
2072
|
onApprovalEvent: async (payload) => {
|
|
1869
2073
|
if (payload.phase !== "requested") return;
|
|
1870
|
-
await
|
|
2074
|
+
await pushPreviewProgress(buildChannelProgressDraftLine({
|
|
1871
2075
|
event: "approval",
|
|
1872
2076
|
phase: payload.phase,
|
|
1873
2077
|
title: payload.title,
|
|
@@ -1878,7 +2082,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1878
2082
|
},
|
|
1879
2083
|
onCommandOutput: async (payload) => {
|
|
1880
2084
|
if (payload.phase !== "end") return;
|
|
1881
|
-
await
|
|
2085
|
+
await pushPreviewProgress(buildChannelProgressDraftLine({
|
|
1882
2086
|
event: "command-output",
|
|
1883
2087
|
itemId: payload.itemId,
|
|
1884
2088
|
toolCallId: payload.toolCallId,
|
|
@@ -1891,7 +2095,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1891
2095
|
},
|
|
1892
2096
|
onPatchSummary: async (payload) => {
|
|
1893
2097
|
if (payload.phase !== "end") return;
|
|
1894
|
-
await
|
|
2098
|
+
await pushPreviewProgress(buildChannelProgressDraftLine({
|
|
1895
2099
|
event: "patch",
|
|
1896
2100
|
itemId: payload.itemId,
|
|
1897
2101
|
toolCallId: payload.toolCallId,
|
|
@@ -1920,12 +2124,16 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1920
2124
|
let streamFallbackDelivered = false;
|
|
1921
2125
|
const finalStream = streamSession;
|
|
1922
2126
|
if (finalStream && !finalStream.stopped) try {
|
|
1923
|
-
const completionChunks = useNativeProgressStreaming && !nativeProgressCompletionSent && previewToolProgressLines.length > 0 ?
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
2127
|
+
const completionChunks = useNativeProgressStreaming && !nativeProgressCompletionSent && (previewToolProgressLines.length > 0 || Boolean(latestPlan?.length) || Boolean(latestPlanExplanation) || [...nativeTaskState.values()].some((task) => task.status !== "complete" && task.status !== "error")) ? reconcileSlackNativeTaskChunks({
|
|
2128
|
+
previousTasks: nativeTaskState,
|
|
2129
|
+
chunks: buildSlackProgressStreamCompletionChunks({
|
|
2130
|
+
title: resolveNativeProgressTitle(),
|
|
2131
|
+
lines: resolveNativeProgressLines(),
|
|
2132
|
+
plan: resolveNativeProgressPlan(),
|
|
2133
|
+
maxLineChars: progressDraftMaxLineChars,
|
|
2134
|
+
finalInProgressStatus: dispatchError ? "error" : "complete"
|
|
2135
|
+
})
|
|
2136
|
+
}).chunks : void 0;
|
|
1929
2137
|
if (completionChunks?.length) nativeProgressCompletionSent = true;
|
|
1930
2138
|
const stopResult = await stopSlackStream({
|
|
1931
2139
|
session: finalStream,
|
|
@@ -1981,7 +2189,10 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1981
2189
|
else statusReactions.restoreInitial();
|
|
1982
2190
|
} else await statusReactions.restoreInitial();
|
|
1983
2191
|
const participationThreadTs = usedReplyThreadTs ?? statusThreadTs;
|
|
1984
|
-
if (anyReplyDelivered && participationThreadTs) recordSlackThreadParticipation(account.accountId, message.channel, participationThreadTs, {
|
|
2192
|
+
if (anyReplyDelivered && participationThreadTs) recordSlackThreadParticipation(account.accountId, message.channel, participationThreadTs, {
|
|
2193
|
+
agentId: route.agentId,
|
|
2194
|
+
...prepared.eventScope ? { teamId: prepared.eventScope.teamId } : {}
|
|
2195
|
+
});
|
|
1985
2196
|
if (dispatchError) throw toLintErrorObject(dispatchError, "Slack dispatch failed");
|
|
1986
2197
|
if (!anyReplyDelivered && !draftPreviewCommitted) {
|
|
1987
2198
|
await draftStream?.clear();
|
|
@@ -1997,7 +2208,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1997
2208
|
ackReactionValue: prepared.ackReactionValue,
|
|
1998
2209
|
remove: () => removeSlackReaction(message.channel, prepared.ackReactionMessageTs ?? "", prepared.ackReactionValue, {
|
|
1999
2210
|
token: ctx.botToken,
|
|
2000
|
-
client:
|
|
2211
|
+
client: slackClient
|
|
2001
2212
|
}),
|
|
2002
2213
|
onError: (err) => {
|
|
2003
2214
|
logAckFailure({
|
|
@@ -2017,11 +2228,100 @@ function toLintErrorObject(value, fallbackMessage) {
|
|
|
2017
2228
|
return error;
|
|
2018
2229
|
}
|
|
2019
2230
|
//#endregion
|
|
2231
|
+
//#region extensions/slack/src/monitor/message-handler/preflight-audio.ts
|
|
2232
|
+
const SLACK_DEFAULT_ECHO_TRANSCRIPT_FORMAT = "📝 \"{transcript}\"";
|
|
2233
|
+
const loadSlackPreflightAudioRuntime = createLazyRuntimeModule(() => import("./preflight-audio.runtime-D5Bgags0.js"));
|
|
2234
|
+
function isSlackAudioFile(file) {
|
|
2235
|
+
if (file.subtype === "slack_audio") return true;
|
|
2236
|
+
if ((file.mimetype?.split(";")[0]?.trim().toLowerCase())?.startsWith("audio/")) return true;
|
|
2237
|
+
return Boolean(mimeTypeFromFilePath(file.name)?.startsWith("audio/"));
|
|
2238
|
+
}
|
|
2239
|
+
function findCaptionlessSlackAudioFile(message) {
|
|
2240
|
+
if (message.text?.trim()) return;
|
|
2241
|
+
return message.files?.slice(0, 8).find(isSlackAudioFile);
|
|
2242
|
+
}
|
|
2243
|
+
function formatSlackAudioTranscriptForAgent(params) {
|
|
2244
|
+
return [`[Audio transcript (machine-generated, untrusted)]: ${JSON.stringify(params.transcript)}`, params.rawBody].filter(Boolean).join("\n");
|
|
2245
|
+
}
|
|
2246
|
+
function suppressSlackPreflightAudioEcho(cfg) {
|
|
2247
|
+
const audio = cfg.tools?.media?.audio;
|
|
2248
|
+
if (!audio?.echoTranscript) return cfg;
|
|
2249
|
+
return {
|
|
2250
|
+
...cfg,
|
|
2251
|
+
tools: {
|
|
2252
|
+
...cfg.tools,
|
|
2253
|
+
media: {
|
|
2254
|
+
...cfg.tools?.media,
|
|
2255
|
+
audio: {
|
|
2256
|
+
...audio,
|
|
2257
|
+
echoTranscript: false
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
async function resolveSlackPreflightAudioTranscript(params) {
|
|
2264
|
+
const mediaIndex = params.media.findIndex((entry) => entry.contentType?.toLowerCase().startsWith("audio/"));
|
|
2265
|
+
if (mediaIndex < 0) return null;
|
|
2266
|
+
try {
|
|
2267
|
+
const { transcribeFirstAudio } = await loadSlackPreflightAudioRuntime();
|
|
2268
|
+
const transcript = await transcribeFirstAudio({
|
|
2269
|
+
ctx: {
|
|
2270
|
+
MediaPaths: params.media.map((entry) => entry.path),
|
|
2271
|
+
MediaTypes: params.media.map((entry) => entry.contentType ?? ""),
|
|
2272
|
+
Provider: "slack",
|
|
2273
|
+
Surface: "slack",
|
|
2274
|
+
OriginatingChannel: "slack",
|
|
2275
|
+
OriginatingTo: params.originatingTo,
|
|
2276
|
+
AccountId: params.accountId,
|
|
2277
|
+
MessageThreadId: params.messageThreadId,
|
|
2278
|
+
ChatType: "channel",
|
|
2279
|
+
SessionKey: params.sessionKey
|
|
2280
|
+
},
|
|
2281
|
+
cfg: suppressSlackPreflightAudioEcho(params.cfg)
|
|
2282
|
+
});
|
|
2283
|
+
return transcript ? {
|
|
2284
|
+
transcript,
|
|
2285
|
+
mediaIndex
|
|
2286
|
+
} : null;
|
|
2287
|
+
} catch (err) {
|
|
2288
|
+
logVerbose(`slack: audio preflight transcription failed: ${String(err)}`);
|
|
2289
|
+
return null;
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
function formatSlackAudioTranscriptEcho(transcript, format) {
|
|
2293
|
+
return format.replace("{transcript}", () => transcript);
|
|
2294
|
+
}
|
|
2295
|
+
async function sendSlackPreflightAudioTranscriptEcho(params) {
|
|
2296
|
+
const audio = params.cfg.tools?.media?.audio;
|
|
2297
|
+
if (!audio?.echoTranscript) return;
|
|
2298
|
+
const text = formatSlackAudioTranscriptEcho(params.transcript, audio.echoFormat ?? SLACK_DEFAULT_ECHO_TRANSCRIPT_FORMAT);
|
|
2299
|
+
try {
|
|
2300
|
+
const { sendDurableMessageBatch } = await loadSlackPreflightAudioRuntime();
|
|
2301
|
+
const send = await sendDurableMessageBatch({
|
|
2302
|
+
cfg: params.cfg,
|
|
2303
|
+
channel: "slack",
|
|
2304
|
+
to: params.originatingTo,
|
|
2305
|
+
accountId: params.accountId,
|
|
2306
|
+
threadId: params.messageThreadId,
|
|
2307
|
+
payloads: [{ text }],
|
|
2308
|
+
bestEffort: true,
|
|
2309
|
+
durability: "best_effort"
|
|
2310
|
+
});
|
|
2311
|
+
if (send.status === "failed") throw send.error;
|
|
2312
|
+
} catch (err) {
|
|
2313
|
+
logVerbose(`slack: audio transcript echo failed: ${String(err)}`);
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
async function discardSlackPreflightMedia(media) {
|
|
2317
|
+
await Promise.allSettled((media ?? []).map((entry) => fs.rm(entry.path, { force: true })));
|
|
2318
|
+
}
|
|
2319
|
+
//#endregion
|
|
2020
2320
|
//#region extensions/slack/src/monitor/message-handler/prepare-content.ts
|
|
2021
2321
|
const SLACK_MENTION_RESOLUTION_CONCURRENCY = 4;
|
|
2022
2322
|
const SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE = 20;
|
|
2023
2323
|
const SLACK_USER_MENTION_RE$1 = /<@([A-Z0-9]+)(?:\|[^>]+)?>/gi;
|
|
2024
|
-
const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-
|
|
2324
|
+
const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-BZiuSTIq.js").then((n) => n.g));
|
|
2025
2325
|
function collectUniqueSlackMentionIds$1(texts) {
|
|
2026
2326
|
const seen = /* @__PURE__ */ new Set();
|
|
2027
2327
|
const mentionIds = [];
|
|
@@ -2066,7 +2366,8 @@ async function resolveSlackMessageContent(params) {
|
|
|
2066
2366
|
maxBytes: params.mediaMaxBytes,
|
|
2067
2367
|
readIdleTimeoutMs: params.mediaReadIdleTimeoutMs,
|
|
2068
2368
|
totalTimeoutMs: params.mediaTotalTimeoutMs,
|
|
2069
|
-
abortSignal: params.abortSignal
|
|
2369
|
+
abortSignal: params.abortSignal,
|
|
2370
|
+
preloadedMedia: params.preloadedMedia
|
|
2070
2371
|
})) : Promise.resolve(null);
|
|
2071
2372
|
const attachmentContentPromise = params.message.attachments && params.message.attachments.length > 0 ? loadSlackMediaModule$1().then(({ resolveSlackAttachmentContent }) => resolveSlackAttachmentContent({
|
|
2072
2373
|
attachments: params.message.attachments,
|
|
@@ -2150,7 +2451,7 @@ async function resolveSlackDmHistoryContext(params) {
|
|
|
2150
2451
|
inboundHistory: void 0
|
|
2151
2452
|
};
|
|
2152
2453
|
try {
|
|
2153
|
-
const messages = ((await params.ctx.app.client.conversations.history({
|
|
2454
|
+
const messages = ((await (params.eventScope?.client ?? params.ctx.app.client).conversations.history({
|
|
2154
2455
|
token: params.ctx.botToken,
|
|
2155
2456
|
channel: params.channelId,
|
|
2156
2457
|
...params.currentMessageTs ? {
|
|
@@ -2170,7 +2471,7 @@ async function resolveSlackDmHistoryContext(params) {
|
|
|
2170
2471
|
const resolveUserLabel = async (userId) => {
|
|
2171
2472
|
const cached = userNames.get(userId);
|
|
2172
2473
|
if (cached) return cached;
|
|
2173
|
-
const label = normalizeOptionalString((await params.ctx.resolveUserName(userId)).name) ?? userId;
|
|
2474
|
+
const label = normalizeOptionalString((await params.ctx.resolveUserName(userId, params.eventScope)).name) ?? userId;
|
|
2174
2475
|
userNames.set(userId, label);
|
|
2175
2476
|
return label;
|
|
2176
2477
|
};
|
|
@@ -2267,28 +2568,46 @@ function normalizeSlackRouteBindingConfig(cfg) {
|
|
|
2267
2568
|
return normalizedCfg;
|
|
2268
2569
|
}
|
|
2269
2570
|
function resolveSlackBaseConversationId(params) {
|
|
2270
|
-
|
|
2571
|
+
const raw = params.isDirectMessage ? `user:${params.message.user ?? "unknown"}` : params.message.channel;
|
|
2572
|
+
return params.eventScope ? `team:${encodeURIComponent(params.eventScope.teamId)}:${raw}` : raw;
|
|
2573
|
+
}
|
|
2574
|
+
function qualifySlackPeerId(params) {
|
|
2575
|
+
if (!params.eventScope) return params.id;
|
|
2576
|
+
return `team:${encodeURIComponent(params.eventScope.teamId)}:${params.kind}:${encodeURIComponent(params.id)}`;
|
|
2271
2577
|
}
|
|
2272
2578
|
function resolveSlackInitialAgentRoute(params) {
|
|
2273
|
-
|
|
2579
|
+
const route = resolveAgentRoute({
|
|
2274
2580
|
cfg: normalizeSlackRouteBindingConfig(params.ctx.cfg),
|
|
2275
2581
|
channel: "slack",
|
|
2276
2582
|
accountId: params.account.accountId,
|
|
2277
|
-
teamId: params.ctx.teamId || void 0,
|
|
2583
|
+
teamId: params.eventScope?.teamId || params.ctx.teamId || void 0,
|
|
2278
2584
|
peer: {
|
|
2279
2585
|
kind: params.isDirectMessage ? "direct" : params.isRoom ? "channel" : "group",
|
|
2280
|
-
id:
|
|
2586
|
+
id: qualifySlackPeerId({
|
|
2587
|
+
id: params.isDirectMessage ? params.message.user ?? "unknown" : params.message.channel,
|
|
2588
|
+
kind: params.isDirectMessage ? "user" : "channel",
|
|
2589
|
+
eventScope: params.eventScope
|
|
2590
|
+
})
|
|
2281
2591
|
}
|
|
2282
2592
|
});
|
|
2593
|
+
if (!params.eventScope || !params.isDirectMessage || route.dmScope !== "main") return route;
|
|
2594
|
+
const partition = `account:${encodeURIComponent(params.account.accountId).toLowerCase()}:team:${encodeURIComponent(params.eventScope.teamId).toLowerCase()}`;
|
|
2595
|
+
const sessionKey = `${route.sessionKey}:${partition}`;
|
|
2596
|
+
return {
|
|
2597
|
+
...route,
|
|
2598
|
+
sessionKey,
|
|
2599
|
+
mainSessionKey: sessionKey
|
|
2600
|
+
};
|
|
2283
2601
|
}
|
|
2284
2602
|
function resolveSlackRoutingContext(params) {
|
|
2285
|
-
const { ctx, account, message, isDirectMessage, isGroupDm, isRoom, isRoomish, channelConfig, seedTopLevelRoomThread, assistantThreadTs } = params;
|
|
2603
|
+
const { ctx, account, message, isDirectMessage, isGroupDm, isRoom, isRoomish, channelConfig, seedTopLevelRoomThread, assistantThreadTs, eventScope } = params;
|
|
2286
2604
|
let route = resolveSlackInitialAgentRoute({
|
|
2287
2605
|
ctx,
|
|
2288
2606
|
account,
|
|
2289
2607
|
message,
|
|
2290
2608
|
isDirectMessage,
|
|
2291
|
-
isRoom
|
|
2609
|
+
isRoom,
|
|
2610
|
+
eventScope
|
|
2292
2611
|
});
|
|
2293
2612
|
const chatType = isDirectMessage ? "direct" : isGroupDm ? "group" : "channel";
|
|
2294
2613
|
const replyToMode = channelConfig?.replyToMode ?? resolveSlackReplyToMode(account, chatType);
|
|
@@ -2304,10 +2623,11 @@ function resolveSlackRoutingContext(params) {
|
|
|
2304
2623
|
const routedThreadId = (isDirectMessage ? assistantThreadTs : isRoomish ? isThreadReply && threadTs ? threadTs : void 0 : isThreadReply ? threadTs : autoThreadId) ?? (isRoomish ? !isThreadReply && isRoom && seedTopLevelRoomThread && replyToMode !== "off" && seedCandidateThreadId ? seedCandidateThreadId : void 0 : void 0);
|
|
2305
2624
|
const baseConversationId = resolveSlackBaseConversationId({
|
|
2306
2625
|
message,
|
|
2307
|
-
isDirectMessage
|
|
2626
|
+
isDirectMessage,
|
|
2627
|
+
eventScope
|
|
2308
2628
|
});
|
|
2309
2629
|
const runtimeBindingThreadId = routedThreadId ?? (isDirectMessage && isThreadReply ? threadTs : void 0);
|
|
2310
|
-
const boundThreadRoute = runtimeBindingThreadId ? resolveRuntimeConversationBindingRoute({
|
|
2630
|
+
const boundThreadRoute = !eventScope && runtimeBindingThreadId ? resolveRuntimeConversationBindingRoute({
|
|
2311
2631
|
route,
|
|
2312
2632
|
conversation: {
|
|
2313
2633
|
channel: "slack",
|
|
@@ -2316,7 +2636,11 @@ function resolveSlackRoutingContext(params) {
|
|
|
2316
2636
|
parentConversationId: baseConversationId
|
|
2317
2637
|
}
|
|
2318
2638
|
}) : null;
|
|
2319
|
-
const runtimeRoute =
|
|
2639
|
+
const runtimeRoute = eventScope ? {
|
|
2640
|
+
route,
|
|
2641
|
+
bindingRecord: null,
|
|
2642
|
+
boundSessionKey: void 0
|
|
2643
|
+
} : boundThreadRoute?.boundSessionKey || boundThreadRoute?.bindingRecord ? boundThreadRoute : resolveRuntimeConversationBindingRoute({
|
|
2320
2644
|
route,
|
|
2321
2645
|
conversation: {
|
|
2322
2646
|
channel: "slack",
|
|
@@ -2327,7 +2651,7 @@ function resolveSlackRoutingContext(params) {
|
|
|
2327
2651
|
let configuredBinding = null;
|
|
2328
2652
|
let configuredBindingSessionKey = "";
|
|
2329
2653
|
if (runtimeRoute.boundSessionKey || runtimeRoute.bindingRecord) route = runtimeRoute.route;
|
|
2330
|
-
else {
|
|
2654
|
+
else if (!eventScope) {
|
|
2331
2655
|
const configuredRoute = resolveConfiguredBindingRoute({
|
|
2332
2656
|
cfg: ctx.cfg,
|
|
2333
2657
|
route,
|
|
@@ -2350,7 +2674,7 @@ function resolveSlackRoutingContext(params) {
|
|
|
2350
2674
|
parentSessionKey: routedThreadId && ctx.threadInheritParent ? route.sessionKey : void 0
|
|
2351
2675
|
});
|
|
2352
2676
|
const sessionKey = threadKeys.sessionKey;
|
|
2353
|
-
const historyKey = isThreadReply && ctx.threadHistoryScope === "thread" ? sessionKey : message.channel;
|
|
2677
|
+
const historyKey = isThreadReply && ctx.threadHistoryScope === "thread" ? sessionKey : eventScope ? `${account.accountId}:${eventScope.teamId}:${message.channel}` : message.channel;
|
|
2354
2678
|
return {
|
|
2355
2679
|
route,
|
|
2356
2680
|
runtimeBinding: runtimeRoute.bindingRecord,
|
|
@@ -2419,7 +2743,7 @@ function formatSlackThreadLabelSnippet(text) {
|
|
|
2419
2743
|
}
|
|
2420
2744
|
//#endregion
|
|
2421
2745
|
//#region extensions/slack/src/monitor/message-handler/prepare-thread-context.ts
|
|
2422
|
-
const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-
|
|
2746
|
+
const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-BZiuSTIq.js").then((n) => n.g));
|
|
2423
2747
|
const SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY = 4;
|
|
2424
2748
|
function resolveSlackThreadSessionFreshness(params) {
|
|
2425
2749
|
return params.ctx.channelRuntime?.session?.resolveEntryResetFreshness?.({
|
|
@@ -2455,7 +2779,7 @@ async function resolveSlackThreadUserMap(params) {
|
|
|
2455
2779
|
if (uniqueUserIds.length === 0) return userMap;
|
|
2456
2780
|
const { results } = await runTasksWithConcurrency({
|
|
2457
2781
|
tasks: uniqueUserIds.map((id) => async () => {
|
|
2458
|
-
const user = await params.ctx.resolveUserName(id);
|
|
2782
|
+
const user = await params.ctx.resolveUserName(id, params.eventScope);
|
|
2459
2783
|
return user ? {
|
|
2460
2784
|
id,
|
|
2461
2785
|
user
|
|
@@ -2498,7 +2822,7 @@ async function resolveSlackThreadContextData(params) {
|
|
|
2498
2822
|
threadStarterMedia
|
|
2499
2823
|
};
|
|
2500
2824
|
const starter = params.threadStarter;
|
|
2501
|
-
const starterSenderName = params.allowNameMatching && params.allowFromLower.length > 0 && starter?.userId ? (await params.ctx.resolveUserName(starter.userId))?.name : void 0;
|
|
2825
|
+
const starterSenderName = params.allowNameMatching && params.allowFromLower.length > 0 && starter?.userId ? (await params.ctx.resolveUserName(starter.userId, params.eventScope))?.name : void 0;
|
|
2502
2826
|
const starterIsCurrentBot = Boolean(starter && isCurrentBotAuthor({
|
|
2503
2827
|
userId: starter.userId,
|
|
2504
2828
|
botId: starter.botId
|
|
@@ -2523,7 +2847,7 @@ async function resolveSlackThreadContextData(params) {
|
|
|
2523
2847
|
const { resolveSlackMedia } = await loadSlackMediaModule();
|
|
2524
2848
|
threadStarterMedia = await resolveSlackMedia({
|
|
2525
2849
|
files: starter.files,
|
|
2526
|
-
client: params.ctx.app.client,
|
|
2850
|
+
client: params.eventScope?.client ?? params.ctx.app.client,
|
|
2527
2851
|
token: params.ctx.botToken,
|
|
2528
2852
|
maxBytes: params.ctx.mediaMaxBytes
|
|
2529
2853
|
});
|
|
@@ -2551,7 +2875,7 @@ async function resolveSlackThreadContextData(params) {
|
|
|
2551
2875
|
history: await resolveSlackThreadHistory({
|
|
2552
2876
|
channelId: params.message.channel,
|
|
2553
2877
|
threadTs: params.threadTs,
|
|
2554
|
-
client: params.ctx.app.client,
|
|
2878
|
+
client: params.eventScope?.client ?? params.ctx.app.client,
|
|
2555
2879
|
currentMessageTs: params.message.ts,
|
|
2556
2880
|
limit: threadInitialHistoryLimit
|
|
2557
2881
|
}),
|
|
@@ -2572,7 +2896,8 @@ async function resolveSlackThreadContextData(params) {
|
|
|
2572
2896
|
});
|
|
2573
2897
|
const userMapForFilter = params.contextVisibilityMode !== "all" && params.allowNameMatching && params.allowFromLower.length > 0 ? await resolveSlackThreadUserMap({
|
|
2574
2898
|
ctx: params.ctx,
|
|
2575
|
-
messages: threadHistoryWithoutCurrentBot
|
|
2899
|
+
messages: threadHistoryWithoutCurrentBot,
|
|
2900
|
+
eventScope: params.eventScope
|
|
2576
2901
|
}) : /* @__PURE__ */ new Map();
|
|
2577
2902
|
const { items: filteredThreadHistory, omitted: omittedHistoryCount } = params.contextVisibilityMode === "all" ? {
|
|
2578
2903
|
items: threadHistoryWithoutCurrentBot,
|
|
@@ -2639,7 +2964,7 @@ async function resolveSlackThreadContextData(params) {
|
|
|
2639
2964
|
//#region extensions/slack/src/monitor/message-handler/subteam-mentions.ts
|
|
2640
2965
|
const SUBTEAM_MENTION_RE = /<!subteam\^([A-Z0-9]+)(?:\|[^>]*)?>/gi;
|
|
2641
2966
|
const SUBTEAM_MEMBER_CACHE_TTL_MS = 300 * 1e3;
|
|
2642
|
-
|
|
2967
|
+
const subteamMemberCache = /* @__PURE__ */ new WeakMap();
|
|
2643
2968
|
function normalizeSlackId(value) {
|
|
2644
2969
|
return typeof value === "string" && value.trim() ? value.trim().toUpperCase() : void 0;
|
|
2645
2970
|
}
|
|
@@ -2756,7 +3081,7 @@ async function restoreSlackAssistantThreadContextFromMetadata(params) {
|
|
|
2756
3081
|
const parentUserId = params.message.parent_user_id?.trim();
|
|
2757
3082
|
if (!params.message.channel || !threadTs || !parentUserId || parentUserId !== params.ctx.botUserId && parentUserId !== params.ctx.botId) return;
|
|
2758
3083
|
try {
|
|
2759
|
-
const response = await params.ctx.app.client.conversations.replies({
|
|
3084
|
+
const response = await (params.eventScope?.client ?? params.ctx.app.client).conversations.replies({
|
|
2760
3085
|
channel: params.message.channel,
|
|
2761
3086
|
ts: threadTs,
|
|
2762
3087
|
oldest: threadTs,
|
|
@@ -2841,7 +3166,7 @@ async function resolveSlackHistoryMediaForPendingRecord(params) {
|
|
|
2841
3166
|
isThreadReply: params.isThreadReply,
|
|
2842
3167
|
threadStarter: params.threadStarter,
|
|
2843
3168
|
isBotMessage: params.isBotMessage,
|
|
2844
|
-
client: params.ctx.app.client,
|
|
3169
|
+
client: params.eventScope?.client ?? params.ctx.app.client,
|
|
2845
3170
|
botToken: params.ctx.botToken,
|
|
2846
3171
|
mediaMaxBytes: Math.min(params.ctx.mediaMaxBytes, SLACK_HISTORY_MEDIA_MAX_BYTES),
|
|
2847
3172
|
mediaReadIdleTimeoutMs: SLACK_HISTORY_MEDIA_IDLE_TIMEOUT_MS,
|
|
@@ -2872,10 +3197,10 @@ async function resolveSlackExplicitMentionState(params) {
|
|
|
2872
3197
|
const normalizedBotUserId = normalizeSlackId(params.ctx.botUserId);
|
|
2873
3198
|
const explicitlyMentionedBotUser = Boolean(normalizedBotUserId && params.mentionedUserIds.includes(normalizedBotUserId));
|
|
2874
3199
|
const explicitlyMentionedBotSubteam = Boolean(params.ctx.botUserId && params.hasSubteamMention) && await isSlackSubteamMentionForBot({
|
|
2875
|
-
client: params.ctx.app.client,
|
|
3200
|
+
client: params.eventScope?.client ?? params.ctx.app.client,
|
|
2876
3201
|
text: params.messageText,
|
|
2877
3202
|
botUserId: params.ctx.botUserId,
|
|
2878
|
-
teamId: params.ctx.teamId,
|
|
3203
|
+
teamId: params.eventScope?.teamId ?? params.ctx.teamId,
|
|
2879
3204
|
log: logVerbose
|
|
2880
3205
|
});
|
|
2881
3206
|
return {
|
|
@@ -2909,8 +3234,8 @@ async function resolveSlackConversationContext(params) {
|
|
|
2909
3234
|
let channelInfo = {};
|
|
2910
3235
|
let resolvedChannelType = normalizeSlackChannelType(message.channel_type, message.channel);
|
|
2911
3236
|
if (resolvedChannelType !== "im" && (!message.channel_type || message.channel_type !== "im")) {
|
|
2912
|
-
channelInfo = await ctx.resolveChannelName(message.channel);
|
|
2913
|
-
resolvedChannelType = normalizeSlackChannelType(message.channel_type ?? channelInfo.type, message.channel);
|
|
3237
|
+
channelInfo = await ctx.resolveChannelName(message.channel, params.eventScope);
|
|
3238
|
+
resolvedChannelType = normalizeSlackChannelType(message.channel_type ?? channelInfo.type ?? ctx.recallSlackChannelType(message.channel, params.eventScope), message.channel);
|
|
2914
3239
|
}
|
|
2915
3240
|
const channelName = channelInfo?.name;
|
|
2916
3241
|
const isDirectMessage = resolvedChannelType === "im";
|
|
@@ -2978,12 +3303,12 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
2978
3303
|
accountId: account.accountId,
|
|
2979
3304
|
senderId: directUserId,
|
|
2980
3305
|
allowFromLower,
|
|
2981
|
-
resolveSenderName: ctx.resolveUserName,
|
|
3306
|
+
resolveSenderName: (userId) => ctx.resolveUserName(userId, params.eventScope),
|
|
2982
3307
|
sendPairingReply: async (text) => {
|
|
2983
3308
|
await sendMessageSlack(message.channel, text, {
|
|
2984
3309
|
cfg: ctx.cfg,
|
|
2985
3310
|
token: ctx.botToken,
|
|
2986
|
-
client: ctx.app.client,
|
|
3311
|
+
client: params.eventScope?.client ?? ctx.app.client,
|
|
2987
3312
|
accountId: account.accountId
|
|
2988
3313
|
});
|
|
2989
3314
|
},
|
|
@@ -3003,18 +3328,25 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
3003
3328
|
}
|
|
3004
3329
|
async function prepareSlackMessage(params) {
|
|
3005
3330
|
const { ctx, account, message, opts } = params;
|
|
3331
|
+
const slackClient = opts.eventScope?.client ?? ctx.app.client;
|
|
3332
|
+
const threadStarterWorkspaceScope = opts.eventScope ? {
|
|
3333
|
+
accountId: account.accountId,
|
|
3334
|
+
teamId: opts.eventScope.teamId
|
|
3335
|
+
} : void 0;
|
|
3006
3336
|
const cfg = ctx.cfg;
|
|
3007
3337
|
const conversation = await resolveSlackConversationContext({
|
|
3008
3338
|
ctx,
|
|
3009
3339
|
account,
|
|
3010
|
-
message
|
|
3340
|
+
message,
|
|
3341
|
+
eventScope: opts.eventScope
|
|
3011
3342
|
});
|
|
3012
3343
|
const { channelInfo, channelName, isDirectMessage, isGroupDm, isRoom, isRoomish, channelConfig, allowBotsMode, isBotMessage } = conversation;
|
|
3013
3344
|
const authorization = await authorizeSlackInboundMessage({
|
|
3014
3345
|
ctx,
|
|
3015
3346
|
account,
|
|
3016
3347
|
message,
|
|
3017
|
-
conversation
|
|
3348
|
+
conversation,
|
|
3349
|
+
eventScope: opts.eventScope
|
|
3018
3350
|
});
|
|
3019
3351
|
if (!authorization) return null;
|
|
3020
3352
|
const { senderId, allowFromLower } = authorization;
|
|
@@ -3023,7 +3355,7 @@ async function prepareSlackMessage(params) {
|
|
|
3023
3355
|
const resolveSenderName = async () => {
|
|
3024
3356
|
if (resolvedSenderName) return resolvedSenderName;
|
|
3025
3357
|
if (message.user) {
|
|
3026
|
-
const normalized = normalizeOptionalString((await ctx.resolveUserName(message.user))?.name);
|
|
3358
|
+
const normalized = normalizeOptionalString((await ctx.resolveUserName(message.user, opts.eventScope))?.name);
|
|
3027
3359
|
if (normalized) {
|
|
3028
3360
|
resolvedSenderName = normalized;
|
|
3029
3361
|
return resolvedSenderName;
|
|
@@ -3037,24 +3369,26 @@ async function prepareSlackMessage(params) {
|
|
|
3037
3369
|
const messageAssistantThreadContext = resolveSlackMessageAssistantThreadContext(message);
|
|
3038
3370
|
const assistantContextLookupChannelId = messageAssistantThreadContext?.assistantChannelId ?? message.channel;
|
|
3039
3371
|
const assistantContextLookupThreadTs = messageAssistantThreadContext?.threadTs ?? message.thread_ts ?? message.ts;
|
|
3040
|
-
const cachedAssistantThreadContext = isDirectMessage ? ctx.getSlackAssistantThreadContext(assistantContextLookupChannelId, assistantContextLookupThreadTs) : void 0;
|
|
3372
|
+
const cachedAssistantThreadContext = isDirectMessage ? ctx.getSlackAssistantThreadContext(assistantContextLookupChannelId, assistantContextLookupThreadTs, opts.eventScope) : void 0;
|
|
3041
3373
|
const restoredAssistantThreadContextPromise = isDirectMessage && !cachedAssistantThreadContext && !hasSlackAssistantThreadMetadata(messageAssistantThreadContext) ? restoreSlackAssistantThreadContextFromMetadata({
|
|
3042
3374
|
ctx,
|
|
3043
|
-
message
|
|
3375
|
+
message,
|
|
3376
|
+
eventScope: opts.eventScope
|
|
3044
3377
|
}) : Promise.resolve(void 0);
|
|
3045
3378
|
const { explicitlyMentionedBotUser, explicitlyMentionedBotSubteam, explicitlyMentioned } = await resolveSlackExplicitMentionState({
|
|
3046
3379
|
ctx,
|
|
3047
3380
|
messageText,
|
|
3048
3381
|
mentionedUserIds,
|
|
3049
3382
|
hasSubteamMention: mentionMetadata.hasSubteamMention,
|
|
3050
|
-
source: opts.source
|
|
3383
|
+
source: opts.source,
|
|
3384
|
+
eventScope: opts.eventScope
|
|
3051
3385
|
});
|
|
3052
3386
|
const channelRequireMention = channelConfig?.requireMention ?? ctx.defaultRequireMention ?? true;
|
|
3053
3387
|
const channelChatType = isDirectMessage ? "direct" : isGroupDm ? "group" : "channel";
|
|
3054
3388
|
const restoredAssistantThreadContext = await restoredAssistantThreadContextPromise;
|
|
3055
3389
|
const assistantThreadContext = mergeSlackAssistantThreadContext(messageAssistantThreadContext, cachedAssistantThreadContext ?? restoredAssistantThreadContext);
|
|
3056
3390
|
const assistantThreadContextToCache = messageAssistantThreadContext || restoredAssistantThreadContext ? assistantThreadContext : void 0;
|
|
3057
|
-
if (assistantThreadContextToCache) ctx.saveSlackAssistantThreadContext(assistantThreadContextToCache);
|
|
3391
|
+
if (assistantThreadContextToCache) ctx.saveSlackAssistantThreadContext(assistantThreadContextToCache, opts.eventScope);
|
|
3058
3392
|
const channelReplyToMode = channelConfig?.replyToMode ?? resolveSlackReplyToMode(account, channelChatType);
|
|
3059
3393
|
const willImplicitlyThreadReply = isRoom && !channelRequireMention && channelReplyToMode !== "off";
|
|
3060
3394
|
const seedTopLevelRoomThreadBySource = opts.source === "app_mention" || opts.wasMentioned === true || explicitlyMentioned || willImplicitlyThreadReply;
|
|
@@ -3068,8 +3402,10 @@ async function prepareSlackMessage(params) {
|
|
|
3068
3402
|
isRoomish,
|
|
3069
3403
|
channelConfig,
|
|
3070
3404
|
seedTopLevelRoomThread: seedTopLevelRoomThreadBySource,
|
|
3071
|
-
assistantThreadTs: assistantThreadContext?.threadTs
|
|
3405
|
+
assistantThreadTs: assistantThreadContext?.threadTs,
|
|
3406
|
+
eventScope: opts.eventScope
|
|
3072
3407
|
});
|
|
3408
|
+
let mentionCheckTranscript;
|
|
3073
3409
|
const resolveWasMentioned = (mentionRegexes) => opts.wasMentioned ?? (!isDirectMessage && matchesMentionWithExplicit({
|
|
3074
3410
|
text: messageText,
|
|
3075
3411
|
mentionRegexes,
|
|
@@ -3077,7 +3413,8 @@ async function prepareSlackMessage(params) {
|
|
|
3077
3413
|
hasAnyMention,
|
|
3078
3414
|
isExplicitlyMentioned: explicitlyMentioned,
|
|
3079
3415
|
canResolveExplicit: Boolean(ctx.botUserId)
|
|
3080
|
-
}
|
|
3416
|
+
},
|
|
3417
|
+
transcript: mentionCheckTranscript
|
|
3081
3418
|
}));
|
|
3082
3419
|
const buildPolicyMentionRegexes = (agentId) => resolveCachedMentionRegexes(ctx, agentId, {
|
|
3083
3420
|
provider: "slack",
|
|
@@ -3087,23 +3424,8 @@ async function prepareSlackMessage(params) {
|
|
|
3087
3424
|
let mentionRegexes = buildPolicyMentionRegexes(routing.route.agentId);
|
|
3088
3425
|
let wasMentioned = resolveWasMentioned(mentionRegexes);
|
|
3089
3426
|
const hasBoundSession = Boolean(routing.runtimeBoundSessionKey || routing.configuredBindingSessionKey);
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
ctx,
|
|
3093
|
-
account,
|
|
3094
|
-
message,
|
|
3095
|
-
isDirectMessage,
|
|
3096
|
-
isGroupDm,
|
|
3097
|
-
isRoom,
|
|
3098
|
-
isRoomish,
|
|
3099
|
-
channelConfig,
|
|
3100
|
-
seedTopLevelRoomThread: true,
|
|
3101
|
-
assistantThreadTs: assistantThreadContext?.threadTs
|
|
3102
|
-
});
|
|
3103
|
-
mentionRegexes = buildPolicyMentionRegexes(routing.route.agentId);
|
|
3104
|
-
wasMentioned = resolveWasMentioned(mentionRegexes);
|
|
3105
|
-
}
|
|
3106
|
-
const { route, runtimeBinding, configuredBinding, configuredBindingSessionKey, replyToMode, threadContext, threadTs, isThreadReply, threadKeys, sessionKey, historyKey } = routing;
|
|
3427
|
+
let { route, runtimeBinding, replyToMode, threadContext, threadTs, isThreadReply, threadKeys, sessionKey, historyKey } = routing;
|
|
3428
|
+
const { configuredBinding, configuredBindingSessionKey } = routing;
|
|
3107
3429
|
const isAssistantThreadMessage = Boolean(isDirectMessage && messageAssistantThreadContext);
|
|
3108
3430
|
const forcedAssistantReplyThreadTs = Boolean(assistantThreadContext?.threadTs && (isThreadReply || isAssistantThreadMessage || replyToMode !== "off")) ? assistantThreadContext?.threadTs : void 0;
|
|
3109
3431
|
if (runtimeBinding && shouldLogVerbose()) logVerbose(`slack: routed via bound conversation ${runtimeBinding.conversation.conversationId} -> ${runtimeBinding.targetSessionKey}`);
|
|
@@ -3135,14 +3457,14 @@ async function prepareSlackMessage(params) {
|
|
|
3135
3457
|
ok: true,
|
|
3136
3458
|
name: void 0
|
|
3137
3459
|
});
|
|
3138
|
-
const directThreadRoutedToDmSession = !assistantThreadContext && isDirectMessage && isThreadReply && threadTs && runtimeBinding?.conversation.conversationId !== threadTs;
|
|
3139
3460
|
let implicitMentionKinds = [];
|
|
3140
3461
|
if (!isDirectMessage && ctx.botUserId && message.thread_ts && !ctx.threadRequireExplicitMention && !wasMentioned) {
|
|
3141
3462
|
const replyToBotKinds = implicitMentionKindWhen("reply_to_bot", message.parent_user_id === ctx.botUserId);
|
|
3142
3463
|
implicitMentionKinds = replyToBotKinds.length > 0 ? replyToBotKinds : implicitMentionKindWhen("bot_thread_participant", await hasSlackThreadParticipationWithPersistence({
|
|
3143
3464
|
accountId: account.accountId,
|
|
3144
3465
|
channelId: message.channel,
|
|
3145
|
-
threadTs: message.thread_ts
|
|
3466
|
+
threadTs: message.thread_ts,
|
|
3467
|
+
...opts.eventScope ? { teamId: opts.eventScope.teamId } : {}
|
|
3146
3468
|
}));
|
|
3147
3469
|
}
|
|
3148
3470
|
const recordDroppedHistory = async (reason) => {
|
|
@@ -3153,7 +3475,8 @@ async function prepareSlackMessage(params) {
|
|
|
3153
3475
|
const skippedThreadStarter = historyMediaCandidate && isThreadReply && threadTs ? await resolveSlackThreadStarter({
|
|
3154
3476
|
channelId: message.channel,
|
|
3155
3477
|
threadTs,
|
|
3156
|
-
client:
|
|
3478
|
+
client: slackClient,
|
|
3479
|
+
workspaceScope: threadStarterWorkspaceScope
|
|
3157
3480
|
}) : null;
|
|
3158
3481
|
const senderName = pendingBody ? await resolveSenderName() : void 0;
|
|
3159
3482
|
await recordDroppedChannelInboundHistory({
|
|
@@ -3190,11 +3513,39 @@ async function prepareSlackMessage(params) {
|
|
|
3190
3513
|
message,
|
|
3191
3514
|
isThreadReply,
|
|
3192
3515
|
threadStarter: skippedThreadStarter,
|
|
3193
|
-
isBotMessage
|
|
3516
|
+
isBotMessage,
|
|
3517
|
+
eventScope: opts.eventScope
|
|
3194
3518
|
})
|
|
3195
3519
|
}
|
|
3196
3520
|
});
|
|
3197
3521
|
};
|
|
3522
|
+
let threadStarterPromise;
|
|
3523
|
+
const getThreadStarter = () => {
|
|
3524
|
+
threadStarterPromise ??= isThreadReply && threadTs ? resolveSlackThreadStarter({
|
|
3525
|
+
channelId: message.channel,
|
|
3526
|
+
threadTs,
|
|
3527
|
+
client: slackClient,
|
|
3528
|
+
workspaceScope: threadStarterWorkspaceScope
|
|
3529
|
+
}) : Promise.resolve(null);
|
|
3530
|
+
return threadStarterPromise;
|
|
3531
|
+
};
|
|
3532
|
+
const resolveMessageContent = (contentMessage, preloadedMedia) => getThreadStarter().then((threadStarter) => resolveSlackMessageContent({
|
|
3533
|
+
message: contentMessage,
|
|
3534
|
+
isThreadReply,
|
|
3535
|
+
threadStarter,
|
|
3536
|
+
isBotMessage,
|
|
3537
|
+
botToken: ctx.botToken,
|
|
3538
|
+
client: slackClient,
|
|
3539
|
+
mediaMaxBytes: ctx.mediaMaxBytes,
|
|
3540
|
+
resolveUserName: (userId) => ctx.resolveUserName(userId, opts.eventScope),
|
|
3541
|
+
preloadedMedia
|
|
3542
|
+
}));
|
|
3543
|
+
let preloadedDirectMedia;
|
|
3544
|
+
let messageContentPromise;
|
|
3545
|
+
const getMessageContent = () => {
|
|
3546
|
+
messageContentPromise ??= resolveMessageContent(message, preloadedDirectMedia);
|
|
3547
|
+
return messageContentPromise;
|
|
3548
|
+
};
|
|
3198
3549
|
const senderNameForAuthResult = await senderNameForAuthPromise;
|
|
3199
3550
|
if (!senderNameForAuthResult.ok) throw senderNameForAuthResult.error;
|
|
3200
3551
|
const senderNameForAuth = senderNameForAuthResult.name;
|
|
@@ -3211,7 +3562,7 @@ async function prepareSlackMessage(params) {
|
|
|
3211
3562
|
}, "skipping ambiguous slack thread reply");
|
|
3212
3563
|
return null;
|
|
3213
3564
|
}
|
|
3214
|
-
|
|
3565
|
+
let canDetectMention = Boolean(ctx.botUserId) || mentionRegexes.length > 0;
|
|
3215
3566
|
const textForCommandDetection = stripSlackMentionsForCommandDetection(message.text ?? "");
|
|
3216
3567
|
const hasControlCommandInMessage = hasControlCommand(textForCommandDetection, cfg);
|
|
3217
3568
|
const hasAbortRequest = isAbortRequestText(textForCommandDetection);
|
|
@@ -3238,19 +3589,9 @@ async function prepareSlackMessage(params) {
|
|
|
3238
3589
|
...ctx.threadRequireExplicitMention ? { allowedImplicitMentionKinds: [] } : {}
|
|
3239
3590
|
}
|
|
3240
3591
|
});
|
|
3241
|
-
const effectiveWasMentioned = messageIngress.activationAccess.effectiveWasMentioned ?? false;
|
|
3242
|
-
const shouldBypassMention = messageIngress.activationAccess.shouldBypassMention ?? false;
|
|
3243
|
-
const matchedImplicitMentionKinds = implicitMentionKinds;
|
|
3244
|
-
const mentionSource = resolveSlackMentionSource({
|
|
3245
|
-
explicitBotMention: explicitlyMentionedBotUser || opts.source === "app_mention",
|
|
3246
|
-
explicitSubteamMention: explicitlyMentionedBotSubteam,
|
|
3247
|
-
matchedImplicitMentionKinds,
|
|
3248
|
-
shouldBypassMention,
|
|
3249
|
-
wasMentioned
|
|
3250
|
-
});
|
|
3251
3592
|
const senderGate = messageIngress.senderAccess.gate;
|
|
3252
|
-
if (
|
|
3253
|
-
logVerbose(`Blocked unauthorized slack sender ${senderId} (not in
|
|
3593
|
+
if (isRoomish && senderGate?.allowed === false) {
|
|
3594
|
+
logVerbose(`Blocked unauthorized slack sender ${senderId} (not in sender allowlist)`);
|
|
3254
3595
|
return null;
|
|
3255
3596
|
}
|
|
3256
3597
|
if (isRoom && isBotMessage && allowBotsMode !== "off" && !await authorizeSlackBotRoomMessage({
|
|
@@ -3259,14 +3600,9 @@ async function prepareSlackMessage(params) {
|
|
|
3259
3600
|
senderId,
|
|
3260
3601
|
senderName: senderNameForAuth,
|
|
3261
3602
|
channelUsers: channelConfig?.users,
|
|
3262
|
-
allowFromLower
|
|
3603
|
+
allowFromLower,
|
|
3604
|
+
eventScope: opts.eventScope
|
|
3263
3605
|
})) return null;
|
|
3264
|
-
if (isBotMessage && allowBotsMode === "mentions") {
|
|
3265
|
-
if (!(isDirectMessage || effectiveWasMentioned || shouldBypassMention)) {
|
|
3266
|
-
logVerbose("slack: drop bot message (allowBots=mentions, missing mention)");
|
|
3267
|
-
return null;
|
|
3268
|
-
}
|
|
3269
|
-
}
|
|
3270
3606
|
const threadContextAllowFromLower = isRoom ? channelUsersAllowlistConfigured ? normalizeAllowListLower(channelConfig?.users) : [] : isDirectMessage ? allowFromLower : [];
|
|
3271
3607
|
const contextVisibilityMode = resolveChannelContextVisibilityMode({
|
|
3272
3608
|
cfg: ctx.cfg,
|
|
@@ -3283,6 +3619,104 @@ async function prepareSlackMessage(params) {
|
|
|
3283
3619
|
});
|
|
3284
3620
|
return null;
|
|
3285
3621
|
}
|
|
3622
|
+
const canSeedMentionedRoomThread = !seedTopLevelRoomThreadBySource && isRoom && !routing.isThreadReply && !hasBoundSession;
|
|
3623
|
+
let seededMentionRouting;
|
|
3624
|
+
const getSeededMentionRouting = () => {
|
|
3625
|
+
seededMentionRouting ??= resolveSlackRoutingContext({
|
|
3626
|
+
ctx,
|
|
3627
|
+
account,
|
|
3628
|
+
message,
|
|
3629
|
+
isDirectMessage,
|
|
3630
|
+
isGroupDm,
|
|
3631
|
+
isRoom,
|
|
3632
|
+
isRoomish,
|
|
3633
|
+
channelConfig,
|
|
3634
|
+
seedTopLevelRoomThread: true,
|
|
3635
|
+
assistantThreadTs: assistantThreadContext?.threadTs,
|
|
3636
|
+
eventScope: opts.eventScope
|
|
3637
|
+
});
|
|
3638
|
+
return seededMentionRouting;
|
|
3639
|
+
};
|
|
3640
|
+
let preflightAudioTranscript;
|
|
3641
|
+
let preflightAudioMedia;
|
|
3642
|
+
const preflightAudioFile = findCaptionlessSlackAudioFile(message);
|
|
3643
|
+
if (isRoom && !isBotMessage && shouldRequireMention && cfg.tools?.media?.audio?.enabled !== false && messageIngress.activationAccess.shouldSkip && mentionRegexes.length > 0 && Boolean(preflightAudioFile) && preflightAudioFile) {
|
|
3644
|
+
const preflightRouting = canSeedMentionedRoomThread ? getSeededMentionRouting() : routing;
|
|
3645
|
+
const preflightMedia = (await resolveMessageContent({
|
|
3646
|
+
...message,
|
|
3647
|
+
files: [preflightAudioFile],
|
|
3648
|
+
attachments: void 0,
|
|
3649
|
+
blocks: void 0
|
|
3650
|
+
}))?.effectiveDirectMedia;
|
|
3651
|
+
const downloadedAudioMedia = preflightMedia?.[0];
|
|
3652
|
+
if (downloadedAudioMedia) preloadedDirectMedia = /* @__PURE__ */ new Map([[preflightAudioFile, downloadedAudioMedia]]);
|
|
3653
|
+
const preflightResult = preflightMedia ? await resolveSlackPreflightAudioTranscript({
|
|
3654
|
+
media: preflightMedia,
|
|
3655
|
+
cfg,
|
|
3656
|
+
accountId: account.accountId,
|
|
3657
|
+
originatingTo: `channel:${message.channel}`,
|
|
3658
|
+
sessionKey: preflightRouting.sessionKey,
|
|
3659
|
+
messageThreadId: preflightRouting.threadContext.messageThreadId
|
|
3660
|
+
}) : null;
|
|
3661
|
+
if (preflightResult) {
|
|
3662
|
+
mentionCheckTranscript = preflightResult.transcript;
|
|
3663
|
+
wasMentioned = resolveWasMentioned(mentionRegexes);
|
|
3664
|
+
if (wasMentioned) {
|
|
3665
|
+
preflightAudioTranscript = preflightResult.transcript;
|
|
3666
|
+
preflightAudioMedia = preflightMedia?.[preflightResult.mediaIndex];
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
if (!preflightAudioTranscript) {
|
|
3670
|
+
await discardSlackPreflightMedia(preflightMedia);
|
|
3671
|
+
preloadedDirectMedia = void 0;
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
if (canSeedMentionedRoomThread && wasMentioned) {
|
|
3675
|
+
routing = getSeededMentionRouting();
|
|
3676
|
+
mentionRegexes = buildPolicyMentionRegexes(routing.route.agentId);
|
|
3677
|
+
wasMentioned = resolveWasMentioned(mentionRegexes);
|
|
3678
|
+
({route, runtimeBinding, replyToMode, threadContext, threadTs, isThreadReply, threadKeys, sessionKey, historyKey} = routing);
|
|
3679
|
+
canDetectMention = Boolean(ctx.botUserId) || mentionRegexes.length > 0;
|
|
3680
|
+
}
|
|
3681
|
+
if (preflightAudioTranscript && !wasMentioned) {
|
|
3682
|
+
await discardSlackPreflightMedia(preloadedDirectMedia ? [...preloadedDirectMedia.values()] : void 0);
|
|
3683
|
+
preloadedDirectMedia = void 0;
|
|
3684
|
+
preflightAudioTranscript = void 0;
|
|
3685
|
+
preflightAudioMedia = void 0;
|
|
3686
|
+
}
|
|
3687
|
+
const directThreadRoutedToDmSession = !assistantThreadContext && isDirectMessage && isThreadReply && threadTs && runtimeBinding?.conversation.conversationId !== threadTs;
|
|
3688
|
+
const mentionDecision = resolveInboundMentionDecision({
|
|
3689
|
+
facts: {
|
|
3690
|
+
canDetectMention,
|
|
3691
|
+
wasMentioned,
|
|
3692
|
+
hasAnyMention,
|
|
3693
|
+
implicitMentionKinds
|
|
3694
|
+
},
|
|
3695
|
+
policy: {
|
|
3696
|
+
isGroup: isRoom,
|
|
3697
|
+
requireMention: shouldRequireMention,
|
|
3698
|
+
allowTextCommands,
|
|
3699
|
+
hasControlCommand: hasControlCommandInMessage,
|
|
3700
|
+
commandAuthorized,
|
|
3701
|
+
...ctx.threadRequireExplicitMention ? { allowedImplicitMentionKinds: [] } : {}
|
|
3702
|
+
}
|
|
3703
|
+
});
|
|
3704
|
+
const effectiveWasMentioned = mentionDecision.effectiveWasMentioned;
|
|
3705
|
+
const shouldBypassMention = mentionDecision.shouldBypassMention;
|
|
3706
|
+
const matchedImplicitMentionKinds = mentionDecision.matchedImplicitMentionKinds;
|
|
3707
|
+
const mentionSource = resolveSlackMentionSource({
|
|
3708
|
+
explicitBotMention: explicitlyMentionedBotUser || opts.source === "app_mention",
|
|
3709
|
+
explicitSubteamMention: explicitlyMentionedBotSubteam,
|
|
3710
|
+
matchedImplicitMentionKinds,
|
|
3711
|
+
shouldBypassMention,
|
|
3712
|
+
wasMentioned
|
|
3713
|
+
});
|
|
3714
|
+
if (isBotMessage && allowBotsMode === "mentions") {
|
|
3715
|
+
if (!(isDirectMessage || effectiveWasMentioned || shouldBypassMention)) {
|
|
3716
|
+
logVerbose("slack: drop bot message (allowBots=mentions, missing mention)");
|
|
3717
|
+
return null;
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3286
3720
|
if (isRoom && shouldRequireMention && !canDetectMention && !effectiveWasMentioned) {
|
|
3287
3721
|
ctx.logger.info({
|
|
3288
3722
|
channel: message.channel,
|
|
@@ -3302,7 +3736,7 @@ async function prepareSlackMessage(params) {
|
|
|
3302
3736
|
await recordDroppedHistory("slack-other-mention");
|
|
3303
3737
|
return null;
|
|
3304
3738
|
}
|
|
3305
|
-
if (isRoom && shouldRequireMention &&
|
|
3739
|
+
if (isRoom && shouldRequireMention && mentionDecision.shouldSkip) {
|
|
3306
3740
|
ctx.logger.info({
|
|
3307
3741
|
channel: message.channel,
|
|
3308
3742
|
reason: "no-mention"
|
|
@@ -3310,11 +3744,6 @@ async function prepareSlackMessage(params) {
|
|
|
3310
3744
|
await recordDroppedHistory("slack-no-mention");
|
|
3311
3745
|
return null;
|
|
3312
3746
|
}
|
|
3313
|
-
const threadStarterPromise = isThreadReply && threadTs ? resolveSlackThreadStarter({
|
|
3314
|
-
channelId: message.channel,
|
|
3315
|
-
threadTs,
|
|
3316
|
-
client: ctx.app.client
|
|
3317
|
-
}) : Promise.resolve(null);
|
|
3318
3747
|
const chatType = resolveSlackChatType(conversation.resolvedChannelType);
|
|
3319
3748
|
const inboundEventKind = classifyChannelInboundEvent({
|
|
3320
3749
|
conversation: { kind: chatType },
|
|
@@ -3326,19 +3755,14 @@ async function prepareSlackMessage(params) {
|
|
|
3326
3755
|
hasControlCommand: hasControlCommandInMessage,
|
|
3327
3756
|
hasAbortRequest
|
|
3328
3757
|
});
|
|
3329
|
-
const threadStarter = await
|
|
3330
|
-
const resolvedMessageContent = await
|
|
3331
|
-
message,
|
|
3332
|
-
isThreadReply,
|
|
3333
|
-
threadStarter,
|
|
3334
|
-
isBotMessage,
|
|
3335
|
-
botToken: ctx.botToken,
|
|
3336
|
-
client: ctx.app.client,
|
|
3337
|
-
mediaMaxBytes: ctx.mediaMaxBytes,
|
|
3338
|
-
resolveUserName: ctx.resolveUserName
|
|
3339
|
-
});
|
|
3758
|
+
const threadStarter = await getThreadStarter();
|
|
3759
|
+
const resolvedMessageContent = await getMessageContent();
|
|
3340
3760
|
if (!resolvedMessageContent) return null;
|
|
3341
3761
|
const { rawBody, effectiveDirectMedia } = resolvedMessageContent;
|
|
3762
|
+
const bodyForAgent = preflightAudioTranscript ? formatSlackAudioTranscriptForAgent({
|
|
3763
|
+
transcript: preflightAudioTranscript,
|
|
3764
|
+
rawBody
|
|
3765
|
+
}) : rawBody;
|
|
3342
3766
|
const ackReaction = resolveAckReaction(cfg, route.agentId, {
|
|
3343
3767
|
channel: "slack",
|
|
3344
3768
|
accountId: account.accountId
|
|
@@ -3370,14 +3794,14 @@ async function prepareSlackMessage(params) {
|
|
|
3370
3794
|
const statusReactionsWillHandle = Boolean(ackReactionMessageTs) && !isRoomEvent && statusReactionsExplicitlyEnabled && shouldSendAckReaction;
|
|
3371
3795
|
const ackReactionPromise = !statusReactionsWillHandle && shouldSendAckReaction && ackReactionMessageTs && ackReactionValue ? reactSlackMessage(message.channel, ackReactionMessageTs, ackReactionValue, {
|
|
3372
3796
|
token: ctx.botToken,
|
|
3373
|
-
client:
|
|
3797
|
+
client: slackClient
|
|
3374
3798
|
}).then(() => true, (err) => {
|
|
3375
3799
|
logVerbose(`slack react failed for channel ${message.channel}: ${formatSlackError(err)}`);
|
|
3376
3800
|
return false;
|
|
3377
3801
|
}) : statusReactionsWillHandle ? Promise.resolve(true) : null;
|
|
3378
3802
|
const roomLabel = channelName ? `#${channelName}` : `#${message.channel}`;
|
|
3379
3803
|
const senderName = await resolveSenderName();
|
|
3380
|
-
const preview =
|
|
3804
|
+
const preview = truncateUtf16Safe(bodyForAgent.replace(/\s+/g, " "), 160);
|
|
3381
3805
|
const inboundLabel = isDirectMessage ? `Slack DM from ${senderName}` : `Slack message in ${roomLabel} from ${senderName}`;
|
|
3382
3806
|
const slackFrom = isDirectMessage ? `slack:${message.user}` : isRoom ? `slack:channel:${message.channel}` : `slack:group:${message.channel}`;
|
|
3383
3807
|
enqueueSystemEvent(inboundLabel, {
|
|
@@ -3391,7 +3815,7 @@ async function prepareSlackMessage(params) {
|
|
|
3391
3815
|
From: slackFrom
|
|
3392
3816
|
}) ?? (isDirectMessage ? senderName : roomLabel);
|
|
3393
3817
|
const threadInfo = isThreadReply && threadTs ? ` thread_ts: ${threadTs}${message.parent_user_id ? ` parent_user_id: ${message.parent_user_id}` : ""}` : "";
|
|
3394
|
-
const textWithId = `${
|
|
3818
|
+
const textWithId = `${bodyForAgent}\n[slack message id: ${message.ts} channel: ${message.channel}${threadInfo}]`;
|
|
3395
3819
|
const storePath = resolveStorePath(ctx.cfg.session?.store, { agentId: route.agentId });
|
|
3396
3820
|
const envelopeOptions = resolveEnvelopeFormatOptions(ctx.cfg);
|
|
3397
3821
|
const previousTimestamp = readSessionUpdatedAt({
|
|
@@ -3426,7 +3850,8 @@ async function prepareSlackMessage(params) {
|
|
|
3426
3850
|
channelId: message.channel,
|
|
3427
3851
|
currentMessageTs: message.ts,
|
|
3428
3852
|
limit: dmHistoryLimit,
|
|
3429
|
-
envelopeOptions
|
|
3853
|
+
envelopeOptions,
|
|
3854
|
+
eventScope: opts.eventScope
|
|
3430
3855
|
}) : {
|
|
3431
3856
|
body: void 0,
|
|
3432
3857
|
inboundHistory: void 0
|
|
@@ -3467,9 +3892,11 @@ async function prepareSlackMessage(params) {
|
|
|
3467
3892
|
allowNameMatching: ctx.allowNameMatching,
|
|
3468
3893
|
contextVisibilityMode,
|
|
3469
3894
|
envelopeOptions,
|
|
3470
|
-
effectiveDirectMedia
|
|
3895
|
+
effectiveDirectMedia,
|
|
3896
|
+
eventScope: opts.eventScope
|
|
3471
3897
|
});
|
|
3472
3898
|
const effectiveMedia = effectiveDirectMedia ?? threadStarterMedia;
|
|
3899
|
+
const inboundMedia = toInboundMediaFacts(effectiveMedia, { transcribed: (entry) => effectiveMedia === effectiveDirectMedia && entry === preflightAudioMedia });
|
|
3473
3900
|
const inboundHistory = isRoomish && ctx.historyLimit > 0 ? channelHistory.buildInboundHistory({
|
|
3474
3901
|
historyKey,
|
|
3475
3902
|
limit: ctx.historyLimit
|
|
@@ -3480,7 +3907,7 @@ async function prepareSlackMessage(params) {
|
|
|
3480
3907
|
const ctxPayload = buildChannelInboundEventContext({
|
|
3481
3908
|
channel: "slack",
|
|
3482
3909
|
accountId: route.accountId,
|
|
3483
|
-
messageId:
|
|
3910
|
+
messageId: threadContext.messageTs,
|
|
3484
3911
|
timestamp: resolveSlackTimestampMs(message.ts),
|
|
3485
3912
|
from: slackFrom,
|
|
3486
3913
|
sender: {
|
|
@@ -3493,7 +3920,7 @@ async function prepareSlackMessage(params) {
|
|
|
3493
3920
|
kind: chatType,
|
|
3494
3921
|
id: message.channel,
|
|
3495
3922
|
label: envelopeFrom,
|
|
3496
|
-
spaceId: ctx.teamId || void 0,
|
|
3923
|
+
spaceId: opts.eventScope?.teamId || ctx.teamId || void 0,
|
|
3497
3924
|
threadId: directThreadRoutedToDmSession ? void 0 : effectiveMessageThreadId,
|
|
3498
3925
|
nativeChannelId: message.channel
|
|
3499
3926
|
},
|
|
@@ -3512,7 +3939,7 @@ async function prepareSlackMessage(params) {
|
|
|
3512
3939
|
message: {
|
|
3513
3940
|
inboundEventKind,
|
|
3514
3941
|
body: combinedBody,
|
|
3515
|
-
bodyForAgent
|
|
3942
|
+
bodyForAgent,
|
|
3516
3943
|
rawBody,
|
|
3517
3944
|
commandBody,
|
|
3518
3945
|
inboundHistory
|
|
@@ -3528,7 +3955,7 @@ async function prepareSlackMessage(params) {
|
|
|
3528
3955
|
},
|
|
3529
3956
|
commands: { authorized: commandAuthorized }
|
|
3530
3957
|
},
|
|
3531
|
-
media:
|
|
3958
|
+
media: inboundMedia,
|
|
3532
3959
|
supplemental: {
|
|
3533
3960
|
thread: {
|
|
3534
3961
|
starterBody: !directThreadRoutedToDmSession && shouldSeedInitialThreadContext ? threadStarterBody : void 0,
|
|
@@ -3545,6 +3972,7 @@ async function prepareSlackMessage(params) {
|
|
|
3545
3972
|
SlackAssistantThreadContextChannelId: assistantThreadContext?.channelId,
|
|
3546
3973
|
SlackAssistantThreadContextTeamId: assistantThreadContext?.teamId,
|
|
3547
3974
|
SlackAssistantThreadContextEnterpriseId: assistantThreadContext?.enterpriseId ?? void 0,
|
|
3975
|
+
Transcript: preflightAudioTranscript,
|
|
3548
3976
|
IsFirstThreadTurn: isThreadReply && threadTs && !directThreadRoutedToDmSession && shouldSeedInitialThreadContext ? true : void 0,
|
|
3549
3977
|
...buildSlackMentionContextPayload({
|
|
3550
3978
|
isRoomish,
|
|
@@ -3575,6 +4003,13 @@ async function prepareSlackMessage(params) {
|
|
|
3575
4003
|
}) : null;
|
|
3576
4004
|
const replyTarget = isDirectMessage ? `channel:${message.channel}` : ctxPayload.To ?? void 0;
|
|
3577
4005
|
if (!replyTarget) return null;
|
|
4006
|
+
if (preflightAudioTranscript) await sendSlackPreflightAudioTranscriptEcho({
|
|
4007
|
+
transcript: preflightAudioTranscript,
|
|
4008
|
+
cfg,
|
|
4009
|
+
accountId: account.accountId,
|
|
4010
|
+
originatingTo: `channel:${message.channel}`,
|
|
4011
|
+
messageThreadId: threadContext.messageThreadId
|
|
4012
|
+
});
|
|
3578
4013
|
if (shouldLogVerbose()) logVerbose(`slack inbound: account=${route.accountId} agent=${route.agentId} channel=${message.channel} message_ts=${message.ts ?? "unknown"} thread_ts=${effectiveMessageThreadId ?? "none"} from=${slackFrom} chat=${chatType} chars=${rawBody.length}`);
|
|
3579
4014
|
const updateLastRouteSessionKey = resolveInboundLastRouteSessionKey({
|
|
3580
4015
|
route,
|
|
@@ -3585,6 +4020,7 @@ async function prepareSlackMessage(params) {
|
|
|
3585
4020
|
account,
|
|
3586
4021
|
message,
|
|
3587
4022
|
...opts.relayIdentity ? { relayIdentity: opts.relayIdentity } : {},
|
|
4023
|
+
...opts.eventScope ? { eventScope: opts.eventScope } : {},
|
|
3588
4024
|
route,
|
|
3589
4025
|
channelConfig,
|
|
3590
4026
|
replyTarget,
|