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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/{account-inspect-DEKpg9Ip.js → account-inspect-Drw2gAiN.js} +1 -1
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/{accounts-CHAeNZOX.js → accounts-CK4AVI2_.js} +2 -2
  4. package/dist/{accounts.runtime-BHD4Ma2R.js → accounts.runtime-Cb7QwWBz.js} +1 -1
  5. package/dist/{action-runtime-DfuYoMjG.js → action-runtime-BkGe7ny_.js} +148 -76
  6. package/dist/action-runtime.runtime-D9I6uc0Q.js +2 -0
  7. package/dist/{actions-D3NuJvva.js → actions-BTDXwwE3.js} +85 -44
  8. package/dist/actions.runtime-DLFyUkvg.js +2 -0
  9. package/dist/api.js +15 -15
  10. package/dist/{approval-handler.runtime-BA05P0QT.js → approval-handler.runtime-CJaNzAq2.js} +51 -14
  11. package/dist/{block-text-DkHaoGem.js → block-text-YtiNkqmx.js} +1 -1
  12. package/dist/{channel-B9fVdW3s.js → channel-D_tovcUL.js} +151 -91
  13. package/dist/channel-config-api.js +1 -1
  14. package/dist/channel-plugin-api.js +1 -1
  15. package/dist/{channel-type-DXyos1d2.js → channel-type-B9MSdoGN.js} +9 -8
  16. package/dist/{config-adapter-CxBQH8fq.js → channel.setup-Bch9dYmA.js} +83 -25
  17. package/dist/{config-schema-SgZT6nXV.js → config-schema-xto5jFIx.js} +5 -11
  18. package/dist/{directory-config-CnCIAktf.js → directory-config-CNBz8lJa.js} +3 -3
  19. package/dist/directory-contract-api.js +1 -1
  20. package/dist/{directory-live-DKunWh3A.js → directory-live-CFKUtW9E.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-c4RP9bjc.js} +248 -163
  24. package/dist/index.js +4 -1
  25. package/dist/{message-tool-api-DkhFpMoy.js → message-tool-api-BykQB4AU.js} +14 -3
  26. package/dist/message-tool-api.js +1 -1
  27. package/dist/{monitor-CmKz0wtI.js → monitor-1nF1NtqT.js} +3 -3
  28. package/dist/{outbound-adapter-BkuMp2Vs.js → outbound-adapter-S81tsswN.js} +19 -17
  29. package/dist/{pipeline.runtime-DgtmuYCP.js → pipeline.runtime-BvYsM5JO.js} +1087 -682
  30. package/dist/{policy-PRysPn23.js → policy-TapwbeaT.js} +18 -14
  31. package/dist/{probe-CgDsSops.js → probe-HoEg_WxB.js} +38 -18
  32. package/dist/{provider-DmRizMZt.js → provider-CDNvBXUi.js} +3422 -2590
  33. package/dist/{relay-source-Blxj8xb4.js → relay-source-C9leF11u.js} +11 -21
  34. package/dist/{replies-4s_Vq_z9.js → replies-CeOfv82I.js} +52 -6
  35. package/dist/{resolve-channels-yEqmM-3I.js → resolve-channels-CNPrxaXY.js} +39 -12
  36. package/dist/{resolve-users-BOrt5K-5.js → resolve-users-Dzomuwnr.js} +28 -5
  37. package/dist/{runtime-api-QMb-Q5Ko.js → runtime-api-Cj9nxBNv.js} +3 -3
  38. package/dist/runtime-api.js +11 -11
  39. package/dist/{scopes-BJSrkAp6.js → scopes-cYXKkrRD.js} +1 -1
  40. package/dist/{send-DsLeZpST.js → send-Nor0iBwY.js} +211 -105
  41. package/dist/{send.runtime-gUdK6V3h.js → send.runtime-BmgNe6S9.js} +1 -1
  42. package/dist/setup-entry.js +1 -4
  43. package/dist/setup-plugin-api.js +1 -1
  44. package/dist/{setup-surface-BeNzrMrJ.js → setup-surface-CbX1-Knh.js} +6 -6
  45. package/dist/{shared-DgIN3Dcr.js → shared-zdT5AMp-.js} +36 -42
  46. package/dist/{slash-dispatch.runtime-zPkyl7dB.js → slash-dispatch.runtime-Bnr9GR1S.js} +1 -1
  47. package/dist/{streaming-compat-3_X-21wW.js → streaming-compat-B-e0mqM0.js} +3 -8
  48. package/dist/{target-parsing-N6lHogYZ.js → target-parsing-BnMD2ZqZ.js} +40 -2
  49. package/dist/{targets-KmjP2E8s.js → targets-Cx5W_n3W.js} +1 -1
  50. package/node_modules/@slack/bolt/node_modules/@types/node/README.md +1 -1
  51. package/node_modules/@slack/bolt/node_modules/@types/node/crypto.d.ts +1 -1
  52. package/node_modules/@slack/bolt/node_modules/@types/node/package.json +2 -2
  53. package/node_modules/@slack/socket-mode/node_modules/@types/node/README.md +1 -1
  54. package/node_modules/@slack/socket-mode/node_modules/@types/node/crypto.d.ts +1 -1
  55. package/node_modules/@slack/socket-mode/node_modules/@types/node/package.json +2 -2
  56. package/node_modules/@slack/web-api/node_modules/@types/node/README.md +1 -1
  57. package/node_modules/@slack/web-api/node_modules/@types/node/crypto.d.ts +1 -1
  58. package/node_modules/@slack/web-api/node_modules/@types/node/package.json +2 -2
  59. package/node_modules/undici/lib/cache/memory-cache-store.js +54 -9
  60. package/node_modules/undici/lib/cache/sqlite-cache-store.js +7 -1
  61. package/node_modules/undici/lib/core/request.js +12 -1
  62. package/node_modules/undici/lib/dispatcher/client-h1.js +11 -2
  63. package/node_modules/undici/lib/handler/cache-handler.js +246 -64
  64. package/node_modules/undici/lib/handler/cache-revalidation-handler.js +3 -3
  65. package/node_modules/undici/lib/handler/retry-handler.js +24 -0
  66. package/node_modules/undici/lib/interceptor/cache.js +159 -52
  67. package/node_modules/undici/lib/util/cache.js +324 -62
  68. package/node_modules/undici/lib/util/date.js +23 -6
  69. package/node_modules/undici/lib/web/cookies/util.js +79 -9
  70. package/node_modules/undici/package.json +1 -1
  71. package/openclaw.plugin.json +239 -34
  72. package/package.json +9 -9
  73. package/skills/slack/SKILL.md +10 -68
  74. package/dist/action-runtime.runtime-DQErZYpz.js +0 -2
  75. package/dist/actions.runtime-BIRU_G4M.js +0 -2
  76. package/dist/channel.setup-CLP5oTvh.js +0 -56
  77. package/dist/preflight-audio.runtime-D5Bgags0.js +0 -3
  78. package/dist/slash-plugin-commands.runtime-Dosfcx3X.js +0 -2
