@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,14 +1,16 @@
1
- import { a as resolveSlackAccount, i as resolveDefaultSlackAccountId, n as listSlackAccountIds, o as resolveSlackAccountAllowFrom, r as mergeSlackAccountConfig } from "./accounts-CHAeNZOX.js";
2
- import { i as parseSlackTarget, t as canonicalizeSlackApiTargetId } from "./target-parsing-N6lHogYZ.js";
1
+ import { a as resolveSlackAccount, i as resolveDefaultSlackAccountId, n as listSlackAccountIds, o as resolveSlackAccountAllowFrom, r as mergeSlackAccountConfig } from "./accounts-CK4AVI2_.js";
2
+ import { a as parseSlackTarget, n as formatSlackTarget, t as canonicalizeSlackApiTargetId } from "./target-parsing-BnMD2ZqZ.js";
3
3
  import { t as isSlackPluginAccountConfigured } from "./account-configured-BwfXBo_F.js";
4
4
  import { normalizeAccountId } from "openclaw/plugin-sdk/account-resolution";
5
- import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringifiedOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
5
+ import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringifiedOptionalString, readNonBlankString } from "openclaw/plugin-sdk/string-coerce-runtime";
6
6
  import { normalizeMessageChannel } from "openclaw/plugin-sdk/routing";
7
7
  import { FormatCapabilityProfile, chunkTextForOutbound, markdownToIR, renderMarkdownIRChunksWithinLimit, renderMarkdownWithMarkers } from "openclaw/plugin-sdk/text-chunking";
