@openclaw/slack 2026.7.1-beta.6 → 2026.7.2-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/accounts.runtime-KvftRRek.js +2 -0
  2. package/dist/{action-runtime-DoQGk_8D.js → action-runtime-p6M2ULpv.js} +205 -29
  3. package/dist/action-runtime.runtime-DQstDkHG.js +2 -0
  4. package/dist/{actions-DMpDmKEA.js → actions-DkTcWmpT.js} +87 -138
  5. package/dist/{actions.runtime-C103_BPW.js → actions.runtime-BSxpwgUc.js} +1 -1
  6. package/dist/api.js +14 -13
  7. package/dist/{approval-handler.runtime-CIUbOt5T.js → approval-handler.runtime-UGbnz9OC.js} +3 -3
  8. package/dist/{channel-BjlsaGHn.js → channel-B0_j6C6z.js} +116 -140
  9. package/dist/channel-config-api.js +1 -1
  10. package/dist/channel-plugin-api.js +1 -1
  11. package/dist/channel-type-BpeTezY4.js +109 -0
  12. package/dist/{channel.setup-Dq6syTNU.js → channel.setup-BQFut21T.js} +2 -2
  13. package/dist/{client-DGxwSL7f.js → client-D2F6m8CP.js} +38 -2
  14. package/dist/config-api-Df9a1B8C.js +3 -0
  15. package/dist/{config-schema-BzqzFW__.js → config-schema-DYI01PV2.js} +20 -37
  16. package/dist/cursor-pages-DSmTg0su.js +13 -0
  17. package/dist/{directory-config-BZuS8141.js → directory-config-DRkFujwG.js} +2 -2
  18. package/dist/directory-contract-api.js +1 -1
  19. package/dist/{directory-live-BNsxCgqP.js → directory-live-DX7yFsM-.js} +2 -2
  20. package/dist/{doctor-contract-XL6HeNCb.js → doctor-contract-DddfkMlF.js} +25 -38
  21. package/dist/doctor-contract-api.js +1 -1
  22. package/dist/group-policy-C-b2ZnSl.js +2332 -0
  23. package/dist/{interactive-replies-CXpT-vA_.js → interactive-replies-BilWUdcI.js} +5 -1
  24. package/dist/interactive-replies-api.js +1 -1
  25. package/dist/{message-tool-api-BgWKyu2r.js → message-tool-api-CA9jZrCY.js} +1 -1
  26. package/dist/message-tool-api.js +1 -1
  27. package/dist/{monitor-Ux-wzUWI.js → monitor-BN_uuvrQ.js} +3 -3
  28. package/dist/outbound-adapter-ECl0go7d.js +228 -0
  29. package/dist/{pipeline.runtime-rpVpay59.js → pipeline.runtime-f0eJJoJL.js} +492 -178
  30. package/dist/{policy-Dj-puVPb.js → policy-f8b0edSi.js} +1 -1
  31. package/dist/preflight-audio.runtime-D5Bgags0.js +3 -0
  32. package/dist/{probe-CuwRDE5j.js → probe-CfALk0l4.js} +2 -2
  33. package/dist/{provider-C1-DFSpw.js → provider-Ds-y-imP.js} +658 -192
  34. package/dist/{relay-source-D2jv1iGg.js → relay-source-B-6LFUdz.js} +6 -17
  35. package/dist/replies-BMvf3ihj.js +420 -0
  36. package/dist/{resolve-channels-CstkChBE.js → resolve-channels-DOMgNCWb.js} +17 -6
  37. package/dist/{resolve-users-C3NP2c-Q.js → resolve-users-aL6-Ssee.js} +11 -6
  38. package/dist/{runtime-BOk7xkOl.js → runtime-CauEvswP.js} +1 -1
  39. package/dist/{runtime-api-CEmqYxR-.js → runtime-api-CVb6CQjX.js} +3 -3
  40. package/dist/runtime-api.js +11 -11
  41. package/dist/runtime-setter-api.js +1 -1
  42. package/dist/{scopes-DCWVD5-8.js → scopes-Dw8wbU0j.js} +2 -2
  43. package/dist/{secret-contract-C3ChHo5A.js → secret-contract-CMohXFWS.js} +18 -113
  44. package/dist/secret-contract-api.js +1 -1
  45. package/dist/{send-DKDXoeoV.js → send-NEfJ0GvM.js} +662 -325
  46. package/dist/{send.runtime-GPRa63l1.js → send.runtime-BkKpitPu.js} +1 -1
  47. package/dist/setup-plugin-api.js +1 -1
  48. package/dist/{setup-surface-Dv2fIJNB.js → setup-surface-BN2WeG5c.js} +3 -3
  49. package/dist/{shared-CosfcFri.js → shared-BLnB9R1T.js} +7 -5
  50. package/dist/{slash-dispatch.runtime-CcMbbT3_.js → slash-dispatch.runtime-C0Otuf_T.js} +1 -1
  51. package/dist/{streaming-compat-DjlgH-Be.js → streaming-compat-3_X-21wW.js} +1 -0
  52. package/dist/{target-parsing-3jYH8LfB.js → target-parsing-N6lHogYZ.js} +16 -3
  53. package/dist/{targets-Dll4NJgs.js → targets-KmjP2E8s.js} +1 -1
  54. package/node_modules/p-map/index.d.ts +155 -0
  55. package/node_modules/p-map/index.js +284 -0
  56. package/node_modules/p-map/license +9 -0
  57. package/node_modules/p-map/package.json +57 -0
  58. package/node_modules/p-map/readme.md +190 -0
  59. package/npm-shrinkwrap.json +16 -3
  60. package/openclaw.plugin.json +16 -0
  61. package/package.json +6 -4
  62. package/dist/accounts.runtime-DWvaCb-X.js +0 -2
  63. package/dist/action-runtime.runtime-BABj3aM-.js +0 -2
  64. package/dist/config-api-DnT9ohtE.js +0 -2
  65. package/dist/format-YCNoNkM2.js +0 -107
  66. package/dist/outbound-adapter-DVoBfBW5.js +0 -272
  67. package/dist/replies-DpF38e1y.js +0 -287
  68. package/dist/reply-blocks-BGQ6zSqM.js +0 -877
  69. package/dist/resolve-allowlist-common-CP8SH0Zf.js +0 -43
@@ -1,81 +1,424 @@
1
1
  import { a as resolveSlackAccount, l as resolveSlackOperationToken, p as resolveSlackBotToken } 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, i as validateSlackBlocksArray } from "./reply-blocks-BGQ6zSqM.js";
2
+ import { i as parseSlackTarget, t as canonicalizeSlackApiTargetId } from "./target-parsing-N6lHogYZ.js";
3
+ import { A as buildSlackCompleteBlocksFallbackText, C as hasSlackNativeDataBlock, D as markdownToSlackMrkdwnChunks, E as chunkSlackMrkdwnText, F as SLACK_TEXT_LIMIT, P as SLACK_MESSAGE_TEXT_HARD_LIMIT, S as createSlackNativeDataBaseTextConsumer, T as stripSlackNativeDataBlocks, Z as validateSlackBlocksArray, b as appendSlackNativeDataPlainTextFallback, i as assertSlackDirectSendAllowed, j as renderSlackBlockFallbackText, w as isSlackInvalidBlocksError, x as buildSlackNativeDataAccessibilityText, z as truncateSlackText } from "./group-policy-C-b2ZnSl.js";
5
4
  import { n as resolveSlackThreadTsValue, t as normalizeSlackThreadTsCandidate } from "./thread-ts-DUGhaYKq.js";
6
- import { a as getSlackWriteClient, n as createSlackTokenCacheKey, r as createSlackWebClient } from "./client-DGxwSL7f.js";
7
- import { t as getOptionalSlackRuntime } from "./runtime-BOk7xkOl.js";
8
- import { t as markdownToSlackMrkdwnChunks } from "./format-YCNoNkM2.js";
9
- import { i as loadOutboundMediaFromUrl } from "./runtime-api-CEmqYxR-.js";
5
+ import { i as createSlackWebClient, r as createSlackTokenCacheKey, s as getSlackWriteClient } from "./client-D2F6m8CP.js";
6
+ import { t as getOptionalSlackRuntime } from "./runtime-CauEvswP.js";
7
+ import { i as loadOutboundMediaFromUrl } from "./runtime-api-CVb6CQjX.js";
10
8
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
11
9
  import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
12
10
  import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
13
- import { createHash, createHmac, timingSafeEqual } from "node:crypto";
11
+ import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
12
+ import { createHash, createHmac } from "node:crypto";
14
13
  import { withTrustedEnvProxyGuardedFetchMode } from "openclaw/plugin-sdk/fetch-runtime";
15
14
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
15
+ import { PlatformMessageNotDispatchedError, extractErrorCode, readErrorName } from "openclaw/plugin-sdk/error-runtime";
16
16
  import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
