@openclaw/slack 2026.7.2-beta.7 → 2026.8.1-beta.3

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 (87) hide show
  1. package/dist/{account-inspect-DEKpg9Ip.js → account-inspect-YJvFBbCR.js} +1 -1
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/{accounts-CHAeNZOX.js → accounts-BlzxuYle.js} +7 -3
  4. package/dist/{accounts.runtime-BHD4Ma2R.js → accounts.runtime-CbabBL8x.js} +1 -1
  5. package/dist/{action-runtime-DfuYoMjG.js → action-runtime-D6jg2CQd.js} +193 -125
  6. package/dist/action-runtime.runtime-JtoOK9l5.js +2 -0
  7. package/dist/{actions-D3NuJvva.js → actions-DFk0ANVB.js} +116 -235
  8. package/dist/actions.runtime-CYLsYRai.js +2 -0
  9. package/dist/api.js +17 -17
  10. package/dist/{approval-handler.runtime-BA05P0QT.js → approval-handler.runtime-BNvAoKdK.js} +53 -14
  11. package/dist/{channel-B9fVdW3s.js → channel-CA5w7qDF.js} +209 -110
  12. package/dist/channel-config-api.js +1 -1
  13. package/dist/channel-plugin-api.js +1 -1
  14. package/dist/{channel-type-DXyos1d2.js → channel-type-CZWv55Wu.js} +9 -12
  15. package/dist/{config-adapter-CxBQH8fq.js → channel.setup-CpyYsEXH.js} +83 -25
  16. package/dist/{config-schema-SgZT6nXV.js → config-schema-CN3XZRX5.js} +27 -17
  17. package/dist/conversation-binding-route-DFVLUdYF.js +115 -0
  18. package/dist/{directory-config-CnCIAktf.js → directory-config-BGYyuzWh.js} +3 -3
  19. package/dist/directory-contract-api.js +1 -1
  20. package/dist/{directory-live-DKunWh3A.js → directory-live-DjUJQLbq.js} +13 -22
  21. package/dist/{doctor-contract-CsGH1Dfc.js → doctor-contract-BAy7-EUQ.js} +30 -3
  22. package/dist/doctor-contract-api.js +1 -1
  23. package/dist/{group-policy-NQZti9t-.js → group-policy-DyfB_ZIE.js} +282 -183
  24. package/dist/index.js +4 -1
  25. package/dist/{message-tool-api-DkhFpMoy.js → message-tool-api-CqMr5Rmc.js} +14 -3
  26. package/dist/message-tool-api.js +1 -1
  27. package/dist/{monitor-CmKz0wtI.js → monitor-Chrecrb9.js} +3 -3
  28. package/dist/{outbound-adapter-BkuMp2Vs.js → outbound-adapter-DuRqAQXK.js} +35 -23
  29. package/dist/{pipeline.runtime-DgtmuYCP.js → pipeline.runtime-Bbfd5VFW.js} +1386 -929
  30. package/dist/{policy-PRysPn23.js → policy-BKXSxrIS.js} +18 -14
  31. package/dist/{probe-CgDsSops.js → probe-BVA65leh.js} +38 -18
  32. package/dist/{provider-DmRizMZt.js → provider-CUUiwsg2.js} +3454 -2601
  33. package/dist/{relay-source-Blxj8xb4.js → relay-source-mhpMMRG-.js} +11 -21
  34. package/dist/{replies-4s_Vq_z9.js → replies-SnWyXcsL.js} +84 -27
  35. package/dist/{resolve-channels-yEqmM-3I.js → resolve-channels-BSpqC8jU.js} +39 -12
  36. package/dist/{resolve-users-BOrt5K-5.js → resolve-users-Dvz9pDe0.js} +28 -5
  37. package/dist/{runtime-api-QMb-Q5Ko.js → runtime-api-DBi7xc_g.js} +3 -3
  38. package/dist/runtime-api.js +11 -11
  39. package/dist/{scopes-BJSrkAp6.js → scopes-BSk4GZvZ.js} +1 -1
  40. package/dist/security-contract-api.js +1 -1
  41. package/dist/{send-DsLeZpST.js → send-CtnOxKCc.js} +249 -163
  42. package/dist/{send.runtime-gUdK6V3h.js → send.runtime-kYctMHSd.js} +1 -1
  43. package/dist/setup-entry.js +1 -4
  44. package/dist/setup-plugin-api.js +1 -1
  45. package/dist/{setup-surface-BeNzrMrJ.js → setup-surface-BPFfp6So.js} +6 -6
  46. package/dist/{shared-DgIN3Dcr.js → shared-DJBaslN7.js} +45 -45
  47. package/dist/{block-text-DkHaoGem.js → slack-client-kind-BXyOOICI.js} +17 -2
  48. package/dist/{slash-dispatch.runtime-zPkyl7dB.js → slash-dispatch.runtime-Cf2FTrnf.js} +2 -2
  49. package/dist/{streaming-compat-3_X-21wW.js → streaming-compat-B-e0mqM0.js} +3 -8
  50. package/dist/{target-parsing-N6lHogYZ.js → target-parsing-BnMD2ZqZ.js} +40 -2
  51. package/dist/{targets-KmjP2E8s.js → targets-Cx5W_n3W.js} +1 -1
  52. package/node_modules/@slack/bolt/node_modules/@types/node/README.md +1 -1
  53. package/node_modules/@slack/bolt/node_modules/@types/node/crypto.d.ts +1 -1
  54. package/node_modules/@slack/bolt/node_modules/@types/node/package.json +2 -2
  55. package/node_modules/@slack/socket-mode/node_modules/@types/node/README.md +1 -1
  56. package/node_modules/@slack/socket-mode/node_modules/@types/node/crypto.d.ts +1 -1
  57. package/node_modules/@slack/socket-mode/node_modules/@types/node/package.json +2 -2
  58. package/node_modules/@slack/web-api/node_modules/@types/node/README.md +1 -1
  59. package/node_modules/@slack/web-api/node_modules/@types/node/crypto.d.ts +1 -1
  60. package/node_modules/@slack/web-api/node_modules/@types/node/package.json +2 -2
  61. package/node_modules/undici/lib/cache/memory-cache-store.js +54 -9
  62. package/node_modules/undici/lib/cache/sqlite-cache-store.js +7 -1
  63. package/node_modules/undici/lib/core/request.js +12 -1
  64. package/node_modules/undici/lib/dispatcher/client-h1.js +11 -2
  65. package/node_modules/undici/lib/handler/cache-handler.js +246 -64
  66. package/node_modules/undici/lib/handler/cache-revalidation-handler.js +3 -3
  67. package/node_modules/undici/lib/handler/retry-handler.js +24 -0
  68. package/node_modules/undici/lib/interceptor/cache.js +159 -52
  69. package/node_modules/undici/lib/util/cache.js +324 -62
  70. package/node_modules/undici/lib/util/date.js +23 -6
  71. package/node_modules/undici/lib/web/cookies/util.js +79 -9
  72. package/node_modules/undici/package.json +1 -1
  73. package/openclaw.plugin.json +280 -33
  74. package/package.json +9 -11
  75. package/skills/slack/SKILL.md +10 -68
  76. package/dist/account-reply-mode-DrUXogvO.js +0 -14
  77. package/dist/action-runtime.runtime-DQErZYpz.js +0 -2
  78. package/dist/actions.runtime-BIRU_G4M.js +0 -2
  79. package/dist/channel.setup-CLP5oTvh.js +0 -56
  80. package/dist/preflight-audio.runtime-D5Bgags0.js +0 -3
  81. package/dist/slash-plugin-commands.runtime-Dosfcx3X.js +0 -2
  82. package/node_modules/p-map/index.d.ts +0 -155
  83. package/node_modules/p-map/index.js +0 -285
  84. package/node_modules/p-map/license +0 -9
  85. package/node_modules/p-map/package.json +0 -57
  86. package/node_modules/p-map/readme.md +0 -190
  87. package/dist/{security-audit-DR097212.js → security-audit-B_yKr7MW.js} +1 -1