8
- import { createNativeApprovalChannelRouteGates, doesApprovalRequestMatchChannelAccount, resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
8
+ import { buildChannelKeyCandidates } from "openclaw/plugin-sdk/channel-targets";
9
+ import { createNativeApprovalChannelRouteGates, doesApprovalRequestSelectChannelAccount, resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
9
10
  import { createChannelApprovalAuth, resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
10
11
  import { createChannelExecApprovalProfile, isChannelExecApprovalClientEnabledFromConfig, isChannelExecApprovalTargetRecipient, matchesApprovalRequestFilters } from "openclaw/plugin-sdk/approval-client-runtime";
11
12
  import { channelRouteTargetsMatchExact, stringifyRouteThreadId } from "openclaw/plugin-sdk/channel-route";
13
+ import { resolveGlobalMap } from "openclaw/plugin-sdk/global-singleton";
12
14
  import { normalizeMessagePresentation, reduceLegacyInteractiveReply, renderMessagePresentationChartFallbackText, renderMessagePresentationFallbackText, renderMessagePresentationTableFallbackText, resolveMessagePresentationButtonAction, resolveMessagePresentationOptionAction } from "openclaw/plugin-sdk/interactive-runtime";
13
15
  import { parseExecApprovalCommandText } from "openclaw/plugin-sdk/approval-reply-runtime";
14
16
  import { buildApprovalResolutionRef } from "openclaw/plugin-sdk/approval-reference-runtime";
@@ -17,7 +19,7 @@ import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
17
19
  import { eastAsianWidthType } from "get-east-asian-width";
18
20
  import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
19
21
  import { compileAllowlist, resolveCompiledAllowlistMatch } from "openclaw/plugin-sdk/allow-from";
20
- import { normalizeHyphenSlug, normalizeStringEntries as normalizeStringEntries$1, normalizeStringEntriesLower as normalizeStringEntriesLower$1 } from "openclaw/plugin-sdk/string-normalization-runtime";
22
+ import { normalizeHyphenSlug, normalizeStringEntries as normalizeStringEntries$1, normalizeStringEntriesLower } from "openclaw/plugin-sdk/string-normalization-runtime";
21
23
  import { resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
22
24
  //#region extensions/slack/src/exec-approvals.ts
23
25
  function normalizeSlackUserLikeId(value) {
@@ -60,13 +62,7 @@ const slackExecApprovalProfile = createChannelExecApprovalProfile({
60
62
  resolveConfig: (params) => resolveSlackAccount(params).config.execApprovals,
61
63
  resolveApprovers: getSlackExecApprovalApprovers,
62
64
  normalizeSenderId: normalizeSlackApproverId,
63
- isTargetRecipient: isSlackExecApprovalTargetRecipient,
64
- matchesRequestAccount: (params) => doesApprovalRequestMatchChannelAccount({
65
- cfg: params.cfg,
66
- request: params.request,
67
- channel: "slack",
68
- accountId: params.accountId
69
- })
65
+ isTargetRecipient: isSlackExecApprovalTargetRecipient
70
66
  });
71
67
  const isSlackExecApprovalClientEnabled = slackExecApprovalProfile.isClientEnabled;
72
68
  const isSlackExecApprovalAuthorizedSender = slackExecApprovalProfile.isAuthorizedSender;
@@ -96,6 +92,34 @@ const slackApproval = createChannelApprovalAuth({
96
92
  const getSlackApprovalApprovers = slackApproval.resolveApprovers;
97
93
  const isSlackApprovalAuthorizedSender = slackApproval.isAuthorizedSender;
98
94
  //#endregion
95
+ //#region extensions/slack/src/installation-identity-state.ts
96
+ const slackInstallationStates = resolveGlobalMap(Symbol.for("openclaw.slack.installation-identities"), "close-and-restart");
97
+ function registerSlackInstallationState(accountId, kind) {
98
+ const normalizedAccountId = normalizeAccountId(accountId);
99
+ const owner = Symbol(`slack-installation:${normalizedAccountId}`);
100
+ slackInstallationStates.set(normalizedAccountId, {
101
+ kind,
102
+ owner
103
+ });
104
+ return {
105
+ update: (nextKind) => {
106
+ if (slackInstallationStates.get(normalizedAccountId)?.owner === owner) slackInstallationStates.set(normalizedAccountId, {
107
+ kind: nextKind,
108
+ owner
109
+ });
110
+ },
111
+ release: () => {
112
+ if (slackInstallationStates.get(normalizedAccountId)?.owner === owner) slackInstallationStates.delete(normalizedAccountId);
113
+ }
114
+ };
115
+ }
116
+ function getSlackInstallationKind(accountId) {
117
+ return slackInstallationStates.get(normalizeAccountId(accountId))?.kind;
118
+ }
119
+ function isSlackWorkspaceInstallation(accountId) {
120
+ return getSlackInstallationKind(accountId) === "workspace";
121
+ }
122
+ //#endregion
99
123
  //#region extensions/slack/src/approval-native-gates.ts
100
124
  const DEFAULT_APPROVAL_FORWARDING_MODE = "session";
101
125
  const SLACK_DM_CHANNEL_ID_RE = /^D[A-Z0-9]{8,}$/i;
@@ -106,8 +130,7 @@ function resolveSlackApprovalKind(request) {
106
130
  return isExec ? "exec" : "plugin";
107
131
  }
108
132
  function isSlackApprovalTransportEnabled(params) {
109
- const account = resolveSlackAccount(params);
110
- return account.config.enterpriseOrgInstall !== true && isSlackPluginAccountConfigured(account);
133
+ return isSlackPluginAccountConfigured(resolveSlackAccount(params));
111
134
  }
112
135
  function resolveSlackNativeApprovalConfig(params) {
113
136
  return resolveSlackAccount(params).config.execApprovals;
@@ -140,7 +163,10 @@ function resolveTurnSourceSlackOriginTarget(request) {
140
163
  }) });
141
164
  if (!parsed) return null;
142
165
  return {
143
- to: `${parsed.kind}:${parsed.id}`,
166
+ to: formatSlackTarget({
167
+ ...parsed,
168
+ explicitKind: true
169
+ }),
144
170
  threadId: stringifyRouteThreadId(request.request.turnSourceThreadId)
145
171
  };
146
172
  }
@@ -160,7 +186,11 @@ function resolveSlackFallbackOriginTarget(request) {
160
186
  const parsed = parseSlackTarget(sessionTarget.id, { defaultKind: "channel" });
161
187
  if (!parsed) return null;
162
188
  return {
163
- to: `${parsed.kind}:${canonicalizeSlackApiTargetId(parsed.kind, parsed.id)}`,
189
+ to: formatSlackTarget({
190
+ ...parsed,
191
+ id: canonicalizeSlackApiTargetId(parsed.kind, parsed.id),
192
+ explicitKind: true
193
+ }),
164
194
  threadId: sessionTarget.threadId
165
195
  };
166
196
  }
@@ -177,6 +207,7 @@ function isSlackDmChannelToUserRoutePair(a, b) {
177
207
  const left = parseComparableSlackTarget(a);
178
208
  const right = parseComparableSlackTarget(b);
179
209
  if (!left || !right) return false;
210
+ if (left.teamId?.toLowerCase() !== right.teamId?.toLowerCase()) return false;
180
211
  return left.kind === "channel" && SLACK_DM_CHANNEL_ID_RE.test(left.id) && right.kind === "user" || right.kind === "channel" && SLACK_DM_CHANNEL_ID_RE.test(right.id) && left.kind === "user";
181
212
  }
182
213
  function slackTargetsMatch(a, b) {
@@ -201,7 +232,10 @@ function normalizeSlackForwardTarget(target) {
201
232
  const parsed = parseSlackTarget(to, { defaultKind: SLACK_USER_ID_RE.test(to) ? "user" : "channel" });
202
233
  if (!parsed) return null;
203
234
  return {
204
- to: `${parsed.kind}:${parsed.id}`,
235
+ to: formatSlackTarget({
236
+ ...parsed,
237
+ explicitKind: true
238
+ }),
205
239
  accountId: normalizeOptionalString(target.accountId),
206
240
  threadId: stringifyRouteThreadId(target.threadId)
207
241
  };
@@ -235,21 +269,18 @@ function isSlackPluginNativeApprovalClientEnabled(params) {
235
269
  return isSlackPluginNativeApprovalClientConfigEnabled(params) || isSlackPluginForwardingRoutePotentiallyEnabled(params);
236
270
  }
237
271
  function shouldHandleSlackPluginViaNativeClientConfig(params) {
238
- if (!doesApprovalRequestMatchChannelAccount({
239
- cfg: params.cfg,
240
- request: params.request,
272
+ if (!doesApprovalRequestSelectChannelAccount({
273
+ ...params,
241
274
  channel: "slack",
242
- accountId: params.accountId
243
- })) return false;
244
- const config = resolveSlackNativeApprovalConfig(params);
245
- if (!isChannelExecApprovalClientEnabledFromConfig({
246
- enabled: config?.enabled,
247
- approverCount: getSlackApprovalApprovers(params).length
275
+ defaultAccountId: resolveDefaultSlackAccountId(params.cfg),
276
+ eligibleAccountIds: listSlackNativeApprovalEligibleAccountIds({
277
+ ...params,
278
+ approvalKind: "plugin"
279
+ })
248
280
  })) return false;
249
- return matchesSlackNativeApprovalFilters({
250
- request: params.request,
251
- agentFilter: config?.agentFilter,
252
- sessionFilter: config?.sessionFilter
281
+ return isSlackNativeApprovalAccountEligible({
282
+ ...params,
283
+ approvalKind: "plugin"
253
284
  });
254
285
  }
255
286
  function matchesSlackNativeApprovalFilters(params) {
@@ -259,6 +290,25 @@ function matchesSlackNativeApprovalFilters(params) {
259
290
  sessionFilter: params.sessionFilter
260
291
  });
261
292
  }
293
+ function isSlackNativeApprovalAccountEligible(params) {
294
+ const config = resolveSlackNativeApprovalConfig(params);
295
+ const approverCount = params.approvalKind === "exec" ? getSlackExecApprovalApprovers(params).length : getSlackApprovalApprovers(params).length;
296
+ return isSlackApprovalTransportEnabled(params) && isChannelExecApprovalClientEnabledFromConfig({
297
+ enabled: config?.enabled,
298
+ approverCount
299
+ }) && matchesSlackNativeApprovalFilters({
300
+ request: params.request,
301
+ agentFilter: config?.agentFilter,
302
+ sessionFilter: config?.sessionFilter
303
+ });
304
+ }
305
+ function listSlackNativeApprovalEligibleAccountIds(params) {
306
+ const accountId = params.accountId ?? resolveDefaultSlackAccountId(params.cfg);
307
+ return isSlackNativeApprovalAccountEligible({
308
+ ...params,
309
+ accountId
310
+ }) ? [accountId] : [];
311
+ }
262
312
  function isAnyForwardedSlackExplicitTargetEligible(params) {
263
313
  return (resolvePluginApprovalForwardingConfig(params.cfg)?.targets ?? []).some((target) => isForwardedSlackExplicitTargetEligible({
264
314
  ...params,
@@ -292,24 +342,32 @@ function isSlackAnyNativeApprovalClientEnabled(params) {
292
342
  });
293
343
  }
294
344
  function shouldHandleSlackNativeApprovalRequest(params) {
345
+ if (getSlackInstallationKind(resolveSlackAccount(params).accountId) === "enterprise" && !resolveEnterpriseApprovalTeamId(params.request)) return false;
295
346
  if ((params.approvalKind ?? resolveSlackApprovalKind(params.request)) === "plugin") return shouldHandleSlackPluginViaNativeClientConfig(params) || shouldHandleSlackPluginViaForwarding(params);
296
- if (!doesApprovalRequestMatchChannelAccount({
297
- cfg: params.cfg,
298
- request: params.request,
347
+ const turnSourceChannel = normalizeMessageChannel(params.request.request.turnSourceChannel);
348
+ if (turnSourceChannel && turnSourceChannel !== "slack") return false;
349
+ if (!doesApprovalRequestSelectChannelAccount({
350
+ ...params,
299
351
  channel: "slack",
300
- accountId: params.accountId
301
- })) return false;
302
- const config = resolveSlackNativeApprovalConfig(params);
303
- if (!isChannelExecApprovalClientEnabledFromConfig({
304
- enabled: config?.enabled,
305
- approverCount: getSlackExecApprovalApprovers(params).length
352
+ defaultAccountId: resolveDefaultSlackAccountId(params.cfg),
353
+ eligibleAccountIds: listSlackNativeApprovalEligibleAccountIds({
354
+ ...params,
355
+ approvalKind: "exec"
356
+ })
306
357
  })) return false;
307
- return matchesSlackNativeApprovalFilters({
308
- request: params.request,
309
- agentFilter: config?.agentFilter,
310
- sessionFilter: config?.sessionFilter
358
+ return isSlackNativeApprovalAccountEligible({
359
+ ...params,
360
+ approvalKind: "exec"
311
361
  });
312
362
  }
363
+ function resolveEnterpriseApprovalTeamId(request) {
364
+ try {
365
+ const target = resolveTurnSourceSlackOriginTarget(request);
366
+ return target ? parseSlackTarget(target.to)?.teamId : void 0;
367
+ } catch {
368
+ return;
369
+ }
370
+ }
313
371
  //#endregion
314
372
  //#region extensions/slack/src/blocks-input.ts
315
373
  const SLACK_MAX_BLOCKS = 50;
@@ -500,42 +558,36 @@ function renderSlackMessagePresentationFallbackText(params) {
500
558
  //#region extensions/slack/src/data-table.ts
501
559
  const SLACK_DATA_TABLE_COLUMNS_MAX = 20;
502
560
  const SLACK_DATA_TABLE_ROWS_MAX = 100;
503
- function asRecord$3(value) {
504
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
505
- }
506
- function readNonEmptyString$1(value) {
507
- return typeof value === "string" && value.trim().length > 0 ? value : void 0;
508
- }
509
561
  function countCharacters(value) {
510
562
  return Array.from(value).length;
511
563
  }
512
564
  function readRichTextLeaf(record) {
513
565
  if (record.type === "text" && typeof record.text === "string") return record.text;
514
- const text = readNonEmptyString$1(record.text);
566
+ const text = readNonBlankString(record.text);
515
567
  if (text) return text;
516
568
  switch (record.type) {
517
- case "link": return readNonEmptyString$1(record.url) ?? "";
569
+ case "link": return readNonBlankString(record.url) ?? "";
518
570
  case "user": {
519
- const userId = readNonEmptyString$1(record.user_id);
571
+ const userId = readNonBlankString(record.user_id);
520
572
  return userId ? `<@${userId}>` : "";
521
573
  }
522
574
  case "channel": {
523
- const channelId = readNonEmptyString$1(record.channel_id);
575
+ const channelId = readNonBlankString(record.channel_id);
524
576
  return channelId ? `<#${channelId}>` : "";
525
577
  }
526
578
  case "usergroup": {
527
- const usergroupId = readNonEmptyString$1(record.usergroup_id);
579
+ const usergroupId = readNonBlankString(record.usergroup_id);
528
580
  return usergroupId ? `<!subteam^${usergroupId}>` : "";
529
581
  }
530
582
  case "broadcast": {
531
- const range = readNonEmptyString$1(record.range);
583
+ const range = readNonBlankString(record.range);
532
584
  return range ? `<!${range}>` : "";
533
585
  }
534
586
  case "emoji": {
535
- const name = readNonEmptyString$1(record.name);
587
+ const name = readNonBlankString(record.name);
536
588
  return name ? `:${name}:` : "";
537
589
  }
538
- case "date": return readNonEmptyString$1(record.fallback) ?? "";
590
+ case "date": return readNonBlankString(record.fallback) ?? "";
539
591
  default: return "";
540
592
  }
541
593
  }
@@ -543,7 +595,7 @@ function readRichTextElements(value, separator = "") {
543
595
  if (!Array.isArray(value)) return "";
544
596
  const parts = [];
545
597
  for (const rawElement of value) {
546
- const element = asRecord$3(rawElement);
598
+ const element = asOptionalRecord(rawElement);
547
599
  if (!element) continue;
548
600
  if (Array.isArray(element.elements)) {
549
601
  const rendered = readRichTextElements(element.elements, element.type === "rich_text_list" ? "\n" : "");
@@ -556,7 +608,7 @@ function readRichTextElements(value, separator = "") {
556
608
  return parts.join(separator);
557
609
  }
558
610
  function readSlackBasicTableCell(value) {
559
- const cell = asRecord$3(value);
611
+ const cell = asOptionalRecord(value);
560
612
  if (!cell) return "";
561
613
  if (cell.type === "raw_text") return typeof cell.text === "string" ? cell.text : "";
562
614
  if (cell.type === "raw_number") {
@@ -567,7 +619,7 @@ function readSlackBasicTableCell(value) {
567
619
  return cell.type === "rich_text" ? readRichTextElements(cell.elements, "\n") : "";
568
620
  }
569
621
  function parseSlackBasicTableRows(value) {
570
- const block = asRecord$3(value);
622
+ const block = asOptionalRecord(value);
571
623
  if (block?.type !== "table" || !Array.isArray(block.rows)) return;
572
624
  if (block.rows.length < 1 || block.rows.length > SLACK_DATA_TABLE_ROWS_MAX) return;
573
625
  let characterCount = 0;
@@ -582,15 +634,15 @@ function parseSlackBasicTableRows(value) {
582
634
  return rows.some((row) => row.some((cell) => cell.length > 0)) ? rows : void 0;
583
635
  }
584
636
  function readSlackDataTableCell(value, allowRichText) {
585
- const cell = asRecord$3(value);
637
+ const cell = asOptionalRecord(value);
586
638
  if (!cell) return;
587
- if (cell.type === "raw_text") return readNonEmptyString$1(cell.text);
588
- if (cell.type === "raw_number") return typeof cell.value === "number" && Number.isFinite(cell.value) ? readNonEmptyString$1(cell.text) : void 0;
589
- if (allowRichText && cell.type === "rich_text") return readNonEmptyString$1(readRichTextElements(cell.elements));
639
+ if (cell.type === "raw_text") return readNonBlankString(cell.text);
640
+ if (cell.type === "raw_number") return typeof cell.value === "number" && Number.isFinite(cell.value) ? readNonBlankString(cell.text) : void 0;
641
+ if (allowRichText && cell.type === "rich_text") return readNonBlankString(readRichTextElements(cell.elements));
590
642
  }
591
643
  function parseSlackDataTable(value, options = {}) {
592
- const block = asRecord$3(value);
593
- const caption = readNonEmptyString$1(block?.caption);
644
+ const block = asOptionalRecord(value);
645
+ const caption = readNonBlankString(block?.caption);
594
646
  if (block?.type !== "data_table" || !caption || !Array.isArray(block.rows)) return;
595
647
  if (block.rows.length < 2) return;
596
648
  const rawHeader = block.rows[0];
@@ -614,7 +666,7 @@ function parseSlackDataTable(value, options = {}) {
614
666
  }
615
667
  /** Detect current native table blocks without depending on unreleased Slack SDK types. */
616
668
  function hasSlackDataTableBlock(blocks) {
617
- return blocks?.some((block) => asRecord$3(block)?.type === "data_table") ?? false;
669
+ return blocks?.some((block) => asOptionalRecord(block)?.type === "data_table") ?? false;
618
670
  }
619
671
  function countSlackDataTableCellCharacters(value) {
620
672
  return parseSlackDataTable(value, { enforceNativeLimits: true })?.cellCharacterCount;
@@ -678,7 +730,7 @@ function buildSlackDataTableBlock(block, options = {}) {
678
730
  }
679
731
  /** Extract a deterministic accessible summary from a native Slack table block. */
680
732
  function renderSlackDataTableFallbackText(value) {
681
- const block = asRecord$3(value);
733
+ const block = asOptionalRecord(value);
682
734
  if (block?.type !== "data_table") return;
683
735
  const parsed = parseSlackDataTable(block);
684
736
  if (parsed) return renderMessagePresentationTableFallbackText({
@@ -687,7 +739,7 @@ function renderSlackDataTableFallbackText(value) {
687
739
  headers: parsed.headers,
688
740
  rows: parsed.rows
689
741
  });
690
- return readNonEmptyString$1(block.caption)?.trim();
742
+ return readNonBlankString(block.caption)?.trim();
691
743
  }
692
744
  function escapeCompactFallbackCell(value) {
693
745
  return value.replaceAll("\\", "\\\\").replaceAll(" ", "\\t").replaceAll("\r", "\\r").replaceAll("\n", "\\n");
@@ -708,10 +760,10 @@ function renderSlackTableMrkdwnFallbackText(value) {
708
760
  }
709
761
  /** Render each native table cell once for bounded, formatting-disabled delivery. */
710
762
  function renderSlackDataTableCompactPlainTextFallback(value) {
711
- const block = asRecord$3(value);
763
+ const block = asOptionalRecord(value);
712
764
  if (block?.type !== "data_table") return;
713
765
  const parsed = parseSlackDataTable(block);
714
- if (!parsed) return readNonEmptyString$1(block.caption)?.trim();
766
+ if (!parsed) return readNonBlankString(block.caption)?.trim();
715
767
  return [
716
768
  `${escapeCompactFallbackCell(parsed.caption)} (table)`,
717
769
  parsed.headers.map(escapeCompactFallbackCell).join(" "),
@@ -720,7 +772,7 @@ function renderSlackDataTableCompactPlainTextFallback(value) {
720
772
  }
721
773
  /** Render a native table as mrkdwn without activating raw cell control tokens. */
722
774
  function renderSlackDataTableMrkdwnFallbackText(value) {
723
- const block = asRecord$3(value);
775
+ const block = asOptionalRecord(value);
724
776
  if (block?.type !== "data_table") return;
725
777
  const parsed = parseSlackDataTable(block);
726
778
  if (parsed) return renderSlackMessagePresentationTableFallbackText({
@@ -729,7 +781,7 @@ function renderSlackDataTableMrkdwnFallbackText(value) {
729
781
  headers: parsed.headers,
730
782
  rows: parsed.rows
731
783
  });
732
- const caption = readNonEmptyString$1(block.caption)?.trim();
784
+ const caption = readNonBlankString(block.caption)?.trim();
733
785
  return caption ? escapeSlackMrkdwn(caption) : void 0;
734
786
  }
735
787
  //#endregion
@@ -739,12 +791,9 @@ const SLACK_CHART_LABEL_MAX = 20;
739
791
  const SLACK_CHART_AXIS_LABEL_MAX = 50;
740
792
  const SLACK_CHART_SERIES_MAX = 12;
741
793
  const SLACK_CHART_DATA_POINTS_MAX = 20;
742
- function asRecord$2(value) {
743
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
744
- }
745
794
  /** Detect native chart blocks without depending on unreleased Slack SDK types. */
746
795
  function hasSlackDataVisualizationBlock(blocks) {
747
- return blocks?.some((block) => asRecord$2(block)?.type === "data_visualization") ?? false;
796
+ return blocks?.some((block) => asOptionalRecord(block)?.type === "data_visualization") ?? false;
748
797
  }
749
798
  function isStringWithin(value, maxLength) {
750
799
  return typeof value === "string" && value.trim().length > 0 && Array.from(value).length <= maxLength;
@@ -791,7 +840,7 @@ function buildSlackDataVisualizationBlock(block) {
791
840
  };
792
841
  }
793
842
  function readSlackChartDatum(value) {
794
- const record = asRecord$2(value);
843
+ const record = asOptionalRecord(value);
795
844
  const label = record?.label;
796
845
  const datumValue = record?.value;
797
846
  return typeof label === "string" && typeof datumValue === "number" ? {
@@ -800,9 +849,9 @@ function readSlackChartDatum(value) {
800
849
  } : void 0;
801
850
  }
802
851
  function parseSlackDataVisualizationBlock(value) {
803
- const block = asRecord$2(value);
852
+ const block = asOptionalRecord(value);
804
853
  const title = block?.title;
805
- const chart = asRecord$2(block?.chart);
854
+ const chart = asOptionalRecord(block?.chart);
806
855
  if (block?.type !== "data_visualization" || typeof title !== "string" || !chart) return;
807
856
  if (chart.type === "pie") {
808
857
  if (!Array.isArray(chart.segments)) return;
@@ -817,12 +866,12 @@ function parseSlackDataVisualizationBlock(value) {
817
866
  return normalizedBlock?.type === "chart" ? normalizedBlock : void 0;
818
867
  }
819
868
  if (chart.type !== "bar" && chart.type !== "area" && chart.type !== "line") return;
820
- const axisConfig = asRecord$2(chart.axis_config);
869
+ const axisConfig = asOptionalRecord(chart.axis_config);
821
870
  const categories = axisConfig?.categories;
822
871
  if (!Array.isArray(categories) || !categories.every((category) => typeof category === "string")) return;
823
872
  if (!Array.isArray(chart.series)) return;
824
873
  const series = chart.series.map((rawSeries) => {
825
- const seriesRecord = asRecord$2(rawSeries);
874
+ const seriesRecord = asOptionalRecord(rawSeries);
826
875
  if (typeof seriesRecord?.name !== "string" || !Array.isArray(seriesRecord.data)) return;
827
876
  const data = seriesRecord.data.map(readSlackChartDatum);
828
877
  if (data.some((datum) => !datum) || data.length !== categories.length) return;
@@ -847,7 +896,7 @@ function parseSlackDataVisualizationBlock(value) {
847
896
  }
848
897
  /** Extract a deterministic accessible summary from a native Slack chart block. */
849
898
  function renderSlackDataVisualizationFallbackText(value) {
850
- const block = asRecord$2(value);
899
+ const block = asOptionalRecord(value);
851
900
  if (block?.type !== "data_visualization") return;
852
901
  const parsed = parseSlackDataVisualizationBlock(block);
853
902
  if (parsed) return renderMessagePresentationChartFallbackText(parsed);
@@ -855,7 +904,7 @@ function renderSlackDataVisualizationFallbackText(value) {
855
904
  }
856
905
  /** Render a native chart as mrkdwn without activating raw data control tokens. */
857
906
  function renderSlackDataVisualizationMrkdwnFallbackText(value) {
858
- const block = asRecord$2(value);
907
+ const block = asOptionalRecord(value);
859
908
  if (block?.type !== "data_visualization") return;
860
909
  const parsed = parseSlackDataVisualizationBlock(block);
861
910
  if (parsed) return renderSlackMessagePresentationChartFallbackText(parsed);
@@ -898,15 +947,24 @@ async function resolveSlackQuestionAction(params) {
898
947
  //#region extensions/slack/src/reply-action-ids.ts
899
948
  const SLACK_REPLY_BUTTON_ACTION_ID = "openclaw:reply_button";
900
949
  const SLACK_REPLY_LINK_ACTION_ID = "openclaw:reply_link";
950
+ const SLACK_SESSION_LINK_ACTION_ID = "openclaw:session_link";
901
951
  const SLACK_REPLY_SELECT_ACTION_ID = "openclaw:reply_select";
902
952
  const SLACK_CALLBACK_BUTTON_ACTION_ID = "openclaw:callback_button";
903
953
  const SLACK_CALLBACK_SELECT_ACTION_ID = "openclaw:callback_select";
904
954
  const SLACK_APPROVAL_BUTTON_ACTION_ID = "openclaw:approval_button";
905
955
  const SLACK_APPROVAL_SELECT_ACTION_ID = "openclaw:approval_select";
906
956
  const SLACK_QUESTION_BUTTON_ACTION_ID = "openclaw:question_button";
957
+ const SLACK_QUESTION_FINALIZATION_BLOCKS = Symbol("slackQuestionFinalizationBlocks");
907
958
  function isSlackQuestionActionId(actionId) {
908
959
  return actionId === "openclaw:question_button" || actionId.startsWith(`openclaw:question_button:`);
909
960
  }
961
+ /** Read only question control identities from the blocks actually sent to Slack. */
962
+ function resolveSlackQuestionActionIds(blocks) {
963
+ return (blocks ?? []).flatMap((block) => {
964
+ if (block.type !== "actions") return [];
965
+ return (block.elements ?? []).flatMap(({ action_id }) => action_id && isSlackQuestionActionId(action_id) ? [action_id] : []);
966
+ });
967
+ }
910
968
  function isSlackApprovalActionId(actionId) {
911
969
  return actionId === "openclaw:approval_button" || actionId === "openclaw:approval_select" || actionId.startsWith(`openclaw:approval_button:`) || actionId.startsWith(`openclaw:approval_select:`);
912
970
  }
@@ -1351,6 +1409,7 @@ function buildSlackPresentationSelectBlock(block, selectIndex) {
1351
1409
  //#endregion
1352
1410
  //#region extensions/slack/src/limits.ts
1353
1411
  const SLACK_TEXT_LIMIT = 8e3;
1412
+ const SLACK_MESSAGE_TEXT_RECOMMENDED_LIMIT = 4e3;
1354
1413
  const SLACK_EDIT_TEXT_MAX_BYTES = 4e3;
1355
1414
  const SLACK_MESSAGE_TEXT_HARD_LIMIT = 4e4;
1356
1415
  //#endregion
@@ -1394,54 +1453,46 @@ const SLACK_SELECT_ELEMENT_TYPES = /* @__PURE__ */ new Set([
1394
1453
  "channels_select",
1395
1454
  "multi_channels_select"
1396
1455
  ]);
1397
- function asRecord$1(value) {
1398
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1399
- }
1400
- function readNonEmptyString(value) {
1401
- if (typeof value !== "string") return;
1402
- const trimmed = value.trim();
1403
- return trimmed.length > 0 ? trimmed : void 0;
1404
- }
1405
1456
  function readTextObject(value, options = {}) {
1406
- const record = asRecord$1(value);
1457
+ const record = asOptionalRecord(value);
1407
1458
  if (!record) return;
1408
- const text = readNonEmptyString(record?.text);
1459
+ const text = normalizeOptionalString(record?.text);
1409
1460
  if (!text) return;
1410
1461
  return record.type === "plain_text" && options.nativeDataFormat !== "plain" ? escapeSlackMrkdwn(text) : text;
1411
1462
  }
1412
1463
  function readTextValue(value, options = {}) {
1413
- return readNonEmptyString(value) ?? readTextObject(value, options);
1464
+ return normalizeOptionalString(value) ?? readTextObject(value, options);
1414
1465
  }
1415
1466
  function renderSlackRichTextLeaf(element) {
1416
1467
  switch (element.type) {
1417
1468
  case "text": return typeof element.text === "string" ? escapeSlackMrkdwn(element.text) : "";
1418
- case "link": return escapeSlackMrkdwn(readNonEmptyString(element.text) ?? readNonEmptyString(element.url) ?? "");
1469
+ case "link": return escapeSlackMrkdwn(normalizeOptionalString(element.text) ?? normalizeOptionalString(element.url) ?? "");
1419
1470
  case "user": {
1420
- const userId = readNonEmptyString(element.user_id);
1471
+ const userId = normalizeOptionalString(element.user_id);
1421
1472
  return userId ? escapeSlackMrkdwn(`<@${userId}>`) : "";
1422
1473
  }
1423
1474
  case "channel": {
1424
- const channelId = readNonEmptyString(element.channel_id);
1475
+ const channelId = normalizeOptionalString(element.channel_id);
1425
1476
  return channelId ? escapeSlackMrkdwn(`<#${channelId}>`) : "";
1426
1477
  }
1427
1478
  case "usergroup": {
1428
- const usergroupId = readNonEmptyString(element.usergroup_id);
1479
+ const usergroupId = normalizeOptionalString(element.usergroup_id);
1429
1480
  return usergroupId ? escapeSlackMrkdwn(`<!subteam^${usergroupId}>`) : "";
1430
1481
  }
1431
1482
  case "broadcast": {
1432
- const range = readNonEmptyString(element.range);
1483
+ const range = normalizeOptionalString(element.range);
1433
1484
  return range ? escapeSlackMrkdwn(`<!${range}>`) : "";
1434
1485
  }
1435
1486
  case "emoji": {
1436
- const name = readNonEmptyString(element.name);
1487
+ const name = normalizeOptionalString(element.name);
1437
1488
  return name ? `:${name}:` : "";
1438
1489
  }
1439
- case "date": return escapeSlackMrkdwn(readNonEmptyString(element.fallback) ?? "");
1490
+ case "date": return escapeSlackMrkdwn(normalizeOptionalString(element.fallback) ?? "");
1440
1491
  default: return "";
1441
1492
  }
1442
1493
  }
1443
1494
  function renderSlackRichTextElement(value) {
1444
- const element = asRecord$1(value);
1495
+ const element = asOptionalRecord(value);
1445
1496
  if (!element) return "";
1446
1497
  switch (element.type) {
1447
1498
  case "rich_text_section":
@@ -1456,25 +1507,25 @@ function renderSlackRichTextElements(value, separator) {
1456
1507
  return value.map(renderSlackRichTextElement).filter(Boolean).join(separator);
1457
1508
  }
1458
1509
  function readImageText(block) {
1459
- const altText = readNonEmptyString(block.alt_text);
1510
+ const altText = normalizeOptionalString(block.alt_text);
1460
1511
  return (altText ? escapeSlackMrkdwn(altText) : void 0) ?? readTextObject(block.title);
1461
1512
  }
1462
1513
  function readVideoText(block, options = {}) {
1463
- const altText = readNonEmptyString(block.alt_text);
1514
+ const altText = normalizeOptionalString(block.alt_text);
1464
1515
  return readTextObject(block.title, options) ?? (altText ? escapeSlackMrkdwn(altText) : void 0);
1465
1516
  }
1466
1517
  function readContextText(block, options = {}) {
1467
1518
  if (!Array.isArray(block.elements)) return;
1468
1519
  const parts = block.elements.map((element) => {
1469
- const record = asRecord$1(element);
1470
- const altText = readNonEmptyString(record?.alt_text);
1520
+ const record = asOptionalRecord(element);
1521
+ const altText = normalizeOptionalString(record?.alt_text);
1471
1522
  return readTextObject(record, options) ?? (altText ? escapeSlackMrkdwn(altText) : void 0);
1472
1523
  }).filter((part) => Boolean(part));
1473
1524
  return parts.length > 0 ? parts.join(" ") : void 0;
1474
1525
  }
1475
1526
  function readControlElementText(value, options = {}) {
1476
- const element = asRecord$1(value);
1477
- const type = readNonEmptyString(element?.type);
1527
+ const element = asOptionalRecord(value);
1528
+ const type = normalizeOptionalString(element?.type);
1478
1529
  if (type === "button" || type === "workflow_button") return readTextValue(element?.text, options);
1479
1530
  if (type && SLACK_SELECT_ELEMENT_TYPES.has(type)) return readTextObject(element?.placeholder, options);
1480
1531
  }
@@ -1501,10 +1552,10 @@ function readActionsText(block, options = {}) {
1501
1552
  }
1502
1553
  /** Read only user-visible text from one Slack block. */
1503
1554
  function renderSlackBlockFallbackText(raw, options = {}) {
1504
- const block = asRecord$1(raw);
1555
+ const block = asOptionalRecord(raw);
1505
1556
  if (!block) return;
1506
1557
  switch (block.type) {
1507
- case "rich_text": return readNonEmptyString(renderSlackRichTextElements(block.elements, "\n"));
1558
+ case "rich_text": return normalizeOptionalString(renderSlackRichTextElements(block.elements, "\n"));
1508
1559
  case "header": return readTextObject(block.text, options);
1509
1560
  case "section": return readSectionText(block, options);
1510
1561
  case "image": return readImageText(block) ?? "Shared an image";
@@ -1894,9 +1945,6 @@ function markdownToSlackMrkdwnChunks(markdown, limit, options = {}) {
1894
1945
  const SLACK_MALFORMED_NATIVE_DATA_FALLBACK = "Slack could not render this chart or table data.";
1895
1946
  const SLACK_RESPONSE_URL_BODY_LIMIT_BYTES = 16 * 1024;
1896
1947
  const SLACK_RESPONSE_URL_BODY_TIMEOUT_MS = 3e4;
1897
- function asRecord(value) {
1898
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1899
- }
1900
1948
  /** Detect a native Slack chart or table block. */
1901
1949
  function hasSlackNativeDataBlock(blocks) {
1902
1950
  return hasSlackDataVisualizationBlock(blocks) || hasSlackDataTableBlock(blocks);
@@ -1904,23 +1952,23 @@ function hasSlackNativeDataBlock(blocks) {
1904
1952
  /** Keep every sibling block while removing Slack's native data blocks. */
1905
1953
  function stripSlackNativeDataBlocks(blocks) {
1906
1954
  return (blocks ?? []).filter((block) => {
1907
- const type = asRecord(block)?.type;
1955
+ const type = asOptionalRecord(block)?.type;
1908
1956
  return type !== "data_table" && type !== "data_visualization";
1909
1957
  });
1910
1958
  }
1911
1959
  /** Match Slack's Web API and response_url `invalid_blocks` error shapes. */
1912
1960
  function isSlackInvalidBlocksError(error) {
1913
- const record = asRecord(error);
1961
+ const record = asOptionalRecord(error);
1914
1962
  const rawData = record?.data;
1915
- const data = asRecord(rawData);
1916
- const rawResponseData = asRecord(record?.response)?.data;
1917
- const responseData = asRecord(rawResponseData);
1963
+ const data = asOptionalRecord(rawData);
1964
+ const rawResponseData = asOptionalRecord(record?.response)?.data;
1965
+ const responseData = asOptionalRecord(rawResponseData);
1918
1966
  const code = data?.error ?? (typeof rawData === "string" ? rawData : void 0) ?? responseData?.error ?? (typeof rawResponseData === "string" ? rawResponseData : void 0) ?? record?.error;
1919
1967
  return typeof code === "string" && code.trim().toLowerCase() === "invalid_blocks";
1920
1968
  }
1921
1969
  function isSlackResponseLike(value) {
1922
- const record = asRecord(value);
1923
- const body = asRecord(record?.body);
1970
+ const record = asOptionalRecord(value);
1971
+ const body = asOptionalRecord(record?.body);
1924
1972
  return typeof record?.status === "number" && (typeof record.arrayBuffer === "function" || typeof body?.getReader === "function");
1925
1973
  }
1926
1974
  /** Consume Bolt 5's native response_url body under strict time and byte bounds. */
@@ -1937,12 +1985,12 @@ async function isSlackInvalidBlocksResponse(response) {
1937
1985
  /** Bolt 5 omits the response body from RespondError; 400 is contextual here. */
1938
1986
  function isSlackNativeResponseUrlRejection(error) {
1939
1987
  if (isSlackInvalidBlocksError(error)) return true;
1940
- const record = asRecord(error);
1988
+ const record = asOptionalRecord(error);
1941
1989
  return record?.code === "slack_bolt_respond_error" && record.statusCode === 400;
1942
1990
  }
1943
1991
  /** Extract a complete accessible summary from a supported native data block. */
1944
1992
  function renderSlackNativeDataFallbackText(value) {
1945
- const type = asRecord(value)?.type;
1993
+ const type = asOptionalRecord(value)?.type;
1946
1994
  if (type === "data_visualization") return renderSlackDataVisualizationMrkdwnFallbackText(value);
1947
1995
  if (type === "data_table") return renderSlackDataTableMrkdwnFallbackText(value);
1948
1996
  }
@@ -1984,7 +2032,7 @@ function appendSlackNativeDataFallback(text, blocks, render) {
1984
2032
  return [base, ...dataTexts].filter(Boolean).join("\n\n");
1985
2033
  }
1986
2034
  function renderSlackNativeDataPlainTextBlock(value) {
1987
- const type = asRecord(value)?.type;
2035
+ const type = asOptionalRecord(value)?.type;
1988
2036
  if (type === "data_table") return renderSlackDataTableCompactPlainTextFallback(value);
1989
2037
  if (type === "data_visualization") return renderSlackDataVisualizationFallbackText(value);
1990
2038
  }
@@ -2312,8 +2360,15 @@ function resolveSlackReplyBlocks(payload) {
2312
2360
  return segments.length === 1 && segments[0]?.kind === "blocks" ? segments[0].blocks : void 0;
2313
2361
  }
2314
2362
  //#endregion
2363
+ //#region extensions/slack/src/detached-target-admission.ts
2364
+ function assertSlackDetachedTargetAllowed(accountId, teamId) {
2365
+ const installationKind = getSlackInstallationKind(accountId);
2366
+ if (installationKind && installationKind !== "workspace" && !teamId) throw new Error("unsupported_enterprise_slack_delivery: detached Slack operations require team:<team-id>:channel:<channel-id> or team:<team-id>:user:<user-id> until a workspace install is authenticated");
2367
+ }
2368
+ //#endregion
2315
2369
  //#region extensions/slack/src/monitor/allow-list.ts
2316
2370
  const SLACK_SLUG_CACHE_MAX = 512;
2371
+ const SLACK_STABLE_USER_ID_RE = /^[ubw][a-z0-9]+$/;
2317
2372
  const slackSlugCache = /* @__PURE__ */ new Map();
2318
2373
  function normalizeSlackSlug(raw) {
2319
2374
  const key = raw ?? "";
@@ -2331,49 +2386,61 @@ function normalizeAllowList(list) {
2331
2386
  return normalizeStringEntries$1(list);
2332
2387
  }
2333
2388
  function normalizeAllowListLower(list) {
2334
- return normalizeStringEntriesLower$1(list);
2389
+ return normalizeStringEntriesLower(list);
2335
2390
  }
2336
2391
  function normalizeSlackAllowOwnerEntry(entry) {
2337
2392
  const trimmed = normalizeOptionalLowercaseString(entry);
2338
2393
  if (!trimmed || trimmed === "*") return;
2394
+ try {
2395
+ const target = parseSlackTarget(trimmed);
2396
+ if (target?.kind === "user" && target.teamId) return target.id.toLowerCase();
2397
+ } catch {
2398
+ return;
2399
+ }
2339
2400
  const withoutPrefix = trimmed.replace(/^(slack:|user:)/, "");
2340
- return /^u[a-z0-9]+$/.test(withoutPrefix) ? withoutPrefix : void 0;
2401
+ return SLACK_STABLE_USER_ID_RE.test(withoutPrefix) ? withoutPrefix : void 0;
2341
2402
  }
2342
2403
  function resolveSlackAllowListMatch(params) {
2343
2404
  const compiledAllowList = compileAllowlist(params.allowList);
2405
+ const teamId = normalizeOptionalLowercaseString(params.teamId);
2344
2406
  const id = normalizeOptionalLowercaseString(params.id);
2345
2407
  const name = normalizeOptionalLowercaseString(params.name);
2346
2408
  const slug = normalizeSlackSlug(name);
2347
- return resolveCompiledAllowlistMatch({
2348
- compiledAllowlist: compiledAllowList,
2349
- candidates: [
2409
+ const scopedCandidates = [{
2410
+ value: teamId && id ? `team:${teamId}:user:${id}` : void 0,
2411
+ source: "workspace-id"
2412
+ }];
2413
+ const unscopedCandidates = [
2414
+ {
2415
+ value: id,
2416
+ source: "id"
2417
+ },
2418
+ {
2419
+ value: id ? `slack:${id}` : void 0,
2420
+ source: "prefixed-id"
2421
+ },
2422
+ {
2423
+ value: id ? `user:${id}` : void 0,
2424
+ source: "prefixed-user"
2425
+ },
2426
+ ...params.allowNameMatching === true ? [
2350
2427
  {
2351
- value: id,
2352
- source: "id"
2428
+ value: name,
2429
+ source: "name"
2353
2430
  },
2354
2431
  {
2355
- value: id ? `slack:${id}` : void 0,
2356
- source: "prefixed-id"
2432
+ value: name ? `slack:${name}` : void 0,
2433
+ source: "prefixed-name"
2357
2434
  },
2358
2435
  {
2359
- value: id ? `user:${id}` : void 0,
2360
- source: "prefixed-user"
2361
- },
2362
- ...params.allowNameMatching === true ? [
2363
- {
2364
- value: name,
2365
- source: "name"
2366
- },
2367
- {
2368
- value: name ? `slack:${name}` : void 0,
2369
- source: "prefixed-name"
2370
- },
2371
- {
2372
- value: slug,
2373
- source: "slug"
2374
- }
2375
- ] : []
2376
- ]
2436
+ value: slug,
2437
+ source: "slug"
2438
+ }
2439
+ ] : []
2440
+ ];
2441
+ return resolveCompiledAllowlistMatch({
2442
+ compiledAllowlist: compiledAllowList,
2443
+ candidates: [...scopedCandidates, ...unscopedCandidates]
2377
2444
  });
2378
2445
  }
2379
2446
  function allowListMatches(params) {
@@ -2384,18 +2451,41 @@ function resolveSlackUserAllowed(params) {
2384
2451
  if (allowList.length === 0) return true;
2385
2452
  return allowListMatches({
2386
2453
  allowList,
2454
+ teamId: params.teamId,
2387
2455
  id: params.userId,
2388
2456
  name: params.userName,
2389
2457
  allowNameMatching: params.allowNameMatching
2390
2458
  });
2391
2459
  }
2392
- //#endregion
2393
- //#region extensions/slack/src/direct-send-admission.ts
2394
- function assertSlackDirectSendAllowed(account) {
2395
- if (account.config.enterpriseOrgInstall === true) throw new Error("unsupported_enterprise_slack_delivery");
2460
+ function resolveSlackUserAllowListForTeam(params) {
2461
+ const allowList = normalizeAllowListLower(params.allowList);
2462
+ const teamId = normalizeOptionalLowercaseString(params.teamId);
2463
+ return allowList.flatMap((entry) => {
2464
+ if (entry === "*") return [entry];
2465
+ if (!entry.startsWith("team:")) return [entry];
2466
+ try {
2467
+ const target = parseSlackTarget(entry);
2468
+ if (target?.kind === "user" && target.teamId?.toLowerCase() === teamId) return [entry];
2469
+ return params.preserveUnmatchedScopedEntries ? [entry] : [];
2470
+ } catch {
2471
+ return params.preserveUnmatchedScopedEntries ? [entry] : [];
2472
+ }
2473
+ });
2396
2474
  }
2397
2475
  //#endregion
2398
2476
  //#region extensions/slack/src/group-policy.ts
2477
+ function buildSlackChannelIdCandidates(channelId, teamId, options) {
2478
+ const trimmedId = channelId?.trim();
2479
+ if (!trimmedId) return [];
2480
+ const lowercaseId = trimmedId.toLowerCase();
2481
+ const uppercaseId = trimmedId.toUpperCase();
2482
+ const exactTeamId = teamId || void 0;
2483
+ const lowercaseTeamId = exactTeamId?.toLowerCase();
2484
+ const uppercaseTeamId = exactTeamId?.toUpperCase();
2485
+ const scopedCandidates = buildChannelKeyCandidates(exactTeamId ? `team:${exactTeamId}:channel:${trimmedId}` : void 0, lowercaseTeamId ? `team:${lowercaseTeamId}:channel:${lowercaseId}` : void 0, uppercaseTeamId ? `team:${uppercaseTeamId}:channel:${uppercaseId}` : void 0);
2486
+ if (exactTeamId && options?.allowUnscoped !== true) return scopedCandidates;
2487
+ return buildChannelKeyCandidates(...scopedCandidates, trimmedId, lowercaseId, uppercaseId, `channel:${trimmedId}`, `channel:${lowercaseId}`, `channel:${uppercaseId}`);
2488
+ }
2399
2489
  function buildSlackChannelPolicyScope(params) {
2400
2490
  const channels = params.channels ?? {};
2401
2491
  const tree = { scopes: channels };
@@ -2413,16 +2503,11 @@ function buildSlackChannelPolicyScope(params) {
2413
2503
  function resolveSlackGroupPolicyScope(params) {
2414
2504
  const accountId = normalizeAccountId(params.accountId ?? resolveDefaultSlackAccountId(params.cfg));
2415
2505
  const channels = mergeSlackAccountConfig(params.cfg, accountId).channels;
2416
- const channelId = params.groupId?.trim();
2417
2506
  const channelName = params.groupChannel?.replace(/^#/, "");
2507
+ const allowUnscoped = getSlackInstallationKind(accountId) !== "enterprise";
2418
2508
  return buildSlackChannelPolicyScope({
2419
2509
  channels,
2420
- candidates: [
2421
- channelId,
2422
- channelName ? `#${channelName}` : void 0,
2423
- channelName,
2424
- normalizeHyphenSlug(channelName)
2425
- ].filter((candidate) => Boolean(candidate))
2510
+ candidates: buildChannelKeyCandidates(...buildSlackChannelIdCandidates(params.groupId, params.groupSpace, { allowUnscoped }), channelName ? `#${channelName}` : void 0, channelName, normalizeHyphenSlug(channelName))
2426
2511
  });
2427
2512
  }
2428
2513
  function resolveSlackGroupRequireMention(params) {
@@ -2439,4 +2524,4 @@ function resolveSlackGroupToolPolicy(params) {
2439
2524
  });
2440
2525
  }
2441
2526
  //#endregion
2442
- export { escapeSlackMrkdwn as $, normalizeSlackOutboundText as A, canRenderSlackPresentation as B, hasSlackNativeDataBlock as C, stripSlackNativeDataBlocks as D, isSlackNativeResponseUrlRejection as E, SLACK_EDIT_TEXT_MAX_BYTES as F, SLACK_REPLY_LINK_ACTION_ID as G, truncateSlackText as H, SLACK_MESSAGE_TEXT_HARD_LIMIT as I, isSlackCallbackActionId as J, SLACK_REPLY_SELECT_ACTION_ID as K, SLACK_TEXT_LIMIT as L, buildSlackCompleteBlocksFallbackText as M, renderSlackBlockFallbackText as N, chunkSlackMrkdwnText as O, resolveSlackAuthoredTextPlacement as P, renderSlackMessagePresentationFallbackText as Q, buildSlackInteractiveBlocks as R, createSlackNativeDataBaseTextConsumer as S, shouldSuppressLocalSlackExecApprovalPrompt as St, isSlackInvalidBlocksResponse as T, truncateSlackTextByUtf8Bytes as U, countSlackTextUtf8Bytes as V, SLACK_REPLY_BUTTON_ACTION_ID as W, decodeSlackQuestionAction as X, isSlackQuestionActionId as Y, resolveSlackQuestionAction as Z, resolveSlackReplyRenderPlan as _, getSlackExecApprovalApprovers as _t, allowListMatches as a, hasSlackPluginApprovers as at, appendSlackNativeDataPlainTextFallback as b, normalizeSlackApproverId as bt, normalizeSlackAllowOwnerEntry as c, normalizeSlackOriginTarget as ct, resolveSlackUserAllowed as d, resolveTurnSourceSlackOriginTarget as dt, decodeSlackApprovalAction as et, hasSlackReplyStructuredContent as f, shouldHandleSlackNativeApprovalRequest as ft, resolveSlackReplyDeliveryMessages as g, isSlackApprovalAuthorizedSender as gt, resolveSlackReplyBlocks as h, getSlackApprovalApprovers as ht, assertSlackDirectSendAllowed as i, validateSlackBlocksArray as it, buildSlackBlocksFallbackText as j, markdownToSlackMrkdwnChunks as k, normalizeSlackSlug as l, resolveSessionSlackOriginTarget as lt, resolveSlackReplyBlockResolution as m, slackTargetsMatch as mt, resolveSlackGroupRequireMention as n, SLACK_MAX_BLOCKS as nt, normalizeAllowList as o, isSlackAnyNativeApprovalClientEnabled as ot, parseSlackReplyBlockSegments as p, shouldHandleSlackPluginViaForwardingSession as pt, isSlackApprovalActionId as q, resolveSlackGroupToolPolicy as r, parseSlackBlocksInput as rt, normalizeAllowListLower as s, normalizeSlackForwardTarget as st, buildSlackChannelPolicyScope as t, SLACK_PRESENTATION_CAPABILITIES as tt, resolveSlackAllowListMatch as u, resolveSlackFallbackOriginTarget as ut, SLACK_MALFORMED_NATIVE_DATA_FALLBACK as v, isSlackExecApprovalAuthorizedSender as vt, isSlackInvalidBlocksError as w, buildSlackNativeDataAccessibilityText as x, resolveSlackExecApprovalTarget as xt, appendSlackNativeDataFallbackText as y, isSlackExecApprovalClientEnabled as yt, buildSlackPresentationBlocks as z };
2527
+ export { isSlackCallbackActionId as $, chunkSlackMrkdwnText as A, normalizeSlackApproverId as At, SLACK_TEXT_LIMIT as B, buildSlackNativeDataAccessibilityText as C, isSlackWorkspaceInstallation as Ct, isSlackInvalidBlocksResponse as D, getSlackExecApprovalApprovers as Dt, isSlackInvalidBlocksError as E, isSlackApprovalAuthorizedSender as Et, renderSlackBlockFallbackText as F, truncateSlackText as G, buildSlackPresentationBlocks as H, resolveSlackAuthoredTextPlacement as I, SLACK_REPLY_BUTTON_ACTION_ID as J, truncateSlackTextByUtf8Bytes as K, SLACK_EDIT_TEXT_MAX_BYTES as L, normalizeSlackOutboundText as M, shouldSuppressLocalSlackExecApprovalPrompt as Mt, buildSlackBlocksFallbackText as N, isSlackNativeResponseUrlRejection as O, isSlackExecApprovalAuthorizedSender as Ot, buildSlackCompleteBlocksFallbackText as P, isSlackApprovalActionId as Q, SLACK_MESSAGE_TEXT_HARD_LIMIT as R, appendSlackNativeDataPlainTextFallback as S, getSlackInstallationKind as St, hasSlackNativeDataBlock as T, getSlackApprovalApprovers as Tt, canRenderSlackPresentation as U, buildSlackInteractiveBlocks as V, countSlackTextUtf8Bytes as W, SLACK_REPLY_SELECT_ACTION_ID as X, SLACK_REPLY_LINK_ACTION_ID as Y, SLACK_SESSION_LINK_ACTION_ID as Z, resolveSlackReplyBlocks as _, resolveSlackFallbackOriginTarget as _t, allowListMatches as a, escapeSlackMrkdwn as at, SLACK_MALFORMED_NATIVE_DATA_FALLBACK as b, shouldHandleSlackPluginViaForwardingSession as bt, normalizeSlackAllowOwnerEntry as c, SLACK_MAX_BLOCKS as ct, resolveSlackUserAllowListForTeam as d, hasSlackPluginApprovers as dt, isSlackQuestionActionId as et, resolveSlackUserAllowed as f, isSlackAnyNativeApprovalClientEnabled as ft, resolveSlackReplyBlockResolution as g, resolveSessionSlackOriginTarget as gt, parseSlackReplyBlockSegments as h, resolveEnterpriseApprovalTeamId as ht, resolveSlackGroupToolPolicy as i, renderSlackMessagePresentationFallbackText as it, markdownToSlackMrkdwnChunks as j, resolveSlackExecApprovalTarget as jt, stripSlackNativeDataBlocks as k, isSlackExecApprovalClientEnabled as kt, normalizeSlackSlug as l, parseSlackBlocksInput as lt, hasSlackReplyStructuredContent as m, normalizeSlackOriginTarget as mt, buildSlackChannelPolicyScope as n, decodeSlackQuestionAction as nt, normalizeAllowList as o, decodeSlackApprovalAction as ot, assertSlackDetachedTargetAllowed as p, normalizeSlackForwardTarget as pt, SLACK_QUESTION_FINALIZATION_BLOCKS as q, resolveSlackGroupRequireMention as r, resolveSlackQuestionAction as rt, normalizeAllowListLower as s, SLACK_PRESENTATION_CAPABILITIES as st, buildSlackChannelIdCandidates as t, resolveSlackQuestionActionIds as tt, resolveSlackAllowListMatch as u, validateSlackBlocksArray as ut, resolveSlackReplyDeliveryMessages as v, resolveTurnSourceSlackOriginTarget as vt, createSlackNativeDataBaseTextConsumer as w, registerSlackInstallationState as wt, appendSlackNativeDataFallbackText as x, slackTargetsMatch as xt, resolveSlackReplyRenderPlan as y, shouldHandleSlackNativeApprovalRequest as yt, SLACK_MESSAGE_TEXT_RECOMMENDED_LIMIT as z };