@openclaw/slack 2026.5.27-beta.1 → 2026.5.28-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/{account-inspect-vVg3pT03.js → account-inspect-CdGk6R7l.js} +1 -1
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/{accounts-BnLQ3fe2.js → accounts-f6Xcv9Vi.js} +42 -1
  4. package/dist/accounts.runtime-BJt1IxOS.js +2 -0
  5. package/dist/{action-runtime-JVb7KyQs.js → action-runtime-BOEgcnv6.js} +7 -8
  6. package/dist/action-runtime.runtime-BXQYV0yA.js +2 -0
  7. package/dist/{actions-1o9nMIY8.js → actions-zfVWcIY6.js} +4 -4
  8. package/dist/{actions.runtime-qQtdNww-.js → actions.runtime-CoijPN8g.js} +1 -1
  9. package/dist/api.js +14 -17
  10. package/dist/{approval-handler.runtime-Ba8nwnYi.js → approval-handler.runtime-CWz3XLfN.js} +12 -69
  11. package/dist/{channel-Bd7eept6.js → channel-B9uavFQM.js} +25 -28
  12. package/dist/channel-config-api.js +1 -1
  13. package/dist/channel-plugin-api.js +1 -1
  14. package/dist/{channel.setup-WNNVmCm0.js → channel.setup-oGp4gSTP.js} +4 -4
  15. package/dist/{client-Dc2Ji2YN.js → client-DowBk5k0.js} +6 -24
  16. package/dist/{config-schema-CUiDK8HV.js → config-schema-C0RewpJQ.js} +4 -0
  17. package/dist/contract-api.js +1 -1
  18. package/dist/{directory-config-9_djLTOK.js → directory-config-8UPAEyNg.js} +1 -1
  19. package/dist/directory-contract-api.js +1 -1
  20. package/dist/{directory-live-Cl83cGLZ.js → directory-live-BFB1pSax.js} +2 -2
  21. package/dist/http-routes-api.js +1 -1
  22. package/dist/index.js +3 -7
  23. package/dist/{interactive-replies-DtYu4Sf5.js → interactive-replies-DrBq4Mld.js} +1 -1
  24. package/dist/interactive-replies-api.js +1 -1
  25. package/dist/{message-tool-api-BieXX5lk.js → message-tool-api-B9M0zzlQ.js} +2 -2
  26. package/dist/message-tool-api.js +1 -1
  27. package/dist/{monitor-Cn7LUDFL.js → monitor-BzzGqB63.js} +3 -4
  28. package/dist/{outbound-adapter-ChuR4_0v.js → outbound-adapter-BHZMgblN.js} +4 -7
  29. package/dist/{prepare-Bl5WcC3f.js → pipeline.runtime-XK36f1W3.js} +1769 -47
  30. package/dist/plugin-routes-B9PvcDQJ.js +22 -0
  31. package/dist/{policy-pu8tsF8_.js → policy-BBDU-PQK.js} +1 -1
  32. package/dist/{probe-cBVPhCKZ.js → probe-Djes9Fy6.js} +1 -1
  33. package/dist/{provider-Bccng2Wp.js → provider-CfZypaNV.js} +826 -26
  34. package/dist/{replies-jiEDV6lH.js → replies-DkmWK7JW.js} +2 -4
  35. package/dist/{resolve-channels-CT4oiz_9.js → resolve-channels-zXt5f47h.js} +1 -1
  36. package/dist/{resolve-users-o5S-Ijks.js → resolve-users-BLfGAz1v.js} +1 -1
  37. package/dist/{runtime-api-CyPE-EvY.js → runtime-api-DvpUD2hw.js} +2 -2
  38. package/dist/runtime-api.js +12 -12
  39. package/dist/{scopes-CnyhLIPT.js → scopes-DiiHsqh1.js} +1 -1
  40. package/dist/{send-DvfE8LVm.js → send-BURYyCXI.js} +4 -4
  41. package/dist/send.runtime-CKaMG3s-.js +2 -0
  42. package/dist/{setup-core-DKe7Ug3V.js → setup-core-POfI_bgP.js} +2 -2
  43. package/dist/setup-entry.js +8 -1
  44. package/dist/setup-plugin-api.js +1 -1
  45. package/dist/{setup-surface-gjRthuZA.js → setup-surface-DJTHAguz.js} +5 -5
  46. package/dist/{shared-D8A7iVVH.js → shared-D9WMYymo.js} +5 -5
  47. package/dist/{slash-dispatch.runtime-BBhdJHb8.js → slash-dispatch.runtime-lsyTm_q5.js} +1 -1
  48. package/dist/thread-ts-NSVqWybn.js +646 -0
  49. package/node_modules/semver/classes/range.js +7 -0
  50. package/node_modules/semver/package.json +1 -1
  51. package/node_modules/semver/ranges/subset.js +2 -2
  52. package/npm-shrinkwrap.json +6 -29
  53. package/openclaw.plugin.json +10 -0
  54. package/package.json +8 -7
  55. package/dist/accounts.runtime-BVdtQeuq.js +0 -2
  56. package/dist/action-runtime.runtime-BZa5VDjs.js +0 -2
  57. package/dist/allow-list-B1lkGjwl.js +0 -82
  58. package/dist/blocks-render-CNC4vQnd.js +0 -232
  59. package/dist/inbound-contract-test-api.js +0 -3
  60. package/dist/magic-string.es-BPXBBMwL.js +0 -1011
  61. package/dist/outbound-payload-test-api.js +0 -2
  62. package/dist/outbound-payload.test-harness-8MnHFgR1.js +0 -13551
  63. package/dist/pipeline.runtime-BVK8yXw_.js +0 -1473
  64. package/dist/plugin-routes-DRR3ijKM.js +0 -20
  65. package/dist/prepare.test-helpers-BcAo4KMw.js +0 -49
  66. package/dist/reply-blocks-BlOURkUm.js +0 -290
  67. package/dist/room-context-BmNTBiw5.js +0 -816
  68. package/dist/send.runtime-Dv6ajTGK.js +0 -2
  69. package/dist/send.runtime-v3TSw9xY.js +0 -2
  70. package/dist/test-api.js +0 -8
  71. package/dist/thread-ts-ks-O8cEG.js +0 -52
  72. package/node_modules/agent-base/LICENSE +0 -22
  73. package/node_modules/agent-base/README.md +0 -69
  74. package/node_modules/agent-base/dist/helpers.d.ts +0 -10
  75. package/node_modules/agent-base/dist/helpers.d.ts.map +0 -1
  76. package/node_modules/agent-base/dist/helpers.js +0 -37
  77. package/node_modules/agent-base/dist/helpers.js.map +0 -1
  78. package/node_modules/agent-base/dist/index.d.ts +0 -37
  79. package/node_modules/agent-base/dist/index.d.ts.map +0 -1
  80. package/node_modules/agent-base/dist/index.js +0 -146
  81. package/node_modules/agent-base/dist/index.js.map +0 -1
  82. package/node_modules/agent-base/package.json +0 -46
  83. package/node_modules/https-proxy-agent/LICENSE +0 -22
  84. package/node_modules/https-proxy-agent/README.md +0 -70
  85. package/node_modules/https-proxy-agent/dist/index.d.ts +0 -43
  86. package/node_modules/https-proxy-agent/dist/index.d.ts.map +0 -1
  87. package/node_modules/https-proxy-agent/dist/index.js +0 -150
  88. package/node_modules/https-proxy-agent/dist/index.js.map +0 -1
  89. package/node_modules/https-proxy-agent/dist/parse-proxy-response.d.ts +0 -12
  90. package/node_modules/https-proxy-agent/dist/parse-proxy-response.d.ts.map +0 -1
  91. package/node_modules/https-proxy-agent/dist/parse-proxy-response.js +0 -94
  92. package/node_modules/https-proxy-agent/dist/parse-proxy-response.js.map +0 -1
  93. package/node_modules/https-proxy-agent/package.json +0 -50
@@ -1,36 +1,1790 @@
1
- import { l as resolveSlackReplyToMode } from "./accounts-BnLQ3fe2.js";
1
+ import { l as resolveSlackReplyToMode } from "./accounts-f6Xcv9Vi.js";
2
2
  import { r as parseSlackTarget } from "./target-parsing-C7eeWg7M.js";
3
3
  import "./targets-nUqxHGgg.js";
4
- import { i as normalizeSlackAllowOwnerEntry, o as resolveSlackAllowListMatch, r as normalizeAllowListLower } from "./allow-list-B1lkGjwl.js";
5
- import { i as hasSlackThreadParticipationWithPersistence, t as sendMessageSlack } from "./send-DvfE8LVm.js";
6
- import { _ as resolveSlackThreadStarter, g as resolveSlackThreadHistory, l as reactSlackMessage, y as formatSlackFileReference } from "./actions-1o9nMIY8.js";
4
+ import { b as truncateSlackText, f as normalizeAllowListLower, h as resolveSlackAllowListMatch, p as normalizeSlackAllowOwnerEntry, s as SLACK_TEXT_LIMIT } from "./thread-ts-NSVqWybn.js";
5
+ import { n as isSlackInteractiveRepliesEnabled, t as compileSlackInteractiveReplies } from "./interactive-replies-DrBq4Mld.js";
6
+ import { n as resolveSlackNativeStreaming, r as resolveSlackStreamingMode, t as mapStreamingModeToSlackLegacyDraftStreamMode } from "./streaming-compat-DjlgH-Be.js";
7
+ import { a as recordSlackThreadParticipation, i as hasSlackThreadParticipationWithPersistence, s as normalizeSlackOutboundText, t as sendMessageSlack } from "./send-BURYyCXI.js";
8
+ import { _ as resolveSlackThreadStarter, b as buildSlackEditTextPayload, f as removeSlackReaction, g as resolveSlackThreadHistory, l as reactSlackMessage, r as editSlackMessage, t as deleteSlackMessage, y as formatSlackFileReference } from "./actions-zfVWcIY6.js";
7
9
  import { t as formatSlackError } from "./errors-CZtmv-h0.js";