@@ -1,43 +1,44 @@
1
- import { a as resolveSlackAccount, l as resolveSlackOperationToken } from "./accounts-CHAeNZOX.js";
2
- import { i as parseSlackTarget, t as canonicalizeSlackApiTargetId } from "./target-parsing-N6lHogYZ.js";
3
- import { C as hasSlackNativeDataBlock, D as stripSlackNativeDataBlocks, F as SLACK_EDIT_TEXT_MAX_BYTES, H as truncateSlackText, I as SLACK_MESSAGE_TEXT_HARD_LIMIT, L as SLACK_TEXT_LIMIT, M as buildSlackCompleteBlocksFallbackText, N as renderSlackBlockFallbackText, O as chunkSlackMrkdwnText, S as createSlackNativeDataBaseTextConsumer, U as truncateSlackTextByUtf8Bytes, b as appendSlackNativeDataPlainTextFallback, i as assertSlackDirectSendAllowed, it as validateSlackBlocksArray, k as markdownToSlackMrkdwnChunks, w as isSlackInvalidBlocksError, x as buildSlackNativeDataAccessibilityText } from "./group-policy-NQZti9t-.js";
1
+ import { a as resolveSlackAccount, l as resolveSlackOperationToken } from "./accounts-BlzxuYle.js";
2
+ import { a as parseSlackTarget, t as canonicalizeSlackApiTargetId } from "./target-parsing-BnMD2ZqZ.js";
3
+ import { B as SLACK_EDIT_TEXT_MAX_BYTES, D as createSlackNativeDataBaseTextConsumer, E as buildSlackNativeDataAccessibilityText, H as SLACK_MESSAGE_TEXT_RECOMMENDED_LIMIT, J as truncateSlackText, L as buildSlackCompleteBlocksFallbackText, M as stripSlackNativeDataBlocks, N as chunkSlackMrkdwnText, O as hasSlackNativeDataBlock, P as markdownToSlackMrkdwnChunks, R as renderSlackBlockFallbackText, T as appendSlackNativeDataPlainTextFallback, U as SLACK_TEXT_LIMIT, V as SLACK_MESSAGE_TEXT_HARD_LIMIT, X as SLACK_QUESTION_FINALIZATION_BLOCKS, Y as truncateSlackTextByUtf8Bytes, g as assertSlackDetachedTargetAllowed, it as resolveSlackQuestionActionIds, k as isSlackInvalidBlocksError, mt as validateSlackBlocksArray } from "./group-policy-DyfB_ZIE.js";
4
4
  import { n as resolveSlackThreadTsValue, t as normalizeSlackThreadTsCandidate } from "./thread-ts-DUGhaYKq.js";
5
- import { c as createSlackTokenCacheKey, f as getSlackWriteClient, o as createSlackReadClient } from "./probe-CgDsSops.js";
5
+ import { a as createSlackReadClient, d as getSlackWriteClient, r as formatSlackError, s as createSlackTokenCacheKey } from "./probe-BVA65leh.js";
6
6
  import { t as getOptionalSlackRuntime } from "./runtime-JSVZSWAj.js";
7
- import { i as loadOutboundMediaFromUrl } from "./runtime-api-QMb-Q5Ko.js";
7
+ import { i as loadOutboundMediaFromUrl } from "./runtime-api-DBi7xc_g.js";
8
8
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeTrimmedStringList } from "openclaw/plugin-sdk/string-coerce-runtime";
9
9
  import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
