@openclaw/slack 2026.7.1-beta.6 → 2026.7.2-beta.1
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-p6M2ULpv.js} +205 -29
- package/dist/action-runtime.runtime-DQstDkHG.js +2 -0
- package/dist/{actions-DMpDmKEA.js → actions-DkTcWmpT.js} +87 -138
- package/dist/{actions.runtime-C103_BPW.js → actions.runtime-BSxpwgUc.js} +1 -1
- package/dist/api.js +14 -13
- package/dist/{approval-handler.runtime-CIUbOt5T.js → approval-handler.runtime-UGbnz9OC.js} +3 -3
- package/dist/{channel-BjlsaGHn.js → channel-B0_j6C6z.js} +116 -140
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/channel-type-BpeTezY4.js +109 -0
- package/dist/{channel.setup-Dq6syTNU.js → channel.setup-BQFut21T.js} +2 -2
- package/dist/{client-DGxwSL7f.js → client-D2F6m8CP.js} +38 -2
- package/dist/config-api-Df9a1B8C.js +3 -0
- package/dist/{config-schema-BzqzFW__.js → config-schema-DYI01PV2.js} +20 -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-DX7yFsM-.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-C-b2ZnSl.js +2332 -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-BN_uuvrQ.js} +3 -3
- package/dist/outbound-adapter-ECl0go7d.js +228 -0
- package/dist/{pipeline.runtime-rpVpay59.js → pipeline.runtime-f0eJJoJL.js} +492 -178
- package/dist/{policy-Dj-puVPb.js → policy-f8b0edSi.js} +1 -1
- package/dist/preflight-audio.runtime-D5Bgags0.js +3 -0
- package/dist/{probe-CuwRDE5j.js → probe-CfALk0l4.js} +2 -2
- package/dist/{provider-C1-DFSpw.js → provider-Ds-y-imP.js} +658 -192
- package/dist/{relay-source-D2jv1iGg.js → relay-source-B-6LFUdz.js} +6 -17
- package/dist/replies-BMvf3ihj.js +420 -0
- package/dist/{resolve-channels-CstkChBE.js → resolve-channels-DOMgNCWb.js} +17 -6
- package/dist/{resolve-users-C3NP2c-Q.js → resolve-users-aL6-Ssee.js} +11 -6
- package/dist/{runtime-BOk7xkOl.js → runtime-CauEvswP.js} +1 -1
- package/dist/{runtime-api-CEmqYxR-.js → runtime-api-CVb6CQjX.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-Dw8wbU0j.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-NEfJ0GvM.js} +662 -325
- package/dist/{send.runtime-GPRa63l1.js → send.runtime-BkKpitPu.js} +1 -1
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{setup-surface-Dv2fIJNB.js → setup-surface-BN2WeG5c.js} +3 -3
- package/dist/{shared-CosfcFri.js → shared-BLnB9R1T.js} +7 -5
- package/dist/{slash-dispatch.runtime-CcMbbT3_.js → slash-dispatch.runtime-C0Otuf_T.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 +16 -0
- package/package.json +6 -4
- 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/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
|
@@ -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 { C as hasSlackNativeDataBlock, F as SLACK_TEXT_LIMIT, K as escapeSlackMrkdwn, N as SLACK_EDIT_TEXT_LIMIT, O as normalizeSlackOutboundText, _ as resolveSlackReplyRenderPlan, c as normalizeSlackAllowOwnerEntry, k as buildSlackBlocksFallbackText, r as formatSlackError, s as normalizeAllowListLower, u as resolveSlackAllowListMatch, z as truncateSlackText } from "./group-policy-C-b2ZnSl.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 { C as buildSlackEditTextPayload, S as formatSlackFileReference, _ as resolveSlackThreadHistory, b as resolveSlackBlocksText, f as removeSlackReaction, l as reactSlackMessage, r as editSlackMessage, t as deleteSlackMessage, v as resolveSlackThreadStarter, y as chooseSlackPrimaryText } from "./actions-DkTcWmpT.js";
|
|
8
|
+
import { c as recordSlackThreadParticipation, l as buildSlackNativeDataDeliveryPlan, r as sendMessageSlack, s as hasSlackThreadParticipationWithPersistence } from "./send-NEfJ0GvM.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-Ds-y-imP.js";
|
|
10
|
+
import { n as resolveSlackChannelConfig } from "./policy-f8b0edSi.js";
|
|
11
|
+
import { a as resolveDeliveredSlackReplyThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as resolveSlackThreadTs, s as emitSlackMessageSentHooks, t as createSlackReplyDeliveryPlan } from "./replies-BMvf3ihj.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, 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)}`);
|
|
@@ -503,7 +509,7 @@ function resolveSlackThreadContext(params) {
|
|
|
503
509
|
const messageTs = params.message.ts ?? eventTs;
|
|
504
510
|
const hasThreadTs = typeof incomingThreadTs === "string" && incomingThreadTs.length > 0;
|
|
505
511
|
const isThreadReply = hasThreadTs && (incomingThreadTs !== messageTs || Boolean(params.message.parent_user_id));
|
|
506
|
-
const replyToId = incomingThreadTs
|
|
512
|
+
const replyToId = isThreadReply ? incomingThreadTs : void 0;
|
|
507
513
|
const isAssistantDmThreadRoot = hasThreadTs && !isThreadReply && params.isDirectMessage === true;
|
|
508
514
|
return {
|
|
509
515
|
incomingThreadTs,
|
|
@@ -533,12 +539,44 @@ function resolveSlackThreadTargets(params) {
|
|
|
533
539
|
//#endregion
|
|
534
540
|
//#region extensions/slack/src/monitor/message-handler/preview-finalize.ts
|
|
535
541
|
function buildExpectedSlackEditText(params) {
|
|
536
|
-
|
|
542
|
+
const trimmedText = params.text.trim();
|
|
543
|
+
if (trimmedText) return normalizeSlackOutboundText(trimmedText);
|
|
544
|
+
if (params.blocks?.length) return normalizeSlackOutboundText(buildSlackBlocksFallbackText(params.blocks));
|
|
545
|
+
return " ";
|
|
546
|
+
}
|
|
547
|
+
function buildAcceptedSlackEditTexts(params) {
|
|
548
|
+
const expected = buildExpectedSlackEditText(params);
|
|
549
|
+
const texts = /* @__PURE__ */ new Set([
|
|
550
|
+
expected,
|
|
551
|
+
normalizeSlackOutboundText(truncateSlackText(expected, SLACK_EDIT_TEXT_LIMIT)),
|
|
552
|
+
normalizeSlackOutboundText(buildSlackEditTextPayload(params.text, params.blocks))
|
|
553
|
+
]);
|
|
554
|
+
if (params.blocks?.length && hasSlackNativeDataBlock(params.blocks)) {
|
|
555
|
+
const fallbackPlan = buildSlackNativeDataDeliveryPlan({
|
|
556
|
+
baseText: params.text,
|
|
557
|
+
blocks: params.blocks
|
|
558
|
+
});
|
|
559
|
+
for (const message of fallbackPlan.fallbackMessages) texts.add(normalizeSlackOutboundText(message.text));
|
|
560
|
+
}
|
|
561
|
+
return texts;
|
|
537
562
|
}
|
|
538
563
|
function blocksMatch(expected, actual) {
|
|
539
564
|
if (!expected?.length) return !actual?.length;
|
|
540
|
-
if (!actual?.length)
|
|
541
|
-
|
|
565
|
+
if (!actual?.length) {
|
|
566
|
+
if (!hasSlackNativeDataBlock(expected)) return false;
|
|
567
|
+
return buildSlackNativeDataDeliveryPlan({ blocks: expected }).fallbackMessages.every((message) => !message.blocks?.length);
|
|
568
|
+
}
|
|
569
|
+
if (JSON.stringify(expected) === JSON.stringify(actual)) return true;
|
|
570
|
+
if (!hasSlackNativeDataBlock(expected)) return false;
|
|
571
|
+
try {
|
|
572
|
+
const fallbackPlan = buildSlackNativeDataDeliveryPlan({ blocks: expected });
|
|
573
|
+
const fallbackBlocks = fallbackPlan.fallbackMessages.flatMap((message) => message.blocks ?? []);
|
|
574
|
+
if (JSON.stringify(fallbackBlocks) === JSON.stringify(actual)) return true;
|
|
575
|
+
const fallbackText = fallbackPlan.fallbackMessages.map((message) => message.text).filter(Boolean).join("\n\n");
|
|
576
|
+
return normalizeSlackOutboundText(buildSlackBlocksFallbackText(actual)) === normalizeSlackOutboundText(fallbackText);
|
|
577
|
+
} catch {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
542
580
|
}
|
|
543
581
|
async function readSlackMessageAfterEditError(params) {
|
|
544
582
|
if (params.threadTs) return ((await params.client.conversations.replies({
|
|
@@ -567,8 +605,12 @@ async function didSlackPreviewEditApplyAfterError(params) {
|
|
|
567
605
|
text: params.text,
|
|
568
606
|
blocks: params.blocks
|
|
569
607
|
});
|
|
608
|
+
const acceptedTexts = buildAcceptedSlackEditTexts({
|
|
609
|
+
text: params.text,
|
|
610
|
+
blocks: params.blocks
|
|
611
|
+
});
|
|
570
612
|
const actualText = normalizeSlackOutboundText((readback.text ?? "").trim());
|
|
571
|
-
if (params.blocks?.length) return actualText
|
|
613
|
+
if (params.blocks?.length) return acceptedTexts.has(actualText) && blocksMatch(params.blocks, readback.blocks);
|
|
572
614
|
return actualText === expectedText;
|
|
573
615
|
}
|
|
574
616
|
async function finalizeSlackPreviewEdit(params) {
|
|
@@ -679,16 +721,26 @@ function resolveSlackStreamingThreadHint(params) {
|
|
|
679
721
|
});
|
|
680
722
|
}
|
|
681
723
|
const SLACK_STREAM_RECIPIENT_TEAM_CACHE_MAX = 2e3;
|
|
682
|
-
const
|
|
724
|
+
const slackStreamRecipientTeamCaches = /* @__PURE__ */ new WeakMap();
|
|
725
|
+
function getSlackStreamRecipientTeamCache(client) {
|
|
726
|
+
const existing = slackStreamRecipientTeamCaches.get(client);
|
|
727
|
+
if (existing) return existing;
|
|
728
|
+
const cache = /* @__PURE__ */ new Map();
|
|
729
|
+
slackStreamRecipientTeamCaches.set(client, cache);
|
|
730
|
+
return cache;
|
|
731
|
+
}
|
|
683
732
|
function buildSlackEventDeliveryKey(params) {
|
|
684
733
|
const reply = resolveSendableOutboundReplyParts(params.payload, { text: params.textOverride });
|
|
685
|
-
const
|
|
686
|
-
|
|
734
|
+
const renderPlan = resolveSlackReplyRenderPlan(params.payload, params.textOverride ?? params.payload.text);
|
|
735
|
+
const plannedBlocks = renderPlan.mode === "single" ? renderPlan.blocks : renderPlan.blockPart?.blocks;
|
|
736
|
+
const slackBlocks = readSlackReplyBlocks(params.payload) ?? plannedBlocks;
|
|
737
|
+
const renderedText = renderPlan.mode === "single" ? renderPlan.text : renderPlan.fallbackText;
|
|
738
|
+
if (!reply.hasContent && !slackBlocks?.length && !renderedText.trim()) return null;
|
|
687
739
|
return JSON.stringify({
|
|
688
740
|
kind: params.kind,
|
|
689
741
|
threadTs: params.threadTs ?? "",
|
|
690
742
|
replyToId: params.payload.replyToId ?? null,
|
|
691
|
-
text: reply.trimmedText,
|
|
743
|
+
text: renderedText || reply.trimmedText,
|
|
692
744
|
mediaUrls: reply.mediaUrls,
|
|
693
745
|
blocks: slackBlocks ?? null
|
|
694
746
|
});
|
|
@@ -696,20 +748,22 @@ function buildSlackEventDeliveryKey(params) {
|
|
|
696
748
|
function readSlackStreamRecipientTeamCache(params) {
|
|
697
749
|
if (!params.fallbackTeamId || !params.userId) return;
|
|
698
750
|
const cacheKey = `${params.fallbackTeamId}:${params.userId}`;
|
|
699
|
-
const
|
|
751
|
+
const cache = getSlackStreamRecipientTeamCache(params.client);
|
|
752
|
+
const cached = cache.get(cacheKey);
|
|
700
753
|
if (!cached) return;
|
|
701
|
-
|
|
702
|
-
|
|
754
|
+
cache.delete(cacheKey);
|
|
755
|
+
cache.set(cacheKey, cached);
|
|
703
756
|
return cached;
|
|
704
757
|
}
|
|
705
758
|
function rememberSlackStreamRecipientTeam(params) {
|
|
706
759
|
if (!params.fallbackTeamId || !params.userId) return;
|
|
707
760
|
const cacheKey = `${params.fallbackTeamId}:${params.userId}`;
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
761
|
+
const cache = getSlackStreamRecipientTeamCache(params.client);
|
|
762
|
+
if (cache.has(cacheKey)) cache.delete(cacheKey);
|
|
763
|
+
cache.set(cacheKey, params.teamId);
|
|
764
|
+
if (cache.size > SLACK_STREAM_RECIPIENT_TEAM_CACHE_MAX) {
|
|
765
|
+
const oldest = cache.keys().next().value;
|
|
766
|
+
if (oldest) cache.delete(oldest);
|
|
713
767
|
}
|
|
714
768
|
}
|
|
715
769
|
function normalizeSlackReasoningProgressLine(text) {
|
|
@@ -793,6 +847,8 @@ async function resolveSlackStreamRecipientTeamId(params) {
|
|
|
793
847
|
}
|
|
794
848
|
async function dispatchPreparedSlackMessage(prepared) {
|
|
795
849
|
const { ctx, account, message, route } = prepared;
|
|
850
|
+
const slackClient = prepared.eventScope?.client ?? ctx.app.client;
|
|
851
|
+
const slackStreamFallbackTeamId = prepared.eventScope?.teamId ?? ctx.teamId;
|
|
796
852
|
const cfg = ctx.cfg;
|
|
797
853
|
const runtime = ctx.runtime;
|
|
798
854
|
const outboundIdentity = resolveAgentOutboundIdentity(cfg, route.agentId);
|
|
@@ -841,6 +897,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
841
897
|
ctx: prepared.ctxPayload
|
|
842
898
|
});
|
|
843
899
|
const sourceRepliesAreToolOnly = sourceReplyDeliveryMode === "message_tool_only";
|
|
900
|
+
const suppressRoomEventTyping = prepared.ctxPayload.InboundEventKind === "room_event";
|
|
844
901
|
const messageSentHookTarget = prepared.ctxPayload.OriginatingTo ?? prepared.ctxPayload.To ?? prepared.replyTarget;
|
|
845
902
|
const messageSentHookContext = {
|
|
846
903
|
sessionKeyForInternalHooks: prepared.ctxPayload.SessionKey ?? route.sessionKey,
|
|
@@ -860,7 +917,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
860
917
|
setReaction: async (emoji) => {
|
|
861
918
|
await reactSlackMessage(message.channel, reactionMessageTs ?? "", emoji, {
|
|
862
919
|
token: ctx.botToken,
|
|
863
|
-
client:
|
|
920
|
+
client: slackClient
|
|
864
921
|
}).catch((err) => {
|
|
865
922
|
if (formatErrorMessage(err).includes("already_reacted")) return;
|
|
866
923
|
throw err;
|
|
@@ -869,7 +926,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
869
926
|
removeReaction: async (emoji) => {
|
|
870
927
|
await removeSlackReaction(message.channel, reactionMessageTs ?? "", emoji, {
|
|
871
928
|
token: ctx.botToken,
|
|
872
|
-
client:
|
|
929
|
+
client: slackClient
|
|
873
930
|
}).catch((err) => {
|
|
874
931
|
if (formatErrorMessage(err).includes("no_reaction")) return;
|
|
875
932
|
throw err;
|
|
@@ -925,12 +982,15 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
925
982
|
channelId: message.channel,
|
|
926
983
|
threadTs: statusThreadTs,
|
|
927
984
|
status: "is typing...",
|
|
928
|
-
loadingMessages: SLACK_THREAD_LOADING_MESSAGES
|
|
985
|
+
loadingMessages: SLACK_THREAD_LOADING_MESSAGES,
|
|
986
|
+
eventScope: prepared.eventScope
|
|
929
987
|
});
|
|
930
988
|
if (typingReaction && message.ts) await reactSlackMessage(message.channel, message.ts, typingReaction, {
|
|
931
989
|
token: ctx.botToken,
|
|
932
|
-
client:
|
|
933
|
-
}).catch(() => {
|
|
990
|
+
client: slackClient
|
|
991
|
+
}).catch((err) => {
|
|
992
|
+
logVerbose(`slack send: typing reaction failed: ${formatSlackError(err)}`);
|
|
993
|
+
});
|
|
934
994
|
},
|
|
935
995
|
stop: async () => {
|
|
936
996
|
if (!didSetStatus) return;
|
|
@@ -938,12 +998,15 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
938
998
|
await ctx.setSlackThreadStatus({
|
|
939
999
|
channelId: message.channel,
|
|
940
1000
|
threadTs: statusThreadTs,
|
|
941
|
-
status: ""
|
|
1001
|
+
status: "",
|
|
1002
|
+
eventScope: prepared.eventScope
|
|
942
1003
|
});
|
|
943
1004
|
if (typingReaction && message.ts) await removeSlackReaction(message.channel, message.ts, typingReaction, {
|
|
944
1005
|
token: ctx.botToken,
|
|
945
|
-
client:
|
|
946
|
-
}).catch(() => {
|
|
1006
|
+
client: slackClient
|
|
1007
|
+
}).catch((err) => {
|
|
1008
|
+
logVerbose(`slack send: typing reaction removal failed: ${formatSlackError(err)}`);
|
|
1009
|
+
});
|
|
947
1010
|
},
|
|
948
1011
|
onStartError: (err) => {
|
|
949
1012
|
logTypingFailure({
|
|
@@ -1113,12 +1176,14 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1113
1176
|
accountId: account.accountId,
|
|
1114
1177
|
runtime,
|
|
1115
1178
|
textLimit: ctx.textLimit,
|
|
1179
|
+
mediaMaxBytes: ctx.mediaMaxBytes,
|
|
1116
1180
|
replyThreadTs: session.threadTs,
|
|
1117
1181
|
replyToMode: replyDeliveryMode,
|
|
1118
1182
|
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1119
1183
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
|
|
1120
1184
|
...messageSentDeliveryHookContext,
|
|
1121
|
-
deferMessageSentHooks: true
|
|
1185
|
+
deferMessageSentHooks: true,
|
|
1186
|
+
...prepared.eventScope ? { eventScope: prepared.eventScope } : {}
|
|
1122
1187
|
});
|
|
1123
1188
|
markSlackStreamFallbackDelivered(session);
|
|
1124
1189
|
if (!session.stopped) try {
|
|
@@ -1160,11 +1225,13 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1160
1225
|
accountId: account.accountId,
|
|
1161
1226
|
runtime,
|
|
1162
1227
|
textLimit: ctx.textLimit,
|
|
1228
|
+
mediaMaxBytes: ctx.mediaMaxBytes,
|
|
1163
1229
|
replyThreadTs: deliveryReplyThreadTs,
|
|
1164
1230
|
replyToMode: replyDeliveryMode,
|
|
1165
1231
|
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1166
1232
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
|
|
1167
|
-
...messageSentDeliveryHookContext
|
|
1233
|
+
...messageSentDeliveryHookContext,
|
|
1234
|
+
...prepared.eventScope ? { eventScope: prepared.eventScope } : {}
|
|
1168
1235
|
});
|
|
1169
1236
|
observedReplyDelivery = true;
|
|
1170
1237
|
if (params.kind === "final") observedFinalReplyDelivery = true;
|
|
@@ -1201,7 +1268,9 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1201
1268
|
const deliverWithStreaming = async (params) => {
|
|
1202
1269
|
if (params.payload.isReasoning === true) return;
|
|
1203
1270
|
const reply = resolveSendableOutboundReplyParts(params.payload);
|
|
1204
|
-
|
|
1271
|
+
const renderPlan = resolveSlackReplyRenderPlan(params.payload);
|
|
1272
|
+
const plannedBlocks = renderPlan.mode === "single" ? renderPlan.blocks : renderPlan.blockPart?.blocks;
|
|
1273
|
+
if (streamFailed || reply.hasMedia || renderPlan.mode === "split" || Boolean(plannedBlocks?.length) || readSlackReplyBlocks(params.payload)?.length || !reply.hasText) {
|
|
1205
1274
|
await deliverNormally({
|
|
1206
1275
|
payload: params.payload,
|
|
1207
1276
|
kind: params.kind,
|
|
@@ -1250,16 +1319,16 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1250
1319
|
return;
|
|
1251
1320
|
}
|
|
1252
1321
|
streamSession = await startSlackStream({
|
|
1253
|
-
client:
|
|
1322
|
+
client: slackClient,
|
|
1254
1323
|
channel: message.channel,
|
|
1255
1324
|
threadTs: streamThreadTs,
|
|
1256
1325
|
text,
|
|
1257
1326
|
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1258
1327
|
teamId: await resolveSlackStreamRecipientTeamId({
|
|
1259
|
-
client:
|
|
1328
|
+
client: slackClient,
|
|
1260
1329
|
token: ctx.botToken,
|
|
1261
1330
|
userId: message.user,
|
|
1262
|
-
fallbackTeamId:
|
|
1331
|
+
fallbackTeamId: slackStreamFallbackTeamId
|
|
1263
1332
|
}),
|
|
1264
1333
|
userId: message.user
|
|
1265
1334
|
});
|
|
@@ -1379,11 +1448,15 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1379
1448
|
return;
|
|
1380
1449
|
}
|
|
1381
1450
|
const reply = resolveSendableOutboundReplyParts(payload);
|
|
1382
|
-
const slackBlocks = readSlackReplyBlocks(payload);
|
|
1383
1451
|
const ttsSupplement = getReplyPayloadTtsSupplement(payload);
|
|
1384
|
-
const
|
|
1452
|
+
const replySourceText = payload.text ?? ttsSupplement?.spokenText;
|
|
1453
|
+
const replyRenderPlan = resolveSlackReplyRenderPlan(payload, replySourceText);
|
|
1454
|
+
const slackBlocks = replyRenderPlan.mode === "single" ? replyRenderPlan.blocks : replyRenderPlan.blockPart?.blocks;
|
|
1455
|
+
const requiresSeparateFallbackDelivery = replyRenderPlan.mode === "split";
|
|
1456
|
+
const trimmedFinalText = replyRenderPlan.mode === "single" ? replyRenderPlan.text.trim() : replyRenderPlan.fallbackText.trim();
|
|
1457
|
+
const previewFinalText = replyRenderPlan.mode === "single" && replyRenderPlan.textIsSlackMrkdwn ? trimmedFinalText : normalizeSlackOutboundText((replySourceText ?? "").trim());
|
|
1385
1458
|
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) {
|
|
1459
|
+
if (info.kind === "final" && ttsSupplement && draftStream && !draftPreviewCommitted && !observedFinalReplyDelivery && previewStreamingEnabled && !payload.isError && !requiresSeparateFallbackDelivery && trimmedFinalText.length > 0) {
|
|
1387
1460
|
const channelId = draftStream.channelId();
|
|
1388
1461
|
const messageId = draftStream.messageId();
|
|
1389
1462
|
if (channelId && messageId) {
|
|
@@ -1392,12 +1465,12 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1392
1465
|
await draftStream.seal();
|
|
1393
1466
|
try {
|
|
1394
1467
|
await finalizeSlackPreviewEdit({
|
|
1395
|
-
client:
|
|
1468
|
+
client: slackClient,
|
|
1396
1469
|
token: ctx.botToken,
|
|
1397
1470
|
accountId: account.accountId,
|
|
1398
1471
|
channelId,
|
|
1399
1472
|
messageId,
|
|
1400
|
-
text:
|
|
1473
|
+
text: previewFinalText,
|
|
1401
1474
|
...slackBlocks?.length ? { blocks: slackBlocks } : {},
|
|
1402
1475
|
threadTs: finalThreadTs
|
|
1403
1476
|
});
|
|
@@ -1409,7 +1482,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1409
1482
|
await deliverNormally({
|
|
1410
1483
|
payload: payload.text?.trim() ? payload : {
|
|
1411
1484
|
...payload,
|
|
1412
|
-
text:
|
|
1485
|
+
text: ttsSupplement.spokenText
|
|
1413
1486
|
},
|
|
1414
1487
|
kind: info.kind,
|
|
1415
1488
|
forcedThreadTs: finalThreadTs
|
|
@@ -1456,9 +1529,9 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1456
1529
|
}
|
|
1457
1530
|
} : void 0,
|
|
1458
1531
|
buildFinalEdit: () => {
|
|
1459
|
-
if (!previewStreamingEnabled || reply.hasMedia && !ttsSupplement || payload.isError || trimmedFinalText.length === 0 && !slackBlocks?.length) return;
|
|
1532
|
+
if (!previewStreamingEnabled || reply.hasMedia && !ttsSupplement || payload.isError || requiresSeparateFallbackDelivery || trimmedFinalText.length === 0 && !slackBlocks?.length) return;
|
|
1460
1533
|
return {
|
|
1461
|
-
text:
|
|
1534
|
+
text: previewFinalText,
|
|
1462
1535
|
blocks: slackBlocks,
|
|
1463
1536
|
threadTs: usedReplyThreadTs ?? statusThreadTs
|
|
1464
1537
|
};
|
|
@@ -1470,7 +1543,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1470
1543
|
threadTs: edit.threadTs
|
|
1471
1544
|
})) return;
|
|
1472
1545
|
await finalizeSlackPreviewEdit({
|
|
1473
|
-
client:
|
|
1546
|
+
client: slackClient,
|
|
1474
1547
|
token: ctx.botToken,
|
|
1475
1548
|
accountId: account.accountId,
|
|
1476
1549
|
channelId: preview.channelId,
|
|
@@ -1540,6 +1613,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1540
1613
|
cfg,
|
|
1541
1614
|
token: ctx.botToken,
|
|
1542
1615
|
accountId: account.accountId,
|
|
1616
|
+
...prepared.eventScope ? { eventScope: prepared.eventScope } : {},
|
|
1543
1617
|
identity: slackIdentity,
|
|
1544
1618
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
|
|
1545
1619
|
maxChars: Math.min(ctx.textLimit, SLACK_TEXT_LIMIT),
|
|
@@ -1554,7 +1628,11 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1554
1628
|
let hasStreamedMessage = false;
|
|
1555
1629
|
const streamMode = slackStreaming.draftMode;
|
|
1556
1630
|
const useNativeProgressStreaming = useStreaming && slackStreaming.mode === "progress";
|
|
1557
|
-
const
|
|
1631
|
+
const progressDraftActive = Boolean(draftStream) || useNativeProgressStreaming;
|
|
1632
|
+
const previewToolProgressEnabled = progressDraftActive && resolveChannelStreamingPreviewToolProgress(account.config);
|
|
1633
|
+
const commentaryProgressEnabled = progressDraftActive && resolveChannelStreamingProgressCommentary(account.config);
|
|
1634
|
+
const commentaryDraftEnabled = progressDraftActive && (slackStreaming.mode === "progress" ? resolveChannelStreamingProgressCommentary(account.config, previewToolProgressEnabled) : previewToolProgressEnabled);
|
|
1635
|
+
let shouldYieldDraftProgress = () => false;
|
|
1558
1636
|
const suppressDefaultToolProgressMessages = resolveChannelStreamingSuppressDefaultToolProgressMessages(account.config, {
|
|
1559
1637
|
draftStreamActive: Boolean(draftStream) || useNativeProgressStreaming,
|
|
1560
1638
|
previewToolProgressEnabled,
|
|
@@ -1630,17 +1708,17 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1630
1708
|
nativeProgressStreamThreadTs = streamThreadTs;
|
|
1631
1709
|
const startPromise = (async () => {
|
|
1632
1710
|
const session = await startSlackStream({
|
|
1633
|
-
client:
|
|
1711
|
+
client: slackClient,
|
|
1634
1712
|
channel: message.channel,
|
|
1635
1713
|
threadTs: streamThreadTs,
|
|
1636
1714
|
chunks,
|
|
1637
1715
|
taskDisplayMode: "plan",
|
|
1638
1716
|
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1639
1717
|
teamId: await resolveSlackStreamRecipientTeamId({
|
|
1640
|
-
client:
|
|
1718
|
+
client: slackClient,
|
|
1641
1719
|
token: ctx.botToken,
|
|
1642
1720
|
userId: message.user,
|
|
1643
|
-
fallbackTeamId:
|
|
1721
|
+
fallbackTeamId: slackStreamFallbackTeamId
|
|
1644
1722
|
}),
|
|
1645
1723
|
userId: message.user
|
|
1646
1724
|
});
|
|
@@ -1690,8 +1768,11 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1690
1768
|
if (useNativeProgressStreaming) await updateNativeProgressStream();
|
|
1691
1769
|
else renderProgressDraft();
|
|
1692
1770
|
};
|
|
1693
|
-
const
|
|
1771
|
+
const pushPreviewProgress = async (line, options) => {
|
|
1772
|
+
const lane = options?.lane ?? "tool";
|
|
1773
|
+
const progressEnabled = lane === "commentary" ? commentaryDraftEnabled : previewToolProgressEnabled;
|
|
1694
1774
|
if (!draftStream && !useNativeProgressStreaming) return;
|
|
1775
|
+
if (lane === "commentary" && commentaryProgressEnabled && shouldYieldDraftProgress()) return;
|
|
1695
1776
|
if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return;
|
|
1696
1777
|
const normalized = line?.text.replace(/\s+/g, " ").trim();
|
|
1697
1778
|
if (!line || !normalized) {
|
|
@@ -1702,7 +1783,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1702
1783
|
return;
|
|
1703
1784
|
}
|
|
1704
1785
|
if (streamMode !== "status_final") {
|
|
1705
|
-
if (!
|
|
1786
|
+
if (!progressEnabled || previewToolProgressSuppressed) return;
|
|
1706
1787
|
const nextLines = mergeChannelProgressDraftLine(previewToolProgressLines, line, { maxLines: resolveChannelProgressDraftMaxLines(account.config) });
|
|
1707
1788
|
if (nextLines === previewToolProgressLines) return;
|
|
1708
1789
|
previewToolProgressLines = nextLines;
|
|
@@ -1715,7 +1796,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1715
1796
|
hasStreamedMessage = true;
|
|
1716
1797
|
return;
|
|
1717
1798
|
}
|
|
1718
|
-
if (
|
|
1799
|
+
if (progressEnabled && !previewToolProgressSuppressed) {
|
|
1719
1800
|
previewToolProgressLines = mergeChannelProgressDraftLine(previewToolProgressLines, line, { maxLines: resolveChannelProgressDraftMaxLines(account.config) });
|
|
1720
1801
|
if (previewToolProgressLines.length > 0) lastNonEmptyPreviewToolProgressLines = previewToolProgressLines;
|
|
1721
1802
|
}
|
|
@@ -1766,7 +1847,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1766
1847
|
reasoningProgressRawText = mergeSlackReasoningProgressText(reasoningProgressRawText, payload.text, { snapshot: payload.isReasoningSnapshot === true });
|
|
1767
1848
|
const normalized = normalizeSlackReasoningProgressLine(reasoningProgressRawText);
|
|
1768
1849
|
if (!normalized) return;
|
|
1769
|
-
await
|
|
1850
|
+
await pushPreviewProgress({
|
|
1770
1851
|
id: "reasoning",
|
|
1771
1852
|
kind: "item",
|
|
1772
1853
|
text: normalized,
|
|
@@ -1813,10 +1894,15 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1813
1894
|
replyOptions: {
|
|
1814
1895
|
skillFilter: prepared.channelConfig?.skills,
|
|
1815
1896
|
sourceReplyDeliveryMode,
|
|
1897
|
+
suppressTyping: suppressRoomEventTyping ? true : void 0,
|
|
1816
1898
|
hasRepliedRef,
|
|
1817
1899
|
disableBlockStreaming,
|
|
1818
1900
|
onModelSelected,
|
|
1819
1901
|
suppressDefaultToolProgressMessages: suppressDefaultToolProgressMessages ? true : void 0,
|
|
1902
|
+
commentaryProgressEnabled: commentaryProgressEnabled ? true : void 0,
|
|
1903
|
+
onVerboseProgressVisibility: commentaryProgressEnabled ? (isActive) => {
|
|
1904
|
+
shouldYieldDraftProgress = isActive;
|
|
1905
|
+
} : void 0,
|
|
1820
1906
|
allowProgressCallbacksWhenSourceDeliverySuppressed: sourceReplyDeliveryMode === "message_tool_only" && statusReactionsEnabled ? true : void 0,
|
|
1821
1907
|
allowToolLifecycleWhenProgressHidden: statusReactionsEnabled ? true : void 0,
|
|
1822
1908
|
onPartialReply: useStreaming ? void 0 : !previewStreamingEnabled ? void 0 : async (payload) => {
|
|
@@ -1831,7 +1917,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1831
1917
|
} : void 0,
|
|
1832
1918
|
onToolStart: async (payload) => {
|
|
1833
1919
|
if (statusReactionsEnabled) await statusReactions.setTool(payload.name);
|
|
1834
|
-
await
|
|
1920
|
+
await pushPreviewProgress(buildChannelProgressDraftLineForEntry(account.config, {
|
|
1835
1921
|
event: "tool",
|
|
1836
1922
|
itemId: payload.itemId,
|
|
1837
1923
|
toolCallId: payload.toolCallId,
|
|
@@ -1841,7 +1927,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1841
1927
|
}, payload.detailMode ? { detailMode: payload.detailMode } : void 0), { toolName: payload.name });
|
|
1842
1928
|
},
|
|
1843
1929
|
onItemEvent: async (payload) => {
|
|
1844
|
-
await
|
|
1930
|
+
await pushPreviewProgress(buildChannelProgressDraftLineForEntry(account.config, {
|
|
1845
1931
|
event: "item",
|
|
1846
1932
|
itemId: payload.itemId,
|
|
1847
1933
|
toolCallId: payload.toolCallId,
|
|
@@ -1853,11 +1939,11 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1853
1939
|
summary: payload.summary,
|
|
1854
1940
|
progressText: payload.progressText,
|
|
1855
1941
|
meta: payload.meta
|
|
1856
|
-
}));
|
|
1942
|
+
}), payload.kind === "preamble" ? { lane: "commentary" } : void 0);
|
|
1857
1943
|
},
|
|
1858
1944
|
onPlanUpdate: async (payload) => {
|
|
1859
1945
|
if (payload.phase !== "update") return;
|
|
1860
|
-
await
|
|
1946
|
+
await pushPreviewProgress(buildChannelProgressDraftLine({
|
|
1861
1947
|
event: "plan",
|
|
1862
1948
|
phase: payload.phase,
|
|
1863
1949
|
title: payload.title,
|
|
@@ -1867,7 +1953,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1867
1953
|
},
|
|
1868
1954
|
onApprovalEvent: async (payload) => {
|
|
1869
1955
|
if (payload.phase !== "requested") return;
|
|
1870
|
-
await
|
|
1956
|
+
await pushPreviewProgress(buildChannelProgressDraftLine({
|
|
1871
1957
|
event: "approval",
|
|
1872
1958
|
phase: payload.phase,
|
|
1873
1959
|
title: payload.title,
|
|
@@ -1878,7 +1964,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1878
1964
|
},
|
|
1879
1965
|
onCommandOutput: async (payload) => {
|
|
1880
1966
|
if (payload.phase !== "end") return;
|
|
1881
|
-
await
|
|
1967
|
+
await pushPreviewProgress(buildChannelProgressDraftLine({
|
|
1882
1968
|
event: "command-output",
|
|
1883
1969
|
itemId: payload.itemId,
|
|
1884
1970
|
toolCallId: payload.toolCallId,
|
|
@@ -1891,7 +1977,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1891
1977
|
},
|
|
1892
1978
|
onPatchSummary: async (payload) => {
|
|
1893
1979
|
if (payload.phase !== "end") return;
|
|
1894
|
-
await
|
|
1980
|
+
await pushPreviewProgress(buildChannelProgressDraftLine({
|
|
1895
1981
|
event: "patch",
|
|
1896
1982
|
itemId: payload.itemId,
|
|
1897
1983
|
toolCallId: payload.toolCallId,
|
|
@@ -1981,7 +2067,10 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1981
2067
|
else statusReactions.restoreInitial();
|
|
1982
2068
|
} else await statusReactions.restoreInitial();
|
|
1983
2069
|
const participationThreadTs = usedReplyThreadTs ?? statusThreadTs;
|
|
1984
|
-
if (anyReplyDelivered && participationThreadTs) recordSlackThreadParticipation(account.accountId, message.channel, participationThreadTs, {
|
|
2070
|
+
if (anyReplyDelivered && participationThreadTs) recordSlackThreadParticipation(account.accountId, message.channel, participationThreadTs, {
|
|
2071
|
+
agentId: route.agentId,
|
|
2072
|
+
...prepared.eventScope ? { teamId: prepared.eventScope.teamId } : {}
|
|
2073
|
+
});
|
|
1985
2074
|
if (dispatchError) throw toLintErrorObject(dispatchError, "Slack dispatch failed");
|
|
1986
2075
|
if (!anyReplyDelivered && !draftPreviewCommitted) {
|
|
1987
2076
|
await draftStream?.clear();
|
|
@@ -1997,7 +2086,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
1997
2086
|
ackReactionValue: prepared.ackReactionValue,
|
|
1998
2087
|
remove: () => removeSlackReaction(message.channel, prepared.ackReactionMessageTs ?? "", prepared.ackReactionValue, {
|
|
1999
2088
|
token: ctx.botToken,
|
|
2000
|
-
client:
|
|
2089
|
+
client: slackClient
|
|
2001
2090
|
}),
|
|
2002
2091
|
onError: (err) => {
|
|
2003
2092
|
logAckFailure({
|
|
@@ -2017,11 +2106,100 @@ function toLintErrorObject(value, fallbackMessage) {
|
|
|
2017
2106
|
return error;
|
|
2018
2107
|
}
|
|
2019
2108
|
//#endregion
|
|
2109
|
+
//#region extensions/slack/src/monitor/message-handler/preflight-audio.ts
|
|
2110
|
+
const SLACK_DEFAULT_ECHO_TRANSCRIPT_FORMAT = "📝 \"{transcript}\"";
|
|
2111
|
+
const loadSlackPreflightAudioRuntime = createLazyRuntimeModule(() => import("./preflight-audio.runtime-D5Bgags0.js"));
|
|
2112
|
+
function isSlackAudioFile(file) {
|
|
2113
|
+
if (file.subtype === "slack_audio") return true;
|
|
2114
|
+
if ((file.mimetype?.split(";")[0]?.trim().toLowerCase())?.startsWith("audio/")) return true;
|
|
2115
|
+
return Boolean(mimeTypeFromFilePath(file.name)?.startsWith("audio/"));
|
|
2116
|
+
}
|
|
2117
|
+
function findCaptionlessSlackAudioFile(message) {
|
|
2118
|
+
if (message.text?.trim()) return;
|
|
2119
|
+
return message.files?.slice(0, 8).find(isSlackAudioFile);
|
|
2120
|
+
}
|
|
2121
|
+
function formatSlackAudioTranscriptForAgent(params) {
|
|
2122
|
+
return [`[Audio transcript (machine-generated, untrusted)]: ${JSON.stringify(params.transcript)}`, params.rawBody].filter(Boolean).join("\n");
|
|
2123
|
+
}
|
|
2124
|
+
function suppressSlackPreflightAudioEcho(cfg) {
|
|
2125
|
+
const audio = cfg.tools?.media?.audio;
|
|
2126
|
+
if (!audio?.echoTranscript) return cfg;
|
|
2127
|
+
return {
|
|
2128
|
+
...cfg,
|
|
2129
|
+
tools: {
|
|
2130
|
+
...cfg.tools,
|
|
2131
|
+
media: {
|
|
2132
|
+
...cfg.tools?.media,
|
|
2133
|
+
audio: {
|
|
2134
|
+
...audio,
|
|
2135
|
+
echoTranscript: false
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
async function resolveSlackPreflightAudioTranscript(params) {
|
|
2142
|
+
const mediaIndex = params.media.findIndex((entry) => entry.contentType?.toLowerCase().startsWith("audio/"));
|
|
2143
|
+
if (mediaIndex < 0) return null;
|
|
2144
|
+
try {
|
|
2145
|
+
const { transcribeFirstAudio } = await loadSlackPreflightAudioRuntime();
|
|
2146
|
+
const transcript = await transcribeFirstAudio({
|
|
2147
|
+
ctx: {
|
|
2148
|
+
MediaPaths: params.media.map((entry) => entry.path),
|
|
2149
|
+
MediaTypes: params.media.map((entry) => entry.contentType ?? ""),
|
|
2150
|
+
Provider: "slack",
|
|
2151
|
+
Surface: "slack",
|
|
2152
|
+
OriginatingChannel: "slack",
|
|
2153
|
+
OriginatingTo: params.originatingTo,
|
|
2154
|
+
AccountId: params.accountId,
|
|
2155
|
+
MessageThreadId: params.messageThreadId,
|
|
2156
|
+
ChatType: "channel",
|
|
2157
|
+
SessionKey: params.sessionKey
|
|
2158
|
+
},
|
|
2159
|
+
cfg: suppressSlackPreflightAudioEcho(params.cfg)
|
|
2160
|
+
});
|
|
2161
|
+
return transcript ? {
|
|
2162
|
+
transcript,
|
|
2163
|
+
mediaIndex
|
|
2164
|
+
} : null;
|
|
2165
|
+
} catch (err) {
|
|
2166
|
+
logVerbose(`slack: audio preflight transcription failed: ${String(err)}`);
|
|
2167
|
+
return null;
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
function formatSlackAudioTranscriptEcho(transcript, format) {
|
|
2171
|
+
return format.replace("{transcript}", () => transcript);
|
|
2172
|
+
}
|
|
2173
|
+
async function sendSlackPreflightAudioTranscriptEcho(params) {
|
|
2174
|
+
const audio = params.cfg.tools?.media?.audio;
|
|
2175
|
+
if (!audio?.echoTranscript) return;
|
|
2176
|
+
const text = formatSlackAudioTranscriptEcho(params.transcript, audio.echoFormat ?? SLACK_DEFAULT_ECHO_TRANSCRIPT_FORMAT);
|
|
2177
|
+
try {
|
|
2178
|
+
const { sendDurableMessageBatch } = await loadSlackPreflightAudioRuntime();
|
|
2179
|
+
const send = await sendDurableMessageBatch({
|
|
2180
|
+
cfg: params.cfg,
|
|
2181
|
+
channel: "slack",
|
|
2182
|
+
to: params.originatingTo,
|
|
2183
|
+
accountId: params.accountId,
|
|
2184
|
+
threadId: params.messageThreadId,
|
|
2185
|
+
payloads: [{ text }],
|
|
2186
|
+
bestEffort: true,
|
|
2187
|
+
durability: "best_effort"
|
|
2188
|
+
});
|
|
2189
|
+
if (send.status === "failed") throw send.error;
|
|
2190
|
+
} catch (err) {
|
|
2191
|
+
logVerbose(`slack: audio transcript echo failed: ${String(err)}`);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
async function discardSlackPreflightMedia(media) {
|
|
2195
|
+
await Promise.allSettled((media ?? []).map((entry) => fs.rm(entry.path, { force: true })));
|
|
2196
|
+
}
|
|
2197
|
+
//#endregion
|
|
2020
2198
|
//#region extensions/slack/src/monitor/message-handler/prepare-content.ts
|
|
2021
2199
|
const SLACK_MENTION_RESOLUTION_CONCURRENCY = 4;
|
|
2022
2200
|
const SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE = 20;
|
|
2023
2201
|
const SLACK_USER_MENTION_RE$1 = /<@([A-Z0-9]+)(?:\|[^>]+)?>/gi;
|
|
2024
|
-
const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-
|
|
2202
|
+
const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-DkTcWmpT.js").then((n) => n.g));
|
|
2025
2203
|
function collectUniqueSlackMentionIds$1(texts) {
|
|
2026
2204
|
const seen = /* @__PURE__ */ new Set();
|
|
2027
2205
|
const mentionIds = [];
|
|
@@ -2066,7 +2244,8 @@ async function resolveSlackMessageContent(params) {
|
|
|
2066
2244
|
maxBytes: params.mediaMaxBytes,
|
|
2067
2245
|
readIdleTimeoutMs: params.mediaReadIdleTimeoutMs,
|
|
2068
2246
|
totalTimeoutMs: params.mediaTotalTimeoutMs,
|
|
2069
|
-
abortSignal: params.abortSignal
|
|
2247
|
+
abortSignal: params.abortSignal,
|
|
2248
|
+
preloadedMedia: params.preloadedMedia
|
|
2070
2249
|
})) : Promise.resolve(null);
|
|
2071
2250
|
const attachmentContentPromise = params.message.attachments && params.message.attachments.length > 0 ? loadSlackMediaModule$1().then(({ resolveSlackAttachmentContent }) => resolveSlackAttachmentContent({
|
|
2072
2251
|
attachments: params.message.attachments,
|
|
@@ -2150,7 +2329,7 @@ async function resolveSlackDmHistoryContext(params) {
|
|
|
2150
2329
|
inboundHistory: void 0
|
|
2151
2330
|
};
|
|
2152
2331
|
try {
|
|
2153
|
-
const messages = ((await params.ctx.app.client.conversations.history({
|
|
2332
|
+
const messages = ((await (params.eventScope?.client ?? params.ctx.app.client).conversations.history({
|
|
2154
2333
|
token: params.ctx.botToken,
|
|
2155
2334
|
channel: params.channelId,
|
|
2156
2335
|
...params.currentMessageTs ? {
|
|
@@ -2170,7 +2349,7 @@ async function resolveSlackDmHistoryContext(params) {
|
|
|
2170
2349
|
const resolveUserLabel = async (userId) => {
|
|
2171
2350
|
const cached = userNames.get(userId);
|
|
2172
2351
|
if (cached) return cached;
|
|
2173
|
-
const label = normalizeOptionalString((await params.ctx.resolveUserName(userId)).name) ?? userId;
|
|
2352
|
+
const label = normalizeOptionalString((await params.ctx.resolveUserName(userId, params.eventScope)).name) ?? userId;
|
|
2174
2353
|
userNames.set(userId, label);
|
|
2175
2354
|
return label;
|
|
2176
2355
|
};
|
|
@@ -2267,28 +2446,46 @@ function normalizeSlackRouteBindingConfig(cfg) {
|
|
|
2267
2446
|
return normalizedCfg;
|
|
2268
2447
|
}
|
|
2269
2448
|
function resolveSlackBaseConversationId(params) {
|
|
2270
|
-
|
|
2449
|
+
const raw = params.isDirectMessage ? `user:${params.message.user ?? "unknown"}` : params.message.channel;
|
|
2450
|
+
return params.eventScope ? `team:${encodeURIComponent(params.eventScope.teamId)}:${raw}` : raw;
|
|
2451
|
+
}
|
|
2452
|
+
function qualifySlackPeerId(params) {
|
|
2453
|
+
if (!params.eventScope) return params.id;
|
|
2454
|
+
return `team:${encodeURIComponent(params.eventScope.teamId)}:${params.kind}:${encodeURIComponent(params.id)}`;
|
|
2271
2455
|
}
|
|
2272
2456
|
function resolveSlackInitialAgentRoute(params) {
|
|
2273
|
-
|
|
2457
|
+
const route = resolveAgentRoute({
|
|
2274
2458
|
cfg: normalizeSlackRouteBindingConfig(params.ctx.cfg),
|
|
2275
2459
|
channel: "slack",
|
|
2276
2460
|
accountId: params.account.accountId,
|
|
2277
|
-
teamId: params.ctx.teamId || void 0,
|
|
2461
|
+
teamId: params.eventScope?.teamId || params.ctx.teamId || void 0,
|
|
2278
2462
|
peer: {
|
|
2279
2463
|
kind: params.isDirectMessage ? "direct" : params.isRoom ? "channel" : "group",
|
|
2280
|
-
id:
|
|
2464
|
+
id: qualifySlackPeerId({
|
|
2465
|
+
id: params.isDirectMessage ? params.message.user ?? "unknown" : params.message.channel,
|
|
2466
|
+
kind: params.isDirectMessage ? "user" : "channel",
|
|
2467
|
+
eventScope: params.eventScope
|
|
2468
|
+
})
|
|
2281
2469
|
}
|
|
2282
2470
|
});
|
|
2471
|
+
if (!params.eventScope || !params.isDirectMessage || route.dmScope !== "main") return route;
|
|
2472
|
+
const partition = `account:${encodeURIComponent(params.account.accountId).toLowerCase()}:team:${encodeURIComponent(params.eventScope.teamId).toLowerCase()}`;
|
|
2473
|
+
const sessionKey = `${route.sessionKey}:${partition}`;
|
|
2474
|
+
return {
|
|
2475
|
+
...route,
|
|
2476
|
+
sessionKey,
|
|
2477
|
+
mainSessionKey: sessionKey
|
|
2478
|
+
};
|
|
2283
2479
|
}
|
|
2284
2480
|
function resolveSlackRoutingContext(params) {
|
|
2285
|
-
const { ctx, account, message, isDirectMessage, isGroupDm, isRoom, isRoomish, channelConfig, seedTopLevelRoomThread, assistantThreadTs } = params;
|
|
2481
|
+
const { ctx, account, message, isDirectMessage, isGroupDm, isRoom, isRoomish, channelConfig, seedTopLevelRoomThread, assistantThreadTs, eventScope } = params;
|
|
2286
2482
|
let route = resolveSlackInitialAgentRoute({
|
|
2287
2483
|
ctx,
|
|
2288
2484
|
account,
|
|
2289
2485
|
message,
|
|
2290
2486
|
isDirectMessage,
|
|
2291
|
-
isRoom
|
|
2487
|
+
isRoom,
|
|
2488
|
+
eventScope
|
|
2292
2489
|
});
|
|
2293
2490
|
const chatType = isDirectMessage ? "direct" : isGroupDm ? "group" : "channel";
|
|
2294
2491
|
const replyToMode = channelConfig?.replyToMode ?? resolveSlackReplyToMode(account, chatType);
|
|
@@ -2304,10 +2501,11 @@ function resolveSlackRoutingContext(params) {
|
|
|
2304
2501
|
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
2502
|
const baseConversationId = resolveSlackBaseConversationId({
|
|
2306
2503
|
message,
|
|
2307
|
-
isDirectMessage
|
|
2504
|
+
isDirectMessage,
|
|
2505
|
+
eventScope
|
|
2308
2506
|
});
|
|
2309
2507
|
const runtimeBindingThreadId = routedThreadId ?? (isDirectMessage && isThreadReply ? threadTs : void 0);
|
|
2310
|
-
const boundThreadRoute = runtimeBindingThreadId ? resolveRuntimeConversationBindingRoute({
|
|
2508
|
+
const boundThreadRoute = !eventScope && runtimeBindingThreadId ? resolveRuntimeConversationBindingRoute({
|
|
2311
2509
|
route,
|
|
2312
2510
|
conversation: {
|
|
2313
2511
|
channel: "slack",
|
|
@@ -2316,7 +2514,11 @@ function resolveSlackRoutingContext(params) {
|
|
|
2316
2514
|
parentConversationId: baseConversationId
|
|
2317
2515
|
}
|
|
2318
2516
|
}) : null;
|
|
2319
|
-
const runtimeRoute =
|
|
2517
|
+
const runtimeRoute = eventScope ? {
|
|
2518
|
+
route,
|
|
2519
|
+
bindingRecord: null,
|
|
2520
|
+
boundSessionKey: void 0
|
|
2521
|
+
} : boundThreadRoute?.boundSessionKey || boundThreadRoute?.bindingRecord ? boundThreadRoute : resolveRuntimeConversationBindingRoute({
|
|
2320
2522
|
route,
|
|
2321
2523
|
conversation: {
|
|
2322
2524
|
channel: "slack",
|
|
@@ -2327,7 +2529,7 @@ function resolveSlackRoutingContext(params) {
|
|
|
2327
2529
|
let configuredBinding = null;
|
|
2328
2530
|
let configuredBindingSessionKey = "";
|
|
2329
2531
|
if (runtimeRoute.boundSessionKey || runtimeRoute.bindingRecord) route = runtimeRoute.route;
|
|
2330
|
-
else {
|
|
2532
|
+
else if (!eventScope) {
|
|
2331
2533
|
const configuredRoute = resolveConfiguredBindingRoute({
|
|
2332
2534
|
cfg: ctx.cfg,
|
|
2333
2535
|
route,
|
|
@@ -2350,7 +2552,7 @@ function resolveSlackRoutingContext(params) {
|
|
|
2350
2552
|
parentSessionKey: routedThreadId && ctx.threadInheritParent ? route.sessionKey : void 0
|
|
2351
2553
|
});
|
|
2352
2554
|
const sessionKey = threadKeys.sessionKey;
|
|
2353
|
-
const historyKey = isThreadReply && ctx.threadHistoryScope === "thread" ? sessionKey : message.channel;
|
|
2555
|
+
const historyKey = isThreadReply && ctx.threadHistoryScope === "thread" ? sessionKey : eventScope ? `${account.accountId}:${eventScope.teamId}:${message.channel}` : message.channel;
|
|
2354
2556
|
return {
|
|
2355
2557
|
route,
|
|
2356
2558
|
runtimeBinding: runtimeRoute.bindingRecord,
|
|
@@ -2419,7 +2621,7 @@ function formatSlackThreadLabelSnippet(text) {
|
|
|
2419
2621
|
}
|
|
2420
2622
|
//#endregion
|
|
2421
2623
|
//#region extensions/slack/src/monitor/message-handler/prepare-thread-context.ts
|
|
2422
|
-
const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-
|
|
2624
|
+
const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-DkTcWmpT.js").then((n) => n.g));
|
|
2423
2625
|
const SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY = 4;
|
|
2424
2626
|
function resolveSlackThreadSessionFreshness(params) {
|
|
2425
2627
|
return params.ctx.channelRuntime?.session?.resolveEntryResetFreshness?.({
|
|
@@ -2455,7 +2657,7 @@ async function resolveSlackThreadUserMap(params) {
|
|
|
2455
2657
|
if (uniqueUserIds.length === 0) return userMap;
|
|
2456
2658
|
const { results } = await runTasksWithConcurrency({
|
|
2457
2659
|
tasks: uniqueUserIds.map((id) => async () => {
|
|
2458
|
-
const user = await params.ctx.resolveUserName(id);
|
|
2660
|
+
const user = await params.ctx.resolveUserName(id, params.eventScope);
|
|
2459
2661
|
return user ? {
|
|
2460
2662
|
id,
|
|
2461
2663
|
user
|
|
@@ -2498,7 +2700,7 @@ async function resolveSlackThreadContextData(params) {
|
|
|
2498
2700
|
threadStarterMedia
|
|
2499
2701
|
};
|
|
2500
2702
|
const starter = params.threadStarter;
|
|
2501
|
-
const starterSenderName = params.allowNameMatching && params.allowFromLower.length > 0 && starter?.userId ? (await params.ctx.resolveUserName(starter.userId))?.name : void 0;
|
|
2703
|
+
const starterSenderName = params.allowNameMatching && params.allowFromLower.length > 0 && starter?.userId ? (await params.ctx.resolveUserName(starter.userId, params.eventScope))?.name : void 0;
|
|
2502
2704
|
const starterIsCurrentBot = Boolean(starter && isCurrentBotAuthor({
|
|
2503
2705
|
userId: starter.userId,
|
|
2504
2706
|
botId: starter.botId
|
|
@@ -2523,7 +2725,7 @@ async function resolveSlackThreadContextData(params) {
|
|
|
2523
2725
|
const { resolveSlackMedia } = await loadSlackMediaModule();
|
|
2524
2726
|
threadStarterMedia = await resolveSlackMedia({
|
|
2525
2727
|
files: starter.files,
|
|
2526
|
-
client: params.ctx.app.client,
|
|
2728
|
+
client: params.eventScope?.client ?? params.ctx.app.client,
|
|
2527
2729
|
token: params.ctx.botToken,
|
|
2528
2730
|
maxBytes: params.ctx.mediaMaxBytes
|
|
2529
2731
|
});
|
|
@@ -2551,7 +2753,7 @@ async function resolveSlackThreadContextData(params) {
|
|
|
2551
2753
|
history: await resolveSlackThreadHistory({
|
|
2552
2754
|
channelId: params.message.channel,
|
|
2553
2755
|
threadTs: params.threadTs,
|
|
2554
|
-
client: params.ctx.app.client,
|
|
2756
|
+
client: params.eventScope?.client ?? params.ctx.app.client,
|
|
2555
2757
|
currentMessageTs: params.message.ts,
|
|
2556
2758
|
limit: threadInitialHistoryLimit
|
|
2557
2759
|
}),
|
|
@@ -2572,7 +2774,8 @@ async function resolveSlackThreadContextData(params) {
|
|
|
2572
2774
|
});
|
|
2573
2775
|
const userMapForFilter = params.contextVisibilityMode !== "all" && params.allowNameMatching && params.allowFromLower.length > 0 ? await resolveSlackThreadUserMap({
|
|
2574
2776
|
ctx: params.ctx,
|
|
2575
|
-
messages: threadHistoryWithoutCurrentBot
|
|
2777
|
+
messages: threadHistoryWithoutCurrentBot,
|
|
2778
|
+
eventScope: params.eventScope
|
|
2576
2779
|
}) : /* @__PURE__ */ new Map();
|
|
2577
2780
|
const { items: filteredThreadHistory, omitted: omittedHistoryCount } = params.contextVisibilityMode === "all" ? {
|
|
2578
2781
|
items: threadHistoryWithoutCurrentBot,
|
|
@@ -2639,7 +2842,7 @@ async function resolveSlackThreadContextData(params) {
|
|
|
2639
2842
|
//#region extensions/slack/src/monitor/message-handler/subteam-mentions.ts
|
|
2640
2843
|
const SUBTEAM_MENTION_RE = /<!subteam\^([A-Z0-9]+)(?:\|[^>]*)?>/gi;
|
|
2641
2844
|
const SUBTEAM_MEMBER_CACHE_TTL_MS = 300 * 1e3;
|
|
2642
|
-
|
|
2845
|
+
const subteamMemberCache = /* @__PURE__ */ new WeakMap();
|
|
2643
2846
|
function normalizeSlackId(value) {
|
|
2644
2847
|
return typeof value === "string" && value.trim() ? value.trim().toUpperCase() : void 0;
|
|
2645
2848
|
}
|
|
@@ -2756,7 +2959,7 @@ async function restoreSlackAssistantThreadContextFromMetadata(params) {
|
|
|
2756
2959
|
const parentUserId = params.message.parent_user_id?.trim();
|
|
2757
2960
|
if (!params.message.channel || !threadTs || !parentUserId || parentUserId !== params.ctx.botUserId && parentUserId !== params.ctx.botId) return;
|
|
2758
2961
|
try {
|
|
2759
|
-
const response = await params.ctx.app.client.conversations.replies({
|
|
2962
|
+
const response = await (params.eventScope?.client ?? params.ctx.app.client).conversations.replies({
|
|
2760
2963
|
channel: params.message.channel,
|
|
2761
2964
|
ts: threadTs,
|
|
2762
2965
|
oldest: threadTs,
|
|
@@ -2841,7 +3044,7 @@ async function resolveSlackHistoryMediaForPendingRecord(params) {
|
|
|
2841
3044
|
isThreadReply: params.isThreadReply,
|
|
2842
3045
|
threadStarter: params.threadStarter,
|
|
2843
3046
|
isBotMessage: params.isBotMessage,
|
|
2844
|
-
client: params.ctx.app.client,
|
|
3047
|
+
client: params.eventScope?.client ?? params.ctx.app.client,
|
|
2845
3048
|
botToken: params.ctx.botToken,
|
|
2846
3049
|
mediaMaxBytes: Math.min(params.ctx.mediaMaxBytes, SLACK_HISTORY_MEDIA_MAX_BYTES),
|
|
2847
3050
|
mediaReadIdleTimeoutMs: SLACK_HISTORY_MEDIA_IDLE_TIMEOUT_MS,
|
|
@@ -2872,10 +3075,10 @@ async function resolveSlackExplicitMentionState(params) {
|
|
|
2872
3075
|
const normalizedBotUserId = normalizeSlackId(params.ctx.botUserId);
|
|
2873
3076
|
const explicitlyMentionedBotUser = Boolean(normalizedBotUserId && params.mentionedUserIds.includes(normalizedBotUserId));
|
|
2874
3077
|
const explicitlyMentionedBotSubteam = Boolean(params.ctx.botUserId && params.hasSubteamMention) && await isSlackSubteamMentionForBot({
|
|
2875
|
-
client: params.ctx.app.client,
|
|
3078
|
+
client: params.eventScope?.client ?? params.ctx.app.client,
|
|
2876
3079
|
text: params.messageText,
|
|
2877
3080
|
botUserId: params.ctx.botUserId,
|
|
2878
|
-
teamId: params.ctx.teamId,
|
|
3081
|
+
teamId: params.eventScope?.teamId ?? params.ctx.teamId,
|
|
2879
3082
|
log: logVerbose
|
|
2880
3083
|
});
|
|
2881
3084
|
return {
|
|
@@ -2909,8 +3112,8 @@ async function resolveSlackConversationContext(params) {
|
|
|
2909
3112
|
let channelInfo = {};
|
|
2910
3113
|
let resolvedChannelType = normalizeSlackChannelType(message.channel_type, message.channel);
|
|
2911
3114
|
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);
|
|
3115
|
+
channelInfo = await ctx.resolveChannelName(message.channel, params.eventScope);
|
|
3116
|
+
resolvedChannelType = normalizeSlackChannelType(message.channel_type ?? channelInfo.type ?? ctx.recallSlackChannelType(message.channel, params.eventScope), message.channel);
|
|
2914
3117
|
}
|
|
2915
3118
|
const channelName = channelInfo?.name;
|
|
2916
3119
|
const isDirectMessage = resolvedChannelType === "im";
|
|
@@ -2978,12 +3181,12 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
2978
3181
|
accountId: account.accountId,
|
|
2979
3182
|
senderId: directUserId,
|
|
2980
3183
|
allowFromLower,
|
|
2981
|
-
resolveSenderName: ctx.resolveUserName,
|
|
3184
|
+
resolveSenderName: (userId) => ctx.resolveUserName(userId, params.eventScope),
|
|
2982
3185
|
sendPairingReply: async (text) => {
|
|
2983
3186
|
await sendMessageSlack(message.channel, text, {
|
|
2984
3187
|
cfg: ctx.cfg,
|
|
2985
3188
|
token: ctx.botToken,
|
|
2986
|
-
client: ctx.app.client,
|
|
3189
|
+
client: params.eventScope?.client ?? ctx.app.client,
|
|
2987
3190
|
accountId: account.accountId
|
|
2988
3191
|
});
|
|
2989
3192
|
},
|
|
@@ -3003,18 +3206,25 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
3003
3206
|
}
|
|
3004
3207
|
async function prepareSlackMessage(params) {
|
|
3005
3208
|
const { ctx, account, message, opts } = params;
|
|
3209
|
+
const slackClient = opts.eventScope?.client ?? ctx.app.client;
|
|
3210
|
+
const threadStarterWorkspaceScope = opts.eventScope ? {
|
|
3211
|
+
accountId: account.accountId,
|
|
3212
|
+
teamId: opts.eventScope.teamId
|
|
3213
|
+
} : void 0;
|
|
3006
3214
|
const cfg = ctx.cfg;
|
|
3007
3215
|
const conversation = await resolveSlackConversationContext({
|
|
3008
3216
|
ctx,
|
|
3009
3217
|
account,
|
|
3010
|
-
message
|
|
3218
|
+
message,
|
|
3219
|
+
eventScope: opts.eventScope
|
|
3011
3220
|
});
|
|
3012
3221
|
const { channelInfo, channelName, isDirectMessage, isGroupDm, isRoom, isRoomish, channelConfig, allowBotsMode, isBotMessage } = conversation;
|
|
3013
3222
|
const authorization = await authorizeSlackInboundMessage({
|
|
3014
3223
|
ctx,
|
|
3015
3224
|
account,
|
|
3016
3225
|
message,
|
|
3017
|
-
conversation
|
|
3226
|
+
conversation,
|
|
3227
|
+
eventScope: opts.eventScope
|
|
3018
3228
|
});
|
|
3019
3229
|
if (!authorization) return null;
|
|
3020
3230
|
const { senderId, allowFromLower } = authorization;
|
|
@@ -3023,7 +3233,7 @@ async function prepareSlackMessage(params) {
|
|
|
3023
3233
|
const resolveSenderName = async () => {
|
|
3024
3234
|
if (resolvedSenderName) return resolvedSenderName;
|
|
3025
3235
|
if (message.user) {
|
|
3026
|
-
const normalized = normalizeOptionalString((await ctx.resolveUserName(message.user))?.name);
|
|
3236
|
+
const normalized = normalizeOptionalString((await ctx.resolveUserName(message.user, opts.eventScope))?.name);
|
|
3027
3237
|
if (normalized) {
|
|
3028
3238
|
resolvedSenderName = normalized;
|
|
3029
3239
|
return resolvedSenderName;
|
|
@@ -3037,24 +3247,26 @@ async function prepareSlackMessage(params) {
|
|
|
3037
3247
|
const messageAssistantThreadContext = resolveSlackMessageAssistantThreadContext(message);
|
|
3038
3248
|
const assistantContextLookupChannelId = messageAssistantThreadContext?.assistantChannelId ?? message.channel;
|
|
3039
3249
|
const assistantContextLookupThreadTs = messageAssistantThreadContext?.threadTs ?? message.thread_ts ?? message.ts;
|
|
3040
|
-
const cachedAssistantThreadContext = isDirectMessage ? ctx.getSlackAssistantThreadContext(assistantContextLookupChannelId, assistantContextLookupThreadTs) : void 0;
|
|
3250
|
+
const cachedAssistantThreadContext = isDirectMessage ? ctx.getSlackAssistantThreadContext(assistantContextLookupChannelId, assistantContextLookupThreadTs, opts.eventScope) : void 0;
|
|
3041
3251
|
const restoredAssistantThreadContextPromise = isDirectMessage && !cachedAssistantThreadContext && !hasSlackAssistantThreadMetadata(messageAssistantThreadContext) ? restoreSlackAssistantThreadContextFromMetadata({
|
|
3042
3252
|
ctx,
|
|
3043
|
-
message
|
|
3253
|
+
message,
|
|
3254
|
+
eventScope: opts.eventScope
|
|
3044
3255
|
}) : Promise.resolve(void 0);
|
|
3045
3256
|
const { explicitlyMentionedBotUser, explicitlyMentionedBotSubteam, explicitlyMentioned } = await resolveSlackExplicitMentionState({
|
|
3046
3257
|
ctx,
|
|
3047
3258
|
messageText,
|
|
3048
3259
|
mentionedUserIds,
|
|
3049
3260
|
hasSubteamMention: mentionMetadata.hasSubteamMention,
|
|
3050
|
-
source: opts.source
|
|
3261
|
+
source: opts.source,
|
|
3262
|
+
eventScope: opts.eventScope
|
|
3051
3263
|
});
|
|
3052
3264
|
const channelRequireMention = channelConfig?.requireMention ?? ctx.defaultRequireMention ?? true;
|
|
3053
3265
|
const channelChatType = isDirectMessage ? "direct" : isGroupDm ? "group" : "channel";
|
|
3054
3266
|
const restoredAssistantThreadContext = await restoredAssistantThreadContextPromise;
|
|
3055
3267
|
const assistantThreadContext = mergeSlackAssistantThreadContext(messageAssistantThreadContext, cachedAssistantThreadContext ?? restoredAssistantThreadContext);
|
|
3056
3268
|
const assistantThreadContextToCache = messageAssistantThreadContext || restoredAssistantThreadContext ? assistantThreadContext : void 0;
|
|
3057
|
-
if (assistantThreadContextToCache) ctx.saveSlackAssistantThreadContext(assistantThreadContextToCache);
|
|
3269
|
+
if (assistantThreadContextToCache) ctx.saveSlackAssistantThreadContext(assistantThreadContextToCache, opts.eventScope);
|
|
3058
3270
|
const channelReplyToMode = channelConfig?.replyToMode ?? resolveSlackReplyToMode(account, channelChatType);
|
|
3059
3271
|
const willImplicitlyThreadReply = isRoom && !channelRequireMention && channelReplyToMode !== "off";
|
|
3060
3272
|
const seedTopLevelRoomThreadBySource = opts.source === "app_mention" || opts.wasMentioned === true || explicitlyMentioned || willImplicitlyThreadReply;
|
|
@@ -3068,8 +3280,10 @@ async function prepareSlackMessage(params) {
|
|
|
3068
3280
|
isRoomish,
|
|
3069
3281
|
channelConfig,
|
|
3070
3282
|
seedTopLevelRoomThread: seedTopLevelRoomThreadBySource,
|
|
3071
|
-
assistantThreadTs: assistantThreadContext?.threadTs
|
|
3283
|
+
assistantThreadTs: assistantThreadContext?.threadTs,
|
|
3284
|
+
eventScope: opts.eventScope
|
|
3072
3285
|
});
|
|
3286
|
+
let mentionCheckTranscript;
|
|
3073
3287
|
const resolveWasMentioned = (mentionRegexes) => opts.wasMentioned ?? (!isDirectMessage && matchesMentionWithExplicit({
|
|
3074
3288
|
text: messageText,
|
|
3075
3289
|
mentionRegexes,
|
|
@@ -3077,7 +3291,8 @@ async function prepareSlackMessage(params) {
|
|
|
3077
3291
|
hasAnyMention,
|
|
3078
3292
|
isExplicitlyMentioned: explicitlyMentioned,
|
|
3079
3293
|
canResolveExplicit: Boolean(ctx.botUserId)
|
|
3080
|
-
}
|
|
3294
|
+
},
|
|
3295
|
+
transcript: mentionCheckTranscript
|
|
3081
3296
|
}));
|
|
3082
3297
|
const buildPolicyMentionRegexes = (agentId) => resolveCachedMentionRegexes(ctx, agentId, {
|
|
3083
3298
|
provider: "slack",
|
|
@@ -3087,23 +3302,8 @@ async function prepareSlackMessage(params) {
|
|
|
3087
3302
|
let mentionRegexes = buildPolicyMentionRegexes(routing.route.agentId);
|
|
3088
3303
|
let wasMentioned = resolveWasMentioned(mentionRegexes);
|
|
3089
3304
|
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;
|
|
3305
|
+
let { route, runtimeBinding, replyToMode, threadContext, threadTs, isThreadReply, threadKeys, sessionKey, historyKey } = routing;
|
|
3306
|
+
const { configuredBinding, configuredBindingSessionKey } = routing;
|
|
3107
3307
|
const isAssistantThreadMessage = Boolean(isDirectMessage && messageAssistantThreadContext);
|
|
3108
3308
|
const forcedAssistantReplyThreadTs = Boolean(assistantThreadContext?.threadTs && (isThreadReply || isAssistantThreadMessage || replyToMode !== "off")) ? assistantThreadContext?.threadTs : void 0;
|
|
3109
3309
|
if (runtimeBinding && shouldLogVerbose()) logVerbose(`slack: routed via bound conversation ${runtimeBinding.conversation.conversationId} -> ${runtimeBinding.targetSessionKey}`);
|
|
@@ -3135,14 +3335,14 @@ async function prepareSlackMessage(params) {
|
|
|
3135
3335
|
ok: true,
|
|
3136
3336
|
name: void 0
|
|
3137
3337
|
});
|
|
3138
|
-
const directThreadRoutedToDmSession = !assistantThreadContext && isDirectMessage && isThreadReply && threadTs && runtimeBinding?.conversation.conversationId !== threadTs;
|
|
3139
3338
|
let implicitMentionKinds = [];
|
|
3140
3339
|
if (!isDirectMessage && ctx.botUserId && message.thread_ts && !ctx.threadRequireExplicitMention && !wasMentioned) {
|
|
3141
3340
|
const replyToBotKinds = implicitMentionKindWhen("reply_to_bot", message.parent_user_id === ctx.botUserId);
|
|
3142
3341
|
implicitMentionKinds = replyToBotKinds.length > 0 ? replyToBotKinds : implicitMentionKindWhen("bot_thread_participant", await hasSlackThreadParticipationWithPersistence({
|
|
3143
3342
|
accountId: account.accountId,
|
|
3144
3343
|
channelId: message.channel,
|
|
3145
|
-
threadTs: message.thread_ts
|
|
3344
|
+
threadTs: message.thread_ts,
|
|
3345
|
+
...opts.eventScope ? { teamId: opts.eventScope.teamId } : {}
|
|
3146
3346
|
}));
|
|
3147
3347
|
}
|
|
3148
3348
|
const recordDroppedHistory = async (reason) => {
|
|
@@ -3153,7 +3353,8 @@ async function prepareSlackMessage(params) {
|
|
|
3153
3353
|
const skippedThreadStarter = historyMediaCandidate && isThreadReply && threadTs ? await resolveSlackThreadStarter({
|
|
3154
3354
|
channelId: message.channel,
|
|
3155
3355
|
threadTs,
|
|
3156
|
-
client:
|
|
3356
|
+
client: slackClient,
|
|
3357
|
+
workspaceScope: threadStarterWorkspaceScope
|
|
3157
3358
|
}) : null;
|
|
3158
3359
|
const senderName = pendingBody ? await resolveSenderName() : void 0;
|
|
3159
3360
|
await recordDroppedChannelInboundHistory({
|
|
@@ -3190,11 +3391,39 @@ async function prepareSlackMessage(params) {
|
|
|
3190
3391
|
message,
|
|
3191
3392
|
isThreadReply,
|
|
3192
3393
|
threadStarter: skippedThreadStarter,
|
|
3193
|
-
isBotMessage
|
|
3394
|
+
isBotMessage,
|
|
3395
|
+
eventScope: opts.eventScope
|
|
3194
3396
|
})
|
|
3195
3397
|
}
|
|
3196
3398
|
});
|
|
3197
3399
|
};
|
|
3400
|
+
let threadStarterPromise;
|
|
3401
|
+
const getThreadStarter = () => {
|
|
3402
|
+
threadStarterPromise ??= isThreadReply && threadTs ? resolveSlackThreadStarter({
|
|
3403
|
+
channelId: message.channel,
|
|
3404
|
+
threadTs,
|
|
3405
|
+
client: slackClient,
|
|
3406
|
+
workspaceScope: threadStarterWorkspaceScope
|
|
3407
|
+
}) : Promise.resolve(null);
|
|
3408
|
+
return threadStarterPromise;
|
|
3409
|
+
};
|
|
3410
|
+
const resolveMessageContent = (contentMessage, preloadedMedia) => getThreadStarter().then((threadStarter) => resolveSlackMessageContent({
|
|
3411
|
+
message: contentMessage,
|
|
3412
|
+
isThreadReply,
|
|
3413
|
+
threadStarter,
|
|
3414
|
+
isBotMessage,
|
|
3415
|
+
botToken: ctx.botToken,
|
|
3416
|
+
client: slackClient,
|
|
3417
|
+
mediaMaxBytes: ctx.mediaMaxBytes,
|
|
3418
|
+
resolveUserName: (userId) => ctx.resolveUserName(userId, opts.eventScope),
|
|
3419
|
+
preloadedMedia
|
|
3420
|
+
}));
|
|
3421
|
+
let preloadedDirectMedia;
|
|
3422
|
+
let messageContentPromise;
|
|
3423
|
+
const getMessageContent = () => {
|
|
3424
|
+
messageContentPromise ??= resolveMessageContent(message, preloadedDirectMedia);
|
|
3425
|
+
return messageContentPromise;
|
|
3426
|
+
};
|
|
3198
3427
|
const senderNameForAuthResult = await senderNameForAuthPromise;
|
|
3199
3428
|
if (!senderNameForAuthResult.ok) throw senderNameForAuthResult.error;
|
|
3200
3429
|
const senderNameForAuth = senderNameForAuthResult.name;
|
|
@@ -3211,7 +3440,7 @@ async function prepareSlackMessage(params) {
|
|
|
3211
3440
|
}, "skipping ambiguous slack thread reply");
|
|
3212
3441
|
return null;
|
|
3213
3442
|
}
|
|
3214
|
-
|
|
3443
|
+
let canDetectMention = Boolean(ctx.botUserId) || mentionRegexes.length > 0;
|
|
3215
3444
|
const textForCommandDetection = stripSlackMentionsForCommandDetection(message.text ?? "");
|
|
3216
3445
|
const hasControlCommandInMessage = hasControlCommand(textForCommandDetection, cfg);
|
|
3217
3446
|
const hasAbortRequest = isAbortRequestText(textForCommandDetection);
|
|
@@ -3238,19 +3467,9 @@ async function prepareSlackMessage(params) {
|
|
|
3238
3467
|
...ctx.threadRequireExplicitMention ? { allowedImplicitMentionKinds: [] } : {}
|
|
3239
3468
|
}
|
|
3240
3469
|
});
|
|
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
3470
|
const senderGate = messageIngress.senderAccess.gate;
|
|
3252
|
-
if (
|
|
3253
|
-
logVerbose(`Blocked unauthorized slack sender ${senderId} (not in
|
|
3471
|
+
if (isRoomish && senderGate?.allowed === false) {
|
|
3472
|
+
logVerbose(`Blocked unauthorized slack sender ${senderId} (not in sender allowlist)`);
|
|
3254
3473
|
return null;
|
|
3255
3474
|
}
|
|
3256
3475
|
if (isRoom && isBotMessage && allowBotsMode !== "off" && !await authorizeSlackBotRoomMessage({
|
|
@@ -3259,14 +3478,9 @@ async function prepareSlackMessage(params) {
|
|
|
3259
3478
|
senderId,
|
|
3260
3479
|
senderName: senderNameForAuth,
|
|
3261
3480
|
channelUsers: channelConfig?.users,
|
|
3262
|
-
allowFromLower
|
|
3481
|
+
allowFromLower,
|
|
3482
|
+
eventScope: opts.eventScope
|
|
3263
3483
|
})) 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
3484
|
const threadContextAllowFromLower = isRoom ? channelUsersAllowlistConfigured ? normalizeAllowListLower(channelConfig?.users) : [] : isDirectMessage ? allowFromLower : [];
|
|
3271
3485
|
const contextVisibilityMode = resolveChannelContextVisibilityMode({
|
|
3272
3486
|
cfg: ctx.cfg,
|
|
@@ -3283,6 +3497,104 @@ async function prepareSlackMessage(params) {
|
|
|
3283
3497
|
});
|
|
3284
3498
|
return null;
|
|
3285
3499
|
}
|
|
3500
|
+
const canSeedMentionedRoomThread = !seedTopLevelRoomThreadBySource && isRoom && !routing.isThreadReply && !hasBoundSession;
|
|
3501
|
+
let seededMentionRouting;
|
|
3502
|
+
const getSeededMentionRouting = () => {
|
|
3503
|
+
seededMentionRouting ??= resolveSlackRoutingContext({
|
|
3504
|
+
ctx,
|
|
3505
|
+
account,
|
|
3506
|
+
message,
|
|
3507
|
+
isDirectMessage,
|
|
3508
|
+
isGroupDm,
|
|
3509
|
+
isRoom,
|
|
3510
|
+
isRoomish,
|
|
3511
|
+
channelConfig,
|
|
3512
|
+
seedTopLevelRoomThread: true,
|
|
3513
|
+
assistantThreadTs: assistantThreadContext?.threadTs,
|
|
3514
|
+
eventScope: opts.eventScope
|
|
3515
|
+
});
|
|
3516
|
+
return seededMentionRouting;
|
|
3517
|
+
};
|
|
3518
|
+
let preflightAudioTranscript;
|
|
3519
|
+
let preflightAudioMedia;
|
|
3520
|
+
const preflightAudioFile = findCaptionlessSlackAudioFile(message);
|
|
3521
|
+
if (isRoom && !isBotMessage && shouldRequireMention && cfg.tools?.media?.audio?.enabled !== false && messageIngress.activationAccess.shouldSkip && mentionRegexes.length > 0 && Boolean(preflightAudioFile) && preflightAudioFile) {
|
|
3522
|
+
const preflightRouting = canSeedMentionedRoomThread ? getSeededMentionRouting() : routing;
|
|
3523
|
+
const preflightMedia = (await resolveMessageContent({
|
|
3524
|
+
...message,
|
|
3525
|
+
files: [preflightAudioFile],
|
|
3526
|
+
attachments: void 0,
|
|
3527
|
+
blocks: void 0
|
|
3528
|
+
}))?.effectiveDirectMedia;
|
|
3529
|
+
const downloadedAudioMedia = preflightMedia?.[0];
|
|
3530
|
+
if (downloadedAudioMedia) preloadedDirectMedia = /* @__PURE__ */ new Map([[preflightAudioFile, downloadedAudioMedia]]);
|
|
3531
|
+
const preflightResult = preflightMedia ? await resolveSlackPreflightAudioTranscript({
|
|
3532
|
+
media: preflightMedia,
|
|
3533
|
+
cfg,
|
|
3534
|
+
accountId: account.accountId,
|
|
3535
|
+
originatingTo: `channel:${message.channel}`,
|
|
3536
|
+
sessionKey: preflightRouting.sessionKey,
|
|
3537
|
+
messageThreadId: preflightRouting.threadContext.messageThreadId
|
|
3538
|
+
}) : null;
|
|
3539
|
+
if (preflightResult) {
|
|
3540
|
+
mentionCheckTranscript = preflightResult.transcript;
|
|
3541
|
+
wasMentioned = resolveWasMentioned(mentionRegexes);
|
|
3542
|
+
if (wasMentioned) {
|
|
3543
|
+
preflightAudioTranscript = preflightResult.transcript;
|
|
3544
|
+
preflightAudioMedia = preflightMedia?.[preflightResult.mediaIndex];
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
if (!preflightAudioTranscript) {
|
|
3548
|
+
await discardSlackPreflightMedia(preflightMedia);
|
|
3549
|
+
preloadedDirectMedia = void 0;
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
if (canSeedMentionedRoomThread && wasMentioned) {
|
|
3553
|
+
routing = getSeededMentionRouting();
|
|
3554
|
+
mentionRegexes = buildPolicyMentionRegexes(routing.route.agentId);
|
|
3555
|
+
wasMentioned = resolveWasMentioned(mentionRegexes);
|
|
3556
|
+
({route, runtimeBinding, replyToMode, threadContext, threadTs, isThreadReply, threadKeys, sessionKey, historyKey} = routing);
|
|
3557
|
+
canDetectMention = Boolean(ctx.botUserId) || mentionRegexes.length > 0;
|
|
3558
|
+
}
|
|
3559
|
+
if (preflightAudioTranscript && !wasMentioned) {
|
|
3560
|
+
await discardSlackPreflightMedia(preloadedDirectMedia ? [...preloadedDirectMedia.values()] : void 0);
|
|
3561
|
+
preloadedDirectMedia = void 0;
|
|
3562
|
+
preflightAudioTranscript = void 0;
|
|
3563
|
+
preflightAudioMedia = void 0;
|
|
3564
|
+
}
|
|
3565
|
+
const directThreadRoutedToDmSession = !assistantThreadContext && isDirectMessage && isThreadReply && threadTs && runtimeBinding?.conversation.conversationId !== threadTs;
|
|
3566
|
+
const mentionDecision = resolveInboundMentionDecision({
|
|
3567
|
+
facts: {
|
|
3568
|
+
canDetectMention,
|
|
3569
|
+
wasMentioned,
|
|
3570
|
+
hasAnyMention,
|
|
3571
|
+
implicitMentionKinds
|
|
3572
|
+
},
|
|
3573
|
+
policy: {
|
|
3574
|
+
isGroup: isRoom,
|
|
3575
|
+
requireMention: shouldRequireMention,
|
|
3576
|
+
allowTextCommands,
|
|
3577
|
+
hasControlCommand: hasControlCommandInMessage,
|
|
3578
|
+
commandAuthorized,
|
|
3579
|
+
...ctx.threadRequireExplicitMention ? { allowedImplicitMentionKinds: [] } : {}
|
|
3580
|
+
}
|
|
3581
|
+
});
|
|
3582
|
+
const effectiveWasMentioned = mentionDecision.effectiveWasMentioned;
|
|
3583
|
+
const shouldBypassMention = mentionDecision.shouldBypassMention;
|
|
3584
|
+
const matchedImplicitMentionKinds = mentionDecision.matchedImplicitMentionKinds;
|
|
3585
|
+
const mentionSource = resolveSlackMentionSource({
|
|
3586
|
+
explicitBotMention: explicitlyMentionedBotUser || opts.source === "app_mention",
|
|
3587
|
+
explicitSubteamMention: explicitlyMentionedBotSubteam,
|
|
3588
|
+
matchedImplicitMentionKinds,
|
|
3589
|
+
shouldBypassMention,
|
|
3590
|
+
wasMentioned
|
|
3591
|
+
});
|
|
3592
|
+
if (isBotMessage && allowBotsMode === "mentions") {
|
|
3593
|
+
if (!(isDirectMessage || effectiveWasMentioned || shouldBypassMention)) {
|
|
3594
|
+
logVerbose("slack: drop bot message (allowBots=mentions, missing mention)");
|
|
3595
|
+
return null;
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3286
3598
|
if (isRoom && shouldRequireMention && !canDetectMention && !effectiveWasMentioned) {
|
|
3287
3599
|
ctx.logger.info({
|
|
3288
3600
|
channel: message.channel,
|
|
@@ -3302,7 +3614,7 @@ async function prepareSlackMessage(params) {
|
|
|
3302
3614
|
await recordDroppedHistory("slack-other-mention");
|
|
3303
3615
|
return null;
|
|
3304
3616
|
}
|
|
3305
|
-
if (isRoom && shouldRequireMention &&
|
|
3617
|
+
if (isRoom && shouldRequireMention && mentionDecision.shouldSkip) {
|
|
3306
3618
|
ctx.logger.info({
|
|
3307
3619
|
channel: message.channel,
|
|
3308
3620
|
reason: "no-mention"
|
|
@@ -3310,11 +3622,6 @@ async function prepareSlackMessage(params) {
|
|
|
3310
3622
|
await recordDroppedHistory("slack-no-mention");
|
|
3311
3623
|
return null;
|
|
3312
3624
|
}
|
|
3313
|
-
const threadStarterPromise = isThreadReply && threadTs ? resolveSlackThreadStarter({
|
|
3314
|
-
channelId: message.channel,
|
|
3315
|
-
threadTs,
|
|
3316
|
-
client: ctx.app.client
|
|
3317
|
-
}) : Promise.resolve(null);
|
|
3318
3625
|
const chatType = resolveSlackChatType(conversation.resolvedChannelType);
|
|
3319
3626
|
const inboundEventKind = classifyChannelInboundEvent({
|
|
3320
3627
|
conversation: { kind: chatType },
|
|
@@ -3326,19 +3633,14 @@ async function prepareSlackMessage(params) {
|
|
|
3326
3633
|
hasControlCommand: hasControlCommandInMessage,
|
|
3327
3634
|
hasAbortRequest
|
|
3328
3635
|
});
|
|
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
|
-
});
|
|
3636
|
+
const threadStarter = await getThreadStarter();
|
|
3637
|
+
const resolvedMessageContent = await getMessageContent();
|
|
3340
3638
|
if (!resolvedMessageContent) return null;
|
|
3341
3639
|
const { rawBody, effectiveDirectMedia } = resolvedMessageContent;
|
|
3640
|
+
const bodyForAgent = preflightAudioTranscript ? formatSlackAudioTranscriptForAgent({
|
|
3641
|
+
transcript: preflightAudioTranscript,
|
|
3642
|
+
rawBody
|
|
3643
|
+
}) : rawBody;
|
|
3342
3644
|
const ackReaction = resolveAckReaction(cfg, route.agentId, {
|
|
3343
3645
|
channel: "slack",
|
|
3344
3646
|
accountId: account.accountId
|
|
@@ -3370,14 +3672,14 @@ async function prepareSlackMessage(params) {
|
|
|
3370
3672
|
const statusReactionsWillHandle = Boolean(ackReactionMessageTs) && !isRoomEvent && statusReactionsExplicitlyEnabled && shouldSendAckReaction;
|
|
3371
3673
|
const ackReactionPromise = !statusReactionsWillHandle && shouldSendAckReaction && ackReactionMessageTs && ackReactionValue ? reactSlackMessage(message.channel, ackReactionMessageTs, ackReactionValue, {
|
|
3372
3674
|
token: ctx.botToken,
|
|
3373
|
-
client:
|
|
3675
|
+
client: slackClient
|
|
3374
3676
|
}).then(() => true, (err) => {
|
|
3375
3677
|
logVerbose(`slack react failed for channel ${message.channel}: ${formatSlackError(err)}`);
|
|
3376
3678
|
return false;
|
|
3377
3679
|
}) : statusReactionsWillHandle ? Promise.resolve(true) : null;
|
|
3378
3680
|
const roomLabel = channelName ? `#${channelName}` : `#${message.channel}`;
|
|
3379
3681
|
const senderName = await resolveSenderName();
|
|
3380
|
-
const preview =
|
|
3682
|
+
const preview = truncateUtf16Safe(bodyForAgent.replace(/\s+/g, " "), 160);
|
|
3381
3683
|
const inboundLabel = isDirectMessage ? `Slack DM from ${senderName}` : `Slack message in ${roomLabel} from ${senderName}`;
|
|
3382
3684
|
const slackFrom = isDirectMessage ? `slack:${message.user}` : isRoom ? `slack:channel:${message.channel}` : `slack:group:${message.channel}`;
|
|
3383
3685
|
enqueueSystemEvent(inboundLabel, {
|
|
@@ -3391,7 +3693,7 @@ async function prepareSlackMessage(params) {
|
|
|
3391
3693
|
From: slackFrom
|
|
3392
3694
|
}) ?? (isDirectMessage ? senderName : roomLabel);
|
|
3393
3695
|
const threadInfo = isThreadReply && threadTs ? ` thread_ts: ${threadTs}${message.parent_user_id ? ` parent_user_id: ${message.parent_user_id}` : ""}` : "";
|
|
3394
|
-
const textWithId = `${
|
|
3696
|
+
const textWithId = `${bodyForAgent}\n[slack message id: ${message.ts} channel: ${message.channel}${threadInfo}]`;
|
|
3395
3697
|
const storePath = resolveStorePath(ctx.cfg.session?.store, { agentId: route.agentId });
|
|
3396
3698
|
const envelopeOptions = resolveEnvelopeFormatOptions(ctx.cfg);
|
|
3397
3699
|
const previousTimestamp = readSessionUpdatedAt({
|
|
@@ -3426,7 +3728,8 @@ async function prepareSlackMessage(params) {
|
|
|
3426
3728
|
channelId: message.channel,
|
|
3427
3729
|
currentMessageTs: message.ts,
|
|
3428
3730
|
limit: dmHistoryLimit,
|
|
3429
|
-
envelopeOptions
|
|
3731
|
+
envelopeOptions,
|
|
3732
|
+
eventScope: opts.eventScope
|
|
3430
3733
|
}) : {
|
|
3431
3734
|
body: void 0,
|
|
3432
3735
|
inboundHistory: void 0
|
|
@@ -3467,9 +3770,11 @@ async function prepareSlackMessage(params) {
|
|
|
3467
3770
|
allowNameMatching: ctx.allowNameMatching,
|
|
3468
3771
|
contextVisibilityMode,
|
|
3469
3772
|
envelopeOptions,
|
|
3470
|
-
effectiveDirectMedia
|
|
3773
|
+
effectiveDirectMedia,
|
|
3774
|
+
eventScope: opts.eventScope
|
|
3471
3775
|
});
|
|
3472
3776
|
const effectiveMedia = effectiveDirectMedia ?? threadStarterMedia;
|
|
3777
|
+
const inboundMedia = toInboundMediaFacts(effectiveMedia, { transcribed: (entry) => effectiveMedia === effectiveDirectMedia && entry === preflightAudioMedia });
|
|
3473
3778
|
const inboundHistory = isRoomish && ctx.historyLimit > 0 ? channelHistory.buildInboundHistory({
|
|
3474
3779
|
historyKey,
|
|
3475
3780
|
limit: ctx.historyLimit
|
|
@@ -3480,7 +3785,7 @@ async function prepareSlackMessage(params) {
|
|
|
3480
3785
|
const ctxPayload = buildChannelInboundEventContext({
|
|
3481
3786
|
channel: "slack",
|
|
3482
3787
|
accountId: route.accountId,
|
|
3483
|
-
messageId:
|
|
3788
|
+
messageId: threadContext.messageTs,
|
|
3484
3789
|
timestamp: resolveSlackTimestampMs(message.ts),
|
|
3485
3790
|
from: slackFrom,
|
|
3486
3791
|
sender: {
|
|
@@ -3493,7 +3798,7 @@ async function prepareSlackMessage(params) {
|
|
|
3493
3798
|
kind: chatType,
|
|
3494
3799
|
id: message.channel,
|
|
3495
3800
|
label: envelopeFrom,
|
|
3496
|
-
spaceId: ctx.teamId || void 0,
|
|
3801
|
+
spaceId: opts.eventScope?.teamId || ctx.teamId || void 0,
|
|
3497
3802
|
threadId: directThreadRoutedToDmSession ? void 0 : effectiveMessageThreadId,
|
|
3498
3803
|
nativeChannelId: message.channel
|
|
3499
3804
|
},
|
|
@@ -3512,7 +3817,7 @@ async function prepareSlackMessage(params) {
|
|
|
3512
3817
|
message: {
|
|
3513
3818
|
inboundEventKind,
|
|
3514
3819
|
body: combinedBody,
|
|
3515
|
-
bodyForAgent
|
|
3820
|
+
bodyForAgent,
|
|
3516
3821
|
rawBody,
|
|
3517
3822
|
commandBody,
|
|
3518
3823
|
inboundHistory
|
|
@@ -3528,7 +3833,7 @@ async function prepareSlackMessage(params) {
|
|
|
3528
3833
|
},
|
|
3529
3834
|
commands: { authorized: commandAuthorized }
|
|
3530
3835
|
},
|
|
3531
|
-
media:
|
|
3836
|
+
media: inboundMedia,
|
|
3532
3837
|
supplemental: {
|
|
3533
3838
|
thread: {
|
|
3534
3839
|
starterBody: !directThreadRoutedToDmSession && shouldSeedInitialThreadContext ? threadStarterBody : void 0,
|
|
@@ -3545,6 +3850,7 @@ async function prepareSlackMessage(params) {
|
|
|
3545
3850
|
SlackAssistantThreadContextChannelId: assistantThreadContext?.channelId,
|
|
3546
3851
|
SlackAssistantThreadContextTeamId: assistantThreadContext?.teamId,
|
|
3547
3852
|
SlackAssistantThreadContextEnterpriseId: assistantThreadContext?.enterpriseId ?? void 0,
|
|
3853
|
+
Transcript: preflightAudioTranscript,
|
|
3548
3854
|
IsFirstThreadTurn: isThreadReply && threadTs && !directThreadRoutedToDmSession && shouldSeedInitialThreadContext ? true : void 0,
|
|
3549
3855
|
...buildSlackMentionContextPayload({
|
|
3550
3856
|
isRoomish,
|
|
@@ -3575,6 +3881,13 @@ async function prepareSlackMessage(params) {
|
|
|
3575
3881
|
}) : null;
|
|
3576
3882
|
const replyTarget = isDirectMessage ? `channel:${message.channel}` : ctxPayload.To ?? void 0;
|
|
3577
3883
|
if (!replyTarget) return null;
|
|
3884
|
+
if (preflightAudioTranscript) await sendSlackPreflightAudioTranscriptEcho({
|
|
3885
|
+
transcript: preflightAudioTranscript,
|
|
3886
|
+
cfg,
|
|
3887
|
+
accountId: account.accountId,
|
|
3888
|
+
originatingTo: `channel:${message.channel}`,
|
|
3889
|
+
messageThreadId: threadContext.messageThreadId
|
|
3890
|
+
});
|
|
3578
3891
|
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
3892
|
const updateLastRouteSessionKey = resolveInboundLastRouteSessionKey({
|
|
3580
3893
|
route,
|
|
@@ -3585,6 +3898,7 @@ async function prepareSlackMessage(params) {
|
|
|
3585
3898
|
account,
|
|
3586
3899
|
message,
|
|
3587
3900
|
...opts.relayIdentity ? { relayIdentity: opts.relayIdentity } : {},
|
|
3901
|
+
...opts.eventScope ? { eventScope: opts.eventScope } : {},
|
|
3588
3902
|
route,
|
|
3589
3903
|
channelConfig,
|
|
3590
3904
|
replyTarget,
|