@openclaw/slack 2026.7.1 → 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.
Files changed (69) hide show
  1. package/dist/accounts.runtime-KvftRRek.js +2 -0
  2. package/dist/{action-runtime-DoQGk_8D.js → action-runtime-p6M2ULpv.js} +205 -29
  3. package/dist/action-runtime.runtime-DQstDkHG.js +2 -0
  4. package/dist/{actions-DMpDmKEA.js → actions-DkTcWmpT.js} +87 -138
  5. package/dist/{actions.runtime-C103_BPW.js → actions.runtime-BSxpwgUc.js} +1 -1
  6. package/dist/api.js +14 -13
  7. package/dist/{approval-handler.runtime-CIUbOt5T.js → approval-handler.runtime-UGbnz9OC.js} +3 -3
  8. package/dist/{channel-BjlsaGHn.js → channel-B0_j6C6z.js} +116 -140
  9. package/dist/channel-config-api.js +1 -1
  10. package/dist/channel-plugin-api.js +1 -1
  11. package/dist/channel-type-BpeTezY4.js +109 -0
  12. package/dist/{channel.setup-Dq6syTNU.js → channel.setup-BQFut21T.js} +2 -2
  13. package/dist/{client-DGxwSL7f.js → client-D2F6m8CP.js} +38 -2
  14. package/dist/config-api-Df9a1B8C.js +3 -0
  15. package/dist/{config-schema-BzqzFW__.js → config-schema-DYI01PV2.js} +20 -37
  16. package/dist/cursor-pages-DSmTg0su.js +13 -0
  17. package/dist/{directory-config-BZuS8141.js → directory-config-DRkFujwG.js} +2 -2
  18. package/dist/directory-contract-api.js +1 -1
  19. package/dist/{directory-live-BNsxCgqP.js → directory-live-DX7yFsM-.js} +2 -2
  20. package/dist/{doctor-contract-XL6HeNCb.js → doctor-contract-DddfkMlF.js} +25 -38
  21. package/dist/doctor-contract-api.js +1 -1
  22. package/dist/group-policy-C-b2ZnSl.js +2332 -0
  23. package/dist/{interactive-replies-CXpT-vA_.js → interactive-replies-BilWUdcI.js} +5 -1
  24. package/dist/interactive-replies-api.js +1 -1
  25. package/dist/{message-tool-api-BgWKyu2r.js → message-tool-api-CA9jZrCY.js} +1 -1
  26. package/dist/message-tool-api.js +1 -1
  27. package/dist/{monitor-Ux-wzUWI.js → monitor-BN_uuvrQ.js} +3 -3
  28. package/dist/outbound-adapter-ECl0go7d.js +228 -0
  29. package/dist/{pipeline.runtime-rpVpay59.js → pipeline.runtime-f0eJJoJL.js} +492 -178
  30. package/dist/{policy-Dj-puVPb.js → policy-f8b0edSi.js} +1 -1
  31. package/dist/preflight-audio.runtime-D5Bgags0.js +3 -0
  32. package/dist/{probe-CuwRDE5j.js → probe-CfALk0l4.js} +2 -2
  33. package/dist/{provider-C1-DFSpw.js → provider-Ds-y-imP.js} +658 -192
  34. package/dist/{relay-source-D2jv1iGg.js → relay-source-B-6LFUdz.js} +6 -17
  35. package/dist/replies-BMvf3ihj.js +420 -0
  36. package/dist/{resolve-channels-CstkChBE.js → resolve-channels-DOMgNCWb.js} +17 -6
  37. package/dist/{resolve-users-C3NP2c-Q.js → resolve-users-aL6-Ssee.js} +11 -6
  38. package/dist/{runtime-BOk7xkOl.js → runtime-CauEvswP.js} +1 -1
  39. package/dist/{runtime-api-CEmqYxR-.js → runtime-api-CVb6CQjX.js} +3 -3
  40. package/dist/runtime-api.js +11 -11
  41. package/dist/runtime-setter-api.js +1 -1
  42. package/dist/{scopes-DCWVD5-8.js → scopes-Dw8wbU0j.js} +2 -2
  43. package/dist/{secret-contract-C3ChHo5A.js → secret-contract-CMohXFWS.js} +18 -113
  44. package/dist/secret-contract-api.js +1 -1
  45. package/dist/{send-DKDXoeoV.js → send-NEfJ0GvM.js} +662 -325
  46. package/dist/{send.runtime-GPRa63l1.js → send.runtime-BkKpitPu.js} +1 -1
  47. package/dist/setup-plugin-api.js +1 -1
  48. package/dist/{setup-surface-Dv2fIJNB.js → setup-surface-BN2WeG5c.js} +3 -3
  49. package/dist/{shared-CosfcFri.js → shared-BLnB9R1T.js} +7 -5
  50. package/dist/{slash-dispatch.runtime-CcMbbT3_.js → slash-dispatch.runtime-C0Otuf_T.js} +1 -1
  51. package/dist/{streaming-compat-DjlgH-Be.js → streaming-compat-3_X-21wW.js} +1 -0
  52. package/dist/{target-parsing-3jYH8LfB.js → target-parsing-N6lHogYZ.js} +16 -3
  53. package/dist/{targets-Dll4NJgs.js → targets-KmjP2E8s.js} +1 -1
  54. package/node_modules/p-map/index.d.ts +155 -0
  55. package/node_modules/p-map/index.js +284 -0
  56. package/node_modules/p-map/license +9 -0
  57. package/node_modules/p-map/package.json +57 -0
  58. package/node_modules/p-map/readme.md +190 -0
  59. package/npm-shrinkwrap.json +16 -3
  60. package/openclaw.plugin.json +16 -0
  61. package/package.json +6 -4
  62. package/dist/accounts.runtime-DWvaCb-X.js +0 -2
  63. package/dist/action-runtime.runtime-BABj3aM-.js +0 -2
  64. package/dist/config-api-DnT9ohtE.js +0 -2
  65. package/dist/format-YCNoNkM2.js +0 -107
  66. package/dist/outbound-adapter-DVoBfBW5.js +0 -272
  67. package/dist/replies-DpF38e1y.js +0 -287
  68. package/dist/reply-blocks-BGQ6zSqM.js +0 -877
  69. 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 { r as parseSlackTarget } from "./target-parsing-3jYH8LfB.js";