10
- import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
11
- import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
10
+ import { logVerbose, sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
11
+ import { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
12
+ import { resolveGlobalSingleton } from "openclaw/plugin-sdk/global-singleton";
13
+ import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
12
14
  import { createHash, createHmac } from "node:crypto";
13
15
  import { withTrustedEnvProxyGuardedFetchMode } from "openclaw/plugin-sdk/fetch-runtime";
14
16
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
15
17
  import { PlatformMessageNotDispatchedError, extractErrorCode, readErrorName } from "openclaw/plugin-sdk/error-runtime";
16
18
  import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
17
19
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
18
- import { chunkMarkdownTextWithMode, isSilentReplyText, resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
20
+ import { chunkMarkdownTextWithMode, resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
19
21
  import { resolveTextChunksWithFallback } from "openclaw/plugin-sdk/reply-payload";
20
22
  import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
21
23
  import { buildTimeoutAbortSignal } from "openclaw/plugin-sdk/extension-shared";
24
+ import { extensionForMime } from "openclaw/plugin-sdk/media-mime";
25
+ import { retryAsync } from "openclaw/plugin-sdk/retry-runtime";
22
26
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
23
27
  import { createPersistentDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
28
+ import { createPluginStateErrorReporter } from "openclaw/plugin-sdk/plugin-state-runtime";
24
29
  //#region extensions/slack/src/native-data-fallback.ts
25
30
  const SLACK_SECTION_PLAIN_TEXT_MAX = 3e3;
26
31
  const SLACK_EMPTY_BLOCK_FALLBACK = "Shared a Block Kit message";
27
32
  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;
33
+ if (!text) return [];
34
+ return chunkTextForOutbound(text, Math.max(1, Math.floor(limit)), { preserveWhitespace: true });
35
+ }
36
+ function fitsSlackTextLimit(text, limit) {
37
+ if (text.length <= limit) return true;
38
+ return truncateUtf16Safe(text, limit).length === text.length;
38
39
  }
39
- function buildPlainTextBlocks(text) {
40
- return chunkSlackTextAtHardLimit(text, SLACK_SECTION_PLAIN_TEXT_MAX).map((chunk, index) => {
40
+ function buildPlainTextBlocks(text, textLimit) {
41
+ return chunkSlackTextAtHardLimit(text, Math.min(textLimit, SLACK_SECTION_PLAIN_TEXT_MAX)).map((chunk, index) => {
41
42
  const fallbackBlock = {
42
43
  block: {
43
44
  type: "section",
@@ -58,11 +59,11 @@ function renderNativeDataPlainText(block) {
58
59
  function buildOrderedFallbackBlocks(params) {
59
60
  const entries = [];
60
61
  const consumeFromBase = createSlackNativeDataBaseTextConsumer(params.baseText);
61
- if (params.baseText) entries.push(...buildPlainTextBlocks(params.baseText));
62
+ if (params.baseText) entries.push(...buildPlainTextBlocks(params.baseText, params.textLimit));
62
63
  for (const block of params.blocks) {
63
64
  if (hasSlackNativeDataBlock([block])) {
64
65
  const nativeText = renderNativeDataPlainText(block);
65
- if (!consumeFromBase(nativeText)) entries.push(...buildPlainTextBlocks(nativeText));
66
+ if (!consumeFromBase(nativeText)) entries.push(...buildPlainTextBlocks(nativeText, params.textLimit));
66
67
  continue;
67
68
  }
68
69
  const text = renderSlackBlockFallbackText(block, { nativeDataFormat: "plain" });
@@ -73,7 +74,7 @@ function buildOrderedFallbackBlocks(params) {
73
74
  }
74
75
  return entries;
75
76
  }
76
- function buildOrderedBlockMessages(entries) {
77
+ function buildOrderedBlockMessages(entries, textLimit) {
77
78
  const messages = [];
78
79
  let blocks = [];
79
80
  let text = "";
@@ -90,10 +91,10 @@ function buildOrderedBlockMessages(entries) {
90
91
  for (const entry of entries) {
91
92
  const separator = text && entry.text && !entry.continuesText ? "\n\n" : "";
92
93
  const nextText = entry.text ? `${text}${separator}${entry.text}` : text;
93
- if (blocks.length >= 50 || nextText.length > 4e4) flush();
94
+ if (blocks.length >= 50 || !fitsSlackTextLimit(nextText, textLimit)) flush();
94
95
  const freshSeparator = text && entry.text && !entry.continuesText ? "\n\n" : "";
95
96
  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
+ if (!fitsSlackTextLimit(freshText, textLimit)) throw new Error("One Slack fallback block exceeds the resolved message text limit.");
97
98
  blocks.push(entry.block);
98
99
  text = freshText;
99
100
  }
@@ -103,17 +104,19 @@ function buildOrderedBlockMessages(entries) {
103
104
  /** Build one complete, ordered retry plan after Slack rejects native data blocks. */
104
105
  function buildSlackNativeDataDeliveryPlan(params) {
105
106
  const baseText = params.baseText?.trim() ?? "";
107
+ const textLimit = Math.min(SLACK_MESSAGE_TEXT_RECOMMENDED_LIMIT, SLACK_MESSAGE_TEXT_HARD_LIMIT, Math.max(1, Math.floor(params.textLimit ?? 4e4)));
106
108
  const hasNativeData = hasSlackNativeDataBlock(params.blocks);
107
109
  const accessibilityText = buildSlackNativeDataAccessibilityText(baseText, params.blocks) || (hasNativeData ? "Slack could not render this chart or table data." : SLACK_EMPTY_BLOCK_FALLBACK);
108
110
  return {
109
111
  accessibilityText,
110
- fallbackMessages: stripSlackNativeDataBlocks(params.blocks).length === 0 ? chunkSlackTextAtHardLimit(accessibilityText).map((text) => ({
112
+ fallbackMessages: stripSlackNativeDataBlocks(params.blocks).length === 0 ? chunkSlackTextAtHardLimit(accessibilityText, textLimit).map((text) => ({
111
113
  text,
112
114
  mrkdwn: false
113
115
  })) : buildOrderedBlockMessages(buildOrderedFallbackBlocks({
114
116
  baseText,
115
- blocks: params.blocks
116
- })),
117
+ blocks: params.blocks,
118
+ textLimit
119
+ }), textLimit),
117
120
  skipOriginalBlocks: accessibilityText.length > SLACK_MESSAGE_TEXT_HARD_LIMIT
118
121
  };
119
122
  }
@@ -142,6 +145,7 @@ function hasCustomIdentity$1(identity) {
142
145
  async function postSlackMessageWithIdentityFallback(params) {
143
146
  const { basePayload, identity, post } = params;
144
147
  try {
148
+ if (!identity) return await post(basePayload);
145
149
  if (identity?.iconUrl) return await post({
146
150
  ...basePayload,
147
151
  ...identity.username ? { username: identity.username } : {},
@@ -250,11 +254,6 @@ function hasSlackDnsRequestSignal(err) {
250
254
  }
251
255
  return false;
252
256
  }
253
- function delaySlackDnsRetry(attempt) {
254
- return new Promise((resolve) => {
255
- setTimeout(resolve, SLACK_DNS_RETRY_BASE_DELAY_MS * Math.max(1, attempt));
256
- });
257
- }
258
257
  function resolveSlackUploadTimeoutLogUrl(url) {
259
258
  try {
260
259
  return new URL(url).origin;
@@ -328,14 +327,22 @@ function resolveSlackUploadTransportPolicy(params) {
328
327
  };
329
328
  }
330
329
  async function withSlackDnsRequestRetry(operation, fn) {
331
- for (const attempt of Array.from({ length: 3 }, (_, index) => index)) try {
332
- return await fn();
333
- } catch (err) {
334
- if (attempt >= SLACK_DNS_RETRY_ATTEMPTS || !hasSlackDnsRequestSignal(err)) throw err;
335
- logVerbose(`slack send: retrying ${operation} after transient DNS request error (${attempt + 1}/${SLACK_DNS_RETRY_ATTEMPTS})`);
336
- await delaySlackDnsRetry(attempt + 1);
337
- }
338
- throw new Error("unreachable Slack DNS retry loop exit");
330
+ return await retryAsync(fn, {
331
+ attempts: 3,
332
+ minDelayMs: 0,
333
+ shouldRetry: hasSlackDnsRequestSignal,
334
+ delayMs: ({ attempt }) => SLACK_DNS_RETRY_BASE_DELAY_MS * Math.max(1, attempt),
335
+ onRetry: ({ attempt }) => {
336
+ logVerbose(`slack send: retrying ${operation} after transient DNS request error (${attempt}/${SLACK_DNS_RETRY_ATTEMPTS})`);
337
+ },
338
+ sleep: (delayMs) => sleepWithAbort(delayMs)
339
+ });
340
+ }
341
+ function requireSlackPostMessageTimestamp(response) {
342
+ if (response.ok === false) throw new Error(`Slack chat.postMessage failed: ${formatSlackError(response.error, "unknown error")}`);
343
+ const timestamp = response.ts?.trim();
344
+ if (!timestamp) throw new Error("Slack chat.postMessage returned no message timestamp");
345
+ return timestamp;
339
346
  }
340
347
  async function postSlackMessageBestEffort(params) {
341
348
  const basePayload = buildSlackPostMessagePayload(params);
@@ -344,20 +351,29 @@ async function postSlackMessageBestEffort(params) {
344
351
  response: await withSlackDnsRequestRetry("chat.postMessage", () => postChatMessage(payload)),
345
352
  identity
346
353
  });
347
- return await postSlackMessageWithIdentityFallback({
354
+ const posted = await postSlackMessageWithIdentityFallback({
348
355
  basePayload,
349
356
  identity: params.identity,
350
357
  post
351
358
  });
359
+ const timestamp = requireSlackPostMessageTimestamp(posted.response);
360
+ return {
361
+ ...posted,
362
+ response: {
363
+ ...posted.response,
364
+ ts: timestamp
365
+ }
366
+ };
352
367
  }
353
368
  async function uploadSlackFile(params) {
354
369
  const { buffer, contentType, fileName } = await loadOutboundMediaFromUrl(params.mediaUrl, {
355
370
  maxBytes: params.maxBytes,
356
371
  mediaAccess: params.mediaAccess,
357
372
  mediaLocalRoots: params.mediaLocalRoots,
358
- mediaReadFile: params.mediaReadFile
373
+ mediaReadFile: params.mediaReadFile,
374
+ ...params.optimizeImages !== void 0 ? { optimizeImages: params.optimizeImages } : {}
359
375
  });
360
- const uploadFileName = params.uploadFileName ?? fileName ?? "upload";
376
+ const uploadFileName = params.uploadFileName ?? fileName ?? `upload${extensionForMime(contentType) ?? ""}`;
361
377
  const uploadTitle = params.uploadTitle ?? uploadFileName;
362
378
  const uploadUrlResp = await withSlackDnsRequestRetry("files.getUploadURLExternal", () => params.client.files.getUploadURLExternal({
363
379
  filename: uploadFileName,
@@ -416,24 +432,32 @@ async function uploadSlackFile(params) {
416
432
  }
417
433
  //#endregion
418
434
  //#region extensions/slack/src/sent-thread-cache.ts
435
+ /**
436
+ * Cache of Slack threads the bot has participated in.
437
+ * Used to auto-respond in threads without requiring @mention after the first reply.
438
+ */
439
+ const MAX_ENTRIES = 5e3;
440
+ const PERSISTENT_MAX_ENTRIES = 1e3;
441
+ const MAX_FAILURE_NOTICES = 1e3;
442
+ const PERSISTENT_NAMESPACE = "slack.thread-participation";
443
+ /**
444
+ * Keep Slack thread participation shared across bundled chunks so thread
445
+ * auto-reply gating does not diverge between prepare/dispatch call paths.
446
+ */
447
+ const SLACK_THREAD_PARTICIPATION_KEY = Symbol.for("openclaw.slackThreadParticipation");
448
+ const SLACK_THREAD_FAILURE_NOTICES_KEY = Symbol.for("openclaw.slackThreadFailureNotices");
419
449
  const threadParticipation = createPersistentDedupeCache({
420
- globalKey: Symbol.for("openclaw.slackThreadParticipation"),
421
- ttlMs: 1440 * 60 * 1e3,
422
- maxSize: 5e3,
450
+ globalKey: SLACK_THREAD_PARTICIPATION_KEY,
451
+ ttlMs: 0,
452
+ maxSize: MAX_ENTRIES,
423
453
  persistent: {
424
- namespace: "slack.thread-participation",
425
- maxEntries: 1e3,
454
+ namespace: PERSISTENT_NAMESPACE,
455
+ maxEntries: PERSISTENT_MAX_ENTRIES,
426
456
  openStore: (options) => getOptionalSlackRuntime()?.state.openKeyedStore(options),
427
- logError: (error) => {
428
- try {
429
- getOptionalSlackRuntime()?.logging.getChildLogger({
430
- plugin: "slack",
431
- feature: "thread-participation-state"
432
- }).warn("Slack persistent thread participation state failed", { error: String(error) });
433
- } catch {}
434
- }
457
+ logError: createPluginStateErrorReporter(getOptionalSlackRuntime, "slack", "thread-participation-state", "Slack persistent thread participation state failed")
435
458
  }
436
459
  });
460
+ const threadFailureNotices = resolveGlobalSingleton(SLACK_THREAD_FAILURE_NOTICES_KEY, () => /* @__PURE__ */ new Map(), (notices) => notices.clear());
437
461
  function makeKey(accountId, channelId, threadTs, teamId) {
438
462
  return `${accountId}:${teamId ? `${teamId}:` : ""}${channelId}:${threadTs}`;
439
463
  }
@@ -452,8 +476,41 @@ async function hasSlackThreadParticipationWithPersistence(params) {
452
476
  if (!params.accountId || !params.channelId || !params.threadTs) return false;
453
477
  return await threadParticipation.lookup(makeKey(params.accountId, params.channelId, params.threadTs, params.teamId));
454
478
  }
479
+ function makeFailureNoticeKey(params) {
480
+ const scope = params.threadTs ? `thread:${params.threadTs}` : "channel";
481
+ return makeKey(params.accountId, params.channelId, scope, params.teamId);
482
+ }
483
+ /** Returns whether this failure was already delivered in the thread or channel. */
484
+ function hasSlackThreadFailureNotice(params) {
485
+ const { accountId, channelId, failureText } = params;
486
+ const fingerprint = failureText.trim().replace(/\s+/gu, " ");
487
+ if (!accountId || !channelId || !fingerprint) return false;
488
+ return threadFailureNotices.get(makeFailureNoticeKey(params)) === fingerprint;
489
+ }
490
+ /** Records a failure after it was delivered in the thread or channel. */
491
+ function recordSlackThreadFailureNotice(params) {
492
+ const { accountId, channelId, failureText } = params;
493
+ const fingerprint = failureText.trim().replace(/\s+/gu, " ");
494
+ if (!accountId || !channelId || !fingerprint) return false;
495
+ const key = makeFailureNoticeKey(params);
496
+ if (threadFailureNotices.get(key) === fingerprint) return false;
497
+ threadFailureNotices.delete(key);
498
+ threadFailureNotices.set(key, fingerprint);
499
+ if (threadFailureNotices.size > MAX_FAILURE_NOTICES) {
500
+ const oldestKey = threadFailureNotices.keys().next().value;
501
+ if (oldestKey !== void 0) threadFailureNotices.delete(oldestKey);
502
+ }
503
+ return true;
504
+ }
505
+ /** Clears a thread or channel outage notice after a healthy model turn completes. */
506
+ function clearSlackThreadFailureNotice(params) {
507
+ const { accountId, channelId } = params;
508
+ if (!accountId || !channelId) return;
509
+ threadFailureNotices.delete(makeFailureNoticeKey(params));
510
+ }
455
511
  function clearSlackThreadParticipationCache() {
456
512
  threadParticipation.clearForTest();
513
+ threadFailureNotices.clear();
457
514
  }
458
515
  //#endregion
459
516
  //#region extensions/slack/src/send.ts
@@ -537,11 +594,12 @@ async function updateMessageSlack(params) {
537
594
  cfg: requireRuntimeConfig(params.cfg, "Slack update"),
538
595
  accountId: params.accountId
539
596
  });
597
+ assertSlackDetachedTargetAllowed(account.accountId, params.teamId);
540
598
  await getSlackWriteClient(resolveToken({
541
599
  accountId: account.accountId,
542
600
  fallbackToken: account.botToken,
543
601
  fallbackSource: account.botTokenSource
544
- })).chat.update({
602
+ }), { teamId: params.teamId }).chat.update({
545
603
  channel: params.channelId,
546
604
  ts: params.messageTs,
547
605
  text: truncateSlackTextByUtf8Bytes(params.text, SLACK_EDIT_TEXT_MAX_BYTES),
@@ -550,7 +608,7 @@ async function updateMessageSlack(params) {
550
608
  }
551
609
  function createSlackSendReceipt(params) {
552
610
  return createMessageReceiptFromOutboundResults({
553
- results: params.platformMessageIds.map((messageId) => messageId.trim()).filter((messageId) => messageId && messageId !== "unknown" && messageId !== "suppressed").map((messageId) => {
611
+ results: params.platformMessageIds.map((messageId) => messageId.trim()).filter((messageId) => messageId && messageId !== "unknown").map((messageId) => {
554
612
  const result = {
555
613
  channel: "slack",
556
614
  messageId
@@ -577,7 +635,8 @@ function parseRecipient(raw) {
577
635
  if (!target) throw new Error("Recipient is required for Slack sends");
578
636
  return {
579
637
  kind: target.kind,
580
- id: canonicalizeSlackApiTargetId(target.kind, target.id, raw)
638
+ id: canonicalizeSlackApiTargetId(target.kind, target.id, raw),
639
+ teamId: target.teamId
581
640
  };
582
641
  }
583
642
  function parseEnterpriseEventRecipient(raw) {
@@ -588,20 +647,56 @@ function parseEnterpriseEventRecipient(raw) {
588
647
  id: canonicalizeSlackApiTargetId("channel", match[1])
589
648
  };
590
649
  }
591
- function resolveEnterpriseEventScope(params) {
592
- const scope = params.opts.enterpriseEventScope;
593
- if (!scope) {
594
- assertSlackDirectSendAllowed(params.account);
595
- return;
596
- }
597
- if (params.account.config.enterpriseOrgInstall !== true) throw new Error("unexpected_enterprise_slack_listener_scope");
598
- 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");
650
+ function resolveSlackSendEventScope(params) {
651
+ const scope = params.opts.eventScope;
652
+ if (!scope) return;
653
+ if (!/^T[A-Z0-9]+$/i.test(scope.teamId) || !scope.client) throw new Error("invalid_enterprise_slack_listener_scope");
599
654
  return scope;
600
655
  }
656
+ function resolveSlackDelivery(params) {
657
+ if (params.eventScope) {
658
+ if (params.opts.mediaUrl && !params.eventScope.uploadCompletionClient) throw new Error("missing_enterprise_slack_upload_completion_client");
659
+ return Object.freeze({
660
+ client: params.eventScope.client,
661
+ credential: SLACK_ENTERPRISE_LISTENER_QUEUE_CREDENTIAL,
662
+ identity: normalizeSlackSendIdentity(params.opts.identity),
663
+ recipient: params.recipient,
664
+ teamId: params.eventScope.teamId,
665
+ unfurl: { unfurlMedia: params.account.config.unfurlMedia },
666
+ ...params.eventScope.uploadCompletionClient ? { upload: Object.freeze({
667
+ completionClient: params.eventScope.uploadCompletionClient,
668
+ auditContext: "slack-enterprise-immediate-upload"
669
+ }) } : {}
670
+ });
671
+ }
672
+ const credential = resolveToken({
673
+ explicit: params.opts.token,
674
+ accountId: params.account.accountId,
675
+ fallbackToken: resolveSlackOperationToken(params.account, "write"),
676
+ fallbackSource: params.account.identity === "user" ? params.account.userTokenSource : params.account.botTokenSource
677
+ });
678
+ return Object.freeze({
679
+ client: params.recipient.teamId ? getSlackWriteClient(credential, { teamId: params.recipient.teamId }) : params.opts.client ?? getSlackWriteClient(credential),
680
+ credential,
681
+ identity: resolveSlackSendIdentity({
682
+ accountId: params.account.accountId,
683
+ explicit: params.opts.identity
684
+ }),
685
+ recipient: params.recipient,
686
+ teamId: params.recipient.teamId,
687
+ unfurl: params.recipient.teamId ? { unfurlMedia: params.account.config.unfurlMedia } : {
688
+ unfurlLinks: params.account.config.unfurlLinks,
689
+ unfurlMedia: params.account.config.unfurlMedia
690
+ }
691
+ });
692
+ }
693
+ function resolveSlackTextChunkLimit(params) {
694
+ const configuredLimit = params.textLimit ?? resolveTextChunkLimit(params.cfg, "slack", params.accountId, { fallbackLimit: 8e3 });
695
+ return Math.min(configuredLimit, SLACK_TEXT_LIMIT);
696
+ }
601
697
  function resolveSlackTextChunks(params) {
602
698
  const text = params.preservePlainText ? params.text : params.text.trim();
603
- const configuredLimit = params.textLimit ?? resolveTextChunkLimit(params.cfg, "slack", params.accountId, { fallbackLimit: 8e3 });
604
- const chunkLimit = Math.min(configuredLimit, SLACK_TEXT_LIMIT);
699
+ const chunkLimit = resolveSlackTextChunkLimit(params);
605
700
  if (params.preservePlainText) {
606
701
  const chunks = [];
607
702
  let remaining = text;
@@ -867,7 +962,7 @@ async function scanSlackConversationForDelivery(params) {
867
962
  evidence: deliveryParts.size > 0 ? "partial" : "none"
868
963
  };
869
964
  }
870
- async function reconcileSlackUnknownSend(ctx, opts) {
965
+ async function reconcileSlackUnknownSend(ctx) {
871
966
  const account = resolveSlackAccount({
872
967
  cfg: requireRuntimeConfig(ctx.cfg, "Slack delivery reconciliation"),
873
968
  accountId: ctx.accountId ?? void 0
@@ -878,13 +973,22 @@ async function reconcileSlackUnknownSend(ctx, opts) {
878
973
  error: "Slack unknown-send reconciliation requires a durable delivery id",
879
974
  retryable: false
880
975
  };
976
+ const recipient = parseRecipient(ctx.to);
977
+ try {
978
+ assertSlackDetachedTargetAllowed(account.accountId, recipient.teamId);
979
+ } catch (error) {
980
+ return {
981
+ status: "unresolved",
982
+ error: error instanceof Error ? error.message : String(error),
983
+ retryable: false
984
+ };
985
+ }
881
986
  const readToken = resolveSlackOperationToken(account, "read");
882
987
  if (!readToken) return {
883
988
  status: "unresolved",
884
989
  error: `Slack read token missing for account "${account.accountId}"`,
885
990
  retryable: false
886
991
  };
887
- const recipient = parseRecipient(ctx.to);
888
992
  const userRecipient = isSlackUserRecipient(recipient);
889
993
  const writeToken = resolveSlackOperationToken(account, "write");
890
994
  if (userRecipient && !writeToken) return {
@@ -892,8 +996,8 @@ async function reconcileSlackUnknownSend(ctx, opts) {
892
996
  error: `Slack write token missing for direct-message reconciliation on account "${account.accountId}"`,
893
997
  retryable: false
894
998
  };
895
- const readClient = opts?.client ?? createSlackReadClient(readToken);
896
- const writeClient = opts?.client ?? (writeToken ? getSlackWriteClient(writeToken) : void 0);
999
+ const readClient = createSlackReadClient(readToken, { teamId: recipient.teamId });
1000
+ const writeClient = writeToken ? getSlackWriteClient(writeToken, { teamId: recipient.teamId }) : void 0;
897
1001
  const payloadReplyToId = ctx.payloads[0]?.replyToId;
898
1002
  const threadTs = resolveSlackThreadTsValue({
899
1003
  replyToId: Object.hasOwn(ctx, "effectiveReplyToId") ? normalizeOptionalString(ctx.effectiveReplyToId) : payloadReplyToId != null ? normalizeOptionalString(payloadReplyToId) : ctx.replyToMode === "off" ? void 0 : normalizeOptionalString(ctx.replyToId),
@@ -910,7 +1014,7 @@ async function reconcileSlackUnknownSend(ctx, opts) {
910
1014
  accountId: account.accountId,
911
1015
  token: channelToken
912
1016
  });
913
- const lookupClients = opts?.client ? [opts.client] : [readClient, ...writeClient && writeToken !== readToken ? [writeClient] : []];
1017
+ const lookupClients = [readClient, ...writeClient && writeClient !== readClient && writeToken !== readToken ? [writeClient] : []];
914
1018
  let lookupError;
915
1019
  let bestUnresolvedScan;
916
1020
  for (const lookupClient of lookupClients) try {
@@ -947,59 +1051,34 @@ async function sendMessageSlack(to, message, opts) {
947
1051
  cfg,
948
1052
  accountId: opts.accountId
949
1053
  });
950
- const enterpriseEventScope = resolveEnterpriseEventScope({
951
- account,
952
- opts
953
- });
954
- const enterpriseDelivery = enterpriseEventScope ? Object.freeze({
955
- client: enterpriseEventScope.client,
956
- teamId: enterpriseEventScope.teamId,
957
- ...enterpriseEventScope.uploadCompletionClient ? { uploadCompletionClient: enterpriseEventScope.uploadCompletionClient } : {}
958
- }) : void 0;
959
- if (isSilentReplyText(normalizedMessage) && !opts.mediaUrl && !opts.blocks) {
960
- logVerbose("slack send: suppressed NO_REPLY token before API call");
961
- return {
962
- messageId: "suppressed",
963
- channelId: "",
964
- receipt: createSlackSendReceipt({
965
- platformMessageIds: [],
966
- kind: "unknown"
967
- })
968
- };
969
- }
1054
+ const eventScope = resolveSlackSendEventScope({ opts });
1055
+ const recipient = eventScope ? parseEnterpriseEventRecipient(to) : parseRecipient(to);
1056
+ if (!eventScope) assertSlackDetachedTargetAllowed(account.accountId, recipient.teamId);
970
1057
  const blocks = opts.blocks == null ? void 0 : validateSlackBlocksArray(opts.blocks);
971
1058
  if (!normalizedMessage && !opts.mediaUrl && !blocks) throw new Error("Slack send requires text, blocks, or media");
972
- const token = enterpriseDelivery ? SLACK_ENTERPRISE_LISTENER_QUEUE_CREDENTIAL : resolveToken({
973
- explicit: opts.token,
974
- accountId: account.accountId,
975
- fallbackToken: resolveSlackOperationToken(account, "write"),
976
- fallbackSource: account.identity === "user" ? account.userTokenSource : account.botTokenSource
1059
+ const queuedOpts = Object.freeze({ ...opts });
1060
+ const delivery = resolveSlackDelivery({
1061
+ account,
1062
+ eventScope,
1063
+ opts: queuedOpts,
1064
+ recipient
977
1065
  });
978
- const recipient = enterpriseDelivery ? parseEnterpriseEventRecipient(to) : parseRecipient(to);
979
- const queueKey = createSlackSendQueueKey({
1066
+ const result = await runQueuedSlackSend(createSlackSendQueueKey({
980
1067
  accountId: account.accountId,
981
- token,
982
- recipient,
1068
+ token: delivery.credential,
1069
+ recipient: delivery.recipient,
983
1070
  threadTs: opts.threadTs,
984
- ...enterpriseDelivery ? { teamId: enterpriseDelivery.teamId } : {}
985
- });
986
- const queuedOpts = enterpriseDelivery ? Object.freeze({
987
- ...opts,
988
- client: enterpriseDelivery.client
989
- }) : opts;
990
- const result = await runQueuedSlackSend(queueKey, () => sendMessageSlackQueued({
1071
+ teamId: delivery.teamId
1072
+ }), () => sendMessageSlackQueued({
991
1073
  trimmedMessage,
992
1074
  opts: queuedOpts,
993
1075
  cfg,
994
1076
  account,
995
- token,
996
- recipient,
997
1077
  blocks,
998
- ...enterpriseDelivery ? { enterpriseDelivery } : {}
1078
+ delivery
999
1079
  }));
1000
1080
  const threadTs = result.threadTs ?? normalizeSlackThreadTsCandidate(queuedOpts.threadTs);
1001
- if (threadTs && result.channelId && account.accountId) if (enterpriseDelivery) recordSlackThreadParticipation(account.accountId, result.channelId, threadTs, { teamId: enterpriseDelivery.teamId });
1002
- else recordSlackThreadParticipation(account.accountId, result.channelId, threadTs);
1081
+ if (threadTs && result.channelId && account.accountId) recordSlackThreadParticipation(account.accountId, result.channelId, threadTs, { teamId: delivery.teamId });
1003
1082
  return result;
1004
1083
  }
1005
1084
  async function sendMessageSlackQueued(params) {
@@ -1010,17 +1089,9 @@ async function sendMessageSlackQueued(params) {
1010
1089
  }
1011
1090
  }
1012
1091
  async function sendMessageSlackQueuedInner(params) {
1013
- const { opts, cfg, account, token, recipient, blocks, trimmedMessage, enterpriseDelivery } = params;
1014
- const client = enterpriseDelivery?.client ?? opts.client ?? getSlackWriteClient(token);
1015
- const identity = enterpriseDelivery ? normalizeSlackSendIdentity(opts.identity) : resolveSlackSendIdentity({
1016
- accountId: account.accountId,
1017
- explicit: opts.identity
1018
- });
1092
+ const { opts, cfg, account, blocks, trimmedMessage, delivery } = params;
1093
+ const { client, identity, recipient, unfurl } = delivery;
1019
1094
  if (opts.replyBroadcast && opts.mediaUrl) throw new Error("Slack replyBroadcast is only supported for text or block thread replies.");
1020
- const unfurl = enterpriseDelivery ? { unfurlMedia: account.config.unfurlMedia } : {
1021
- unfurlLinks: account.config.unfurlLinks,
1022
- unfurlMedia: account.config.unfurlMedia
1023
- };
1024
1095
  const directUserPostChannelId = opts.deliveryQueueId ? void 0 : resolveDirectUserPostChannelId({
1025
1096
  recipient,
1026
1097
  hasMedia: Boolean(opts.mediaUrl),
@@ -1028,11 +1099,20 @@ async function sendMessageSlackQueuedInner(params) {
1028
1099
  });
1029
1100
  const { channelId } = directUserPostChannelId ? { channelId: directUserPostChannelId } : await resolveChannelId(client, recipient, {
1030
1101
  accountId: account.accountId,
1031
- token
1102
+ token: delivery.credential
1032
1103
  });
1033
- const reportDelivery = async (result) => {
1034
- await opts.onDeliveryResult?.(result);
1035
- return result;
1104
+ const reportDelivery = async (result, deliveredBlocks) => {
1105
+ const slackQuestionActionIds = resolveSlackQuestionActionIds(deliveredBlocks);
1106
+ const deliveryResult = slackQuestionActionIds.length ? {
1107
+ ...result,
1108
+ meta: {
1109
+ ...result.meta,
1110
+ slackQuestionActionIds,
1111
+ [SLACK_QUESTION_FINALIZATION_BLOCKS]: deliveredBlocks.filter((block) => block.type !== "actions")
1112
+ }
1113
+ } : result;
1114
+ await opts.onDeliveryResult?.(deliveryResult);
1115
+ return deliveryResult;
1036
1116
  };
1037
1117
  let didDispatch = false;
1038
1118
  const dispatchOnce = async () => {
@@ -1044,10 +1124,16 @@ async function sendMessageSlackQueuedInner(params) {
1044
1124
  const authoredTextPlacement = opts.authoredTextPlacement ?? "outside-blocks";
1045
1125
  const nativeDataFallbackBase = blocks && authoredTextPlacement === "outside-blocks" ? explicitNativeDataFallbackBase ?? trimmedMessage : "";
1046
1126
  const hasNativeData = Boolean(blocks && hasSlackNativeDataBlock(blocks));
1127
+ const textChunkLimit = resolveSlackTextChunkLimit({
1128
+ cfg,
1129
+ accountId: account.accountId,
1130
+ ...opts.textLimit !== void 0 ? { textLimit: opts.textLimit } : {}
1131
+ });
1047
1132
  const usesOrderedBlockAccessibility = Boolean(blocks && (hasNativeData || opts.authoredTextPlacement !== void 0));
1048
1133
  const orderedBlockDeliveryPlan = blocks && usesOrderedBlockAccessibility ? buildSlackNativeDataDeliveryPlan({
1049
1134
  baseText: nativeDataFallbackBase,
1050
- blocks
1135
+ blocks,
1136
+ textLimit: textChunkLimit
1051
1137
  }) : void 0;
1052
1138
  const orderedBlockAccessibilityText = blocks && usesOrderedBlockAccessibility ? orderedBlockDeliveryPlan?.accessibilityText ?? (buildSlackNativeDataAccessibilityText(nativeDataFallbackBase, blocks) || "Slack could not render this Block Kit message.") : void 0;
1053
1139
  const completeBlockFallbackText = blocks ? buildSlackCompleteBlocksFallbackText(blocks) : "";
@@ -1085,8 +1171,7 @@ async function sendMessageSlackQueuedInner(params) {
1085
1171
  ...usesOrderedBlockAccessibility ? { mrkdwn: false } : {},
1086
1172
  unfurl
1087
1173
  });
1088
- 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"}`);
1089
- const messageId = response.ts ?? "unknown";
1174
+ const messageId = response.ts;
1090
1175
  deliveredChannelId = resolvePostedMessageChannelId(response, channelId);
1091
1176
  const deliveredThreadTs = resolvePostedMessageThreadTs(response) ?? normalizeSlackThreadTsCandidate(opts.threadTs);
1092
1177
  return await reportDelivery({
@@ -1099,7 +1184,7 @@ async function sendMessageSlackQueuedInner(params) {
1099
1184
  kind: "card",
1100
1185
  threadTs: deliveredThreadTs
1101
1186
  })
1102
- });
1187
+ }, blocks);
1103
1188
  } catch (error) {
1104
1189
  if (!hasNativeData || !isSlackInvalidBlocksError(error)) throw error;
1105
1190
  logVerbose("slack send: native data rejected, delivering complete text fallback");
@@ -1108,6 +1193,7 @@ async function sendMessageSlackQueuedInner(params) {
1108
1193
  }
1109
1194
  if (pendingBlockFallback) {
1110
1195
  const fallbackMessages = pendingBlockFallback.fallbackMessages;
1196
+ let questionDelivery;
1111
1197
  for (const [partIndex, fallback] of fallbackMessages.entries()) {
1112
1198
  const metadata = withSlackDeliveryMetadata(partIndex === 0 ? opts.metadata : void 0, {
1113
1199
  queueId: opts.deliveryQueueId,
@@ -1130,15 +1216,13 @@ async function sendMessageSlackQueuedInner(params) {
1130
1216
  unfurl
1131
1217
  });
1132
1218
  const response = posted.response;
1133
- 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"}`);
1134
1219
  sendIdentity = posted.identity;
1135
- lastMessageId = response.ts ?? lastMessageId;
1220
+ lastMessageId = response.ts;
1136
1221
  deliveredChannelId = resolvePostedMessageChannelId(response, deliveredChannelId);
1137
1222
  canonicalDeliveredThreadTs ??= resolvePostedMessageThreadTs(response);
1138
- if (!response.ts) continue;
1139
1223
  sentMessageIds.push(response.ts);
1140
1224
  const deliveredThreadTs = resolvePostedMessageThreadTs(response) ?? normalizeSlackThreadTsCandidate(opts.threadTs);
1141
- await reportDelivery({
1225
+ const fallbackDelivery = await reportDelivery({
1142
1226
  messageId: response.ts,
1143
1227
  channelId: deliveredChannelId,
1144
1228
  threadTs: deliveredThreadTs,
@@ -1148,16 +1232,21 @@ async function sendMessageSlackQueuedInner(params) {
1148
1232
  kind: fallback.blocks ? "card" : "text",
1149
1233
  threadTs: deliveredThreadTs
1150
1234
  })
1151
- });
1235
+ }, fallback.blocks);
1236
+ if (fallbackDelivery.meta?.slackQuestionActionIds.length) questionDelivery = fallbackDelivery;
1152
1237
  }
1153
- const messageId = lastMessageId || "unknown";
1238
+ const messageId = lastMessageId;
1154
1239
  const deliveredThreadTs = canonicalDeliveredThreadTs ?? normalizeSlackThreadTsCandidate(opts.threadTs);
1155
1240
  return {
1156
1241
  messageId,
1157
1242
  channelId: deliveredChannelId,
1158
1243
  threadTs: deliveredThreadTs,
1244
+ ...questionDelivery?.meta ? { meta: {
1245
+ ...questionDelivery.meta,
1246
+ slackQuestionMessageId: questionDelivery.messageId
1247
+ } } : {},
1159
1248
  receipt: createSlackSendReceipt({
1160
- platformMessageIds: sentMessageIds.length ? sentMessageIds : [messageId],
1249
+ platformMessageIds: sentMessageIds,
1161
1250
  channelId: deliveredChannelId,
1162
1251
  kind: fallbackMessages.some((message) => message.blocks) ? "card" : "text",
1163
1252
  threadTs: deliveredThreadTs
@@ -1169,18 +1258,17 @@ async function sendMessageSlackQueuedInner(params) {
1169
1258
  cfg,
1170
1259
  accountId: account.accountId,
1171
1260
  text: trimmedMessage,
1172
- ...opts.textLimit !== void 0 ? { textLimit: opts.textLimit } : {},
1261
+ textLimit: textChunkLimit,
1173
1262
  ...opts.textIsSlackMrkdwn ? { textIsSlackMrkdwn: true } : {},
1174
1263
  ...opts.textIsSlackPlainText ? { preservePlainText: true } : {}
1175
1264
  });
1176
1265
  const mediaMaxBytes = opts.mediaMaxBytes ?? (typeof account.config.mediaMaxMb === "number" ? account.config.mediaMaxMb * 1024 * 1024 : void 0);
1177
1266
  let chunksToPost;
1178
1267
  if (opts.mediaUrl) {
1179
- if (enterpriseDelivery && !enterpriseDelivery.uploadCompletionClient) throw new Error("missing_enterprise_slack_upload_completion_client");
1180
1268
  const [firstChunk, ...rest] = resolvedChunks;
1181
1269
  lastMessageId = await uploadSlackFile({
1182
1270
  client,
1183
- ...enterpriseDelivery?.uploadCompletionClient ? { completionClient: enterpriseDelivery.uploadCompletionClient } : {},
1271
+ ...delivery.upload ? { completionClient: delivery.upload.completionClient } : {},
1184
1272
  channelId,
1185
1273
  mediaUrl: opts.mediaUrl,
1186
1274
  mediaAccess: opts.mediaAccess,
@@ -1191,8 +1279,9 @@ async function sendMessageSlackQueuedInner(params) {
1191
1279
  caption: firstChunk,
1192
1280
  threadTs: opts.threadTs,
1193
1281
  maxBytes: mediaMaxBytes,
1282
+ ...opts.forceDocument ? { optimizeImages: false } : {},
1194
1283
  onPlatformSendDispatch: dispatchOnce,
1195
- ...enterpriseDelivery ? { auditContext: "slack-enterprise-immediate-upload" } : {}
1284
+ ...delivery.upload ? { auditContext: delivery.upload.auditContext } : {}
1196
1285
  });
1197
1286
  sentMessageIds.push(lastMessageId);
1198
1287
  await reportDelivery({
@@ -1231,34 +1320,31 @@ async function sendMessageSlackQueuedInner(params) {
1231
1320
  unfurl
1232
1321
  });
1233
1322
  const response = posted.response;
1234
- 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"}`);
1235
1323
  sendIdentity = posted.identity;
1236
- lastMessageId = response.ts ?? lastMessageId;
1324
+ lastMessageId = response.ts;
1237
1325
  deliveredChannelId = resolvePostedMessageChannelId(response, deliveredChannelId);
1238
1326
  canonicalDeliveredThreadTs ??= resolvePostedMessageThreadTs(response);
1239
- if (response.ts) {
1240
- sentMessageIds.push(response.ts);
1241
- await reportDelivery({
1242
- messageId: response.ts,
1327
+ sentMessageIds.push(response.ts);
1328
+ await reportDelivery({
1329
+ messageId: response.ts,
1330
+ channelId: deliveredChannelId,
1331
+ threadTs: resolvePostedMessageThreadTs(response) ?? normalizeSlackThreadTsCandidate(opts.threadTs),
1332
+ receipt: createSlackSendReceipt({
1333
+ platformMessageIds: [response.ts],
1243
1334
  channelId: deliveredChannelId,
1244
- threadTs: resolvePostedMessageThreadTs(response) ?? normalizeSlackThreadTsCandidate(opts.threadTs),
1245
- receipt: createSlackSendReceipt({
1246
- platformMessageIds: [response.ts],
1247
- channelId: deliveredChannelId,
1248
- kind: "text",
1249
- threadTs: resolvePostedMessageThreadTs(response) ?? normalizeSlackThreadTsCandidate(opts.threadTs)
1250
- })
1251
- });
1252
- }
1335
+ kind: "text",
1336
+ threadTs: resolvePostedMessageThreadTs(response) ?? normalizeSlackThreadTsCandidate(opts.threadTs)
1337
+ })
1338
+ });
1253
1339
  }
1254
- const messageId = lastMessageId || "unknown";
1340
+ const messageId = lastMessageId;
1255
1341
  const deliveredThreadTs = canonicalDeliveredThreadTs ?? normalizeSlackThreadTsCandidate(opts.threadTs);
1256
1342
  return {
1257
1343
  messageId,
1258
1344
  channelId: deliveredChannelId,
1259
1345
  threadTs: deliveredThreadTs,
1260
1346
  receipt: createSlackSendReceipt({
1261
- platformMessageIds: sentMessageIds.length ? sentMessageIds : [messageId],
1347
+ platformMessageIds: sentMessageIds,
1262
1348
  channelId: deliveredChannelId,
1263
1349
  kind: opts.mediaUrl ? "media" : "text",
1264
1350
  threadTs: deliveredThreadTs
@@ -1266,4 +1352,4 @@ async function sendMessageSlackQueuedInner(params) {
1266
1352
  };
1267
1353
  }
1268
1354
  //#endregion
1269
- export { updateMessageSlack as a, hasSlackThreadParticipationWithPersistence as c, chunkSlackTextAtHardLimit as d, setSlackDefaultSendIdentity as i, recordSlackThreadParticipation as l, resolveSlackDmChannelId as n, clearSlackThreadParticipationCache as o, sendMessageSlack as r, hasSlackThreadParticipation as s, reconcileSlackUnknownSend as t, buildSlackNativeDataDeliveryPlan as u };
1355
+ export { updateMessageSlack as a, hasSlackThreadFailureNotice as c, recordSlackThreadFailureNotice as d, recordSlackThreadParticipation as f, chunkSlackTextAtHardLimit as h, setSlackDefaultSendIdentity as i, hasSlackThreadParticipation as l, buildSlackNativeDataDeliveryPlan as m, resolveSlackDmChannelId as n, clearSlackThreadFailureNotice as o, requireSlackPostMessageTimestamp as p, sendMessageSlack as r, clearSlackThreadParticipationCache as s, reconcileSlackUnknownSend as t, hasSlackThreadParticipationWithPersistence as u };