@openclaw/slack 2026.7.1 → 2026.7.2-beta.2

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