3
- import "./targets-Dll4NJgs.js";
4
- import { a as SLACK_TEXT_LIMIT, b as truncateSlackText, c as formatSlackError, d as normalizeAllowListLower, f as normalizeSlackAllowOwnerEntry, m as resolveSlackAllowListMatch } from "./reply-blocks-BGQ6zSqM.js";
5
- import { n as isSlackInteractiveRepliesEnabled, t as compileSlackInteractiveReplies } from "./interactive-replies-CXpT-vA_.js";
6
- import { n as resolveSlackNativeStreaming, r as resolveSlackStreamingMode, t as mapStreamingModeToSlackLegacyDraftStreamMode } from "./streaming-compat-DjlgH-Be.js";
7
- import { c as recordSlackThreadParticipation, r as sendMessageSlack, s as hasSlackThreadParticipationWithPersistence } from "./send-DKDXoeoV.js";
8
- 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-DMpDmKEA.js";
9
- import { n as normalizeSlackOutboundText } from "./format-YCNoNkM2.js";
10
- import { C as stripSlackMentionsForCommandDetection, _ as readSessionUpdatedAt, b as resolveStorePath, c as recordInboundSession, d as resolveSlackCommandIngress, f as resolveSlackEffectiveAllowFrom, g as resolveSlackChatType, h as normalizeSlackChannelType, i as authorizeSlackDirectMessage, l as resolveConversationLabel$1, m as parseSlackAssistantThreadMetadata, p as buildSlackAssistantThreadMetadata, r as resolveSlackRoomContextHints, s as escapeSlackMrkdwn, u as authorizeSlackBotRoomMessage, v as resolveChannelContextVisibilityMode, x as updateLastRoute, y as resolveChannelResetConfig } from "./provider-C1-DFSpw.js";
11
- import { n as resolveSlackChannelConfig } from "./policy-Dj-puVPb.js";
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 ?? messageTs;
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
- return buildSlackEditTextPayload(params.text, params.blocks);
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) return false;
541
- return JSON.stringify(expected) === JSON.stringify(actual);
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 === expectedText && blocksMatch(params.blocks, readback.blocks);
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 slackStreamRecipientTeamCache = /* @__PURE__ */ new Map();
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 slackBlocks = readSlackReplyBlocks(params.payload);
686
- if (!reply.hasContent && !slackBlocks?.length) return null;
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 cached = slackStreamRecipientTeamCache.get(cacheKey);
751
+ const cache = getSlackStreamRecipientTeamCache(params.client);
752
+ const cached = cache.get(cacheKey);
700
753
  if (!cached) return;