@@ -1,9 +1,10 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { a as resolveSlackAccount, f as resolveSlackBotToken } from "./accounts-CHAeNZOX.js";
3
- import { $ as escapeSlackMrkdwn, C as hasSlackNativeDataBlock, D as stripSlackNativeDataBlocks, F as SLACK_EDIT_TEXT_MAX_BYTES, M as buildSlackCompleteBlocksFallbackText, U as truncateSlackTextByUtf8Bytes, V as countSlackTextUtf8Bytes, b as appendSlackNativeDataPlainTextFallback, it as validateSlackBlocksArray, j as buildSlackBlocksFallbackText, w as isSlackInvalidBlocksError, y as appendSlackNativeDataFallbackText } from "./group-policy-NQZti9t-.js";
4
- import { a as createSlackLookupClient, f as getSlackWriteClient } from "./probe-CgDsSops.js";
5
- import { a as resolveSlackMessageText$1, i as resolveSlackBlocksText, n as hasSlackTableBlock, r as isSlackUnfurlAttachment, t as hasSlackMessageTableBlock } from "./block-text-DkHaoGem.js";
6
- import { r as sendMessageSlack, u as buildSlackNativeDataDeliveryPlan } from "./send-DsLeZpST.js";
2
+ import { a as resolveSlackAccount, f as resolveSlackBotToken } from "./accounts-CK4AVI2_.js";
3
+ import { E as isSlackInvalidBlocksError, K as truncateSlackTextByUtf8Bytes, L as SLACK_EDIT_TEXT_MAX_BYTES, M as normalizeSlackOutboundText, N as buildSlackBlocksFallbackText, P as buildSlackCompleteBlocksFallbackText, S as appendSlackNativeDataPlainTextFallback, T as hasSlackNativeDataBlock, W as countSlackTextUtf8Bytes, at as escapeSlackMrkdwn, k as stripSlackNativeDataBlocks, p as assertSlackDetachedTargetAllowed, ut as validateSlackBlocksArray, x as appendSlackNativeDataFallbackText } from "./group-policy-c4RP9bjc.js";
4
+ import { d as getSlackWriteClient, i as createSlackLookupClient } from "./probe-HoEg_WxB.js";
5
+ import { a as resolveSlackMessageText$1, i as resolveSlackBlocksText, n as hasSlackTableBlock, r as isSlackUnfurlAttachment, t as hasSlackMessageTableBlock } from "./block-text-YtiNkqmx.js";
6
+ import { p as buildSlackNativeDataDeliveryPlan, r as sendMessageSlack } from "./send-Nor0iBwY.js";
7
+ import { normalizeAccountId } from "openclaw/plugin-sdk/account-resolution";
7
8
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
8
9
  import { logVerbose, logVerbose as logVerbose$1 } from "openclaw/plugin-sdk/runtime-env";
9
10
  import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