17
17
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
18
18
  import { chunkMarkdownTextWithMode, isSilentReplyText, resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
19
19
  import { resolveTextChunksWithFallback } from "openclaw/plugin-sdk/reply-payload";
20
+ import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
21
+ import { buildTimeoutAbortSignal } from "openclaw/plugin-sdk/extension-shared";
20
22
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
21
23
  import { createPersistentDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
22
- //#region extensions/slack/src/blocks-fallback.ts
23
- function cleanCandidate(value) {
24
- if (typeof value !== "string") return;
25
- const normalized = value.replace(/\s+/g, " ").trim();
26
- return normalized.length > 0 ? normalized : void 0;
27
- }
28
- function readSectionText(block) {
29
- return cleanCandidate(block.text?.text);
30
- }
31
- function readHeaderText(block) {
32
- return cleanCandidate(block.text?.text);
33
- }
34
- function readImageText(block) {
35
- return cleanCandidate(block.alt_text) ?? cleanCandidate(block.title?.text);
36
- }
37
- function readVideoText(block) {
38
- return cleanCandidate(block.title?.text) ?? cleanCandidate(block.alt_text);
39
- }
40
- function readContextText(block) {
41
- if (!Array.isArray(block.elements)) return;
42
- const textParts = block.elements.map((element) => cleanCandidate(element.text)).filter((value) => Boolean(value));
43
- return textParts.length > 0 ? textParts.join(" ") : void 0;
44
- }
45
- function buildSlackBlocksFallbackText(blocks) {
46
- for (const raw of blocks) {
47
- const block = raw;
48
- switch (block.type) {
49
- case "header": {
50
- const text = readHeaderText(block);
51
- if (text) return text;
52
- break;
53
- }
54
- case "section": {
55
- const text = readSectionText(block);
56
- if (text) return text;
57
- break;
58
- }
59
- case "image": {
60
- const text = readImageText(block);
61
- if (text) return text;
62
- return "Shared an image";
63
- }
64
- case "video": {
65
- const text = readVideoText(block);
66
- if (text) return text;
67
- return "Shared a video";
68
- }
69
- case "file": return "Shared a file";
70
- case "context": {
71
- const text = readContextText(block);
72
- if (text) return text;
73
- break;
24
+ //#region extensions/slack/src/native-data-fallback.ts
25
+ const SLACK_SECTION_PLAIN_TEXT_MAX = 3e3;
26
+ const SLACK_EMPTY_BLOCK_FALLBACK = "Shared a Block Kit message";
27
+ function chunkSlackTextAtHardLimit(text, limit = SLACK_MESSAGE_TEXT_HARD_LIMIT) {
28
+ const effectiveLimit = Math.max(2, Math.floor(limit));
29
+ const chunks = [];
30
+ let offset = 0;
31
+ while (offset < text.length) {
32
+ const chunk = sliceUtf16Safe(text, offset, Math.min(text.length, offset + effectiveLimit));
33
+ if (!chunk) throw new Error("Slack plain-text fallback chunking made no progress.");
34
+ chunks.push(chunk);
35
+ offset += chunk.length;
36
+ }
37
+ return chunks;
38
+ }
39
+ function buildPlainTextBlocks(text) {
40
+ return chunkSlackTextAtHardLimit(text, SLACK_SECTION_PLAIN_TEXT_MAX).map((chunk, index) => {
41
+ const fallbackBlock = {
42
+ block: {
43
+ type: "section",
44
+ text: {
45
+ type: "plain_text",
46
+ text: chunk
47
+ }
48
+ },
49
+ text: chunk
50
+ };
51
+ if (index > 0) fallbackBlock.continuesText = true;
52
+ return fallbackBlock;
53
+ });
54
+ }
55
+ function renderNativeDataPlainText(block) {
56
+ return appendSlackNativeDataPlainTextFallback("", [block]).trim() || "Slack could not render this chart or table data.";
57
+ }
58
+ function buildOrderedFallbackBlocks(params) {
59
+ const entries = [];
60
+ const consumeFromBase = createSlackNativeDataBaseTextConsumer(params.baseText);
61
+ if (params.baseText) entries.push(...buildPlainTextBlocks(params.baseText));
62
+ for (const block of params.blocks) {
63
+ if (hasSlackNativeDataBlock([block])) {
64
+ const nativeText = renderNativeDataPlainText(block);
65
+ if (!consumeFromBase(nativeText)) entries.push(...buildPlainTextBlocks(nativeText));
66
+ continue;
67
+ }
68
+ const text = renderSlackBlockFallbackText(block, { nativeDataFormat: "plain" });
69
+ entries.push({
70
+ block,
71
+ ...text ? { text } : {}
72
+ });
73
+ }
74
+ return entries;
75
+ }
76
+ function buildOrderedBlockMessages(entries) {
77
+ const messages = [];
78
+ let blocks = [];
79
+ let text = "";
80
+ const flush = () => {
81
+ if (blocks.length === 0) return;
82
+ messages.push({
83
+ text: text || SLACK_EMPTY_BLOCK_FALLBACK,
84
+ blocks,
85
+ mrkdwn: false
86
+ });
87
+ blocks = [];
88
+ text = "";
89
+ };
90
+ for (const entry of entries) {
91
+ const separator = text && entry.text && !entry.continuesText ? "\n\n" : "";
92
+ const nextText = entry.text ? `${text}${separator}${entry.text}` : text;
93
+ if (blocks.length >= 50 || nextText.length > 4e4) flush();
94
+ const freshSeparator = text && entry.text && !entry.continuesText ? "\n\n" : "";
95
+ const freshText = entry.text ? `${text}${freshSeparator}${entry.text}` : text;
96
+ if (freshText.length > 4e4) throw new Error("One Slack fallback block exceeds the message text hard limit.");
97
+ blocks.push(entry.block);
98
+ text = freshText;
99
+ }
100
+ flush();
101
+ return messages;
102
+ }
103
+ /** Build one complete, ordered retry plan after Slack rejects native data blocks. */
104
+ function buildSlackNativeDataDeliveryPlan(params) {
105
+ const baseText = params.baseText?.trim() ?? "";
106
+ const hasNativeData = hasSlackNativeDataBlock(params.blocks);
107
+ const accessibilityText = buildSlackNativeDataAccessibilityText(baseText, params.blocks) || (hasNativeData ? "Slack could not render this chart or table data." : SLACK_EMPTY_BLOCK_FALLBACK);
108
+ return {
109
+ accessibilityText,
110
+ fallbackMessages: stripSlackNativeDataBlocks(params.blocks).length === 0 ? chunkSlackTextAtHardLimit(accessibilityText).map((text) => ({
111
+ text,
112
+ mrkdwn: false
113
+ })) : buildOrderedBlockMessages(buildOrderedFallbackBlocks({
114
+ baseText,
115
+ blocks: params.blocks
116
+ })),
117
+ skipOriginalBlocks: accessibilityText.length > SLACK_MESSAGE_TEXT_HARD_LIMIT
118
+ };
119
+ }
120
+ //#endregion
121
+ //#region extensions/slack/src/post-message-identity.ts
122
+ function normalizeSlackScopeList$1(value) {
123
+ if (!Array.isArray(value)) return [];
124
+ return value.flatMap((scope) => {
125
+ const normalized = normalizeOptionalString(scope);
126
+ return normalized ? [normalized] : [];
127
+ });
128
+ }
129
+ function getSlackWebApiErrorData$1(err) {
130
+ if (!(err instanceof Error)) return;
131
+ const data = err.data;
132
+ return data && typeof data === "object" ? data : void 0;
133
+ }
134
+ function isSlackCustomizeScopeError(err) {
135
+ const data = getSlackWebApiErrorData$1(err);
136
+ if (normalizeLowercaseStringOrEmpty(normalizeOptionalString(data?.error)) !== "missing_scope") return false;
137
+ if (normalizeLowercaseStringOrEmpty(normalizeOptionalString(data?.needed)).includes("chat:write.customize")) return true;
138
+ return [...normalizeSlackScopeList$1(data?.response_metadata?.scopes), ...normalizeSlackScopeList$1(data?.response_metadata?.acceptedScopes)].map((scope) => normalizeLowercaseStringOrEmpty(scope)).includes("chat:write.customize");
139
+ }
140
+ function isSlackCustomIdentityRejectedError(err) {
141
+ if (isSlackCustomizeScopeError(err)) return true;
142
+ const code = normalizeLowercaseStringOrEmpty(normalizeOptionalString(getSlackWebApiErrorData$1(err)?.error));
143
+ return code === "invalid_arguments" || code === "invalid_arg_name";
144
+ }
145
+ function hasCustomIdentity$1(identity) {
146
+ return Boolean(identity?.username || identity?.iconUrl || identity?.iconEmoji);
147
+ }
148
+ /** Post with the requested identity, degrading only on Slack identity-specific errors. */
149
+ async function postSlackMessageWithIdentityFallback(params) {
150
+ const { basePayload, identity, post } = params;
151
+ try {
152
+ if (identity?.iconUrl) return await post({
153
+ ...basePayload,
154
+ ...identity.username ? { username: identity.username } : {},
155
+ icon_url: identity.iconUrl
156
+ }, identity);
157
+ if (identity?.iconEmoji) return await post({
158
+ ...basePayload,
159
+ ...identity.username ? { username: identity.username } : {},
160
+ icon_emoji: identity.iconEmoji
161
+ }, identity);
162
+ return await post({
163
+ ...basePayload,
164
+ ...identity?.username ? { username: identity.username } : {}
165
+ }, identity);
166
+ } catch (err) {
167
+ if (!identity || !hasCustomIdentity$1(identity) || !isSlackCustomIdentityRejectedError(err)) throw err;
168
+ if (!isSlackCustomizeScopeError(err) && identity.username && (identity.iconUrl || identity.iconEmoji)) {
169
+ logVerbose("slack send: custom icon rejected, retrying with username only");
170
+ try {
171
+ return await post({
172
+ ...basePayload,
173
+ username: identity.username
174
+ }, { username: identity.username });
175
+ } catch (retryError) {
176
+ if (!isSlackCustomIdentityRejectedError(retryError)) throw retryError;
74
177
  }
75
- default: break;
76
178
  }
179
+ logVerbose("slack send: custom identity rejected, retrying without custom identity");
180
+ return post(basePayload);
181
+ }
182
+ }
183
+ //#endregion
184
+ //#region extensions/slack/src/post-message-payload.ts
185
+ function buildSlackUnfurlPayload(options) {
186
+ return {
187
+ unfurl_links: options?.unfurlLinks ?? false,
188
+ ...typeof options?.unfurlMedia === "boolean" ? { unfurl_media: options.unfurlMedia } : {}
189
+ };
190
+ }
191
+ function buildSlackPostMessagePayload(params) {
192
+ const threadPayload = params.replyBroadcast && params.threadTs ? {
193
+ thread_ts: params.threadTs,
194
+ reply_broadcast: true
195
+ } : params.threadTs ? { thread_ts: params.threadTs } : {};
196
+ const unfurlPayload = buildSlackUnfurlPayload(params.unfurl);
197
+ if (params.blocks?.length) return {
198
+ channel: params.channelId,
199
+ text: params.text,
200
+ blocks: params.blocks,
201
+ ...params.metadata ? { metadata: params.metadata } : {},
202
+ ...typeof params.mrkdwn === "boolean" ? { mrkdwn: params.mrkdwn } : {},
203
+ ...threadPayload,
204
+ ...unfurlPayload
205
+ };
206
+ return {
207
+ channel: params.channelId,
208
+ text: params.text,
209
+ ...params.metadata ? { metadata: params.metadata } : {},
210
+ ...typeof params.mrkdwn === "boolean" ? { mrkdwn: params.mrkdwn } : {},
211
+ ...threadPayload,
212
+ ...unfurlPayload
213
+ };
214
+ }
215
+ //#endregion
216
+ //#region extensions/slack/src/client-delivery.ts
217
+ const SLACK_COMMERCIAL_API_HOSTNAME = "slack.com";
218
+ const SLACK_COMMERCIAL_UPLOAD_HOSTNAME = "files.slack.com";
219
+ const SLACK_GOV_API_HOSTNAME = "slack-gov.com";
220
+ const SLACK_GOV_UPLOAD_HOSTNAME = "files.slack-gov.com";
221
+ const SLACK_COMMERCIAL_UPLOAD_SSRF_POLICY = {
222
+ hostnameAllowlist: [SLACK_COMMERCIAL_UPLOAD_HOSTNAME],
223
+ allowRfc2544BenchmarkRange: true
224
+ };
225
+ const SLACK_GOV_UPLOAD_SSRF_POLICY = {
226
+ hostnameAllowlist: [SLACK_GOV_UPLOAD_HOSTNAME],
227
+ allowRfc2544BenchmarkRange: true
228
+ };
229
+ const SLACK_UPLOAD_POST_TIMEOUT_MS = 12e4;
230
+ const SLACK_DNS_RETRY_CODES = /* @__PURE__ */ new Set([
231
+ "EAI_AGAIN",
232
+ "ENOTFOUND",
233
+ "UND_ERR_DNS_RESOLVE_FAILED"
234
+ ]);
235
+ const SLACK_DNS_RETRY_ATTEMPTS = 2;
236
+ const SLACK_DNS_RETRY_BASE_DELAY_MS = 250;
237
+ function readSlackRequestErrorCode(value) {
238
+ if (!value || typeof value !== "object") return;
239
+ const code = value.code;
240
+ return typeof code === "string" ? code.toUpperCase() : void 0;
241
+ }
242
+ function readSlackRequestErrorMessage$1(value) {
243
+ if (value instanceof Error) return value.message;
244
+ return typeof value === "string" ? value : "";
245
+ }
246
+ function hasSlackDnsRequestSignal(err) {
247
+ let current = err;
248
+ const seen = /* @__PURE__ */ new Set();
249
+ for (let depth = 0; current && typeof current === "object" && depth < 6; depth += 1) {
250
+ if (seen.has(current)) return false;
251
+ seen.add(current);
252
+ const code = readSlackRequestErrorCode(current);
253
+ if (code && SLACK_DNS_RETRY_CODES.has(code)) return true;
254
+ const message = readSlackRequestErrorMessage$1(current);
255
+ if (/\b(EAI_AGAIN|ENOTFOUND|UND_ERR_DNS_RESOLVE_FAILED)\b/i.test(message)) return true;
256
+ current = current.original ?? current.cause;
257
+ }
258
+ return false;
259
+ }
260
+ function delaySlackDnsRetry(attempt) {
261
+ return new Promise((resolve) => {
262
+ setTimeout(resolve, SLACK_DNS_RETRY_BASE_DELAY_MS * Math.max(1, attempt));
263
+ });
264
+ }
265
+ function resolveSlackUploadTimeoutLogUrl(url) {
266
+ try {
267
+ return new URL(url).origin;
268
+ } catch {
269
+ return;
270
+ }
271
+ }
272
+ function buildSlackUploadFailureCause(error) {
273
+ const httpStatus = error instanceof Error ? /^Failed to upload file: HTTP (\d{3})$/u.exec(error.message)?.[1] : void 0;
274
+ const cause = /* @__PURE__ */ new Error(httpStatus ? `Slack external upload returned HTTP ${httpStatus}` : "Slack external upload transfer failed");
275
+ cause.name = readErrorName(error) || cause.name;
276
+ const code = extractErrorCode(error) ?? (httpStatus ? `HTTP_${httpStatus}` : void 0);
277
+ if (code) cause.code = code;
278
+ return cause;
279
+ }
280
+ function parseSlackUploadHttpUrl(value, label) {
281
+ try {
282
+ const parsed = new URL(value);
283
+ if (parsed.protocol === "http:" || parsed.protocol === "https:") return parsed;
284
+ } catch {}
285
+ throw new Error(`${label} must use a valid HTTP or HTTPS URL`);
286
+ }
287
+ function normalizeSlackHostname(hostname) {
288
+ return hostname.trim().toLowerCase().replace(/\.$/, "");
289
+ }
290
+ function resolveSlackOwnedUploadPolicy(url) {
291
+ if (url.protocol !== "https:") return;
292
+ switch (normalizeSlackHostname(url.hostname)) {
293
+ case SLACK_COMMERCIAL_UPLOAD_HOSTNAME: return SLACK_COMMERCIAL_UPLOAD_SSRF_POLICY;
294
+ case SLACK_GOV_UPLOAD_HOSTNAME: return SLACK_GOV_UPLOAD_SSRF_POLICY;
295
+ default: return;
296
+ }
297
+ }
298
+ function resolveOfficialSlackApiUploadPolicy(url) {
299
+ if (url.protocol !== "https:" || url.port) return;
300
+ switch (normalizeSlackHostname(url.hostname)) {
301
+ case SLACK_COMMERCIAL_API_HOSTNAME: return SLACK_COMMERCIAL_UPLOAD_SSRF_POLICY;
302
+ case SLACK_GOV_API_HOSTNAME: return SLACK_GOV_UPLOAD_SSRF_POLICY;
303
+ default: return;
304
+ }
305
+ }
306
+ function normalizeSlackOrigin(url) {
307
+ const port = url.port ? `:${url.port}` : "";
308
+ return `${url.protocol}//${normalizeSlackHostname(url.hostname)}${port}`;
309
+ }
310
+ function resolveSlackUploadTransportPolicy(params) {
311
+ if (!params.slackApiUrl) return {
312
+ requireHttps: true,
313
+ policy: SLACK_COMMERCIAL_UPLOAD_SSRF_POLICY
314
+ };
315
+ const apiUrl = parseSlackUploadHttpUrl(params.slackApiUrl, "Configured Slack API URL");
316
+ const officialApiPolicy = resolveOfficialSlackApiUploadPolicy(apiUrl);
317
+ if (officialApiPolicy) return {
318
+ requireHttps: true,
319
+ policy: officialApiPolicy
320
+ };
321
+ const uploadUrl = parseSlackUploadHttpUrl(params.uploadUrl, "Slack external upload URL");
322
+ const slackOwnedUploadPolicy = resolveSlackOwnedUploadPolicy(uploadUrl);
323
+ if (slackOwnedUploadPolicy) return {
324
+ requireHttps: true,
325
+ policy: slackOwnedUploadPolicy
326
+ };
327
+ if (normalizeSlackOrigin(uploadUrl) !== normalizeSlackOrigin(apiUrl)) throw new Error("Slack external upload URL must match the configured Slack API origin");
328
+ return {
329
+ requireHttps: apiUrl.protocol === "https:",
330
+ policy: {
331
+ hostnameAllowlist: [uploadUrl.hostname],
332
+ allowedOrigins: [uploadUrl.origin],
333
+ allowRfc2544BenchmarkRange: true
334
+ }
335
+ };
336
+ }
337
+ async function withSlackDnsRequestRetry(operation, fn) {
338
+ for (const attempt of Array.from({ length: 3 }, (_, index) => index)) try {
339
+ return await fn();
340
+ } catch (err) {
341
+ if (attempt >= SLACK_DNS_RETRY_ATTEMPTS || !hasSlackDnsRequestSignal(err)) throw err;
342
+ logVerbose(`slack send: retrying ${operation} after transient DNS request error (${attempt + 1}/${SLACK_DNS_RETRY_ATTEMPTS})`);
343
+ await delaySlackDnsRetry(attempt + 1);
77
344
  }
78
- return "Shared a Block Kit message";
345
+ throw new Error("unreachable Slack DNS retry loop exit");
346
+ }
347
+ async function postSlackMessageBestEffort(params) {
348
+ const basePayload = buildSlackPostMessagePayload(params);
349
+ const postChatMessage = params.client.chat.postMessage.bind(params.client.chat);
350
+ const post = async (payload, identity) => ({
351
+ response: await withSlackDnsRequestRetry("chat.postMessage", () => postChatMessage(payload)),
352
+ identity
353
+ });
354
+ return await postSlackMessageWithIdentityFallback({
355
+ basePayload,
356
+ identity: params.identity,
357
+ post
358
+ });
359
+ }
360
+ async function uploadSlackFile(params) {
361
+ const { buffer, contentType, fileName } = await loadOutboundMediaFromUrl(params.mediaUrl, {
362
+ maxBytes: params.maxBytes,
363
+ mediaAccess: params.mediaAccess,
364
+ mediaLocalRoots: params.mediaLocalRoots,
365
+ mediaReadFile: params.mediaReadFile
366
+ });
367
+ const uploadFileName = params.uploadFileName ?? fileName ?? "upload";
368
+ const uploadTitle = params.uploadTitle ?? uploadFileName;
369
+ const uploadUrlResp = await withSlackDnsRequestRetry("files.getUploadURLExternal", () => params.client.files.getUploadURLExternal({
370
+ filename: uploadFileName,
371
+ length: buffer.length
372
+ }));
373
+ if (!uploadUrlResp.ok || !uploadUrlResp.upload_url || !uploadUrlResp.file_id) throw new Error(`Failed to get upload URL: ${uploadUrlResp.error ?? "unknown error"}`);
374
+ const uploadFileId = uploadUrlResp.file_id;
375
+ const uploadTransport = resolveSlackUploadTransportPolicy({
376
+ uploadUrl: uploadUrlResp.upload_url,
377
+ slackApiUrl: params.client.slackApiUrl
378
+ });
379
+ const { signal: uploadTimeoutSignal, cleanup: cleanupUploadTimeout } = buildTimeoutAbortSignal({
380
+ timeoutMs: SLACK_UPLOAD_POST_TIMEOUT_MS,
381
+ operation: "slack-upload-file",
382
+ url: resolveSlackUploadTimeoutLogUrl(uploadUrlResp.upload_url)
383
+ });
384
+ try {
385
+ const { response: uploadResp, release } = await fetchWithSsrFGuard(withTrustedEnvProxyGuardedFetchMode({
386
+ url: uploadUrlResp.upload_url,
387
+ init: {
388
+ method: "POST",
389
+ ...contentType ? { headers: { "Content-Type": contentType } } : {},
390
+ body: new Uint8Array(buffer)
391
+ },
392
+ signal: uploadTimeoutSignal,
393
+ requireHttps: uploadTransport.requireHttps,
394
+ policy: uploadTransport.policy,
395
+ capture: false,
396
+ auditContext: params.auditContext ?? "slack-upload-file"
397
+ }));
398
+ try {
399
+ if (uploadResp.status !== 200) throw new Error(`Failed to upload file: HTTP ${uploadResp.status}`);
400
+ } finally {
401
+ await uploadResp.body?.cancel().catch(() => void 0);
402
+ await release();
403
+ }
404
+ } catch (error) {
405
+ throw new PlatformMessageNotDispatchedError(`Slack external upload ${uploadTimeoutSignal?.aborted ? "timed out" : "failed"} before completion dispatch`, { cause: buildSlackUploadFailureCause(error) });
406
+ } finally {
407
+ cleanupUploadTimeout();
408
+ }
409
+ await params.onPlatformSendDispatch?.();
410
+ const completionClient = params.completionClient ?? params.client;
411
+ const completeResp = await withSlackDnsRequestRetry("files.completeUploadExternal", () => completionClient.files.completeUploadExternal({
412
+ files: [{
413
+ id: uploadFileId,
414
+ title: uploadTitle
415
+ }],
416
+ channel_id: params.channelId,
417
+ ...params.caption ? { initial_comment: params.caption } : {},
418
+ ...params.threadTs ? { thread_ts: params.threadTs } : {}
419
+ }));
420
+ if (!completeResp.ok) throw new Error(`Failed to complete upload: ${completeResp.error ?? "unknown error"}`);
421
+ return uploadFileId;
79
422
  }
80
423
  //#endregion
81
424
  //#region extensions/slack/src/sent-thread-cache.ts
@@ -97,45 +440,30 @@ const threadParticipation = createPersistentDedupeCache({
97
440
  }
98
441
  }
99
442
  });
100
- function makeKey(accountId, channelId, threadTs) {
101
- return `${accountId}:${channelId}:${threadTs}`;
443
+ function makeKey(accountId, channelId, threadTs, teamId) {
444
+ return `${accountId}:${teamId ? `${teamId}:` : ""}${channelId}:${threadTs}`;
102
445
  }
103
446
  function recordSlackThreadParticipation(accountId, channelId, threadTs, opts) {
104
447
  if (!accountId || !channelId || !threadTs) return;
105
- threadParticipation.register(makeKey(accountId, channelId, threadTs), {
448
+ threadParticipation.register(makeKey(accountId, channelId, threadTs, opts?.teamId), {
106
449
  ...opts?.agentId ? { agentId: opts.agentId } : {},
107
450
  repliedAt: Date.now()
108
451
  });
109
452
  }
110
- function hasSlackThreadParticipation(accountId, channelId, threadTs) {
453
+ function hasSlackThreadParticipation(accountId, channelId, threadTs, teamId) {
111
454
  if (!accountId || !channelId || !threadTs) return false;
112
- return threadParticipation.peek(makeKey(accountId, channelId, threadTs));
455
+ return threadParticipation.peek(makeKey(accountId, channelId, threadTs, teamId));
113
456
  }
114
457
  async function hasSlackThreadParticipationWithPersistence(params) {
115
458
  if (!params.accountId || !params.channelId || !params.threadTs) return false;
116
- return await threadParticipation.lookup(makeKey(params.accountId, params.channelId, params.threadTs));
459
+ return await threadParticipation.lookup(makeKey(params.accountId, params.channelId, params.threadTs, params.teamId));
117
460
  }
118
461
  function clearSlackThreadParticipationCache() {
119
462
  threadParticipation.clearForTest();
120
463
  }
121
464
  //#endregion
122
465
  //#region extensions/slack/src/send.ts
123
- const SLACK_UPLOAD_SSRF_POLICY = {
124
- allowedHostnames: [
125
- "*.slack.com",
126
- "*.slack-edge.com",
127
- "*.slack-files.com"
128
- ],
129
- allowRfc2544BenchmarkRange: true
130
- };
131
466
  const SLACK_DM_CHANNEL_CACHE_MAX = 1024;
132
- const SLACK_DNS_RETRY_CODES = /* @__PURE__ */ new Set([
133
- "EAI_AGAIN",
134
- "ENOTFOUND",
135
- "UND_ERR_DNS_RESOLVE_FAILED"
136
- ]);
137
- const SLACK_DNS_RETRY_ATTEMPTS = 2;
138
- const SLACK_DNS_RETRY_BASE_DELAY_MS = 250;
139
467
  const SLACK_DELIVERY_METADATA_EVENT = "openclaw_delivery";
140
468
  const SLACK_DELIVERY_METADATA_KEY = "openclaw_delivery_id";
141
469
  const SLACK_DELIVERY_METADATA_PART_INDEX_KEY = "openclaw_delivery_part_index";
@@ -145,7 +473,8 @@ const SLACK_RECONCILE_LOOKBACK_MS = 3e4;
145
473
  const SLACK_RECONCILE_CLOCK_SKEW_MS = 5 * 6e4;
146
474
  const SLACK_RECONCILE_LIMIT = 100;
147
475
  const SLACK_RECONCILE_MAX_PAGES = 10;
148
- const slackDmChannelCache = /* @__PURE__ */ new Map();
476
+ const SLACK_ENTERPRISE_LISTENER_QUEUE_CREDENTIAL = "listener-scoped-enterprise";
477
+ const slackDmChannelCaches = /* @__PURE__ */ new WeakMap();
149
478
  const slackSendQueue = new KeyedAsyncQueue();
150
479
  const slackDefaultSendIdentities = /* @__PURE__ */ new Map();
151
480
  function hasCustomIdentity(identity) {
@@ -176,34 +505,6 @@ function getSlackDefaultSendIdentity(accountId) {
176
505
  function resolveSlackSendIdentity(params) {
177
506
  return normalizeSlackSendIdentity(params.explicit) ?? getSlackDefaultSendIdentity(params.accountId);
178
507
  }
179
- function buildSlackUnfurlPayload(options) {
180
- return {
181
- unfurl_links: options?.unfurlLinks ?? false,
182
- ...typeof options?.unfurlMedia === "boolean" ? { unfurl_media: options.unfurlMedia } : {}
183
- };
184
- }
185
- function buildSlackPostMessagePayload(params) {
186
- const threadPayload = params.replyBroadcast && params.threadTs ? {
187
- thread_ts: params.threadTs,
188
- reply_broadcast: true
189
- } : params.threadTs ? { thread_ts: params.threadTs } : {};
190
- const unfurlPayload = buildSlackUnfurlPayload(params.unfurl);
191
- if (params.blocks?.length) return {
192
- channel: params.channelId,
193
- text: params.text,
194
- blocks: params.blocks,
195
- ...params.metadata ? { metadata: params.metadata } : {},
196
- ...threadPayload,
197
- ...unfurlPayload
198
- };
199
- return {
200
- channel: params.channelId,
201
- text: params.text,
202
- ...params.metadata ? { metadata: params.metadata } : {},
203
- ...threadPayload,
204
- ...unfurlPayload
205
- };
206
- }
207
508
  function normalizeSlackScopeList(value) {
208
509
  if (!Array.isArray(value)) return [];
209
510
  return value.flatMap((scope) => {
@@ -236,96 +537,10 @@ function enrichSlackWebApiError(err) {
236
537
  if (!message || !(err instanceof Error) || message === err.message) return err;
237
538
  return new Error(message);
238
539
  }
239
- function readSlackRequestErrorCode(value) {
240
- if (!value || typeof value !== "object") return;
241
- const code = value.code;
242
- return typeof code === "string" ? code.toUpperCase() : void 0;
243
- }
244
540
  function readSlackRequestErrorMessage(value) {
245
541
  if (value instanceof Error) return value.message;
246
542
  return typeof value === "string" ? value : "";
247
543
  }
248
- function hasSlackDnsRequestSignal(err) {
249
- let current = err;
250
- const seen = /* @__PURE__ */ new Set();
251
- for (let depth = 0; current && typeof current === "object" && depth < 6; depth += 1) {
252
- if (seen.has(current)) return false;
253
- seen.add(current);
254
- const code = readSlackRequestErrorCode(current);
255
- if (code && SLACK_DNS_RETRY_CODES.has(code)) return true;
256
- const message = readSlackRequestErrorMessage(current);
257
- if (/\b(EAI_AGAIN|ENOTFOUND|UND_ERR_DNS_RESOLVE_FAILED)\b/i.test(message)) return true;
258
- current = current.original ?? current.cause;
259
- }
260
- return false;
261
- }
262
- function delaySlackDnsRetry(attempt) {
263
- return new Promise((resolve) => {
264
- setTimeout(resolve, SLACK_DNS_RETRY_BASE_DELAY_MS * Math.max(1, attempt));
265
- });
266
- }
267
- async function withSlackDnsRequestRetry(operation, fn) {
268
- for (const attempt of Array.from({ length: 3 }, (_, index) => index)) try {
269
- return await fn();
270
- } catch (err) {
271
- if (attempt >= SLACK_DNS_RETRY_ATTEMPTS || !hasSlackDnsRequestSignal(err)) throw err;
272
- logVerbose(`slack send: retrying ${operation} after transient DNS request error (${attempt + 1}/${SLACK_DNS_RETRY_ATTEMPTS})`);
273
- await delaySlackDnsRetry(attempt + 1);
274
- }
275
- throw new Error("unreachable Slack DNS retry loop exit");
276
- }
277
- function isSlackCustomizeScopeError(err) {
278
- const data = getSlackWebApiErrorData(err);
279
- if (normalizeLowercaseStringOrEmpty(normalizeOptionalString(data?.error)) !== "missing_scope") return false;
280
- if (normalizeLowercaseStringOrEmpty(normalizeOptionalString(data?.needed))?.includes("chat:write.customize")) return true;
281
- return [...normalizeSlackScopeList(data?.response_metadata?.scopes), ...normalizeSlackScopeList(data?.response_metadata?.acceptedScopes)].map((scope) => normalizeLowercaseStringOrEmpty(scope)).includes("chat:write.customize");
282
- }
283
- function isSlackCustomIdentityRejectedError(err) {
284
- if (isSlackCustomizeScopeError(err)) return true;
285
- const code = normalizeLowercaseStringOrEmpty(normalizeOptionalString(getSlackWebApiErrorData(err)?.error));
286
- return code === "invalid_arguments" || code === "invalid_arg_name";
287
- }
288
- async function postSlackMessageBestEffort(params) {
289
- const basePayload = buildSlackPostMessagePayload(params);
290
- const postChatMessage = params.client.chat.postMessage.bind(params.client.chat);
291
- const post = async (payload, identity) => ({
292
- response: await withSlackDnsRequestRetry("chat.postMessage", () => postChatMessage(payload)),
293
- identity
294
- });
295
- try {
296
- const identity = params.identity;
297
- if (identity?.iconUrl) return await post({
298
- ...basePayload,
299
- ...identity.username ? { username: identity.username } : {},
300
- icon_url: identity.iconUrl
301
- }, identity);
302
- if (identity?.iconEmoji) return await post({
303
- ...basePayload,
304
- ...identity.username ? { username: identity.username } : {},
305
- icon_emoji: identity.iconEmoji
306
- }, identity);
307
- return await post({
308
- ...basePayload,
309
- ...identity?.username ? { username: identity.username } : {}
310
- }, identity);
311
- } catch (err) {
312
- const identity = params.identity;
313
- if (!identity || !hasCustomIdentity(identity) || !isSlackCustomIdentityRejectedError(err)) throw err;
314
- if (!isSlackCustomizeScopeError(err) && identity.username && (identity.iconUrl || identity.iconEmoji)) {
315
- logVerbose("slack send: custom icon rejected, retrying with username only");
316
- try {
317
- return await post({
318
- ...basePayload,
319
- username: identity.username
320
- }, { username: identity.username });
321
- } catch (retryError) {
322
- if (!isSlackCustomIdentityRejectedError(retryError)) throw retryError;
323
- }
324
- }
325
- logVerbose("slack send: custom identity rejected, retrying without custom identity");
326
- return post(basePayload);
327
- }
328
- }
329
544
  function resolvePostedMessageThreadTs(response) {
330
545
  const threadTs = response.message?.thread_ts;
331
546
  return typeof threadTs === "string" ? normalizeSlackThreadTsCandidate(threadTs) : void 0;
@@ -359,12 +574,54 @@ function parseRecipient(raw) {
359
574
  if (!target) throw new Error("Recipient is required for Slack sends");
360
575
  return {
361
576
  kind: target.kind,
362
- id: target.id
577
+ id: canonicalizeSlackApiTargetId(target.kind, target.id, raw)
363
578
  };
364
579
  }
580
+ function parseEnterpriseEventRecipient(raw) {
581
+ const match = /^(?:channel:)?([CDG][A-Z0-9]+)$/i.exec(raw.trim());
582
+ if (!match?.[1]) throw new Error("unsupported_enterprise_slack_delivery_target");
583
+ return {
584
+ kind: "channel",
585
+ id: canonicalizeSlackApiTargetId("channel", match[1])
586
+ };
587
+ }
588
+ function resolveEnterpriseEventScope(params) {
589
+ const scope = params.opts.enterpriseEventScope;
590
+ if (!scope) {
591
+ assertSlackDirectSendAllowed(params.account);
592
+ return;
593
+ }
594
+ if (params.account.config.enterpriseOrgInstall !== true) throw new Error("unexpected_enterprise_slack_listener_scope");
595
+ if (!scope.isEnterpriseInstall || !normalizeOptionalString(scope.apiAppId) || !normalizeOptionalString(scope.enterpriseId) || !/^T[A-Z0-9]+$/i.test(scope.teamId) || !scope.client || params.opts.client !== scope.client) throw new Error("invalid_enterprise_slack_listener_scope");
596
+ return scope;
597
+ }
598
+ function resolveSlackTextChunks(params) {
599
+ const text = params.preservePlainText ? params.text : params.text.trim();
600
+ const configuredLimit = params.textLimit ?? resolveTextChunkLimit(params.cfg, "slack", params.accountId, { fallbackLimit: 8e3 });
601
+ const chunkLimit = Math.min(configuredLimit, SLACK_TEXT_LIMIT);
602
+ if (params.preservePlainText) {
603
+ const chunks = [];
604
+ let remaining = text;
605
+ while (remaining) {
606
+ const chunk = sliceUtf16Safe(remaining, 0, chunkLimit) || Array.from(remaining)[0] || "";
607
+ chunks.push(chunk);
608
+ remaining = remaining.slice(chunk.length);
609
+ }
610
+ return chunks;
611
+ }
612
+ if (params.textIsSlackMrkdwn) return resolveTextChunksWithFallback(text, chunkSlackMrkdwnText(text, chunkLimit));
613
+ const tableMode = resolveMarkdownTableMode({
614
+ cfg: params.cfg,
615
+ channel: "slack",
616
+ ...params.accountId ? { accountId: params.accountId } : {}
617
+ });
618
+ const chunkMode = resolveChunkMode(params.cfg, "slack", params.accountId);
619
+ return resolveTextChunksWithFallback(text, (chunkMode === "newline" ? chunkMarkdownTextWithMode(text, chunkLimit, chunkMode) : [text]).flatMap((markdown) => markdownToSlackMrkdwnChunks(markdown, chunkLimit, { tableMode })));
620
+ }
365
621
  function createSlackSendQueueKey(params) {
366
- const recipientKey = `${params.recipient.kind === "user" || /^U[A-Z0-9]+$/i.test(params.recipient.id) ? "user" : params.recipient.kind}:${params.recipient.id}`;
367
- return `${params.accountId}:${createSlackTokenCacheKey(params.token)}:${recipientKey}:${params.threadTs ?? ""}`;
622
+ const recipientKey = `${params.recipient.kind}:${params.recipient.id}`;
623
+ const workspaceScope = params.teamId ? `:${params.teamId}` : "";
624
+ return `${params.accountId}:${createSlackTokenCacheKey(params.token)}${workspaceScope}:${recipientKey}:${params.threadTs ?? ""}`;
368
625
  }
369
626
  async function runQueuedSlackSend(key, task) {
370
627
  return await slackSendQueue.enqueue(key, task);
@@ -372,16 +629,23 @@ async function runQueuedSlackSend(key, task) {
372
629
  function createSlackDmCacheKey(params) {
373
630
  return `${params.accountId ?? "default"}:${createSlackTokenCacheKey(params.token)}:${params.recipientId}`;
374
631
  }
375
- function setSlackDmChannelCache(key, channelId) {
376
- if (slackDmChannelCache.has(key)) slackDmChannelCache.delete(key);
377
- else if (slackDmChannelCache.size >= SLACK_DM_CHANNEL_CACHE_MAX) {
378
- const oldest = slackDmChannelCache.keys().next().value;
379
- if (oldest) slackDmChannelCache.delete(oldest);
632
+ function getSlackDmChannelCache(client) {
633
+ const existing = slackDmChannelCaches.get(client);
634
+ if (existing) return existing;
635
+ const cache = /* @__PURE__ */ new Map();
636
+ slackDmChannelCaches.set(client, cache);
637
+ return cache;
638
+ }
639
+ function setSlackDmChannelCache(cache, key, channelId) {
640
+ if (cache.has(key)) cache.delete(key);
641
+ else if (cache.size >= SLACK_DM_CHANNEL_CACHE_MAX) {
642
+ const oldest = cache.keys().next().value;
643
+ if (oldest) cache.delete(oldest);
380
644
  }
381
- slackDmChannelCache.set(key, channelId);
645
+ cache.set(key, channelId);
382
646
  }
383
647
  function isSlackUserRecipient(recipient) {
384
- return recipient.kind === "user" || /^U[A-Z0-9]+$/i.test(recipient.id);
648
+ return recipient.kind === "user";
385
649
  }
386
650
  function resolveDirectUserPostChannelId(params) {
387
651
  if (!isSlackUserRecipient(params.recipient) || params.hasMedia || params.threadTs) return;
@@ -397,7 +661,8 @@ async function resolveChannelId(client, recipient, params) {
397
661
  token: params.token,
398
662
  recipientId: recipient.id
399
663
  });
400
- const cachedChannelId = slackDmChannelCache.get(cacheKey);
664
+ const cache = getSlackDmChannelCache(client);
665
+ const cachedChannelId = cache.get(cacheKey);
401
666
  if (cachedChannelId) return {
402
667
  channelId: cachedChannelId,
403
668
  isDm: true,
@@ -405,7 +670,7 @@ async function resolveChannelId(client, recipient, params) {
405
670
  };
406
671
  const channelId = (await withSlackDnsRequestRetry("conversations.open", () => client.conversations.open({ users: recipient.id }))).channel?.id;
407
672
  if (!channelId) throw new Error("Failed to open Slack DM channel");
408
- setSlackDmChannelCache(cacheKey, channelId);
673
+ setSlackDmChannelCache(cache, cacheKey, channelId);
409
674
  return {
410
675
  channelId,
411
676
  isDm: true,
@@ -435,10 +700,6 @@ function createSlackDeliveryMetadataSignature(params) {
435
700
  params.partCount
436
701
  ])).digest("base64url");
437
702
  }
438
- function matchesSlackDeliveryMetadataSignature(actual, expected) {
439
- if (typeof actual !== "string" || actual.length !== expected.length) return false;
440
- return timingSafeEqual(Buffer.from(actual), Buffer.from(expected));
441
- }
442
703
  function withSlackDeliveryMetadata(metadata, params) {
443
704
  const queueId = normalizeOptionalString(params.queueId);
444
705
  const deliveryId = createSlackDeliveryMetadataId(queueId);
@@ -497,7 +758,8 @@ function findSlackConversationDeliveryParts(params) {
497
758
  partIndex,
498
759
  partCount
499
760
  });
500
- if (!matchesSlackDeliveryMetadataSignature(marker[SLACK_DELIVERY_METADATA_SIGNATURE_KEY], expectedSignature)) continue;
761
+ const actualSignature = marker[SLACK_DELIVERY_METADATA_SIGNATURE_KEY];
762
+ if (typeof actualSignature !== "string" || !safeEqualSecret(actualSignature, expectedSignature)) continue;
501
763
  const messageId = typeof message.ts === "string" ? normalizeSlackThreadTsCandidate(message.ts) : void 0;
502
764
  if (!messageId) continue;
503
765
  const threadTs = typeof message.thread_ts === "string" ? normalizeSlackThreadTsCandidate(message.thread_ts) : void 0;
@@ -674,53 +936,24 @@ async function reconcileSlackUnknownSend(ctx, opts) {
674
936
  };
675
937
  }
676
938
  }
677
- async function uploadSlackFile(params) {
678
- const { buffer, contentType, fileName } = await loadOutboundMediaFromUrl(params.mediaUrl, {
679
- maxBytes: params.maxBytes,
680
- mediaAccess: params.mediaAccess,
681
- mediaLocalRoots: params.mediaLocalRoots,
682
- mediaReadFile: params.mediaReadFile
683
- });
684
- const uploadFileName = params.uploadFileName ?? fileName ?? "upload";
685
- const uploadTitle = params.uploadTitle ?? uploadFileName;
686
- const uploadUrlResp = await withSlackDnsRequestRetry("files.getUploadURLExternal", () => params.client.files.getUploadURLExternal({
687
- filename: uploadFileName,
688
- length: buffer.length
689
- }));
690
- if (!uploadUrlResp.ok || !uploadUrlResp.upload_url || !uploadUrlResp.file_id) throw new Error(`Failed to get upload URL: ${uploadUrlResp.error ?? "unknown error"}`);
691
- const uploadFileId = uploadUrlResp.file_id;
692
- const uploadBody = new Uint8Array(buffer);
693
- const { response: uploadResp, release } = await fetchWithSsrFGuard(withTrustedEnvProxyGuardedFetchMode({
694
- url: uploadUrlResp.upload_url,
695
- init: {
696
- method: "POST",
697
- ...contentType ? { headers: { "Content-Type": contentType } } : {},
698
- body: uploadBody
699
- },
700
- policy: SLACK_UPLOAD_SSRF_POLICY,
701
- auditContext: "slack-upload-file"
702
- }));
703
- try {
704
- if (!uploadResp.ok) throw new Error(`Failed to upload file: HTTP ${uploadResp.status}`);
705
- } finally {
706
- await release();
707
- }
708
- await params.onPlatformSendDispatch?.();
709
- const completeResp = await withSlackDnsRequestRetry("files.completeUploadExternal", () => params.client.files.completeUploadExternal({
710
- files: [{
711
- id: uploadFileId,
712
- title: uploadTitle
713
- }],
714
- channel_id: params.channelId,
715
- ...params.caption ? { initial_comment: params.caption } : {},
716
- ...params.threadTs ? { thread_ts: params.threadTs } : {}
717
- }));
718
- if (!completeResp.ok) throw new Error(`Failed to complete upload: ${completeResp.error ?? "unknown error"}`);
719
- return uploadFileId;
720
- }
721
939
  async function sendMessageSlack(to, message, opts) {
722
- const trimmedMessage = normalizeOptionalString(message) ?? "";
723
- if (isSilentReplyText(trimmedMessage) && !opts.mediaUrl && !opts.blocks) {
940
+ const normalizedMessage = normalizeOptionalString(message) ?? "";
941
+ const trimmedMessage = opts.textIsSlackPlainText && normalizedMessage ? message : normalizedMessage;
942
+ const cfg = requireRuntimeConfig(opts.cfg, "Slack send");
943
+ const account = resolveSlackAccount({
944
+ cfg,
945
+ accountId: opts.accountId
946
+ });
947
+ const enterpriseEventScope = resolveEnterpriseEventScope({
948
+ account,
949
+ opts
950
+ });
951
+ const enterpriseDelivery = enterpriseEventScope ? Object.freeze({
952
+ client: enterpriseEventScope.client,
953
+ teamId: enterpriseEventScope.teamId,
954
+ ...enterpriseEventScope.uploadCompletionClient ? { uploadCompletionClient: enterpriseEventScope.uploadCompletionClient } : {}
955
+ }) : void 0;
956
+ if (isSilentReplyText(normalizedMessage) && !opts.mediaUrl && !opts.blocks) {
724
957
  logVerbose("slack send: suppressed NO_REPLY token before API call");
725
958
  return {
726
959
  messageId: "suppressed",
@@ -732,35 +965,38 @@ async function sendMessageSlack(to, message, opts) {
732
965
  };
733
966
  }
734
967
  const blocks = opts.blocks == null ? void 0 : validateSlackBlocksArray(opts.blocks);
735
- if (!trimmedMessage && !opts.mediaUrl && !blocks) throw new Error("Slack send requires text, blocks, or media");
736
- const cfg = requireRuntimeConfig(opts.cfg, "Slack send");
737
- const account = resolveSlackAccount({
738
- cfg,
739
- accountId: opts.accountId
740
- });
741
- const token = resolveToken({
968
+ if (!normalizedMessage && !opts.mediaUrl && !blocks) throw new Error("Slack send requires text, blocks, or media");
969
+ const token = enterpriseDelivery ? SLACK_ENTERPRISE_LISTENER_QUEUE_CREDENTIAL : resolveToken({
742
970
  explicit: opts.token,
743
971
  accountId: account.accountId,
744
972
  fallbackToken: account.botToken,
745
973
  fallbackSource: account.botTokenSource
746
974
  });
747
- const recipient = parseRecipient(to);
748
- const result = await runQueuedSlackSend(createSlackSendQueueKey({
975
+ const recipient = enterpriseDelivery ? parseEnterpriseEventRecipient(to) : parseRecipient(to);
976
+ const queueKey = createSlackSendQueueKey({
749
977
  accountId: account.accountId,
750
978
  token,
751
979
  recipient,
752
- threadTs: opts.threadTs
753
- }), () => sendMessageSlackQueued({
980
+ threadTs: opts.threadTs,
981
+ ...enterpriseDelivery ? { teamId: enterpriseDelivery.teamId } : {}
982
+ });
983
+ const queuedOpts = enterpriseDelivery ? Object.freeze({
984
+ ...opts,
985
+ client: enterpriseDelivery.client
986
+ }) : opts;
987
+ const result = await runQueuedSlackSend(queueKey, () => sendMessageSlackQueued({
754
988
  trimmedMessage,
755
- opts,
989
+ opts: queuedOpts,
756
990
  cfg,
757
991
  account,
758
992
  token,
759
993
  recipient,
760
- blocks
994
+ blocks,
995
+ ...enterpriseDelivery ? { enterpriseDelivery } : {}
761
996
  }));
762
- const threadTs = result.threadTs ?? normalizeSlackThreadTsCandidate(opts.threadTs);
763
- if (threadTs && result.channelId && account.accountId) recordSlackThreadParticipation(account.accountId, result.channelId, threadTs);
997
+ const threadTs = result.threadTs ?? normalizeSlackThreadTsCandidate(queuedOpts.threadTs);
998
+ if (threadTs && result.channelId && account.accountId) if (enterpriseDelivery) recordSlackThreadParticipation(account.accountId, result.channelId, threadTs, { teamId: enterpriseDelivery.teamId });
999
+ else recordSlackThreadParticipation(account.accountId, result.channelId, threadTs);
764
1000
  return result;
765
1001
  }
766
1002
  async function sendMessageSlackQueued(params) {
@@ -771,14 +1007,14 @@ async function sendMessageSlackQueued(params) {
771
1007
  }
772
1008
  }
773
1009
  async function sendMessageSlackQueuedInner(params) {
774
- const { opts, cfg, account, token, recipient, blocks, trimmedMessage } = params;
775
- const client = opts.client ?? getSlackWriteClient(token);
776
- const identity = resolveSlackSendIdentity({
1010
+ const { opts, cfg, account, token, recipient, blocks, trimmedMessage, enterpriseDelivery } = params;
1011
+ const client = enterpriseDelivery?.client ?? opts.client ?? getSlackWriteClient(token);
1012
+ const identity = enterpriseDelivery ? normalizeSlackSendIdentity(opts.identity) : resolveSlackSendIdentity({
777
1013
  accountId: account.accountId,
778
1014
  explicit: opts.identity
779
1015
  });
780
1016
  if (opts.replyBroadcast && opts.mediaUrl) throw new Error("Slack replyBroadcast is only supported for text or block thread replies.");
781
- const unfurl = {
1017
+ const unfurl = enterpriseDelivery ? { unfurlMedia: account.config.unfurlMedia } : {
782
1018
  unfurlLinks: account.config.unfurlLinks,
783
1019
  unfurlMedia: account.config.unfurlMedia
784
1020
  };
@@ -795,55 +1031,153 @@ async function sendMessageSlackQueuedInner(params) {
795
1031
  await opts.onDeliveryResult?.(result);
796
1032
  return result;
797
1033
  };
798
- if (blocks) {
799
- if (opts.mediaUrl) throw new Error("Slack send does not support blocks with mediaUrl");
800
- const fallbackText = truncateSlackText(trimmedMessage || buildSlackBlocksFallbackText(blocks), SLACK_TEXT_LIMIT);
1034
+ let didDispatch = false;
1035
+ const dispatchOnce = async () => {
1036
+ if (didDispatch) return;
1037
+ didDispatch = true;
801
1038
  await opts.onPlatformSendDispatch?.();
802
- const { response } = await postSlackMessageBestEffort({
803
- client,
804
- channelId,
805
- text: fallbackText,
806
- threadTs: opts.threadTs,
807
- replyBroadcast: opts.replyBroadcast,
808
- identity,
809
- blocks,
810
- metadata: opts.metadata,
811
- unfurl
812
- });
813
- const messageId = response.ts ?? "unknown";
814
- const deliveredChannelId = resolvePostedMessageChannelId(response, channelId);
815
- const deliveredThreadTs = resolvePostedMessageThreadTs(response) ?? normalizeSlackThreadTsCandidate(opts.threadTs);
816
- return await reportDelivery({
817
- messageId,
818
- channelId: deliveredChannelId,
819
- threadTs: deliveredThreadTs,
820
- receipt: createSlackSendReceipt({
821
- platformMessageIds: [messageId],
822
- channelId: deliveredChannelId,
823
- kind: "card",
824
- threadTs: deliveredThreadTs
825
- })
826
- });
827
- }
828
- const textLimit = resolveTextChunkLimit(cfg, "slack", account.accountId, { fallbackLimit: SLACK_TEXT_LIMIT });
829
- const chunkLimit = Math.min(textLimit, SLACK_TEXT_LIMIT);
830
- const tableMode = resolveMarkdownTableMode({
831
- cfg,
832
- channel: "slack",
833
- accountId: account.accountId
834
- });
835
- const chunkMode = resolveChunkMode(cfg, "slack", account.accountId);
836
- const resolvedChunks = resolveTextChunksWithFallback(trimmedMessage, (chunkMode === "newline" ? chunkMarkdownTextWithMode(trimmedMessage, chunkLimit, chunkMode) : [trimmedMessage]).flatMap((markdown) => markdownToSlackMrkdwnChunks(markdown, chunkLimit, { tableMode })));
837
- const mediaMaxBytes = typeof account.config.mediaMaxMb === "number" ? account.config.mediaMaxMb * 1024 * 1024 : void 0;
1039
+ };
1040
+ const explicitNativeDataFallbackBase = Object.hasOwn(opts, "nativeDataFallbackBaseText") ? opts.nativeDataFallbackBaseText?.trim() ?? "" : void 0;
1041
+ const authoredTextPlacement = opts.authoredTextPlacement ?? "outside-blocks";
1042
+ const nativeDataFallbackBase = blocks && authoredTextPlacement === "outside-blocks" ? explicitNativeDataFallbackBase ?? trimmedMessage : "";
1043
+ const hasNativeData = Boolean(blocks && hasSlackNativeDataBlock(blocks));
1044
+ const usesOrderedBlockAccessibility = Boolean(blocks && (hasNativeData || opts.authoredTextPlacement !== void 0));
1045
+ const orderedBlockDeliveryPlan = blocks && usesOrderedBlockAccessibility ? buildSlackNativeDataDeliveryPlan({
1046
+ baseText: nativeDataFallbackBase,
1047
+ blocks
1048
+ }) : void 0;
1049
+ const orderedBlockAccessibilityText = blocks && usesOrderedBlockAccessibility ? orderedBlockDeliveryPlan?.accessibilityText ?? (buildSlackNativeDataAccessibilityText(nativeDataFallbackBase, blocks) || "Slack could not render this Block Kit message.") : void 0;
1050
+ const completeBlockFallbackText = blocks ? buildSlackCompleteBlocksFallbackText(blocks) : "";
1051
+ const rawBlockAccessibilityText = trimmedMessage ? [trimmedMessage, ...completeBlockFallbackText.includes(trimmedMessage) ? [] : [completeBlockFallbackText]].join("\n\n") : completeBlockFallbackText;
1052
+ const blockAccessibilityText = blocks ? orderedBlockAccessibilityText ?? rawBlockAccessibilityText : void 0;
1053
+ let pendingBlockFallback = (hasNativeData || opts.authoredTextPlacement !== void 0) && orderedBlockDeliveryPlan?.skipOriginalBlocks ? orderedBlockDeliveryPlan : void 0;
838
1054
  const sentMessageIds = [];
839
1055
  let lastMessageId = "";
840
1056
  let deliveredChannelId = channelId;
841
1057
  let canonicalDeliveredThreadTs;
1058
+ let sendIdentity = identity;
1059
+ if (blocks && opts.mediaUrl) throw new Error("Slack send does not support blocks with mediaUrl");
1060
+ if (blocks) {
1061
+ if (pendingBlockFallback) logVerbose("slack send: native data accessibility exceeds hard limit, using text fallback");
1062
+ else {
1063
+ const accessibilityText = hasNativeData ? blockAccessibilityText ?? "Slack could not render this Block Kit message." : usesOrderedBlockAccessibility ? blockAccessibilityText ?? "Slack could not render this Block Kit message." : truncateSlackText(blockAccessibilityText ?? "", SLACK_TEXT_LIMIT);
1064
+ const initialBlockMetadata = withSlackDeliveryMetadata(opts.metadata, {
1065
+ queueId: opts.deliveryQueueId,
1066
+ channelId,
1067
+ threadTs: opts.threadTs,
1068
+ partIndex: 0,
1069
+ partCount: 1
1070
+ });
1071
+ await dispatchOnce();
1072
+ try {
1073
+ const { response } = await postSlackMessageBestEffort({
1074
+ client,
1075
+ channelId,
1076
+ text: accessibilityText,
1077
+ threadTs: opts.threadTs,
1078
+ replyBroadcast: opts.replyBroadcast,
1079
+ identity,
1080
+ blocks,
1081
+ metadata: initialBlockMetadata,
1082
+ ...usesOrderedBlockAccessibility ? { mrkdwn: false } : {},
1083
+ unfurl
1084
+ });
1085
+ if (enterpriseDelivery && (!response.ok || !response.ts)) throw new Error(response.ok ? "Slack chat.postMessage returned no message timestamp" : `Slack chat.postMessage failed: ${response.error ?? "unknown error"}`);
1086
+ const messageId = response.ts ?? "unknown";
1087
+ deliveredChannelId = resolvePostedMessageChannelId(response, channelId);
1088
+ const deliveredThreadTs = resolvePostedMessageThreadTs(response) ?? normalizeSlackThreadTsCandidate(opts.threadTs);
1089
+ return await reportDelivery({
1090
+ messageId,
1091
+ channelId: deliveredChannelId,
1092
+ threadTs: deliveredThreadTs,
1093
+ receipt: createSlackSendReceipt({
1094
+ platformMessageIds: [messageId],
1095
+ channelId: deliveredChannelId,
1096
+ kind: "card",
1097
+ threadTs: deliveredThreadTs
1098
+ })
1099
+ });
1100
+ } catch (error) {
1101
+ if (!hasNativeData || !isSlackInvalidBlocksError(error)) throw error;
1102
+ logVerbose("slack send: native data rejected, delivering complete text fallback");
1103
+ pendingBlockFallback = orderedBlockDeliveryPlan;
1104
+ }
1105
+ }
1106
+ if (pendingBlockFallback) {
1107
+ const fallbackMessages = pendingBlockFallback.fallbackMessages;
1108
+ for (const [partIndex, fallback] of fallbackMessages.entries()) {
1109
+ const metadata = withSlackDeliveryMetadata(partIndex === 0 ? opts.metadata : void 0, {
1110
+ queueId: opts.deliveryQueueId,
1111
+ channelId,
1112
+ threadTs: opts.threadTs,
1113
+ partIndex,
1114
+ partCount: fallbackMessages.length
1115
+ });
1116
+ if (partIndex === 0) await dispatchOnce();
1117
+ const posted = await postSlackMessageBestEffort({
1118
+ client,
1119
+ channelId,
1120
+ text: fallback.text,
1121
+ threadTs: opts.threadTs,
1122
+ replyBroadcast: partIndex === 0 ? opts.replyBroadcast : void 0,
1123
+ identity: sendIdentity,
1124
+ ...fallback.blocks ? { blocks: fallback.blocks } : {},
1125
+ metadata,
1126
+ mrkdwn: false,
1127
+ unfurl
1128
+ });
1129
+ const response = posted.response;
1130
+ if (enterpriseDelivery && (!response.ok || !response.ts)) throw new Error(response.ok ? "Slack chat.postMessage returned no message timestamp" : `Slack chat.postMessage failed: ${response.error ?? "unknown error"}`);
1131
+ sendIdentity = posted.identity;
1132
+ lastMessageId = response.ts ?? lastMessageId;
1133
+ deliveredChannelId = resolvePostedMessageChannelId(response, deliveredChannelId);
1134
+ canonicalDeliveredThreadTs ??= resolvePostedMessageThreadTs(response);
1135
+ if (!response.ts) continue;
1136
+ sentMessageIds.push(response.ts);
1137
+ const deliveredThreadTs = resolvePostedMessageThreadTs(response) ?? normalizeSlackThreadTsCandidate(opts.threadTs);
1138
+ await reportDelivery({
1139
+ messageId: response.ts,
1140
+ channelId: deliveredChannelId,
1141
+ threadTs: deliveredThreadTs,
1142
+ receipt: createSlackSendReceipt({
1143
+ platformMessageIds: [response.ts],
1144
+ channelId: deliveredChannelId,
1145
+ kind: fallback.blocks ? "card" : "text",
1146
+ threadTs: deliveredThreadTs
1147
+ })
1148
+ });
1149
+ }
1150
+ const messageId = lastMessageId || "unknown";
1151
+ const deliveredThreadTs = canonicalDeliveredThreadTs ?? normalizeSlackThreadTsCandidate(opts.threadTs);
1152
+ return {
1153
+ messageId,
1154
+ channelId: deliveredChannelId,
1155
+ threadTs: deliveredThreadTs,
1156
+ receipt: createSlackSendReceipt({
1157
+ platformMessageIds: sentMessageIds.length ? sentMessageIds : [messageId],
1158
+ channelId: deliveredChannelId,
1159
+ kind: fallbackMessages.some((message) => message.blocks) ? "card" : "text",
1160
+ threadTs: deliveredThreadTs
1161
+ })
1162
+ };
1163
+ }
1164
+ }
1165
+ const resolvedChunks = resolveSlackTextChunks({
1166
+ cfg,
1167
+ accountId: account.accountId,
1168
+ text: trimmedMessage,
1169
+ ...opts.textLimit !== void 0 ? { textLimit: opts.textLimit } : {},
1170
+ ...opts.textIsSlackMrkdwn ? { textIsSlackMrkdwn: true } : {},
1171
+ ...opts.textIsSlackPlainText ? { preservePlainText: true } : {}
1172
+ });
1173
+ const mediaMaxBytes = opts.mediaMaxBytes ?? (typeof account.config.mediaMaxMb === "number" ? account.config.mediaMaxMb * 1024 * 1024 : void 0);
842
1174
  let chunksToPost;
843
1175
  if (opts.mediaUrl) {
1176
+ if (enterpriseDelivery && !enterpriseDelivery.uploadCompletionClient) throw new Error("missing_enterprise_slack_upload_completion_client");
844
1177
  const [firstChunk, ...rest] = resolvedChunks;
845
1178
  lastMessageId = await uploadSlackFile({
846
1179
  client,
1180
+ ...enterpriseDelivery?.uploadCompletionClient ? { completionClient: enterpriseDelivery.uploadCompletionClient } : {},
847
1181
  channelId,
848
1182
  mediaUrl: opts.mediaUrl,
849
1183
  mediaAccess: opts.mediaAccess,
@@ -854,7 +1188,8 @@ async function sendMessageSlackQueuedInner(params) {
854
1188
  caption: firstChunk,
855
1189
  threadTs: opts.threadTs,
856
1190
  maxBytes: mediaMaxBytes,
857
- onPlatformSendDispatch: opts.onPlatformSendDispatch
1191
+ onPlatformSendDispatch: dispatchOnce,
1192
+ ...enterpriseDelivery ? { auditContext: "slack-enterprise-immediate-upload" } : {}
858
1193
  });
859
1194
  sentMessageIds.push(lastMessageId);
860
1195
  await reportDelivery({
@@ -870,9 +1205,9 @@ async function sendMessageSlackQueuedInner(params) {
870
1205
  });
871
1206
  chunksToPost = rest;
872
1207
  } else chunksToPost = resolvedChunks.length ? resolvedChunks : [""];
873
- let sendIdentity = identity;
874
1208
  for (const [partIndex, chunk] of chunksToPost.entries()) {
875
- const baseMetadata = sentMessageIds.length === 0 ? opts.metadata : void 0;
1209
+ const carriesPrimaryMessageOptions = partIndex === 0 && !opts.mediaUrl && sentMessageIds.length === 0;
1210
+ const baseMetadata = carriesPrimaryMessageOptions ? opts.metadata : void 0;
876
1211
  const metadata = opts.mediaUrl ? baseMetadata : withSlackDeliveryMetadata(baseMetadata, {
877
1212
  queueId: opts.deliveryQueueId,
878
1213
  channelId,
@@ -880,18 +1215,20 @@ async function sendMessageSlackQueuedInner(params) {
880
1215
  partIndex,
881
1216
  partCount: chunksToPost.length
882
1217
  });
883
- if (partIndex === 0 && !opts.mediaUrl) await opts.onPlatformSendDispatch?.();
1218
+ if (partIndex === 0 && !opts.mediaUrl) await dispatchOnce();
884
1219
  const posted = await postSlackMessageBestEffort({
885
1220
  client,
886
1221
  channelId,
887
1222
  text: chunk,
888
1223
  threadTs: opts.threadTs,
889
- replyBroadcast: sentMessageIds.length === 0 ? opts.replyBroadcast : void 0,
1224
+ replyBroadcast: carriesPrimaryMessageOptions ? opts.replyBroadcast : void 0,
890
1225
  identity: sendIdentity,
891
1226
  metadata,
1227
+ ...opts.textIsSlackPlainText ? { mrkdwn: false } : {},
892
1228
  unfurl
893
1229
  });
894
1230
  const response = posted.response;
1231
+ if (enterpriseDelivery && (!response.ok || !response.ts)) throw new Error(response.ok ? "Slack chat.postMessage returned no message timestamp" : `Slack chat.postMessage failed: ${response.error ?? "unknown error"}`);
895
1232
  sendIdentity = posted.identity;
896
1233
  lastMessageId = response.ts ?? lastMessageId;
897
1234
  deliveredChannelId = resolvePostedMessageChannelId(response, deliveredChannelId);
@@ -926,4 +1263,4 @@ async function sendMessageSlackQueuedInner(params) {
926
1263
  };
927
1264
  }
928
1265
  //#endregion
929
- export { clearSlackThreadParticipationCache as a, recordSlackThreadParticipation as c, setSlackDefaultSendIdentity as i, buildSlackBlocksFallbackText as l, resolveSlackDmChannelId as n, hasSlackThreadParticipation as o, sendMessageSlack as r, hasSlackThreadParticipationWithPersistence as s, reconcileSlackUnknownSend as t };
1266
+ export { clearSlackThreadParticipationCache as a, recordSlackThreadParticipation as c, setSlackDefaultSendIdentity as i, buildSlackNativeDataDeliveryPlan as l, resolveSlackDmChannelId as n, hasSlackThreadParticipation as o, sendMessageSlack as r, hasSlackThreadParticipationWithPersistence as s, reconcileSlackUnknownSend as t, chunkSlackTextAtHardLimit as u };