701
- slackStreamRecipientTeamCache.delete(cacheKey);
702
- slackStreamRecipientTeamCache.set(cacheKey, cached);
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
- if (slackStreamRecipientTeamCache.has(cacheKey)) slackStreamRecipientTeamCache.delete(cacheKey);
709
- slackStreamRecipientTeamCache.set(cacheKey, params.teamId);
710
- if (slackStreamRecipientTeamCache.size > SLACK_STREAM_RECIPIENT_TEAM_CACHE_MAX) {
711
- const oldest = slackStreamRecipientTeamCache.keys().next().value;
712
- if (oldest) slackStreamRecipientTeamCache.delete(oldest);
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: ctx.app.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: ctx.app.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: ctx.app.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: ctx.app.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
- if (streamFailed || reply.hasMedia || readSlackReplyBlocks(params.payload)?.length || !reply.hasText) {
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: ctx.app.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: ctx.app.client,
1328
+ client: slackClient,
1260
1329
  token: ctx.botToken,
1261
1330
  userId: message.user,
1262
- fallbackTeamId: ctx.teamId
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 trimmedFinalText = (payload.text ?? ttsSupplement?.spokenText ?? "").trim();
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: ctx.app.client,
1468
+ client: slackClient,
1396
1469
  token: ctx.botToken,
1397
1470
  accountId: account.accountId,
1398
1471
  channelId,
1399
1472
  messageId,
1400
- text: normalizeSlackOutboundText(trimmedFinalText),
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: trimmedFinalText
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: normalizeSlackOutboundText(trimmedFinalText),
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: ctx.app.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 previewToolProgressEnabled = (Boolean(draftStream) || useNativeProgressStreaming) && resolveChannelStreamingPreviewToolProgress(account.config);
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: ctx.app.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: ctx.app.client,
1718
+ client: slackClient,
1641
1719
  token: ctx.botToken,
1642
1720
  userId: message.user,
1643
- fallbackTeamId: ctx.teamId
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 pushPreviewToolProgress = async (line, options) => {
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 (!previewToolProgressEnabled || previewToolProgressSuppressed) return;
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 (previewToolProgressEnabled && !previewToolProgressSuppressed) {
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 pushPreviewToolProgress({
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 pushPreviewToolProgress(buildChannelProgressDraftLineForEntry(account.config, {
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 pushPreviewToolProgress(buildChannelProgressDraftLineForEntry(account.config, {
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 pushPreviewToolProgress(buildChannelProgressDraftLine({
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 pushPreviewToolProgress(buildChannelProgressDraftLine({
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 pushPreviewToolProgress(buildChannelProgressDraftLine({
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 pushPreviewToolProgress(buildChannelProgressDraftLine({
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, { agentId: route.agentId });
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: ctx.app.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-DMpDmKEA.js").then((n) => n.g));
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
- return params.isDirectMessage ? `user:${params.message.user ?? "unknown"}` : params.message.channel;
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
- return resolveAgentRoute({
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: params.isDirectMessage ? params.message.user ?? "unknown" : params.message.channel
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 = boundThreadRoute?.boundSessionKey || boundThreadRoute?.bindingRecord ? boundThreadRoute : resolveRuntimeConversationBindingRoute({
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-DMpDmKEA.js").then((n) => n.g));
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
- let subteamMemberCache = /* @__PURE__ */ new WeakMap();
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
- if (!seedTopLevelRoomThreadBySource && wasMentioned && isRoom && !routing.isThreadReply && !hasBoundSession) {
3091
- routing = resolveSlackRoutingContext({
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: ctx.app.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
- const canDetectMention = Boolean(ctx.botUserId) || mentionRegexes.length > 0;
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 (isRoom && senderGate?.allowed === false) {
3253
- logVerbose(`Blocked unauthorized slack sender ${senderId} (not in channel users)`);
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 && messageIngress.activationAccess.shouldSkip) {
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 threadStarterPromise;
3330
- const resolvedMessageContent = await resolveSlackMessageContent({
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: ctx.app.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 = rawBody.replace(/\s+/g, " ").slice(0, 160);
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 = `${rawBody}\n[slack message id: ${message.ts} channel: ${message.channel}${threadInfo}]`;
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: message.ts,
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: rawBody,
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: toInboundMediaFacts(effectiveMedia),
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,