@@ -33,8 +34,15 @@ function buildSlackEditTextPayload(content, blocks) {
33
34
  //#region extensions/slack/src/file-reference.ts
34
35
  function formatSlackFileReference(file) {
35
36
  const name = normalizeOptionalString(file?.name) ?? "file";
37
+ const mimetype = normalizeOptionalString(file?.mimetype);
38
+ const size = file?.size;
36
39
  const fileId = normalizeOptionalString(file?.id);
37
- return fileId ? `${name} (fileId: ${fileId})` : name;
40
+ const metadata = [
41
+ mimetype,
42
+ typeof size === "number" && Number.isSafeInteger(size) && size >= 0 ? `${size} bytes` : void 0,
43
+ fileId ? `fileId: ${fileId}` : void 0
44
+ ].filter((value) => value !== void 0);
45
+ return metadata.length > 0 ? `${name} (${metadata.join(", ")})` : name;
38
46
  }
39
47
  function formatSlackFileReferenceList(files) {
40
48
  if (!files?.length) return "file";
@@ -92,12 +100,12 @@ function resolveSlackMessageText(message) {
92
100
  }
93
101
  async function resolveSlackThreadStarter(params) {
94
102
  evictThreadStarterCache();
95
- const cacheKey = params.workspaceScope ? JSON.stringify([
103
+ const cacheKey = JSON.stringify([
96
104
  params.workspaceScope.accountId,
97
105
  params.workspaceScope.teamId,
98
106
  params.channelId,
99
107
  params.threadTs
100
- ]) : `${params.channelId}:${params.threadTs}`;
108
+ ]);
101
109
  const cached = THREAD_STARTER_CACHE.get(cacheKey);
102
110
  if (cached) {
103
111
  const now = asDateTimestampMs(Date.now());
@@ -199,16 +207,26 @@ var media_exports = /* @__PURE__ */ __exportAll({
199
207
  resolveSlackThreadHistory: () => resolveSlackThreadHistory,
200
208
  resolveSlackThreadStarter: () => resolveSlackThreadStarter
201
209
  });
202
- function isSlackHostname(hostname) {
210
+ function isGovSlackClient(client) {
211
+ if (!client?.slackApiUrl) return false;
212
+ try {
213
+ const apiUrl = new URL(client.slackApiUrl);
214
+ return apiUrl.protocol === "https:" && !apiUrl.port && normalizeHostname(apiUrl.hostname) === "slack-gov.com";
215
+ } catch {
216
+ return false;
217
+ }
218
+ }
219
+ function isSlackHostname(hostname, govSlack) {
203
220
  const normalized = normalizeHostname(hostname);
204
221
  if (!normalized) return false;
222
+ if (govSlack) return normalized === "files.slack-gov.com";
205
223
  return [
206
224
  "slack.com",
207
225
  "slack-edge.com",
208
226
  "slack-files.com"
209
227
  ].some((suffix) => normalized === suffix || normalized.endsWith(`.${suffix}`));
210
228
  }
211
- function assertSlackFileUrl(rawUrl) {
229
+ function assertSlackFileUrl(rawUrl, govSlack) {
212
230
  let parsed;
213
231
  try {
214
232
  parsed = new URL(rawUrl);
@@ -216,15 +234,15 @@ function assertSlackFileUrl(rawUrl) {
216
234
  throw new Error(`Invalid Slack file URL: ${rawUrl}`);
217
235
  }
218
236
  if (parsed.protocol !== "https:") throw new Error(`Refusing Slack file URL with non-HTTPS protocol: ${parsed.protocol}`);
219
- if (!isSlackHostname(parsed.hostname)) throw new Error(`Refusing to send Slack token to non-Slack host "${parsed.hostname}" (url: ${rawUrl})`);
237
+ if (!isSlackHostname(parsed.hostname, govSlack)) throw new Error(`Refusing to send Slack token to non-Slack host "${parsed.hostname}" (url: ${rawUrl})`);
220
238
  return parsed;
221
239
  }
222
240
  function createSlackAuthHeaders(token) {
223
241
  return { Authorization: `Bearer ${token}` };
224
242
  }
225
- function createSlackMediaRequest(url, token) {
243
+ function createSlackMediaRequest(url, token, govSlack) {
226
244
  return {
227
- url: assertSlackFileUrl(url).href,
245
+ url: assertSlackFileUrl(url, govSlack).href,
228
246
  requestInit: { headers: createSlackAuthHeaders(token) }
229
247
  };
230
248
  }
@@ -233,11 +251,11 @@ function isMockedFetch(fetchImpl) {
233
251
  const candidate = fetchImpl;
234
252
  return candidate.mock !== void 0 || candidate["_isMockFunction"] === true;
235
253
  }
236
- function createSlackMediaFetch() {
254
+ function createSlackMediaFetch(govSlack) {
237
255
  return async (input, init) => {
238
256
  const url = resolveRequestUrl(input);
239
257
  if (!url) throw new Error("Unsupported fetch input: expected string, URL, or Request");
240
- const parsed = assertSlackFileUrl(url);
258
+ const parsed = assertSlackFileUrl(url, govSlack);
241
259
  return ("dispatcher" in (init ?? {}) && !isMockedFetch(globalThis.fetch) ? fetchWithRuntimeDispatcher : globalThis.fetch)(parsed.href, {
242
260
  ...init,
243
261
  redirect: "manual"
@@ -257,6 +275,10 @@ const SLACK_MEDIA_SSRF_POLICY = {
257
275
  ],
258
276
  allowRfc2544BenchmarkRange: true
259
277
  };
278
+ const SLACK_GOV_MEDIA_SSRF_POLICY = {
279
+ hostnameAllowlist: ["files.slack-gov.com"],
280
+ allowRfc2544BenchmarkRange: true
281
+ };
260
282
  const SLACK_MEDIA_READ_IDLE_TIMEOUT_MS = 6e4;
261
283
  const SLACK_MEDIA_TOTAL_TIMEOUT_MS = 12e4;
262
284
  async function saveSlackMedia(params) {
@@ -340,16 +362,16 @@ async function fetchFreshSlackFileUrl(params) {
340
362
  }
341
363
  }
342
364
  async function downloadSlackMediaFile(params) {
343
- const { url: slackUrl, requestInit } = createSlackMediaRequest(params.url, params.token);
365
+ const { url: slackUrl, requestInit } = createSlackMediaRequest(params.url, params.token, params.govSlack);
344
366
  const saved = await saveSlackMedia({
345
367
  options: {
346
368
  url: slackUrl,
347
- fetchImpl: createSlackMediaFetch(),
369
+ fetchImpl: createSlackMediaFetch(params.govSlack),
348
370
  requestInit,
349
371
  filePathHint: params.file.name,
350
372
  fallbackContentType: resolveSlackMediaMimetype(params.file, params.file.mimetype),
351
373
  maxBytes: params.maxBytes,
352
- ssrfPolicy: SLACK_MEDIA_SSRF_POLICY
374
+ ssrfPolicy: params.govSlack ? SLACK_GOV_MEDIA_SSRF_POLICY : SLACK_MEDIA_SSRF_POLICY
353
375
  },
354
376
  readIdleTimeoutMs: params.readIdleTimeoutMs,
355
377
  totalTimeoutMs: params.totalTimeoutMs ?? SLACK_MEDIA_TOTAL_TIMEOUT_MS,
@@ -378,12 +400,12 @@ async function downloadSlackMediaFile(params) {
378
400
  function isForwardedSlackAttachment(attachment) {
379
401
  return attachment.is_share === true;
380
402
  }
381
- function resolveForwardedAttachmentImageUrl(attachment) {
403
+ function resolveForwardedAttachmentImageUrl(attachment, govSlack) {
382
404
  const rawUrl = attachment.image_url?.trim();
383
405
  if (!rawUrl) return null;
384
406
  try {
385
407
  const parsed = new URL(rawUrl);
386
- if (parsed.protocol !== "https:" || !isSlackHostname(parsed.hostname)) return null;
408
+ if (parsed.protocol !== "https:" || !isSlackHostname(parsed.hostname, govSlack)) return null;
387
409
  return parsed.toString();
388
410
  } catch {
389
411
  return null;
@@ -394,6 +416,7 @@ function resolveForwardedAttachmentImageUrl(attachment) {
394
416
  * Returns `null` when no files could be downloaded.
395
417
  */
396
418
  async function resolveSlackMedia(params) {
419
+ const govSlack = isGovSlackClient(params.client);
397
420
  const files = params.files ?? [];
398
421
  const resolved = await pMap(files.length > 8 ? files.slice(0, 8) : files, async (file) => {
399
422
  const preloaded = params.preloadedMedia?.get(file);
@@ -411,7 +434,8 @@ async function resolveSlackMedia(params) {
411
434
  maxBytes: params.maxBytes,
412
435
  readIdleTimeoutMs: params.readIdleTimeoutMs,
413
436
  totalTimeoutMs: params.totalTimeoutMs,
414
- abortSignal: params.abortSignal
437
+ abortSignal: params.abortSignal,
438
+ govSlack
415
439
  }).catch(() => null);
416
440
  if (result || !eventUrl) return result ?? pMapSkip;
417
441
  const freshUrl = await fetchFreshSlackFileUrl({
@@ -426,7 +450,8 @@ async function resolveSlackMedia(params) {
426
450
  maxBytes: params.maxBytes,
427
451
  readIdleTimeoutMs: params.readIdleTimeoutMs,
428
452
  totalTimeoutMs: params.totalTimeoutMs,
429
- abortSignal: params.abortSignal
453
+ abortSignal: params.abortSignal,
454
+ govSlack
430
455
  }).catch(() => null) ?? pMapSkip;
431
456
  }, {
432
457
  concurrency: MAX_SLACK_MEDIA_CONCURRENCY,
@@ -442,6 +467,9 @@ async function resolveSlackAttachmentContent(params) {
442
467
  if (forwardedAttachments.length === 0) return null;
443
468
  const textBlocks = [];
444
469
  const allMedia = [];
470
+ const allFiles = forwardedAttachments.flatMap((attachment) => attachment.files ?? []);
471
+ let unavailableImageCount = 0;
472
+ const govSlack = isGovSlackClient(params.client);
445
473
  for (const att of forwardedAttachments) {
446
474
  const text = att.text?.trim() || att.fallback?.trim();
447
475
  if (text) {
@@ -449,16 +477,16 @@ async function resolveSlackAttachmentContent(params) {
449
477
  const heading = author ? `[Forwarded message from ${author}]` : "[Forwarded message]";
450
478
  textBlocks.push(`${heading}\n${text}`);
451
479
  }
452
- const imageUrl = resolveForwardedAttachmentImageUrl(att);
480
+ const imageUrl = resolveForwardedAttachmentImageUrl(att, govSlack);
453
481
  if (imageUrl) try {
454
- const { url: slackUrl, requestInit } = createSlackMediaRequest(imageUrl, params.token);
482
+ const { url: slackUrl, requestInit } = createSlackMediaRequest(imageUrl, params.token, govSlack);
455
483
  const saved = await saveSlackMedia({
456
484
  options: {
457
485
  url: slackUrl,
458
- fetchImpl: createSlackMediaFetch(),
486
+ fetchImpl: createSlackMediaFetch(govSlack),
459
487
  requestInit,
460
488
  maxBytes: params.maxBytes,
461
- ssrfPolicy: SLACK_MEDIA_SSRF_POLICY
489
+ ssrfPolicy: govSlack ? SLACK_GOV_MEDIA_SSRF_POLICY : SLACK_MEDIA_SSRF_POLICY
462
490
  },
463
491
  readIdleTimeoutMs: params.readIdleTimeoutMs,
464
492
  totalTimeoutMs: params.totalTimeoutMs ?? SLACK_MEDIA_TOTAL_TIMEOUT_MS,
@@ -470,7 +498,9 @@ async function resolveSlackAttachmentContent(params) {
470
498
  contentType: saved.contentType,
471
499
  placeholder: `[Forwarded image: ${label}]`
472
500
  });
473
- } catch {}
501
+ } catch {
502
+ unavailableImageCount += 1;
503
+ }
474
504
  if (att.files && att.files.length > 0) {
475
505
  const fileMedia = await resolveSlackMedia({
476
506
  files: att.files,
@@ -485,10 +515,12 @@ async function resolveSlackAttachmentContent(params) {
485
515
  }
486
516
  }
487
517
  const combinedText = textBlocks.join("\n\n");
488
- if (!combinedText && allMedia.length === 0) return null;
518
+ if (!combinedText && allMedia.length === 0 && allFiles.length === 0 && unavailableImageCount === 0) return null;
489
519
  return {
490
520
  text: combinedText,
491
- media: allMedia
521
+ media: allMedia,
522
+ unavailableImageCount,
523
+ ...allFiles.length > 0 ? { files: allFiles } : {}
492
524
  };
493
525
  }
494
526
  //#endregion
@@ -595,8 +627,13 @@ function hasSlackPlatformError(err, code) {
595
627
  }
596
628
  async function getClient(opts = {}, mode = "read") {
597
629
  if (opts.client) return opts.client;
630
+ assertSlackDetachedTargetAllowed(opts.cfg ? resolveSlackAccount({
631
+ cfg: requireRuntimeConfig(opts.cfg, "Slack actions"),
632
+ accountId: opts.accountId
633
+ }).accountId : normalizeAccountId(opts.accountId), opts.teamId);
598
634
  const token = resolveToken(opts.token, opts.accountId, opts.cfg);
599
- return mode === "write" ? getSlackWriteClient(token) : createSlackLookupClient(token);
635
+ if (mode === "write") return getSlackWriteClient(token, { teamId: opts.teamId });
636
+ return createSlackLookupClient(token, { teamId: opts.teamId });
600
637
  }
601
638
  async function resolveBotUserId(client) {
602
639
  const auth = await client.auth.test();
@@ -659,6 +696,7 @@ async function sendSlackMessage(to, content, opts) {
659
696
  cfg: opts.cfg,
660
697
  token: opts.token,
661
698
  mediaUrl: opts.mediaUrl,
699
+ ...opts.forceDocument ? { forceDocument: true } : {},
662
700
  mediaAccess: opts.mediaAccess,
663
701
  mediaLocalRoots: opts.mediaLocalRoots,
664
702
  mediaReadFile: opts.mediaReadFile,
@@ -675,6 +713,9 @@ async function sendSlackMessage(to, content, opts) {
675
713
  });
676
714
  }
677
715
  async function editSlackMessage(channelId, messageId, content, opts = {}) {
716
+ await editSlackRenderedMessage(channelId, messageId, normalizeSlackOutboundText(content), opts);
717
+ }
718
+ async function editSlackRenderedMessage(channelId, messageId, content, opts = {}) {
678
719
  const client = await getClient(opts, "write");
679
720
  const blocks = opts.blocks == null ? void 0 : validateSlackBlocksArray(opts.blocks);
680
721
  const editText = buildSlackEditTextPayload(content, blocks);
@@ -726,18 +767,20 @@ async function readSlackMessages(channelId, opts = {}) {
726
767
  const exactBounds = exactMessageId ? {
727
768
  inclusive: true,
728
769
  latest: exactMessageId,
729
- oldest: void 0
770
+ oldest: exactMessageId
730
771
  } : {
731
772
  latest: normalizeSlackReadTimestamp(opts.before, "before"),
732
773
  oldest: normalizeSlackReadTimestamp(opts.after, "after")
733
774
  };
734
775
  const client = await getClient(opts);
735
776
  if (opts.threadId) {
777
+ const oldest = exactMessageId ? exactMessageId : exactBounds.oldest && Number(exactBounds.oldest) > Number(opts.threadId) ? exactBounds.oldest : opts.threadId;
736
778
  const result = await client.conversations.replies({
737
779
  channel: channelId,
738
780
  ts: opts.threadId,
739
781
  limit: readLimit,
740
- ...exactBounds
782
+ ...exactBounds,
783
+ oldest
741
784
  });
742
785
  return {
743
786
  messages: (result.messages ?? []).filter((message) => {
@@ -778,10 +821,6 @@ async function unpinSlackMessage(channelId, messageId, opts = {}) {
778
821
  async function listSlackPins(channelId, opts = {}) {
779
822
  return (await (await getClient(opts)).pins.list({ channel: channelId })).items ?? [];
780
823
  }
781
- function normalizeSlackScopeValue(value) {
782
- const trimmed = value?.trim();
783
- return trimmed ? trimmed : void 0;
784
- }
785
824
  function collectSlackDirectShareChannelIds(file) {
786
825
  const ids = /* @__PURE__ */ new Set();
787
826
  for (const group of [
@@ -792,7 +831,7 @@ function collectSlackDirectShareChannelIds(file) {
792
831
  if (!Array.isArray(group)) continue;
793
832
  for (const entry of group) {
794
833
  if (typeof entry !== "string") continue;
795
- const normalized = normalizeSlackScopeValue(entry);
834
+ const normalized = normalizeOptionalString(entry);
796
835
  if (normalized) ids.add(normalized);
797
836
  }
798
837
  }
@@ -806,7 +845,7 @@ function collectSlackShareMaps(file) {
806
845
  function collectSlackSharedChannelIds(file) {
807
846
  const ids = /* @__PURE__ */ new Set();
808
847
  for (const shareMap of collectSlackShareMaps(file)) for (const channelId of Object.keys(shareMap)) {
809
- const normalized = normalizeSlackScopeValue(channelId);
848
+ const normalized = normalizeOptionalString(channelId);
810
849
  if (normalized) ids.add(normalized);
811
850
  }
812
851
  return ids;
@@ -819,8 +858,8 @@ function collectSlackThreadShares(file, channelId) {
819
858
  for (const rawEntry of rawEntries) {
820
859
  if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) continue;
821
860
  const entry = rawEntry;
822
- const ts = typeof entry.ts === "string" ? normalizeSlackScopeValue(entry.ts) : void 0;
823
- const threadTs = typeof entry.thread_ts === "string" ? normalizeSlackScopeValue(entry.thread_ts) : void 0;
861
+ const ts = typeof entry.ts === "string" ? normalizeOptionalString(entry.ts) : void 0;
862
+ const threadTs = typeof entry.thread_ts === "string" ? normalizeOptionalString(entry.thread_ts) : void 0;
824
863
  matches.push({
825
864
  channelId,
826
865
  ts,
@@ -831,9 +870,9 @@ function collectSlackThreadShares(file, channelId) {
831
870
  return matches;
832
871
  }
833
872
  function hasSlackScopeMismatch(params) {
834
- const channelId = normalizeSlackScopeValue(params.channelId);
873
+ const channelId = normalizeOptionalString(params.channelId);
835
874
  if (!channelId) return false;
836
- const threadId = normalizeSlackScopeValue(params.threadId);
875
+ const threadId = normalizeOptionalString(params.threadId);
837
876
  const directIds = collectSlackDirectShareChannelIds(params.file);
838
877
  const sharedIds = collectSlackSharedChannelIds(params.file);
839
878
  const hasChannelEvidence = directIds.size > 0 || sharedIds.size > 0;
@@ -853,7 +892,8 @@ function hasSlackScopeMismatch(params) {
853
892
  */
854
893
  async function downloadSlackFile(fileId, opts) {
855
894
  const token = resolveToken(opts.token, opts.accountId, opts.cfg);
856
- const file = (await (await getClient(opts)).files.info({ file: fileId })).file;
895
+ const client = await getClient(opts);
896
+ const file = (await client.files.info({ file: fileId })).file;
857
897
  if (!file?.url_private_download && !file?.url_private) return null;
858
898
  if (hasSlackScopeMismatch({
859
899
  file,
@@ -868,9 +908,10 @@ async function downloadSlackFile(fileId, opts) {
868
908
  url_private: file.url_private,
869
909
  url_private_download: file.url_private_download
870
910
  }],
911
+ client,
871
912
  token,
872
913
  maxBytes: opts.maxBytes
873
914
  }))?.[0] ?? null;
874
915
  }
875
916
  //#endregion
876
- export { resolveSlackThreadHistory as _, listSlackEmojis as a, buildSlackEditTextPayload as b, pinSlackMessage as c, removeOwnSlackReactions as d, removeSlackReaction as f, media_exports as g, unpinSlackMessage as h, getSlackMemberInfo as i, reactSlackMessage as l, sendSlackMessage as m, downloadSlackFile as n, listSlackPins as o, resolveSlackConversationName as p, editSlackMessage as r, listSlackReactions as s, deleteSlackMessage as t, readSlackMessages as u, resolveSlackThreadStarter as v, formatSlackFileReference as y };
917
+ export { media_exports as _, getSlackMemberInfo as a, formatSlackFileReference as b, listSlackReactions as c, readSlackMessages as d, removeOwnSlackReactions as f, unpinSlackMessage as g, sendSlackMessage as h, editSlackRenderedMessage as i, pinSlackMessage as l, resolveSlackConversationName as m, downloadSlackFile as n, listSlackEmojis as o, removeSlackReaction as p, editSlackMessage as r, listSlackPins as s, deleteSlackMessage as t, reactSlackMessage as u, resolveSlackThreadHistory as v, buildSlackEditTextPayload as x, resolveSlackThreadStarter as y };
@@ -0,0 +1,2 @@
1
+ import { a as getSlackMemberInfo, c as listSlackReactions, d as readSlackMessages, f as removeOwnSlackReactions, g as unpinSlackMessage, h as sendSlackMessage, l as pinSlackMessage, m as resolveSlackConversationName, n as downloadSlackFile, o as listSlackEmojis, p as removeSlackReaction, r as editSlackMessage, s as listSlackPins, t as deleteSlackMessage, u as reactSlackMessage } from "./actions-BTDXwwE3.js";
2
+ export { deleteSlackMessage, downloadSlackFile, editSlackMessage, getSlackMemberInfo, listSlackEmojis, listSlackPins, listSlackReactions, pinSlackMessage, reactSlackMessage, readSlackMessages, removeOwnSlackReactions, removeSlackReaction, resolveSlackConversationName, sendSlackMessage, unpinSlackMessage };
package/dist/api.js CHANGED
@@ -1,18 +1,18 @@
1
1
  import { n as registerSlackHttpHandler, r as normalizeSlackWebhookPath, t as handleSlackHttpRequest } from "./registry-bbjH7IHX.js";
2
- import { a as resolveSlackAccount, i as resolveDefaultSlackAccountId, n as listSlackAccountIds, r as mergeSlackAccountConfig, t as listEnabledSlackAccounts } from "./accounts-CHAeNZOX.js";
2
+ import { a as resolveSlackAccount, i as resolveDefaultSlackAccountId, n as listSlackAccountIds, r as mergeSlackAccountConfig, t as listEnabledSlackAccounts } from "./accounts-CK4AVI2_.js";
3
3
  import { t as resolveSlackReplyToMode } from "./account-reply-mode-DrUXogvO.js";
4
- import { t as inspectSlackAccount } from "./account-inspect-DEKpg9Ip.js";
5
- import { a as resolveSlackChannelId, i as parseSlackTarget, n as looksLikeSlackTargetId, r as normalizeSlackMessagingTarget } from "./target-parsing-N6lHogYZ.js";
6
- import "./targets-KmjP2E8s.js";
7
- import { n as buildSlackThreadingToolContext, r as resolveSlackAutoThreadId, t as slackPlugin } from "./channel-B9fVdW3s.js";
8
- import { R as buildSlackInteractiveBlocks, a as allowListMatches, c as normalizeSlackAllowOwnerEntry, d as resolveSlackUserAllowed, it as validateSlackBlocksArray, l as normalizeSlackSlug, n as resolveSlackGroupRequireMention, nt as SLACK_MAX_BLOCKS, o as normalizeAllowList, r as resolveSlackGroupToolPolicy, rt as parseSlackBlocksInput, s as normalizeAllowListLower, u as resolveSlackAllowListMatch, z as buildSlackPresentationBlocks } from "./group-policy-NQZti9t-.js";
9
- import { n as extractSlackToolSend, r as listSlackMessageActions } from "./message-tool-api-DkhFpMoy.js";
10
- import { _ as resolveSlackWriteClientOptions, c as createSlackTokenCacheKey, f as getSlackWriteClient, g as resolveSlackWebClientOptions, i as clearSlackWriteClientCacheForTest, l as createSlackWebClient, m as SLACK_WRITE_RETRY_OPTIONS, p as SLACK_DEFAULT_RETRY_OPTIONS, t as probeSlack, u as createSlackWriteClient } from "./probe-CgDsSops.js";
11
- import { n as resetSlackChannelTypeCacheForTest, r as resolveSlackChannelType } from "./channel-type-DXyos1d2.js";
12
- import { t as slackSetupPlugin } from "./channel.setup-CLP5oTvh.js";
13
- import { a as listSlackEmojis, c as pinSlackMessage, d as removeOwnSlackReactions, f as removeSlackReaction, h as unpinSlackMessage, i as getSlackMemberInfo, l as reactSlackMessage, m as sendSlackMessage, n as downloadSlackFile, o as listSlackPins, r as editSlackMessage, s as listSlackReactions, t as deleteSlackMessage, u as readSlackMessages } from "./actions-D3NuJvva.js";
14
- import { l as recordSlackThreadParticipation, o as clearSlackThreadParticipationCache, s as hasSlackThreadParticipation } from "./send-DsLeZpST.js";
15
- import { n as listSlackDirectoryGroupsFromConfig, r as listSlackDirectoryPeersFromConfig } from "./directory-config-CnCIAktf.js";
4
+ import { t as inspectSlackAccount } from "./account-inspect-Drw2gAiN.js";
5
+ import { a as parseSlackTarget, i as normalizeSlackMessagingTarget, o as resolveSlackChannelId, r as looksLikeSlackTargetId } from "./target-parsing-BnMD2ZqZ.js";
6
+ import "./targets-Cx5W_n3W.js";
7
+ import { n as buildSlackThreadingToolContext, r as resolveSlackAutoThreadId, t as slackPlugin } from "./channel-D_tovcUL.js";
8
+ import { H as buildSlackPresentationBlocks, V as buildSlackInteractiveBlocks, a as allowListMatches, c as normalizeSlackAllowOwnerEntry, ct as SLACK_MAX_BLOCKS, f as resolveSlackUserAllowed, i as resolveSlackGroupToolPolicy, l as normalizeSlackSlug, lt as parseSlackBlocksInput, o as normalizeAllowList, r as resolveSlackGroupRequireMention, s as normalizeAllowListLower, u as resolveSlackAllowListMatch, ut as validateSlackBlocksArray } from "./group-policy-c4RP9bjc.js";
9
+ import { n as extractSlackToolSend, r as listSlackMessageActions } from "./message-tool-api-BykQB4AU.js";
10
+ import { _ as resolveSlackWriteClientOptions, c as createSlackWebClient, d as getSlackWriteClient, f as SLACK_DEFAULT_RETRY_OPTIONS, g as resolveSlackWebClientOptions, l as createSlackWriteClient, p as SLACK_WRITE_RETRY_OPTIONS, s as createSlackTokenCacheKey, t as probeSlack } from "./probe-HoEg_WxB.js";
11
+ import { n as resolveSlackChannelType } from "./channel-type-B9MSdoGN.js";
12
+ import { t as slackSetupPlugin } from "./channel.setup-Bch9dYmA.js";
13
+ import { a as getSlackMemberInfo, c as listSlackReactions, d as readSlackMessages, f as removeOwnSlackReactions, g as unpinSlackMessage, h as sendSlackMessage, l as pinSlackMessage, n as downloadSlackFile, o as listSlackEmojis, p as removeSlackReaction, r as editSlackMessage, s as listSlackPins, t as deleteSlackMessage, u as reactSlackMessage } from "./actions-BTDXwwE3.js";
14
+ import { f as recordSlackThreadParticipation, l as hasSlackThreadParticipation, s as clearSlackThreadParticipationCache } from "./send-Nor0iBwY.js";
15
+ import { n as listSlackDirectoryGroupsFromConfig, r as listSlackDirectoryPeersFromConfig } from "./directory-config-CNBz8lJa.js";
16
16
  import { t as collectSlackSecurityAuditFindings } from "./security-audit-DR097212.js";
17
- import { n as resolveSlackRuntimeGroupPolicy } from "./provider-DmRizMZt.js";
18
- export { SLACK_DEFAULT_RETRY_OPTIONS, SLACK_MAX_BLOCKS, SLACK_WRITE_RETRY_OPTIONS, resetSlackChannelTypeCacheForTest as __resetSlackChannelTypeCacheForTest, resetSlackChannelTypeCacheForTest, allowListMatches, buildSlackInteractiveBlocks, buildSlackPresentationBlocks, buildSlackThreadingToolContext, clearSlackThreadParticipationCache, clearSlackWriteClientCacheForTest, collectSlackSecurityAuditFindings, createSlackTokenCacheKey, createSlackWebClient, createSlackWriteClient, deleteSlackMessage, downloadSlackFile, editSlackMessage, extractSlackToolSend, getSlackMemberInfo, getSlackWriteClient, handleSlackHttpRequest, hasSlackThreadParticipation, inspectSlackAccount, listEnabledSlackAccounts, listSlackAccountIds, listSlackDirectoryGroupsFromConfig, listSlackDirectoryPeersFromConfig, listSlackEmojis, listSlackMessageActions, listSlackPins, listSlackReactions, looksLikeSlackTargetId, mergeSlackAccountConfig, normalizeAllowList, normalizeAllowListLower, normalizeSlackAllowOwnerEntry, normalizeSlackMessagingTarget, normalizeSlackSlug, normalizeSlackWebhookPath, parseSlackBlocksInput, parseSlackTarget, pinSlackMessage, probeSlack, reactSlackMessage, readSlackMessages, recordSlackThreadParticipation, registerSlackHttpHandler, removeOwnSlackReactions, removeSlackReaction, resolveDefaultSlackAccountId, resolveSlackAccount, resolveSlackAllowListMatch, resolveSlackAutoThreadId, resolveSlackChannelId, resolveSlackChannelType, resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy, resolveSlackReplyToMode, resolveSlackRuntimeGroupPolicy, resolveSlackUserAllowed, resolveSlackWebClientOptions, resolveSlackWriteClientOptions, sendSlackMessage, slackPlugin, slackSetupPlugin, unpinSlackMessage, validateSlackBlocksArray };
17
+ import { n as resolveSlackRuntimeGroupPolicy } from "./provider-CDNvBXUi.js";
18
+ export { SLACK_DEFAULT_RETRY_OPTIONS, SLACK_MAX_BLOCKS, SLACK_WRITE_RETRY_OPTIONS, allowListMatches, buildSlackInteractiveBlocks, buildSlackPresentationBlocks, buildSlackThreadingToolContext, clearSlackThreadParticipationCache, collectSlackSecurityAuditFindings, createSlackTokenCacheKey, createSlackWebClient, createSlackWriteClient, deleteSlackMessage, downloadSlackFile, editSlackMessage, extractSlackToolSend, getSlackMemberInfo, getSlackWriteClient, handleSlackHttpRequest, hasSlackThreadParticipation, inspectSlackAccount, listEnabledSlackAccounts, listSlackAccountIds, listSlackDirectoryGroupsFromConfig, listSlackDirectoryPeersFromConfig, listSlackEmojis, listSlackMessageActions, listSlackPins, listSlackReactions, looksLikeSlackTargetId, mergeSlackAccountConfig, normalizeAllowList, normalizeAllowListLower, normalizeSlackAllowOwnerEntry, normalizeSlackMessagingTarget, normalizeSlackSlug, normalizeSlackWebhookPath, parseSlackBlocksInput, parseSlackTarget, pinSlackMessage, probeSlack, reactSlackMessage, readSlackMessages, recordSlackThreadParticipation, registerSlackHttpHandler, removeOwnSlackReactions, removeSlackReaction, resolveDefaultSlackAccountId, resolveSlackAccount, resolveSlackAllowListMatch, resolveSlackAutoThreadId, resolveSlackChannelId, resolveSlackChannelType, resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy, resolveSlackReplyToMode, resolveSlackRuntimeGroupPolicy, resolveSlackUserAllowed, resolveSlackWebClientOptions, resolveSlackWriteClientOptions, sendSlackMessage, slackPlugin, slackSetupPlugin, unpinSlackMessage, validateSlackBlocksArray };
@@ -1,5 +1,6 @@
1
- import { F as SLACK_EDIT_TEXT_MAX_BYTES, U as truncateSlackTextByUtf8Bytes, bt as normalizeSlackApproverId, ft as shouldHandleSlackNativeApprovalRequest, h as resolveSlackReplyBlocks, ot as isSlackAnyNativeApprovalClientEnabled } from "./group-policy-NQZti9t-.js";
2
- import { r as sendMessageSlack } from "./send-DsLeZpST.js";
1
+ import { a as parseSlackTarget } from "./target-parsing-BnMD2ZqZ.js";
2
+ import { At as normalizeSlackApproverId, K as truncateSlackTextByUtf8Bytes, L as SLACK_EDIT_TEXT_MAX_BYTES, _ as resolveSlackReplyBlocks, ft as isSlackAnyNativeApprovalClientEnabled, yt as shouldHandleSlackNativeApprovalRequest } from "./group-policy-c4RP9bjc.js";
3
+ import { r as sendMessageSlack } from "./send-Nor0iBwY.js";
3
4
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
4
5
  import { buildChannelApprovalNativeTargetKey } from "openclaw/plugin-sdk/approval-native-runtime";
5
6
  import { buildApprovalPresentationFromActionDescriptors } from "openclaw/plugin-sdk/approval-reply-runtime";
@@ -264,7 +265,7 @@ function buildSlackExpiredBlocks(view) {
264
265
  }
265
266
  async function updateMessage(params) {
266
267
  try {
267
- await params.app.client.chat.update({
268
+ await params.client.chat.update({
268
269
  channel: params.channelId,
269
270
  ts: params.messageTs,
270
271
  text: truncateSlackTextByUtf8Bytes(params.text, SLACK_EDIT_TEXT_MAX_BYTES),
@@ -316,13 +317,18 @@ const slackApprovalNativeRuntime = createChannelApprovalNativeRuntimeAdapter({
316
317
  })
317
318
  },
318
319
  transport: {
319
- prepareTarget: ({ plannedTarget }) => ({
320
- dedupeKey: buildChannelApprovalNativeTargetKey(plannedTarget.target),
321
- target: {
322
- to: plannedTarget.target.to,
323
- threadTs: plannedTarget.target.threadId != null ? String(plannedTarget.target.threadId) : void 0
324
- }
325
- }),
320
+ prepareTarget: ({ plannedTarget }) => {
321
+ const parsed = parseSlackTarget(plannedTarget.target.to, { defaultKind: "channel" });
322
+ if (!parsed) throw new Error("Slack approval delivery target is missing");
323
+ return {
324
+ dedupeKey: buildChannelApprovalNativeTargetKey(plannedTarget.target),
325
+ target: {
326
+ to: `${parsed.kind}:${parsed.id}`,
327
+ threadTs: plannedTarget.target.threadId != null ? String(plannedTarget.target.threadId) : void 0,
328
+ teamId: parsed.teamId
329
+ }
330
+ };
331
+ },
326
332
  deliverPending: async ({ cfg, accountId, context, preparedTarget, pendingPayload }) => {
327
333
  const resolved = resolveHandlerContext({
328
334
  cfg,
@@ -330,16 +336,21 @@ const slackApprovalNativeRuntime = createChannelApprovalNativeRuntimeAdapter({
330
336
  context
331
337
  });
332
338
  if (!resolved) return null;
333
- const message = await sendMessageSlack(preparedTarget.to, pendingPayload.text, {
339
+ const client = resolveApprovalClient(resolved.context, preparedTarget.teamId);
340
+ const to = await resolveApprovalChannel(client, preparedTarget.to, preparedTarget.teamId);
341
+ const eventScope = resolveApprovalEventScope(client, preparedTarget.teamId);
342
+ const message = await sendMessageSlack(to, pendingPayload.text, {
334
343
  cfg,
335
344
  accountId: resolved.accountId,
336
345
  threadTs: preparedTarget.threadTs,
337
346
  blocks: pendingPayload.blocks,
338
- client: resolved.context.app.client
347
+ client,
348
+ eventScope
339
349
  });
340
350
  return {
341
351
  channelId: message.channelId,
342
- messageTs: message.messageId
352
+ messageTs: message.messageId,
353
+ teamId: preparedTarget.teamId
343
354
  };
344
355
  },
345
356
  updateEntry: async ({ cfg, accountId, context, entry, payload }) => {
@@ -351,7 +362,7 @@ const slackApprovalNativeRuntime = createChannelApprovalNativeRuntimeAdapter({
351
362
  if (!resolved) return;
352
363
  const nextPayload = payload;
353
364
  await updateMessage({
354
- app: resolved.context.app,
365
+ client: resolveApprovalClient(resolved.context, entry.teamId),
355
366
  channelId: entry.channelId,
356
367
  messageTs: entry.messageTs,
357
368
  text: nextPayload.text,
@@ -363,5 +374,31 @@ const slackApprovalNativeRuntime = createChannelApprovalNativeRuntimeAdapter({
363
374
  logError(`slack approvals: failed to deliver approval ${request.id}: ${String(error)}`);
364
375
  } }
365
376
  });
377
+ function resolveApprovalClient(context, teamId) {
378
+ if (!teamId) return context.app.client;
379
+ if (!context.enterprise || !context.resolveClient) throw new Error("Slack Enterprise Grid approval client is unavailable");
380
+ const client = context.resolveClient(teamId);
381
+ if (!client) throw new Error("Slack Enterprise Grid approval client is unavailable");
382
+ return client;
383
+ }
384
+ function resolveApprovalEventScope(client, teamId) {
385
+ if (!teamId) return;
386
+ return {
387
+ teamId,
388
+ client
389
+ };
390
+ }
391
+ async function resolveApprovalChannel(client, target, teamId) {
392
+ if (!teamId) return target;
393
+ const parsed = parseSlackTarget(target, { defaultKind: "channel" });
394
+ if (!parsed) throw new Error("Slack approval delivery target is missing");
395
+ if (parsed.kind === "channel") return `channel:${parsed.id}`;
396
+ const channelId = normalizeOptionalString((await client.conversations.open({
397
+ users: parsed.id,
398
+ return_im: true
399
+ })).channel?.id);
400
+ if (!channelId) throw new Error("Slack Enterprise Grid approval DM did not return a channel id");
401
+ return `channel:${channelId}`;
402
+ }
366
403
  //#endregion
367
404
  export { slackApprovalNativeRuntime };
@@ -1,4 +1,4 @@
1
- import { N as renderSlackBlockFallbackText } from "./group-policy-NQZti9t-.js";
1
+ import { F as renderSlackBlockFallbackText } from "./group-policy-c4RP9bjc.js";
2
2
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
3
3
  //#region extensions/slack/src/monitor/block-text.ts
4
4
  function readSlackBlockType(block) {