8
- import { C as resolveStorePath, O as stripSlackMentionsForCommandDetection, b as resolveChannelContextVisibilityMode, c as authorizeSlackBotRoomMessage, d as resolveSlackEffectiveAllowFrom, f as buildSlackAssistantThreadMetadata, g as resolveSlackChatType, h as normalizeSlackChannelType, m as parseSlackAssistantThreadMetadata, n as authorizeSlackDirectMessage, o as resolveConversationLabel$1, t as resolveSlackRoomContextHints, u as resolveSlackCommandIngress, y as readSessionUpdatedAt } from "./room-context-BmNTBiw5.js";
9
- import { n as resolveSlackChannelConfig } from "./policy-pu8tsF8_.js";
10
- import "./send.runtime-Dv6ajTGK.js";
11
- import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
10
+ import { _ as resolveStorePath, a as escapeSlackMrkdwn, b as stripSlackMentionsForCommandDetection, c as authorizeSlackBotRoomMessage, d as buildSlackAssistantThreadMetadata, f as parseSlackAssistantThreadMetadata, g as resolveChannelContextVisibilityMode, h as readSessionUpdatedAt, i as authorizeSlackDirectMessage, l as resolveSlackCommandIngress, m as resolveSlackChatType, o as recordInboundSession, p as normalizeSlackChannelType, r as resolveSlackRoomContextHints, s as resolveConversationLabel$1, u as resolveSlackEffectiveAllowFrom, v as updateLastRoute } from "./provider-CfZypaNV.js";
11
+ import { n as resolveSlackChannelConfig } from "./policy-BBDU-PQK.js";
12
+ import { a as resolveDeliveredSlackReplyThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as resolveSlackThreadTs, t as createSlackReplyDeliveryPlan } from "./replies-DkmWK7JW.js";
13
+ import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
12
14
  import { resolveAgentRoute, resolveInboundLastRouteSessionKey, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
13
- import { resolveChannelMessageSourceReplyDeliveryMode } from "openclaw/plugin-sdk/channel-outbound";
14
- import { logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
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";
16
+ import { createHash } from "node:crypto";
17
+ import { danger, logVerbose, shouldLogVerbose, sleep } from "openclaw/plugin-sdk/runtime-env";
15
18
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
19
+ import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
16
20
  import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
17
21
  import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
22
+ import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
23
+ import { mergePairLoopGuardConfig } from "openclaw/plugin-sdk/pair-loop-guard-runtime";
18
24
  import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
19
- import { buildChannelInboundEventContext, buildMentionRegexes, classifyChannelInboundEvent, formatInboundEnvelope, implicitMentionKindWhen, logInboundDrop, matchesMentionWithExplicit, recordDroppedChannelInboundHistory, resolveEnvelopeFormatOptions, resolveUnmentionedGroupInboundPolicy, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
25
+ import { buildChannelInboundEventContext, buildMentionRegexes, classifyChannelInboundEvent, dispatchChannelInboundReply, formatInboundEnvelope, hasVisibleInboundReplyDispatch, implicitMentionKindWhen, logInboundDrop, matchesMentionWithExplicit, recordDroppedChannelInboundHistory, resolveEnvelopeFormatOptions, resolveUnmentionedGroupInboundPolicy, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
20
26
  import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist, shouldIncludeSupplementalContext } from "openclaw/plugin-sdk/security-runtime";
21
- import { resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
27
+ import { DEFAULT_TIMING, createStatusReactionController, logAckFailure, logTypingFailure, removeAckReactionAfterReply, resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
28
+ import { dispatchReplyWithBufferedBlockDispatcher } from "openclaw/plugin-sdk/reply-runtime";
22
29
  import { hasControlCommand } from "openclaw/plugin-sdk/command-detection";
23
30
  import { isAbortRequestText } from "openclaw/plugin-sdk/command-primitives-runtime";
24
31
  import { shouldHandleTextCommands } from "openclaw/plugin-sdk/command-surface";
25
32
  import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
26
33
  import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
34
+ //#region extensions/slack/src/draft-stream.ts
35
+ const DEFAULT_THROTTLE_MS = 1e3;
36
+ function createSlackDraftStream(params) {
37
+ const maxChars = Math.min(params.maxChars ?? 8e3, SLACK_TEXT_LIMIT);
38
+ const throttleMs = Math.max(250, params.throttleMs ?? DEFAULT_THROTTLE_MS);
39
+ const send = params.send ?? sendMessageSlack;
40
+ const edit = params.edit ?? editSlackMessage;
41
+ const remove = params.remove ?? deleteSlackMessage;
42
+ let streamMessageId;
43
+ let streamChannelId;
44
+ let lastSentKey = "";
45
+ let pendingUpdate;
46
+ let stopped = false;
47
+ const normalizeUpdate = (update) => typeof update === "string" ? { text: update } : update;
48
+ const sendOrEditStreamMessage = async (text) => {
49
+ if (stopped) return;
50
+ const trimmed = text.trimEnd();
51
+ if (!trimmed) return;
52
+ if (trimmed.length > maxChars) {
53
+ stopped = true;
54
+ params.warn?.(`slack stream preview stopped (text length ${trimmed.length} > ${maxChars})`);
55
+ return;
56
+ }
57
+ const update = normalizeUpdate(pendingUpdate ?? text);
58
+ const blocks = update.text === text ? update.blocks : void 0;
59
+ const sentKey = `${trimmed}\n${blocks ? JSON.stringify(blocks) : ""}`;
60
+ if (sentKey === lastSentKey) return;
61
+ lastSentKey = sentKey;
62
+ try {
63
+ if (streamChannelId && streamMessageId) {
64
+ await edit(streamChannelId, streamMessageId, trimmed, {
65
+ cfg: params.cfg,
66
+ token: params.token,
67
+ accountId: params.accountId,
68
+ ...blocks ? { blocks } : {}
69
+ });
70
+ return;
71
+ }
72
+ const sent = await send(params.target, trimmed, {
73
+ cfg: params.cfg,
74
+ token: params.token,
75
+ accountId: params.accountId,
76
+ threadTs: params.resolveThreadTs?.(),
77
+ identity: params.identity,
78
+ ...params.metadata ? { metadata: params.metadata } : {},
79
+ ...blocks ? { blocks } : {}
80
+ });
81
+ streamChannelId = sent.channelId || streamChannelId;
82
+ streamMessageId = sent.messageId || streamMessageId;
83
+ if (!streamChannelId || !streamMessageId) {
84
+ stopped = true;
85
+ params.warn?.("slack stream preview stopped (missing identifiers from sendMessage)");
86
+ return;
87
+ }
88
+ params.onMessageSent?.();
89
+ } catch (err) {
90
+ stopped = true;
91
+ params.warn?.(`slack stream preview failed: ${formatSlackError(err)}`);
92
+ }
93
+ };
94
+ const loop = createDraftStreamLoop({
95
+ throttleMs,
96
+ isStopped: () => stopped,
97
+ sendOrEditStreamMessage
98
+ });
99
+ const stop = () => {
100
+ stopped = true;
101
+ loop.stop();
102
+ };
103
+ const discardPending = async () => {
104
+ stop();
105
+ await loop.waitForInFlight();
106
+ };
107
+ const clear = async () => {
108
+ await discardPending();
109
+ const channelId = streamChannelId;
110
+ const messageId = streamMessageId;
111
+ streamChannelId = void 0;
112
+ streamMessageId = void 0;
113
+ lastSentKey = "";
114
+ pendingUpdate = void 0;
115
+ if (!channelId || !messageId) return;
116
+ try {
117
+ await remove(channelId, messageId, {
118
+ token: params.token,
119
+ accountId: params.accountId
120
+ });
121
+ } catch (err) {
122
+ params.warn?.(`slack stream preview cleanup failed: ${formatSlackError(err)}`);
123
+ }
124
+ };
125
+ const forceNewMessage = () => {
126
+ streamMessageId = void 0;
127
+ streamChannelId = void 0;
128
+ lastSentKey = "";
129
+ pendingUpdate = void 0;
130
+ loop.resetPending();
131
+ };
132
+ params.log?.(`slack stream preview ready (maxChars=${maxChars}, throttleMs=${throttleMs})`);
133
+ return {
134
+ update: (update) => {
135
+ const normalized = normalizeUpdate(update);
136
+ pendingUpdate = update;
137
+ loop.update(normalized.text);
138
+ },
139
+ flush: loop.flush,
140
+ clear,
141
+ discardPending,
142
+ seal: discardPending,
143
+ stop,
144
+ forceNewMessage,
145
+ messageId: () => streamMessageId,
146
+ channelId: () => streamChannelId
147
+ };
148
+ }
149
+ //#endregion
150
+ //#region extensions/slack/src/progress-blocks.ts
151
+ const SLACK_PROGRESS_FIELD_MAX = 1800;
152
+ const DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS = 120;
153
+ const DEFAULT_SLACK_PROGRESS_TASK_DETAIL_MAX_CHARS = 48;
154
+ const SLACK_PROGRESS_CHUNK_TEXT_MAX = 256;
155
+ const SLACK_PROGRESS_TASK_TITLE_MAX = 120;
156
+ const SLACK_PROGRESS_PLAN_FALLBACK_TITLE = "Thinking";
157
+ function field(text) {
158
+ return {
159
+ type: "mrkdwn",
160
+ text: truncateSlackText(text, SLACK_PROGRESS_FIELD_MAX)
161
+ };
162
+ }
163
+ function resolveMaxLineChars(value, fallback) {
164
+ return value && value > 0 ? Math.floor(value) : fallback;
165
+ }
166
+ function compactDetail(value, maxChars) {
167
+ const normalized = value.replace(/\s+/g, " ").trim();
168
+ const chars = Array.from(normalized);
169
+ if (chars.length <= maxChars) return normalized;
170
+ if (maxChars <= 1) return "…";
171
+ const keepStart = Math.max(1, Math.ceil((maxChars - 1) * .45));
172
+ const keepEnd = Math.max(1, maxChars - keepStart - 1);
173
+ return `${chars.slice(0, keepStart).join("").trimEnd()}…${chars.slice(-keepEnd).join("").trimStart()}`;
174
+ }
175
+ function compactTitle(value) {
176
+ return truncateSlackText(value.replace(/\s+/g, " ").trim(), SLACK_PROGRESS_TASK_TITLE_MAX);
177
+ }
178
+ function compactChunkText(value) {
179
+ return truncateSlackText(value.replace(/\s+/g, " ").trim(), SLACK_PROGRESS_CHUNK_TEXT_MAX);
180
+ }
181
+ function lineDetailParts(line) {
182
+ return [line.detail, line.status && !line.detail?.includes(line.status) ? line.status : void 0].map((part) => part?.trim()).filter((part) => Boolean(part));
183
+ }
184
+ function legacyLineTitle(line) {
185
+ return `${line.icon ?? "•"} *${escapeSlackMrkdwn(line.label)}*`;
186
+ }
187
+ function legacyLineDetail(line, maxChars) {
188
+ const detail = lineDetailParts(line).join(" · ");
189
+ return detail ? escapeSlackMrkdwn(compactDetail(detail, maxChars)) : "—";
190
+ }
191
+ function lineTaskTitle(line, maxLineChars) {
192
+ const label = line.label.replace(/\s+/g, " ").trim() || line.toolName || line.kind || "Update";
193
+ const detail = lineDetailParts(line).join(" · ");
194
+ const fallback = line.text.replace(/\s+/g, " ").trim();
195
+ if (detail) return compactTitle(`${label} — ${compactDetail(detail, maxLineChars)}`);
196
+ if (fallback && fallback !== label) return compactTitle(fallback);
197
+ return compactTitle(label);
198
+ }
199
+ function lineTaskStatus(line) {
200
+ const normalized = line.status?.replace(/\s+/g, " ").trim().toLowerCase();
201
+ if (!normalized) return "in_progress";
202
+ if (normalized === "complete" || normalized === "completed" || normalized === "done" || normalized === "ok" || normalized === "success" || normalized === "succeeded" || normalized === "successful" || normalized === "exit 0") return "complete";
203
+ if (normalized === "error" || normalized === "failed" || normalized === "failure" || normalized.startsWith("exit ")) return normalized === "exit 0" ? "complete" : "error";
204
+ return "in_progress";
205
+ }
206
+ function slugTaskIdPart(value) {
207
+ return value?.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "task";
208
+ }
209
+ function stableTaskIdPart(value) {
210
+ const suffix = createHash("sha256").update(value).digest("hex").slice(0, 8);
211
+ return `${slugTaskIdPart(value)}_${suffix}`;
212
+ }
213
+ function buildPlanTasks(params) {
214
+ const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_TASK_DETAIL_MAX_CHARS);
215
+ return params.lines.slice(-50).map((line, index) => ({
216
+ id: line.id ? stableTaskIdPart(line.id) : `${slugTaskIdPart(line.toolName ?? line.kind ?? line.label)}_${index + 1}`,
217
+ title: lineTaskTitle(line, maxLineChars),
218
+ status: lineTaskStatus(line)
219
+ }));
220
+ }
221
+ function resolvePlanTitle(params) {
222
+ return compactChunkText(params.title?.trim() || params.label?.trim() || params.tasks.at(-1)?.title || SLACK_PROGRESS_PLAN_FALLBACK_TITLE);
223
+ }
224
+ function buildSlackProgressStreamChunks(params) {
225
+ const tasks = buildPlanTasks({
226
+ lines: params.lines,
227
+ maxLineChars: params.maxLineChars
228
+ });
229
+ if (tasks.length === 0) return;
230
+ return [{
231
+ type: "plan_update",
232
+ title: resolvePlanTitle({
233
+ label: params.label,
234
+ title: params.title,
235
+ tasks
236
+ })
237
+ }, ...tasks.map((task) => ({
238
+ type: "task_update",
239
+ id: task.id,
240
+ title: task.title,
241
+ status: task.status === "in_progress" ? params.finalInProgressStatus ?? (params.completeInProgress ? "complete" : task.status) : task.status
242
+ }))];
243
+ }
244
+ function buildSlackProgressDraftBlocks(params) {
245
+ const label = params.label?.trim() || params.title?.trim();
246
+ const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS);
247
+ const renderedBlocks = [...label ? [{
248
+ type: "section",
249
+ text: field(`*${escapeSlackMrkdwn(label)}*`)
250
+ }] : [], ...params.lines.map((line) => ({
251
+ type: "section",
252
+ fields: [field(legacyLineTitle(line)), field(legacyLineDetail(line, maxLineChars))]
253
+ }))].slice(-50);
254
+ return renderedBlocks.length ? renderedBlocks : void 0;
255
+ }
256
+ function buildSlackProgressStreamStartChunks(params) {
257
+ return buildSlackProgressStreamChunks(params);
258
+ }
259
+ function buildSlackProgressStreamUpdateChunks(params) {
260
+ return buildSlackProgressStreamChunks(params);
261
+ }
262
+ function buildSlackProgressStreamCompletionChunks(params) {
263
+ return buildSlackProgressStreamChunks({
264
+ ...params,
265
+ completeInProgress: true
266
+ });
267
+ }
268
+ //#endregion
269
+ //#region extensions/slack/src/stream-mode.ts
270
+ function resolveSlackStreamingConfig(params) {
271
+ const mode = resolveSlackStreamingMode(params);
272
+ return {
273
+ mode,
274
+ nativeStreaming: resolveSlackNativeStreaming(params),
275
+ draftMode: mapStreamingModeToSlackLegacyDraftStreamMode(mode)
276
+ };
277
+ }
278
+ function applyAppendOnlyStreamUpdate(params) {
279
+ const incoming = params.incoming.trimEnd();
280
+ if (!incoming) return {
281
+ rendered: params.rendered,
282
+ source: params.source,
283
+ changed: false
284
+ };
285
+ if (!params.rendered) return {
286
+ rendered: incoming,
287
+ source: incoming,
288
+ changed: true
289
+ };
290
+ if (incoming === params.source) return {
291
+ rendered: params.rendered,
292
+ source: params.source,
293
+ changed: false
294
+ };
295
+ if (incoming.startsWith(params.source) || incoming.startsWith(params.rendered)) return {
296
+ rendered: incoming,
297
+ source: incoming,
298
+ changed: incoming !== params.rendered
299
+ };
300
+ if (params.source.startsWith(incoming)) return {
301
+ rendered: params.rendered,
302
+ source: params.source,
303
+ changed: false
304
+ };
305
+ const separator = params.rendered.endsWith("\n") ? "" : "\n";
306
+ return {
307
+ rendered: `${params.rendered}${separator}${incoming}`,
308
+ source: incoming,
309
+ changed: true
310
+ };
311
+ }
312
+ //#endregion
313
+ //#region extensions/slack/src/streaming.ts
314
+ /**
315
+ * Thrown when Slack rejects a stream flush/finalize with a recipient-resolution
316
+ * error (see {@link BENIGN_SLACK_FINALIZE_ERROR_CODES}) while text is still
317
+ * only buffered locally by the Slack SDK. Carries the pending text so the
318
+ * caller can deliver it via the normal Slack reply path.
319
+ */
320
+ var SlackStreamNotDeliveredError = class extends Error {
321
+ constructor(pendingText, slackCode) {
322
+ super(`slack-stream: finalize failed with ${slackCode} before any text reached Slack (${pendingText.length} chars pending)`);
323
+ this.name = "SlackStreamNotDeliveredError";
324
+ this.pendingText = pendingText;
325
+ this.slackCode = slackCode;
326
+ }
327
+ };
328
+ /**
329
+ * Start a new Slack text stream.
330
+ *
331
+ * Returns a {@link SlackStreamSession} that should be passed to
332
+ * {@link appendSlackStream} and {@link stopSlackStream}.
333
+ *
334
+ * The first chunk of text can optionally be included via `text`.
335
+ */
336
+ async function startSlackStream(params) {
337
+ const { client, channel, threadTs, text, chunks, taskDisplayMode, teamId, userId } = params;
338
+ logVerbose(`slack-stream: starting stream in ${channel} thread=${threadTs}${teamId ? ` team=${teamId}` : ""}${userId ? ` user=${userId}` : ""}`);
339
+ const streamer = client.chatStream({
340
+ channel,
341
+ thread_ts: threadTs,
342
+ ...taskDisplayMode ? { task_display_mode: taskDisplayMode } : {},
343
+ ...teamId ? { recipient_team_id: teamId } : {},
344
+ ...userId ? { recipient_user_id: userId } : {}
345
+ });
346
+ const session = {
347
+ streamer,
348
+ channel,
349
+ threadTs,
350
+ stopped: false,
351
+ delivered: false,
352
+ pendingText: ""
353
+ };
354
+ if (text || chunks?.length) {
355
+ if (text) session.pendingText += text;
356
+ try {
357
+ const result = await streamer.append({
358
+ ...text ? { markdown_text: text } : {},
359
+ ...chunks?.length ? { chunks } : {}
360
+ });
361
+ if (result) {
362
+ session.delivered = true;
363
+ session.pendingText = "";
364
+ }
365
+ logVerbose(`slack-stream: appended initial payload (${text?.length ?? 0} chars, ${chunks?.length ?? 0} chunks, ${result ? "flushed" : "buffered"})`);
366
+ } catch (err) {
367
+ if (isBenignSlackFinalizeError(err) && session.pendingText) throw new SlackStreamNotDeliveredError(session.pendingText, extractSlackErrorCode(err) ?? "unknown");
368
+ throw err;
369
+ }
370
+ }
371
+ return session;
372
+ }
373
+ /**
374
+ * Append markdown text to an active Slack stream.
375
+ */
376
+ async function appendSlackStream(params) {
377
+ const { session, text, chunks } = params;
378
+ if (session.stopped) {
379
+ logVerbose("slack-stream: attempted to append to a stopped stream, ignoring");
380
+ return;
381
+ }
382
+ if (!text && !chunks?.length) return;
383
+ if (text) session.pendingText += text;
384
+ try {
385
+ const result = await session.streamer.append({
386
+ ...text ? { markdown_text: text } : {},
387
+ ...chunks?.length ? { chunks } : {}
388
+ });
389
+ if (result) {
390
+ session.delivered = true;
391
+ session.pendingText = "";
392
+ }
393
+ logVerbose(`slack-stream: appended ${text?.length ?? 0} chars, ${chunks?.length ?? 0} chunks (${result ? "flushed" : "buffered"})`);
394
+ } catch (err) {
395
+ if (isBenignSlackFinalizeError(err) && session.pendingText) throw new SlackStreamNotDeliveredError(session.pendingText, extractSlackErrorCode(err) ?? "unknown");
396
+ throw err;
397
+ }
398
+ }
399
+ /**
400
+ * Stop (finalize) a Slack stream.
401
+ *
402
+ * After calling this the stream message becomes a normal Slack message.
403
+ * Optionally include final text to append before stopping.
404
+ *
405
+ * If Slack's `chat.stopStream` responds with a known benign finalize error
406
+ * (see {@link BENIGN_SLACK_FINALIZE_ERROR_CODES}) AND any prior `append`
407
+ * has already landed on Slack, the error is swallowed and the session is
408
+ * marked stopped - the already-delivered text stays visible.
409
+ *
410
+ * If the same benign error fires while text is still only buffered locally
411
+ * (e.g. short replies that never exceeded the SDK's buffer_size), this
412
+ * function throws a {@link SlackStreamNotDeliveredError} carrying that pending
413
+ * text so the caller can deliver it through the normal Slack reply path.
414
+ *
415
+ * All other errors propagate unchanged.
416
+ */
417
+ async function stopSlackStream(params) {
418
+ const { session, text, chunks, metadata } = params;
419
+ if (session.stopped) {
420
+ logVerbose("slack-stream: stream already stopped, ignoring duplicate stop");
421
+ return;
422
+ }
423
+ session.stopped = true;
424
+ if (text) session.pendingText += text;
425
+ logVerbose(`slack-stream: stopping stream in ${session.channel} thread=${session.threadTs}${text ? ` (final text: ${text.length} chars)` : ""}`);
426
+ try {
427
+ await session.streamer.stop(text || chunks?.length || metadata ? {
428
+ ...text ? { markdown_text: text } : {},
429
+ ...chunks?.length ? { chunks } : {},
430
+ ...metadata ? { metadata } : {}
431
+ } : void 0);
432
+ session.delivered = true;
433
+ session.pendingText = "";
434
+ } catch (err) {
435
+ if (isBenignSlackFinalizeError(err)) {
436
+ const code = extractSlackErrorCode(err) ?? "unknown";
437
+ if (session.pendingText) throw new SlackStreamNotDeliveredError(session.pendingText, code);
438
+ if (session.delivered) {
439
+ logVerbose(`slack-stream: finalize rejected by Slack (${code}); prior appends delivered, treating stream as stopped`);
440
+ return;
441
+ }
442
+ }
443
+ throw err;
444
+ }
445
+ logVerbose("slack-stream: stream stopped");
446
+ }
447
+ /**
448
+ * Slack API error codes that indicate `chat.stopStream` (or the
449
+ * `chat.startStream` call the SDK issues inside `stop()` when the buffer
450
+ * never flushed) cannot finalize the stream for the current recipient or
451
+ * team. Either the caller falls back to a normal message (see
452
+ * {@link SlackStreamNotDeliveredError}) or, if prior appends already
453
+ * delivered text, the error is logged verbosely and swallowed.
454
+ */
455
+ const BENIGN_SLACK_FINALIZE_ERROR_CODES = new Set([
456
+ "user_not_found",
457
+ "team_not_found",
458
+ "missing_recipient_user_id"
459
+ ]);
460
+ function isBenignSlackFinalizeError(err) {
461
+ const code = extractSlackErrorCode(err);
462
+ return code !== void 0 && BENIGN_SLACK_FINALIZE_ERROR_CODES.has(code);
463
+ }
464
+ function extractSlackErrorCode(err) {
465
+ if (!err || typeof err !== "object") return;
466
+ const record = err;
467
+ if (record.data && typeof record.data === "object") {
468
+ const inner = record.data.error;
469
+ if (typeof inner === "string") return inner;
470
+ }
471
+ return (typeof record.message === "string" ? record.message : "").match(/An API error occurred:\s*([a-z_][a-z0-9_]*)/i)?.[1];
472
+ }
473
+ function markSlackStreamFallbackDelivered(session) {
474
+ const hadNativeDelivery = session.delivered;
475
+ session.pendingText = "";
476
+ session.delivered = true;
477
+ if (!hadNativeDelivery) session.stopped = true;
478
+ }
479
+ //#endregion
480
+ //#region extensions/slack/src/threading.ts
481
+ function resolveSlackThreadContext(params) {
482
+ const incomingThreadTs = params.message.thread_ts;
483
+ const eventTs = params.message.event_ts;
484
+ const messageTs = params.message.ts ?? eventTs;
485
+ const isThreadReply = typeof incomingThreadTs === "string" && incomingThreadTs.length > 0 && (incomingThreadTs !== messageTs || Boolean(params.message.parent_user_id));
486
+ return {
487
+ incomingThreadTs,
488
+ messageTs,
489
+ isThreadReply,
490
+ replyToId: incomingThreadTs ?? messageTs,
491
+ messageThreadId: isThreadReply ? incomingThreadTs : params.replyToMode === "all" ? messageTs : void 0
492
+ };
493
+ }
494
+ /**
495
+ * Resolves Slack thread targeting for replies and status indicators.
496
+ *
497
+ * @returns replyThreadTs - Thread timestamp for reply messages
498
+ * @returns statusThreadTs - Thread timestamp for status indicators (typing, etc.)
499
+ * @returns isThreadReply - true if this is a genuine user reply in a thread,
500
+ * false if thread_ts comes from a bot status message (e.g. typing indicator)
501
+ */
502
+ function resolveSlackThreadTargets(params) {
503
+ const { incomingThreadTs, messageTs, isThreadReply } = resolveSlackThreadContext(params);
504
+ const replyThreadTs = isThreadReply ? incomingThreadTs : params.replyToMode === "all" ? messageTs : void 0;
505
+ return {
506
+ replyThreadTs,
507
+ statusThreadTs: replyThreadTs,
508
+ isThreadReply
509
+ };
510
+ }
511
+ //#endregion
512
+ //#region extensions/slack/src/monitor/message-handler/preview-finalize.ts
513
+ function buildExpectedSlackEditText(params) {
514
+ return buildSlackEditTextPayload(params.text, params.blocks);
515
+ }
516
+ function blocksMatch(expected, actual) {
517
+ if (!expected?.length) return !actual?.length;
518
+ if (!actual?.length) return false;
519
+ return JSON.stringify(expected) === JSON.stringify(actual);
520
+ }
521
+ async function readSlackMessageAfterEditError(params) {
522
+ if (params.threadTs) return ((await params.client.conversations.replies({
523
+ token: params.token,
524
+ channel: params.channelId,
525
+ ts: params.threadTs,
526
+ latest: params.messageId,
527
+ inclusive: true,
528
+ limit: 100
529
+ })).messages ?? []).find((message) => message?.ts === params.messageId) ?? null;
530
+ const message = (await params.client.conversations.history({
531
+ token: params.token,
532
+ channel: params.channelId,
533
+ latest: params.messageId,
534
+ oldest: params.messageId,
535
+ inclusive: true,
536
+ limit: 1
537
+ })).messages?.[0];
538
+ if (!message?.ts || message.ts !== params.messageId) return null;
539
+ return message;
540
+ }
541
+ async function didSlackPreviewEditApplyAfterError(params) {
542
+ const readback = await readSlackMessageAfterEditError(params);
543
+ if (!readback) return false;
544
+ const expectedText = buildExpectedSlackEditText({
545
+ text: params.text,
546
+ blocks: params.blocks
547
+ });
548
+ const actualText = normalizeSlackOutboundText((readback.text ?? "").trim());
549
+ if (params.blocks?.length) return actualText === expectedText && blocksMatch(params.blocks, readback.blocks);
550
+ return actualText === expectedText;
551
+ }
552
+ async function finalizeSlackPreviewEdit(params) {
553
+ try {
554
+ await editSlackMessage(params.channelId, params.messageId, params.text, {
555
+ token: params.token,
556
+ accountId: params.accountId,
557
+ client: params.client,
558
+ ...params.blocks?.length ? { blocks: params.blocks } : {}
559
+ });
560
+ return;
561
+ } catch (err) {
562
+ try {
563
+ if (await didSlackPreviewEditApplyAfterError({
564
+ client: params.client,
565
+ token: params.token,
566
+ channelId: params.channelId,
567
+ messageId: params.messageId,
568
+ text: params.text,
569
+ blocks: params.blocks,
570
+ threadTs: params.threadTs
571
+ })) {
572
+ logVerbose(`slack: preview final edit response failed but readback matched message ${params.channelId}/${params.messageId}; suppressing duplicate fallback send`);
573
+ return;
574
+ }
575
+ } catch (readbackErr) {
576
+ logVerbose(`slack: preview final edit readback failed (${String(readbackErr)})`);
577
+ }
578
+ throw err;
579
+ }
580
+ }
581
+ //#endregion
582
+ //#region extensions/slack/src/monitor/message-handler/dispatch.ts
583
+ const UNICODE_TO_SLACK = {
584
+ "👀": "eyes",
585
+ "🤔": "thinking_face",
586
+ "🔥": "fire",
587
+ "👨‍💻": "male-technologist",
588
+ "👨💻": "male-technologist",
589
+ "👩‍💻": "female-technologist",
590
+ "⚡": "zap",
591
+ "🌐": "globe_with_meridians",
592
+ "✅": "white_check_mark",
593
+ "👍": "thumbsup",
594
+ "❌": "x",
595
+ "😱": "scream",
596
+ "🥱": "yawning_face",
597
+ "😨": "fearful",
598
+ "⏳": "hourglass_flowing_sand",
599
+ "⚠️": "warning",
600
+ "✍": "writing_hand",
601
+ "🗜️": "compression",
602
+ "🗜": "compression",
603
+ "🧠": "brain",
604
+ "🛠️": "hammer_and_wrench",
605
+ "💻": "computer"
606
+ };
607
+ function resolveSlackMessageTimestampMs(message) {
608
+ const ts = message.event_ts ?? message.ts;
609
+ if (!ts) return;
610
+ const parsed = Number(ts);
611
+ return Number.isFinite(parsed) ? Math.trunc(parsed * 1e3) : void 0;
612
+ }
613
+ function resolveSlackBotLoopProtection(prepared) {
614
+ const senderBotId = prepared.message.bot_id;
615
+ if (!senderBotId) return;
616
+ const receiverBotId = prepared.ctx.botId || prepared.ctx.botUserId;
617
+ if (!receiverBotId || senderBotId === prepared.ctx.botId || prepared.message.user === prepared.ctx.botUserId) return;
618
+ return {
619
+ scopeId: prepared.route.accountId,
620
+ conversationId: prepared.message.channel,
621
+ senderId: senderBotId,
622
+ receiverId: receiverBotId,
623
+ config: mergePairLoopGuardConfig(prepared.account.config.botLoopProtection, prepared.channelConfig?.botLoopProtection),
624
+ defaultsConfig: prepared.ctx.cfg.channels?.defaults?.botLoopProtection,
625
+ defaultEnabled: true,
626
+ nowMs: resolveSlackMessageTimestampMs(prepared.message)
627
+ };
628
+ }
629
+ function toSlackEmojiName(emoji) {
630
+ let trimmed = emoji.trim();
631
+ while (trimmed.startsWith(":")) trimmed = trimmed.slice(1);
632
+ while (trimmed.endsWith(":")) trimmed = trimmed.slice(0, -1);
633
+ return UNICODE_TO_SLACK[trimmed] ?? trimmed;
634
+ }
635
+ function isSlackStreamingEnabled(params) {
636
+ if (params.mode === "partial") return params.nativeStreaming;
637
+ if (params.mode === "progress") return params.nativeStreaming && params.nativeProgressTaskCards === true;
638
+ return false;
639
+ }
640
+ function shouldEnableSlackPreviewStreaming(params) {
641
+ return params.mode !== "off";
642
+ }
643
+ function shouldInitializeSlackDraftStream(params) {
644
+ return params.previewStreamingEnabled && !params.useStreaming;
645
+ }
646
+ function resolveSlackDisableBlockStreaming(params) {
647
+ if (params.useStreaming || params.shouldUseDraftStream) return true;
648
+ return typeof params.blockStreamingEnabled === "boolean" ? !params.blockStreamingEnabled : void 0;
649
+ }
650
+ function resolveExplicitSlackProgressTitle(entry) {
651
+ const label = resolveChannelProgressDraftConfig(entry).label;
652
+ if (typeof label !== "string") return;
653
+ const trimmed = label.trim();
654
+ return trimmed && trimmed.toLowerCase() !== "auto" ? trimmed : void 0;
655
+ }
656
+ function resolveSlackNativeProgressTaskCards(entry) {
657
+ const streaming = entry?.streaming;
658
+ if (!streaming || typeof streaming !== "object" || Array.isArray(streaming)) return false;
659
+ const progressConfig = streaming.progress;
660
+ return Boolean(progressConfig) && typeof progressConfig === "object" && !Array.isArray(progressConfig) && progressConfig.nativeTaskCards === true;
661
+ }
662
+ function resolveSlackStreamingThreadHint(params) {
663
+ return resolveSlackThreadTs({
664
+ replyToMode: params.replyToMode,
665
+ incomingThreadTs: params.incomingThreadTs,
666
+ messageTs: params.messageTs,
667
+ hasReplied: false,
668
+ isThreadReply: params.isThreadReply
669
+ });
670
+ }
671
+ const SLACK_STREAM_RECIPIENT_TEAM_CACHE_MAX = 2e3;
672
+ const slackStreamRecipientTeamCache = /* @__PURE__ */ new Map();
673
+ function buildSlackEventDeliveryKey(params) {
674
+ const reply = resolveSendableOutboundReplyParts(params.payload, { text: params.textOverride });
675
+ const slackBlocks = readSlackReplyBlocks(params.payload);
676
+ if (!reply.hasContent && !slackBlocks?.length) return null;
677
+ return JSON.stringify({
678
+ kind: params.kind,
679
+ threadTs: params.threadTs ?? "",
680
+ replyToId: params.payload.replyToId ?? null,
681
+ text: reply.trimmedText,
682
+ mediaUrls: reply.mediaUrls,
683
+ blocks: slackBlocks ?? null
684
+ });
685
+ }
686
+ function readSlackStreamRecipientTeamCache(params) {
687
+ if (!params.fallbackTeamId || !params.userId) return;
688
+ const cacheKey = `${params.fallbackTeamId}:${params.userId}`;
689
+ const cached = slackStreamRecipientTeamCache.get(cacheKey);
690
+ if (!cached) return;
691
+ slackStreamRecipientTeamCache.delete(cacheKey);
692
+ slackStreamRecipientTeamCache.set(cacheKey, cached);
693
+ return cached;
694
+ }
695
+ function rememberSlackStreamRecipientTeam(params) {
696
+ if (!params.fallbackTeamId || !params.userId) return;
697
+ const cacheKey = `${params.fallbackTeamId}:${params.userId}`;
698
+ if (slackStreamRecipientTeamCache.has(cacheKey)) slackStreamRecipientTeamCache.delete(cacheKey);
699
+ slackStreamRecipientTeamCache.set(cacheKey, params.teamId);
700
+ if (slackStreamRecipientTeamCache.size > SLACK_STREAM_RECIPIENT_TEAM_CACHE_MAX) {
701
+ const oldest = slackStreamRecipientTeamCache.keys().next().value;
702
+ if (oldest) slackStreamRecipientTeamCache.delete(oldest);
703
+ }
704
+ }
705
+ function createSlackEventDeliveryTracker() {
706
+ const deliveredKeys = /* @__PURE__ */ new Set();
707
+ return {
708
+ hasDelivered(params) {
709
+ const key = buildSlackEventDeliveryKey(params);
710
+ return key ? deliveredKeys.has(key) : false;
711
+ },
712
+ markDelivered(params) {
713
+ const key = buildSlackEventDeliveryKey(params);
714
+ if (key) deliveredKeys.add(key);
715
+ }
716
+ };
717
+ }
718
+ function shouldUseStreaming(params) {
719
+ if (!params.streamingEnabled) return false;
720
+ if (!params.threadTs) {
721
+ logVerbose("slack-stream: streaming disabled — no reply thread target available");
722
+ return false;
723
+ }
724
+ return true;
725
+ }
726
+ async function resolveSlackStreamRecipientTeamId(params) {
727
+ const cachedTeamId = readSlackStreamRecipientTeamCache(params);
728
+ if (cachedTeamId) return cachedTeamId;
729
+ if (params.userId) try {
730
+ const info = await params.client.users.info({
731
+ token: params.token,
732
+ user: params.userId
733
+ });
734
+ const teamId = info.user?.team_id ?? info.user?.profile?.team;
735
+ if (teamId) {
736
+ rememberSlackStreamRecipientTeam({
737
+ ...params,
738
+ teamId
739
+ });
740
+ return teamId;
741
+ }
742
+ } catch (err) {
743
+ logVerbose(`slack-stream: users.info team lookup failed (${formatErrorMessage(err)})`);
744
+ }
745
+ return params.fallbackTeamId;
746
+ }
747
+ async function dispatchPreparedSlackMessage(prepared) {
748
+ const { ctx, account, message, route } = prepared;
749
+ const cfg = ctx.cfg;
750
+ const runtime = ctx.runtime;
751
+ const outboundIdentity = resolveAgentOutboundIdentity(cfg, route.agentId);
752
+ const slackIdentity = outboundIdentity ? {
753
+ username: outboundIdentity.name,
754
+ iconUrl: outboundIdentity.avatarUrl,
755
+ iconEmoji: outboundIdentity.emoji
756
+ } : void 0;
757
+ if (prepared.isDirectMessage) {
758
+ const sessionCfg = cfg.session;
759
+ const storePath = resolveStorePath(sessionCfg?.store, { agentId: route.agentId });
760
+ const pinnedMainDmOwner = resolvePinnedMainDmOwnerFromAllowlist({
761
+ dmScope: cfg.session?.dmScope,
762
+ allowFrom: ctx.allowFrom,
763
+ normalizeEntry: normalizeSlackAllowOwnerEntry
764
+ });
765
+ const senderRecipient = normalizeOptionalLowercaseString(message.user);
766
+ const inboundLastRouteSessionKey = resolveInboundLastRouteSessionKey({
767
+ route,
768
+ sessionKey: prepared.ctxPayload.SessionKey ?? route.sessionKey
769
+ });
770
+ if (inboundLastRouteSessionKey === route.mainSessionKey && pinnedMainDmOwner && senderRecipient && normalizeOptionalLowercaseString(pinnedMainDmOwner) !== senderRecipient) logVerbose(`slack: skip main-session last route for ${senderRecipient} (pinned owner ${pinnedMainDmOwner})`);
771
+ else await updateLastRoute({
772
+ storePath,
773
+ sessionKey: inboundLastRouteSessionKey,
774
+ deliveryContext: {
775
+ channel: "slack",
776
+ to: `user:${message.user}`,
777
+ accountId: route.accountId,
778
+ threadId: prepared.ctxPayload.MessageThreadId ?? prepared.ctxPayload.TransportThreadId
779
+ },
780
+ ctx: prepared.ctxPayload
781
+ });
782
+ }
783
+ const threadTargets = resolveSlackThreadTargets({
784
+ message,
785
+ replyToMode: prepared.replyToMode
786
+ });
787
+ const forcedReplyThreadTs = prepared.forcedReplyThreadTs;
788
+ const slackMessageMetadata = prepared.slackMessageMetadata;
789
+ const statusThreadTs = forcedReplyThreadTs ?? threadTargets.statusThreadTs;
790
+ const isThreadReply = threadTargets.isThreadReply;
791
+ const replyDeliveryMode = forcedReplyThreadTs ? "off" : prepared.replyToMode;
792
+ const sourceReplyDeliveryMode = resolveChannelMessageSourceReplyDeliveryMode({
793
+ cfg,
794
+ ctx: prepared.ctxPayload
795
+ });
796
+ const sourceRepliesAreToolOnly = sourceReplyDeliveryMode === "message_tool_only";
797
+ const reactionMessageTs = prepared.ackReactionMessageTs;
798
+ const messageTs = message.ts ?? message.event_ts;
799
+ const incomingThreadTs = message.thread_ts;
800
+ let didSetStatus = false;
801
+ const statusReactionsEnabled = Boolean(prepared.ackReactionPromise) && Boolean(reactionMessageTs) && cfg.messages?.statusReactions?.enabled !== false;
802
+ const slackStatusAdapter = {
803
+ setReaction: async (emoji) => {
804
+ await reactSlackMessage(message.channel, reactionMessageTs ?? "", toSlackEmojiName(emoji), {
805
+ token: ctx.botToken,
806
+ client: ctx.app.client
807
+ }).catch((err) => {
808
+ if (formatErrorMessage(err).includes("already_reacted")) return;
809
+ throw err;
810
+ });
811
+ },
812
+ removeReaction: async (emoji) => {
813
+ await removeSlackReaction(message.channel, reactionMessageTs ?? "", toSlackEmojiName(emoji), {
814
+ token: ctx.botToken,
815
+ client: ctx.app.client
816
+ }).catch((err) => {
817
+ if (formatErrorMessage(err).includes("no_reaction")) return;
818
+ throw err;
819
+ });
820
+ }
821
+ };
822
+ const statusReactionTiming = {
823
+ ...DEFAULT_TIMING,
824
+ ...cfg.messages?.statusReactions?.timing
825
+ };
826
+ const statusReactions = createStatusReactionController({
827
+ enabled: statusReactionsEnabled,
828
+ adapter: slackStatusAdapter,
829
+ initialEmoji: prepared.ackReactionValue || "eyes",
830
+ emojis: cfg.messages?.statusReactions?.emojis,
831
+ timing: cfg.messages?.statusReactions?.timing,
832
+ onError: (err) => {
833
+ logAckFailure({
834
+ log: logVerbose,
835
+ channel: "slack",
836
+ target: `${message.channel}/${message.ts}`,
837
+ error: err
838
+ });
839
+ }
840
+ });
841
+ if (statusReactionsEnabled) statusReactions.setQueued();
842
+ const hasRepliedRef = { value: false };
843
+ const replyPlan = createSlackReplyDeliveryPlan({
844
+ replyToMode: replyDeliveryMode,
845
+ incomingThreadTs: forcedReplyThreadTs ?? incomingThreadTs,
846
+ messageTs,
847
+ hasRepliedRef,
848
+ isThreadReply: Boolean(forcedReplyThreadTs) || isThreadReply
849
+ });
850
+ const typingTarget = statusThreadTs ? `${message.channel}/${statusThreadTs}` : message.channel;
851
+ const typingReaction = ctx.typingReaction;
852
+ const { onModelSelected, ...replyPipeline } = createChannelMessageReplyPipeline({
853
+ cfg,
854
+ agentId: route.agentId,
855
+ channel: "slack",
856
+ accountId: route.accountId,
857
+ transformReplyPayload: (payload) => {
858
+ if (payload.isReasoning === true) return null;
859
+ return isSlackInteractiveRepliesEnabled({
860
+ cfg,
861
+ accountId: route.accountId
862
+ }) ? compileSlackInteractiveReplies(payload) : payload;
863
+ },
864
+ typing: {
865
+ start: async () => {
866
+ didSetStatus = true;
867
+ await ctx.setSlackThreadStatus({
868
+ channelId: message.channel,
869
+ threadTs: statusThreadTs,
870
+ status: "is typing..."
871
+ });
872
+ if (typingReaction && message.ts) await reactSlackMessage(message.channel, message.ts, typingReaction, {
873
+ token: ctx.botToken,
874
+ client: ctx.app.client
875
+ }).catch(() => {});
876
+ },
877
+ stop: async () => {
878
+ if (!didSetStatus) return;
879
+ didSetStatus = false;
880
+ await ctx.setSlackThreadStatus({
881
+ channelId: message.channel,
882
+ threadTs: statusThreadTs,
883
+ status: ""
884
+ });
885
+ if (typingReaction && message.ts) await removeSlackReaction(message.channel, message.ts, typingReaction, {
886
+ token: ctx.botToken,
887
+ client: ctx.app.client
888
+ }).catch(() => {});
889
+ },
890
+ onStartError: (err) => {
891
+ logTypingFailure({
892
+ log: (message) => runtime.error?.(danger(message)),
893
+ channel: "slack",
894
+ action: "start",
895
+ target: typingTarget,
896
+ error: err
897
+ });
898
+ },
899
+ onStopError: (err) => {
900
+ logTypingFailure({
901
+ log: (message) => runtime.error?.(danger(message)),
902
+ channel: "slack",
903
+ action: "stop",
904
+ target: typingTarget,
905
+ error: err
906
+ });
907
+ }
908
+ }
909
+ });
910
+ const slackStreaming = resolveSlackStreamingConfig({
911
+ streaming: account.config.streaming,
912
+ nativeStreaming: resolveChannelStreamingNativeTransport(account.config)
913
+ });
914
+ const streamThreadHint = forcedReplyThreadTs ?? resolveSlackStreamingThreadHint({
915
+ replyToMode: replyDeliveryMode,
916
+ incomingThreadTs,
917
+ messageTs,
918
+ isThreadReply
919
+ });
920
+ const previewStreamingEnabled = !sourceRepliesAreToolOnly && shouldEnableSlackPreviewStreaming({ mode: slackStreaming.mode });
921
+ const useStreaming = shouldUseStreaming({
922
+ streamingEnabled: !sourceRepliesAreToolOnly && isSlackStreamingEnabled({
923
+ mode: slackStreaming.mode,
924
+ nativeStreaming: slackStreaming.nativeStreaming,
925
+ nativeProgressTaskCards: resolveSlackNativeProgressTaskCards(account.config)
926
+ }),
927
+ threadTs: streamThreadHint
928
+ });
929
+ const shouldUseDraftStream = shouldInitializeSlackDraftStream({
930
+ previewStreamingEnabled,
931
+ useStreaming
932
+ });
933
+ const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
934
+ const disableBlockStreaming = sourceRepliesAreToolOnly ? true : resolveSlackDisableBlockStreaming({
935
+ useStreaming,
936
+ shouldUseDraftStream,
937
+ blockStreamingEnabled
938
+ });
939
+ let streamSession = null;
940
+ let nativeProgressStreamStartPromise = null;
941
+ let nativeProgressStreamThreadTs;
942
+ let streamFailed = false;
943
+ let usedReplyThreadTs;
944
+ let usedBlockReplyThreadTs;
945
+ let observedReplyDelivery = false;
946
+ let observedFinalReplyDelivery = false;
947
+ const deliveryTracker = createSlackEventDeliveryTracker();
948
+ const resolveDeliveryThreadTs = (params) => {
949
+ const plannedThreadTs = params.forcedThreadTs ? void 0 : replyPlan.nextThreadTs();
950
+ return params.forcedThreadTs ?? plannedThreadTs ?? (params.kind === "block" ? usedBlockReplyThreadTs : void 0);
951
+ };
952
+ const rememberDeliveredThreadTs = (kind, deliveredThreadTs) => {
953
+ if (!deliveredThreadTs) return;
954
+ usedReplyThreadTs ??= deliveredThreadTs;
955
+ if (kind === "block") usedBlockReplyThreadTs = deliveredThreadTs;
956
+ };
957
+ const deliverPendingStreamFallback = async (session, err) => {
958
+ const fallbackText = err.pendingText.trim();
959
+ if (!fallbackText) return false;
960
+ try {
961
+ await deliverReplies({
962
+ cfg: ctx.cfg,
963
+ replies: [{ text: fallbackText }],
964
+ target: prepared.replyTarget,
965
+ token: ctx.botToken,
966
+ accountId: account.accountId,
967
+ runtime,
968
+ textLimit: ctx.textLimit,
969
+ replyThreadTs: session.threadTs,
970
+ replyToMode: replyDeliveryMode,
971
+ ...slackIdentity ? { identity: slackIdentity } : {},
972
+ ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
973
+ });
974
+ markSlackStreamFallbackDelivered(session);
975
+ observedReplyDelivery = true;
976
+ usedReplyThreadTs ??= session.threadTs;
977
+ logVerbose(`slack-stream: streamed delivery failed (${err.slackCode}); delivered ${fallbackText.length} chars via deliverReplies fallback`);
978
+ return true;
979
+ } catch (postErr) {
980
+ runtime.error?.(danger(`slack-stream: fallback deliverReplies failed after ${err.slackCode}: ${formatErrorMessage(postErr)}`));
981
+ return false;
982
+ }
983
+ };
984
+ const deliverNormally = async (params) => {
985
+ if (params.payload.isReasoning === true) return;
986
+ const replyThreadTs = resolveDeliveryThreadTs(params);
987
+ if (deliveryTracker.hasDelivered({
988
+ kind: params.kind,
989
+ payload: params.payload,
990
+ threadTs: replyThreadTs
991
+ })) {
992
+ logVerbose("slack: suppressed duplicate normal delivery within the same turn");
993
+ return;
994
+ }
995
+ await deliverReplies({
996
+ cfg: ctx.cfg,
997
+ replies: [params.payload],
998
+ target: prepared.replyTarget,
999
+ token: ctx.botToken,
1000
+ accountId: account.accountId,
1001
+ runtime,
1002
+ textLimit: ctx.textLimit,
1003
+ replyThreadTs,
1004
+ replyToMode: replyDeliveryMode,
1005
+ ...slackIdentity ? { identity: slackIdentity } : {},
1006
+ ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
1007
+ });
1008
+ observedReplyDelivery = true;
1009
+ if (params.kind === "final") observedFinalReplyDelivery = true;
1010
+ const deliveredThreadTs = resolveDeliveredSlackReplyThreadTs({
1011
+ replyToMode: replyDeliveryMode,
1012
+ payloadReplyToId: params.payload.replyToId,
1013
+ replyThreadTs
1014
+ });
1015
+ rememberDeliveredThreadTs(params.kind, deliveredThreadTs);
1016
+ replyPlan.markSent();
1017
+ deliveryTracker.markDelivered({
1018
+ kind: params.kind,
1019
+ payload: params.payload,
1020
+ threadTs: replyThreadTs
1021
+ });
1022
+ };
1023
+ const deliverBufferedStreamFallback = async (params) => {
1024
+ if (!await deliverPendingStreamFallback(params.session, params.err)) return false;
1025
+ replyPlan.markSent();
1026
+ if (params.kind === "final") observedFinalReplyDelivery = true;
1027
+ deliveryTracker.markDelivered({
1028
+ kind: params.kind,
1029
+ payload: params.payload,
1030
+ threadTs: params.session.threadTs,
1031
+ textOverride: params.textOverride
1032
+ });
1033
+ rememberDeliveredThreadTs(params.kind, params.session.threadTs);
1034
+ return true;
1035
+ };
1036
+ const deliverWithStreaming = async (params) => {
1037
+ if (params.payload.isReasoning === true) return;
1038
+ const reply = resolveSendableOutboundReplyParts(params.payload);
1039
+ if (streamFailed || reply.hasMedia || readSlackReplyBlocks(params.payload)?.length || !reply.hasText) {
1040
+ await deliverNormally({
1041
+ payload: params.payload,
1042
+ kind: params.kind,
1043
+ forcedThreadTs: streamSession?.threadTs ?? nativeProgressStreamThreadTs
1044
+ });
1045
+ return;
1046
+ }
1047
+ const text = reply.trimmedText;
1048
+ let plannedThreadTs;
1049
+ try {
1050
+ if (!streamSession && nativeProgressStreamStartPromise) await nativeProgressStreamStartPromise;
1051
+ if (streamFailed) {
1052
+ await deliverNormally({
1053
+ payload: params.payload,
1054
+ kind: params.kind,
1055
+ forcedThreadTs: streamSession?.threadTs ?? nativeProgressStreamThreadTs
1056
+ });
1057
+ return;
1058
+ }
1059
+ if (useNativeProgressStreaming && !streamSession) {
1060
+ await deliverNormally({
1061
+ payload: params.payload,
1062
+ kind: params.kind
1063
+ });
1064
+ return;
1065
+ }
1066
+ if (!streamSession) {
1067
+ const streamThreadTs = replyPlan.nextThreadTs();
1068
+ plannedThreadTs = streamThreadTs;
1069
+ if (!streamThreadTs) {
1070
+ logVerbose("slack-stream: no reply thread target for stream start, falling back to normal delivery");
1071
+ streamFailed = true;
1072
+ await deliverNormally({
1073
+ payload: params.payload,
1074
+ kind: params.kind
1075
+ });
1076
+ return;
1077
+ }
1078
+ if (deliveryTracker.hasDelivered({
1079
+ kind: params.kind,
1080
+ payload: params.payload,
1081
+ threadTs: streamThreadTs,
1082
+ textOverride: text
1083
+ })) {
1084
+ logVerbose("slack-stream: suppressed duplicate stream start payload");
1085
+ return;
1086
+ }
1087
+ streamSession = await startSlackStream({
1088
+ client: ctx.app.client,
1089
+ channel: message.channel,
1090
+ threadTs: streamThreadTs,
1091
+ text,
1092
+ teamId: await resolveSlackStreamRecipientTeamId({
1093
+ client: ctx.app.client,
1094
+ token: ctx.botToken,
1095
+ userId: message.user,
1096
+ fallbackTeamId: ctx.teamId
1097
+ }),
1098
+ userId: message.user
1099
+ });
1100
+ if (streamSession.delivered) {
1101
+ observedReplyDelivery = true;
1102
+ if (params.kind === "final") observedFinalReplyDelivery = true;
1103
+ }
1104
+ rememberDeliveredThreadTs(params.kind, streamThreadTs);
1105
+ replyPlan.markSent();
1106
+ deliveryTracker.markDelivered({
1107
+ kind: params.kind,
1108
+ payload: params.payload,
1109
+ threadTs: streamThreadTs,
1110
+ textOverride: text
1111
+ });
1112
+ return;
1113
+ }
1114
+ if (deliveryTracker.hasDelivered({
1115
+ kind: params.kind,
1116
+ payload: params.payload,
1117
+ threadTs: streamSession.threadTs,
1118
+ textOverride: text
1119
+ })) {
1120
+ logVerbose("slack-stream: suppressed duplicate append payload");
1121
+ return;
1122
+ }
1123
+ const completionChunks = useNativeProgressStreaming && !nativeProgressCompletionSent && previewToolProgressLines.length > 0 ? buildSlackProgressStreamCompletionChunks({
1124
+ title: explicitProgressTitle,
1125
+ lines: previewToolProgressLines,
1126
+ maxLineChars: progressDraftMaxLineChars,
1127
+ finalInProgressStatus: params.payload.isError ? "error" : "complete"
1128
+ }) : void 0;
1129
+ if (useNativeProgressStreaming) {
1130
+ if (completionChunks?.length) {
1131
+ await appendSlackStream({
1132
+ session: streamSession,
1133
+ chunks: completionChunks
1134
+ });
1135
+ nativeProgressCompletionSent = true;
1136
+ if (streamSession.delivered) observedReplyDelivery = true;
1137
+ }
1138
+ await deliverNormally({
1139
+ payload: params.payload,
1140
+ kind: params.kind,
1141
+ forcedThreadTs: streamSession.threadTs
1142
+ });
1143
+ return;
1144
+ }
1145
+ await appendSlackStream({
1146
+ session: streamSession,
1147
+ text: "\n" + text,
1148
+ chunks: completionChunks
1149
+ });
1150
+ if (completionChunks?.length) nativeProgressCompletionSent = true;
1151
+ if (streamSession.delivered) {
1152
+ observedReplyDelivery = true;
1153
+ if (params.kind === "final") observedFinalReplyDelivery = true;
1154
+ }
1155
+ deliveryTracker.markDelivered({
1156
+ kind: params.kind,
1157
+ payload: params.payload,
1158
+ threadTs: streamSession.threadTs,
1159
+ textOverride: text
1160
+ });
1161
+ } catch (err) {
1162
+ if (err instanceof SlackStreamNotDeliveredError) {
1163
+ streamFailed = true;
1164
+ if (streamSession) {
1165
+ if (await deliverBufferedStreamFallback({
1166
+ session: streamSession,
1167
+ err,
1168
+ payload: params.payload,
1169
+ kind: params.kind,
1170
+ textOverride: text
1171
+ })) return;
1172
+ throw err;
1173
+ }
1174
+ await deliverNormally({
1175
+ payload: params.payload,
1176
+ kind: params.kind,
1177
+ forcedThreadTs: plannedThreadTs
1178
+ });
1179
+ return;
1180
+ }
1181
+ runtime.error?.(danger(`slack-stream: streaming API call failed: ${formatSlackError(err)}, falling back`));
1182
+ streamFailed = true;
1183
+ if (streamSession && streamSession.pendingText) {
1184
+ const bufferedFallbackErr = new SlackStreamNotDeliveredError(streamSession.pendingText, "unknown");
1185
+ if (await deliverBufferedStreamFallback({
1186
+ session: streamSession,
1187
+ err: bufferedFallbackErr,
1188
+ payload: params.payload,
1189
+ kind: params.kind,
1190
+ textOverride: text
1191
+ })) return;
1192
+ }
1193
+ await deliverNormally({
1194
+ payload: params.payload,
1195
+ kind: params.kind,
1196
+ forcedThreadTs: streamSession?.threadTs ?? plannedThreadTs
1197
+ });
1198
+ }
1199
+ };
1200
+ let draftPreviewCommitted = false;
1201
+ const deliverSlackPayload = async (payload, info) => {
1202
+ if (payload.isReasoning === true) return { visibleReplySent: false };
1203
+ if (useStreaming) {
1204
+ await deliverWithStreaming({
1205
+ payload,
1206
+ kind: info.kind
1207
+ });
1208
+ return;
1209
+ }
1210
+ const reply = resolveSendableOutboundReplyParts(payload);
1211
+ const slackBlocks = readSlackReplyBlocks(payload);
1212
+ const ttsSupplement = getReplyPayloadTtsSupplement(payload);
1213
+ const trimmedFinalText = (payload.text ?? ttsSupplement?.spokenText ?? "").trim();
1214
+ const shouldRestoreTtsSupplementTextForPreviewFallback = Boolean(ttsSupplement) && ttsSupplement?.visibleTextAlreadyDelivered !== true && Boolean(draftStream) && !draftPreviewCommitted && !observedFinalReplyDelivery && previewStreamingEnabled && !payload.text?.trim();
1215
+ if (info.kind === "final" && ttsSupplement && draftStream && !draftPreviewCommitted && !observedFinalReplyDelivery && previewStreamingEnabled && !payload.isError && trimmedFinalText.length > 0) {
1216
+ const channelId = draftStream.channelId();
1217
+ const messageId = draftStream.messageId();
1218
+ if (channelId && messageId) {
1219
+ const finalThreadTs = usedReplyThreadTs ?? statusThreadTs;
1220
+ await draftStream.flush();
1221
+ await draftStream.seal();
1222
+ try {
1223
+ await finalizeSlackPreviewEdit({
1224
+ client: ctx.app.client,
1225
+ token: ctx.botToken,
1226
+ accountId: account.accountId,
1227
+ channelId,
1228
+ messageId,
1229
+ text: normalizeSlackOutboundText(trimmedFinalText),
1230
+ ...slackBlocks?.length ? { blocks: slackBlocks } : {},
1231
+ threadTs: finalThreadTs
1232
+ });
1233
+ } catch (err) {
1234
+ logVerbose(`slack: preview final edit failed; falling back to standard send (${formatSlackError(err)})`);
1235
+ await draftStream.discardPending();
1236
+ let delivered = false;
1237
+ try {
1238
+ await deliverNormally({
1239
+ payload: payload.text?.trim() ? payload : {
1240
+ ...payload,
1241
+ text: trimmedFinalText
1242
+ },
1243
+ kind: info.kind,
1244
+ forcedThreadTs: finalThreadTs
1245
+ });
1246
+ delivered = true;
1247
+ } finally {
1248
+ if (delivered) await draftStream.clear();
1249
+ }
1250
+ return;
1251
+ }
1252
+ draftPreviewCommitted = true;
1253
+ observedFinalReplyDelivery = true;
1254
+ observedReplyDelivery = true;
1255
+ replyPlan.markSent();
1256
+ await deliverNormally({
1257
+ payload: buildTtsSupplementMediaPayload(payload),
1258
+ kind: info.kind,
1259
+ forcedThreadTs: finalThreadTs
1260
+ });
1261
+ deliveryTracker.markDelivered({
1262
+ kind: info.kind,
1263
+ payload,
1264
+ threadTs: finalThreadTs
1265
+ });
1266
+ return;
1267
+ }
1268
+ }
1269
+ if ((await deliverWithFinalizableLivePreviewAdapter({
1270
+ kind: info.kind,
1271
+ payload,
1272
+ adapter: defineFinalizableLivePreviewAdapter({
1273
+ draft: draftStream && !draftPreviewCommitted && !observedFinalReplyDelivery ? {
1274
+ flush: draftStream.flush,
1275
+ clear: draftStream.clear,
1276
+ discardPending: draftStream.discardPending,
1277
+ seal: draftStream.seal,
1278
+ id: () => {
1279
+ const channelId = draftStream.channelId();
1280
+ const messageId = draftStream.messageId();
1281
+ return channelId && messageId ? {
1282
+ channelId,
1283
+ messageId
1284
+ } : void 0;
1285
+ }
1286
+ } : void 0,
1287
+ buildFinalEdit: () => {
1288
+ if (!previewStreamingEnabled || reply.hasMedia && !ttsSupplement || payload.isError || trimmedFinalText.length === 0 && !slackBlocks?.length) return;
1289
+ return {
1290
+ text: normalizeSlackOutboundText(trimmedFinalText),
1291
+ blocks: slackBlocks,
1292
+ threadTs: usedReplyThreadTs ?? statusThreadTs
1293
+ };
1294
+ },
1295
+ editFinal: async (preview, edit) => {
1296
+ if (deliveryTracker.hasDelivered({
1297
+ kind: info.kind,
1298
+ payload,
1299
+ threadTs: edit.threadTs
1300
+ })) return;
1301
+ await finalizeSlackPreviewEdit({
1302
+ client: ctx.app.client,
1303
+ token: ctx.botToken,
1304
+ accountId: account.accountId,
1305
+ channelId: preview.channelId,
1306
+ messageId: preview.messageId,
1307
+ text: edit.text,
1308
+ ...edit.blocks?.length ? { blocks: edit.blocks } : {},
1309
+ threadTs: edit.threadTs
1310
+ });
1311
+ draftPreviewCommitted = true;
1312
+ observedFinalReplyDelivery = true;
1313
+ },
1314
+ onPreviewFinalized: (_preview) => {
1315
+ draftPreviewCommitted = true;
1316
+ observedFinalReplyDelivery = true;
1317
+ const finalThreadTs = usedReplyThreadTs ?? statusThreadTs;
1318
+ observedReplyDelivery = true;
1319
+ replyPlan.markSent();
1320
+ deliveryTracker.markDelivered({
1321
+ kind: info.kind,
1322
+ payload,
1323
+ threadTs: finalThreadTs
1324
+ });
1325
+ },
1326
+ buildSupplementalPayload: () => ttsSupplement ? buildTtsSupplementMediaPayload(payload) : void 0,
1327
+ deliverSupplemental: async (supplementalPayload) => {
1328
+ await deliverNormally({
1329
+ payload: supplementalPayload,
1330
+ kind: info.kind
1331
+ });
1332
+ },
1333
+ logPreviewEditFailure: (err) => {
1334
+ logVerbose(`slack: preview final edit failed; falling back to standard send (${formatSlackError(err)})`);
1335
+ }
1336
+ }),
1337
+ deliverNormally: async () => {
1338
+ await deliverNormally({
1339
+ payload: shouldRestoreTtsSupplementTextForPreviewFallback ? {
1340
+ ...payload,
1341
+ text: ttsSupplement?.spokenText
1342
+ } : payload,
1343
+ kind: info.kind
1344
+ });
1345
+ }
1346
+ })).kind === "preview-finalized") return;
1347
+ };
1348
+ const onSlackDeliveryError = (err, info) => {
1349
+ runtime.error?.(danger(`slack ${info.kind} reply failed: ${formatSlackError(err)}`));
1350
+ replyPipeline.typingCallbacks?.onIdle?.();
1351
+ };
1352
+ const draftStream = shouldUseDraftStream ? createSlackDraftStream({
1353
+ target: prepared.replyTarget,
1354
+ cfg,
1355
+ token: ctx.botToken,
1356
+ accountId: account.accountId,
1357
+ identity: slackIdentity,
1358
+ ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
1359
+ maxChars: Math.min(ctx.textLimit, SLACK_TEXT_LIMIT),
1360
+ resolveThreadTs: () => {
1361
+ const ts = replyPlan.peekThreadTs();
1362
+ if (ts) usedReplyThreadTs ??= ts;
1363
+ return ts;
1364
+ },
1365
+ log: logVerbose,
1366
+ warn: logVerbose
1367
+ }) : void 0;
1368
+ let hasStreamedMessage = false;
1369
+ const streamMode = slackStreaming.draftMode;
1370
+ const useNativeProgressStreaming = useStreaming && slackStreaming.mode === "progress";
1371
+ const previewToolProgressEnabled = (Boolean(draftStream) || useNativeProgressStreaming) && resolveChannelStreamingPreviewToolProgress(account.config);
1372
+ const suppressDefaultToolProgressMessages = resolveChannelStreamingSuppressDefaultToolProgressMessages(account.config, {
1373
+ draftStreamActive: Boolean(draftStream) || useNativeProgressStreaming,
1374
+ previewToolProgressEnabled,
1375
+ previewStreamingEnabled
1376
+ });
1377
+ let previewToolProgressSuppressed = false;
1378
+ let previewToolProgressLines = [];
1379
+ let lastNonEmptyPreviewToolProgressLines = [];
1380
+ let appendRenderedText = "";
1381
+ let appendSourceText = "";
1382
+ let statusUpdateCount = 0;
1383
+ let nativeProgressCompletionSent = false;
1384
+ let nativeProgressChunkKey;
1385
+ const progressSeed = `${account.accountId}:${message.channel}`;
1386
+ const useRichProgressDraft = streamMode === "status_final" && resolveChannelProgressDraftRender(account.config) === "rich";
1387
+ const explicitProgressTitle = resolveExplicitSlackProgressTitle(account.config);
1388
+ const progressDraftMaxLineChars = resolveChannelProgressDraftMaxLineChars(account.config);
1389
+ const renderProgressDraft = () => {
1390
+ if (!draftStream || streamMode !== "status_final") return;
1391
+ const progressLines = useRichProgressDraft && previewToolProgressLines.length === 0 ? lastNonEmptyPreviewToolProgressLines : previewToolProgressLines;
1392
+ const previewText = formatChannelProgressDraftText({
1393
+ entry: account.config,
1394
+ lines: progressLines,
1395
+ seed: progressSeed,
1396
+ formatLine: escapeSlackMrkdwn
1397
+ });
1398
+ if (!previewText) return;
1399
+ const richProgressBlocks = useRichProgressDraft ? buildSlackProgressDraftBlocks({
1400
+ title: explicitProgressTitle,
1401
+ lines: progressLines,
1402
+ maxLineChars: resolveChannelProgressDraftMaxLineChars(account.config)
1403
+ }) : void 0;
1404
+ draftStream.update(useRichProgressDraft && richProgressBlocks ? {
1405
+ text: previewText,
1406
+ blocks: richProgressBlocks
1407
+ } : previewText);
1408
+ hasStreamedMessage = true;
1409
+ };
1410
+ const waitForNativeProgressStreamStart = async () => {
1411
+ if (streamSession || !nativeProgressStreamStartPromise) return true;
1412
+ try {
1413
+ await nativeProgressStreamStartPromise;
1414
+ } catch {
1415
+ streamFailed = true;
1416
+ return false;
1417
+ }
1418
+ return !streamFailed;
1419
+ };
1420
+ const buildNativeProgressChunks = () => streamSession ? buildSlackProgressStreamUpdateChunks({
1421
+ title: explicitProgressTitle,
1422
+ lines: previewToolProgressLines,
1423
+ maxLineChars: progressDraftMaxLineChars
1424
+ }) : buildSlackProgressStreamStartChunks({
1425
+ title: explicitProgressTitle,
1426
+ lines: previewToolProgressLines,
1427
+ maxLineChars: progressDraftMaxLineChars
1428
+ });
1429
+ const markNativeProgressDelivered = (session, threadTs) => {
1430
+ if (session.delivered) observedReplyDelivery = true;
1431
+ if (threadTs) {
1432
+ usedReplyThreadTs ??= threadTs;
1433
+ rememberDeliveredThreadTs("block", threadTs);
1434
+ }
1435
+ };
1436
+ const startNativeProgressStream = async (chunks, chunkKey) => {
1437
+ const streamThreadTs = replyPlan.nextThreadTs();
1438
+ if (!streamThreadTs) {
1439
+ logVerbose("slack-stream: no reply thread target for native progress stream start, falling back");
1440
+ streamFailed = true;
1441
+ return;
1442
+ }
1443
+ nativeProgressStreamThreadTs = streamThreadTs;
1444
+ const startPromise = (async () => {
1445
+ const session = await startSlackStream({
1446
+ client: ctx.app.client,
1447
+ channel: message.channel,
1448
+ threadTs: streamThreadTs,
1449
+ chunks,
1450
+ taskDisplayMode: "plan",
1451
+ teamId: await resolveSlackStreamRecipientTeamId({
1452
+ client: ctx.app.client,
1453
+ token: ctx.botToken,
1454
+ userId: message.user,
1455
+ fallbackTeamId: ctx.teamId
1456
+ }),
1457
+ userId: message.user
1458
+ });
1459
+ streamSession = session;
1460
+ return session;
1461
+ })();
1462
+ nativeProgressStreamStartPromise = startPromise;
1463
+ let startedSession = null;
1464
+ try {
1465
+ startedSession = await startPromise;
1466
+ } finally {
1467
+ if (nativeProgressStreamStartPromise === startPromise) nativeProgressStreamStartPromise = null;
1468
+ }
1469
+ if (startedSession) markNativeProgressDelivered(startedSession, streamThreadTs);
1470
+ nativeProgressChunkKey = chunkKey;
1471
+ replyPlan.markSent();
1472
+ };
1473
+ const appendNativeProgressStream = async (chunks, chunkKey) => {
1474
+ if (!streamSession) return;
1475
+ await appendSlackStream({
1476
+ session: streamSession,
1477
+ chunks
1478
+ });
1479
+ markNativeProgressDelivered(streamSession);
1480
+ nativeProgressChunkKey = chunkKey;
1481
+ };
1482
+ const updateNativeProgressStream = async () => {
1483
+ if (!useNativeProgressStreaming || streamFailed || previewToolProgressLines.length === 0) return;
1484
+ if (!await waitForNativeProgressStreamStart()) return;
1485
+ const chunks = buildNativeProgressChunks();
1486
+ if (!chunks?.length) return;
1487
+ const chunkKey = JSON.stringify(chunks);
1488
+ if (chunkKey === nativeProgressChunkKey) return;
1489
+ try {
1490
+ if (!streamSession) {
1491
+ await startNativeProgressStream(chunks, chunkKey);
1492
+ return;
1493
+ }
1494
+ await appendNativeProgressStream(chunks, chunkKey);
1495
+ } catch (err) {
1496
+ runtime.error?.(danger(`slack-stream: native progress stream failed: ${formatSlackError(err)}, falling back`));
1497
+ streamFailed = true;
1498
+ }
1499
+ };
1500
+ const progressDraftGate = createChannelProgressDraftGate({ onStart: useNativeProgressStreaming ? updateNativeProgressStream : renderProgressDraft });
1501
+ const refreshStartedProgressDraft = async () => {
1502
+ if (useNativeProgressStreaming) await updateNativeProgressStream();
1503
+ else renderProgressDraft();
1504
+ };
1505
+ const pushPreviewToolProgress = async (line, options) => {
1506
+ if (!draftStream && !useNativeProgressStreaming) return;
1507
+ if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return;
1508
+ const normalized = line?.text.replace(/\s+/g, " ").trim();
1509
+ if (!line || !normalized) {
1510
+ if (streamMode !== "status_final") return;
1511
+ const alreadyStarted = progressDraftGate.hasStarted;
1512
+ await progressDraftGate.noteWork();
1513
+ if (alreadyStarted && progressDraftGate.hasStarted) await refreshStartedProgressDraft();
1514
+ return;
1515
+ }
1516
+ if (streamMode !== "status_final") {
1517
+ if (!previewToolProgressEnabled || previewToolProgressSuppressed) return;
1518
+ const nextLines = mergeChannelProgressDraftLine(previewToolProgressLines, line, { maxLines: resolveChannelProgressDraftMaxLines(account.config) });
1519
+ if (nextLines === previewToolProgressLines) return;
1520
+ previewToolProgressLines = nextLines;
1521
+ draftStream?.update(formatChannelProgressDraftText({
1522
+ entry: account.config,
1523
+ lines: previewToolProgressLines,
1524
+ seed: progressSeed,
1525
+ formatLine: escapeSlackMrkdwn
1526
+ }));
1527
+ hasStreamedMessage = true;
1528
+ return;
1529
+ }
1530
+ if (previewToolProgressEnabled && !previewToolProgressSuppressed) {
1531
+ previewToolProgressLines = mergeChannelProgressDraftLine(previewToolProgressLines, line, { maxLines: resolveChannelProgressDraftMaxLines(account.config) });
1532
+ if (previewToolProgressLines.length > 0) lastNonEmptyPreviewToolProgressLines = previewToolProgressLines;
1533
+ }
1534
+ if (useNativeProgressStreaming) {
1535
+ if (progressDraftGate.hasStarted) await updateNativeProgressStream();
1536
+ else await progressDraftGate.startNow();
1537
+ return;
1538
+ }
1539
+ const alreadyStarted = progressDraftGate.hasStarted;
1540
+ await progressDraftGate.noteWork();
1541
+ if (alreadyStarted && progressDraftGate.hasStarted) await refreshStartedProgressDraft();
1542
+ };
1543
+ const updateDraftFromPartial = (text) => {
1544
+ const trimmed = text?.trimEnd();
1545
+ if (!trimmed) return;
1546
+ if (streamMode === "append") {
1547
+ previewToolProgressSuppressed = true;
1548
+ previewToolProgressLines = [];
1549
+ const next = applyAppendOnlyStreamUpdate({
1550
+ incoming: trimmed,
1551
+ rendered: appendRenderedText,
1552
+ source: appendSourceText
1553
+ });
1554
+ appendRenderedText = next.rendered;
1555
+ appendSourceText = next.source;
1556
+ if (!next.changed) return;
1557
+ draftStream?.update(next.rendered);
1558
+ hasStreamedMessage = true;
1559
+ return;
1560
+ }
1561
+ if (streamMode === "status_final") {
1562
+ if (!progressDraftGate.hasStarted) return;
1563
+ statusUpdateCount += 1;
1564
+ if (statusUpdateCount > 1 && statusUpdateCount % 4 !== 0) return;
1565
+ renderProgressDraft();
1566
+ return;
1567
+ }
1568
+ previewToolProgressSuppressed = true;
1569
+ previewToolProgressLines = [];
1570
+ draftStream?.update(trimmed);
1571
+ hasStreamedMessage = true;
1572
+ };
1573
+ const onDraftBoundary = !shouldUseDraftStream ? void 0 : async () => {
1574
+ if (hasStreamedMessage) {
1575
+ draftStream?.forceNewMessage();
1576
+ hasStreamedMessage = false;
1577
+ appendRenderedText = "";
1578
+ appendSourceText = "";
1579
+ statusUpdateCount = 0;
1580
+ }
1581
+ previewToolProgressSuppressed = false;
1582
+ previewToolProgressLines = [];
1583
+ };
1584
+ let dispatchError;
1585
+ let queuedFinal = false;
1586
+ let counts = {};
1587
+ try {
1588
+ const turnResult = await dispatchChannelInboundReply({
1589
+ cfg,
1590
+ channel: "slack",
1591
+ accountId: route.accountId,
1592
+ agentId: route.agentId,
1593
+ routeSessionKey: route.sessionKey,
1594
+ storePath: prepared.turn.storePath,
1595
+ ctxPayload: prepared.ctxPayload,
1596
+ recordInboundSession,
1597
+ dispatchReplyWithBufferedBlockDispatcher,
1598
+ dispatcherOptions: {
1599
+ ...replyPipeline,
1600
+ humanDelay: resolveHumanDelayConfig(cfg, route.agentId)
1601
+ },
1602
+ delivery: {
1603
+ deliver: deliverSlackPayload,
1604
+ onError: onSlackDeliveryError
1605
+ },
1606
+ record: prepared.turn.record,
1607
+ history: prepared.turn.history,
1608
+ botLoopProtection: resolveSlackBotLoopProtection(prepared),
1609
+ replyOptions: {
1610
+ skillFilter: prepared.channelConfig?.skills,
1611
+ sourceReplyDeliveryMode,
1612
+ hasRepliedRef,
1613
+ disableBlockStreaming,
1614
+ onModelSelected,
1615
+ suppressDefaultToolProgressMessages: suppressDefaultToolProgressMessages ? true : void 0,
1616
+ onPartialReply: useStreaming ? void 0 : !previewStreamingEnabled ? void 0 : async (payload) => {
1617
+ updateDraftFromPartial(payload.text);
1618
+ },
1619
+ onAssistantMessageStart: onDraftBoundary,
1620
+ onReasoningEnd: onDraftBoundary,
1621
+ onReasoningStream: statusReactionsEnabled ? async () => {
1622
+ await statusReactions.setThinking();
1623
+ } : void 0,
1624
+ onToolStart: async (payload) => {
1625
+ if (statusReactionsEnabled) await statusReactions.setTool(payload.name);
1626
+ await pushPreviewToolProgress(buildChannelProgressDraftLineForEntry(account.config, {
1627
+ event: "tool",
1628
+ itemId: payload.itemId,
1629
+ toolCallId: payload.toolCallId,
1630
+ name: payload.name,
1631
+ phase: payload.phase,
1632
+ args: payload.args
1633
+ }, payload.detailMode ? { detailMode: payload.detailMode } : void 0), { toolName: payload.name });
1634
+ },
1635
+ onItemEvent: async (payload) => {
1636
+ await pushPreviewToolProgress(buildChannelProgressDraftLineForEntry(account.config, {
1637
+ event: "item",
1638
+ itemId: payload.itemId,
1639
+ itemKind: payload.kind,
1640
+ title: payload.title,
1641
+ name: payload.name,
1642
+ phase: payload.phase,
1643
+ status: payload.status,
1644
+ summary: payload.summary,
1645
+ progressText: payload.progressText,
1646
+ meta: payload.meta
1647
+ }));
1648
+ },
1649
+ onPlanUpdate: async (payload) => {
1650
+ if (payload.phase !== "update") return;
1651
+ await pushPreviewToolProgress(buildChannelProgressDraftLine({
1652
+ event: "plan",
1653
+ phase: payload.phase,
1654
+ title: payload.title,
1655
+ explanation: payload.explanation,
1656
+ steps: payload.steps
1657
+ }));
1658
+ },
1659
+ onApprovalEvent: async (payload) => {
1660
+ if (payload.phase !== "requested") return;
1661
+ await pushPreviewToolProgress(buildChannelProgressDraftLine({
1662
+ event: "approval",
1663
+ phase: payload.phase,
1664
+ title: payload.title,
1665
+ command: payload.command,
1666
+ reason: payload.reason,
1667
+ message: payload.message
1668
+ }));
1669
+ },
1670
+ onCommandOutput: async (payload) => {
1671
+ if (payload.phase !== "end") return;
1672
+ await pushPreviewToolProgress(buildChannelProgressDraftLine({
1673
+ event: "command-output",
1674
+ itemId: payload.itemId,
1675
+ toolCallId: payload.toolCallId,
1676
+ phase: payload.phase,
1677
+ title: payload.title,
1678
+ name: payload.name,
1679
+ status: payload.status,
1680
+ exitCode: payload.exitCode
1681
+ }));
1682
+ },
1683
+ onPatchSummary: async (payload) => {
1684
+ if (payload.phase !== "end") return;
1685
+ await pushPreviewToolProgress(buildChannelProgressDraftLine({
1686
+ event: "patch",
1687
+ itemId: payload.itemId,
1688
+ toolCallId: payload.toolCallId,
1689
+ phase: payload.phase,
1690
+ title: payload.title,
1691
+ name: payload.name,
1692
+ added: payload.added,
1693
+ modified: payload.modified,
1694
+ deleted: payload.deleted,
1695
+ summary: payload.summary
1696
+ }));
1697
+ }
1698
+ }
1699
+ });
1700
+ if (turnResult.dispatched) {
1701
+ const result = turnResult.dispatchResult;
1702
+ queuedFinal = result.queuedFinal;
1703
+ counts = result.counts;
1704
+ }
1705
+ } catch (err) {
1706
+ dispatchError = err;
1707
+ } finally {
1708
+ progressDraftGate.cancel();
1709
+ await draftStream?.discardPending();
1710
+ }
1711
+ let streamFallbackDelivered = false;
1712
+ const finalStream = streamSession;
1713
+ if (finalStream && !finalStream.stopped) try {
1714
+ const completionChunks = useNativeProgressStreaming && !nativeProgressCompletionSent && previewToolProgressLines.length > 0 ? buildSlackProgressStreamCompletionChunks({
1715
+ title: explicitProgressTitle,
1716
+ lines: previewToolProgressLines,
1717
+ maxLineChars: progressDraftMaxLineChars,
1718
+ finalInProgressStatus: dispatchError ? "error" : "complete"
1719
+ }) : void 0;
1720
+ if (completionChunks?.length) nativeProgressCompletionSent = true;
1721
+ await stopSlackStream({
1722
+ session: finalStream,
1723
+ ...completionChunks?.length ? { chunks: completionChunks } : {},
1724
+ ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
1725
+ });
1726
+ } catch (err) {
1727
+ if (err instanceof SlackStreamNotDeliveredError) streamFallbackDelivered = await deliverPendingStreamFallback(finalStream, err);
1728
+ else runtime.error?.(danger(`slack-stream: failed to stop stream: ${formatSlackError(err)}`));
1729
+ }
1730
+ const anyReplyDelivered = hasVisibleInboundReplyDispatch({
1731
+ queuedFinal,
1732
+ counts
1733
+ }, {
1734
+ observedReplyDelivery,
1735
+ fallbackDelivered: streamFallbackDelivered
1736
+ });
1737
+ if (statusReactionsEnabled) if (dispatchError) {
1738
+ await statusReactions.setError();
1739
+ if (ctx.removeAckAfterReply) (async () => {
1740
+ await sleep(statusReactionTiming.errorHoldMs);
1741
+ if (anyReplyDelivered) await statusReactions.clear();
1742
+ })();
1743
+ } else if (anyReplyDelivered) {
1744
+ await statusReactions.setDone();
1745
+ if (ctx.removeAckAfterReply) (async () => {
1746
+ await sleep(statusReactionTiming.doneHoldMs);
1747
+ await statusReactions.clear();
1748
+ })();
1749
+ else statusReactions.restoreInitial();
1750
+ } else await statusReactions.restoreInitial();
1751
+ if (dispatchError) throw dispatchError;
1752
+ const participationThreadTs = usedReplyThreadTs ?? statusThreadTs;
1753
+ if (anyReplyDelivered && participationThreadTs) recordSlackThreadParticipation(account.accountId, message.channel, participationThreadTs, { agentId: route.agentId });
1754
+ if (!anyReplyDelivered && !draftPreviewCommitted) {
1755
+ await draftStream?.clear();
1756
+ return;
1757
+ }
1758
+ if (shouldLogVerbose()) {
1759
+ const finalCount = counts.final;
1760
+ logVerbose(`slack: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${prepared.replyTarget}`);
1761
+ }
1762
+ if (!statusReactionsEnabled) removeAckReactionAfterReply({
1763
+ removeAfterReply: ctx.removeAckAfterReply && anyReplyDelivered,
1764
+ ackReactionPromise: prepared.ackReactionPromise,
1765
+ ackReactionValue: prepared.ackReactionValue,
1766
+ remove: () => removeSlackReaction(message.channel, prepared.ackReactionMessageTs ?? "", prepared.ackReactionValue, {
1767
+ token: ctx.botToken,
1768
+ client: ctx.app.client
1769
+ }),
1770
+ onError: (err) => {
1771
+ logAckFailure({
1772
+ log: logVerbose,
1773
+ channel: "slack",
1774
+ target: `${message.channel}/${message.ts}`,
1775
+ error: err
1776
+ });
1777
+ }
1778
+ });
1779
+ }
1780
+ //#endregion
27
1781
  //#region extensions/slack/src/monitor/message-handler/prepare-content.ts
28
1782
  const SLACK_MENTION_RESOLUTION_CONCURRENCY = 4;
29
1783
  const SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE = 20;
30
1784
  const SLACK_USER_MENTION_RE$1 = /<@([A-Z0-9]+)(?:\|[^>]+)?>/gi;
31
1785
  let slackMediaModulePromise$1;
32
1786
  function loadSlackMediaModule$1() {
33
- slackMediaModulePromise$1 ??= import("./actions-1o9nMIY8.js").then((n) => n.h);
1787
+ slackMediaModulePromise$1 ??= import("./actions-zfVWcIY6.js").then((n) => n.h);
34
1788
  return slackMediaModulePromise$1;
35
1789
  }
36
1790
  function collectUniqueSlackMentionIds$1(texts) {
@@ -336,38 +2090,6 @@ async function resolveSlackDmHistoryContext(params) {
336
2090
  }
337
2091
  }
338
2092
  //#endregion
339
- //#region extensions/slack/src/threading.ts
340
- function resolveSlackThreadContext(params) {
341
- const incomingThreadTs = params.message.thread_ts;
342
- const eventTs = params.message.event_ts;
343
- const messageTs = params.message.ts ?? eventTs;
344
- const isThreadReply = typeof incomingThreadTs === "string" && incomingThreadTs.length > 0 && (incomingThreadTs !== messageTs || Boolean(params.message.parent_user_id));
345
- return {
346
- incomingThreadTs,
347
- messageTs,
348
- isThreadReply,
349
- replyToId: incomingThreadTs ?? messageTs,
350
- messageThreadId: isThreadReply ? incomingThreadTs : params.replyToMode === "all" ? messageTs : void 0
351
- };
352
- }
353
- /**
354
- * Resolves Slack thread targeting for replies and status indicators.
355
- *
356
- * @returns replyThreadTs - Thread timestamp for reply messages
357
- * @returns statusThreadTs - Thread timestamp for status indicators (typing, etc.)
358
- * @returns isThreadReply - true if this is a genuine user reply in a thread,
359
- * false if thread_ts comes from a bot status message (e.g. typing indicator)
360
- */
361
- function resolveSlackThreadTargets(params) {
362
- const { incomingThreadTs, messageTs, isThreadReply } = resolveSlackThreadContext(params);
363
- const replyThreadTs = isThreadReply ? incomingThreadTs : params.replyToMode === "all" ? messageTs : void 0;
364
- return {
365
- replyThreadTs,
366
- statusThreadTs: replyThreadTs,
367
- isThreadReply
368
- };
369
- }
370
- //#endregion
371
2093
  //#region extensions/slack/src/monitor/message-handler/prepare-routing.ts
372
2094
  const slackRouteBindingConfigCache = /* @__PURE__ */ new WeakMap();
373
2095
  function slackTargetDefaultKindForPeer(kind) {
@@ -575,7 +2297,7 @@ function formatSlackBotStarterThreadLabel(params) {
575
2297
  //#region extensions/slack/src/monitor/message-handler/prepare-thread-context.ts
576
2298
  let slackMediaModulePromise;
577
2299
  function loadSlackMediaModule() {
578
- slackMediaModulePromise ??= import("./actions-1o9nMIY8.js").then((n) => n.h);
2300
+ slackMediaModulePromise ??= import("./actions-zfVWcIY6.js").then((n) => n.h);
579
2301
  return slackMediaModulePromise;
580
2302
  }
581
2303
  const SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY = 4;
@@ -1710,4 +3432,4 @@ async function prepareSlackMessage(params) {
1710
3432
  };
1711
3433
  }
1712
3434
  //#endregion
1713
- export { resolveSlackThreadTargets as n, prepareSlackMessage as t };
3435
+ export { dispatchPreparedSlackMessage, prepareSlackMessage };