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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/{account-inspect-DEKpg9Ip.js → account-inspect-YJvFBbCR.js} +1 -1
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/{accounts-CHAeNZOX.js → accounts-BlzxuYle.js} +7 -3
  4. package/dist/{accounts.runtime-BHD4Ma2R.js → accounts.runtime-CbabBL8x.js} +1 -1
  5. package/dist/{action-runtime-DfuYoMjG.js → action-runtime-D6jg2CQd.js} +193 -125
  6. package/dist/action-runtime.runtime-JtoOK9l5.js +2 -0
  7. package/dist/{actions-D3NuJvva.js → actions-DFk0ANVB.js} +116 -235
  8. package/dist/actions.runtime-CYLsYRai.js +2 -0
  9. package/dist/api.js +17 -17
  10. package/dist/{approval-handler.runtime-BA05P0QT.js → approval-handler.runtime-BNvAoKdK.js} +53 -14
  11. package/dist/{channel-B9fVdW3s.js → channel-CA5w7qDF.js} +209 -110
  12. package/dist/channel-config-api.js +1 -1
  13. package/dist/channel-plugin-api.js +1 -1
  14. package/dist/{channel-type-DXyos1d2.js → channel-type-CZWv55Wu.js} +9 -12
  15. package/dist/{config-adapter-CxBQH8fq.js → channel.setup-CpyYsEXH.js} +83 -25
  16. package/dist/{config-schema-SgZT6nXV.js → config-schema-CN3XZRX5.js} +27 -17
  17. package/dist/conversation-binding-route-DFVLUdYF.js +115 -0
  18. package/dist/{directory-config-CnCIAktf.js → directory-config-BGYyuzWh.js} +3 -3
  19. package/dist/directory-contract-api.js +1 -1
  20. package/dist/{directory-live-DKunWh3A.js → directory-live-DjUJQLbq.js} +13 -22
  21. package/dist/{doctor-contract-CsGH1Dfc.js → doctor-contract-BAy7-EUQ.js} +30 -3
  22. package/dist/doctor-contract-api.js +1 -1
  23. package/dist/{group-policy-NQZti9t-.js → group-policy-DyfB_ZIE.js} +282 -183
  24. package/dist/index.js +4 -1
  25. package/dist/{message-tool-api-DkhFpMoy.js → message-tool-api-CqMr5Rmc.js} +14 -3
  26. package/dist/message-tool-api.js +1 -1
  27. package/dist/{monitor-CmKz0wtI.js → monitor-Chrecrb9.js} +3 -3
  28. package/dist/{outbound-adapter-BkuMp2Vs.js → outbound-adapter-DuRqAQXK.js} +35 -23
  29. package/dist/{pipeline.runtime-DgtmuYCP.js → pipeline.runtime-Bbfd5VFW.js} +1386 -929
  30. package/dist/{policy-PRysPn23.js → policy-BKXSxrIS.js} +18 -14
  31. package/dist/{probe-CgDsSops.js → probe-BVA65leh.js} +38 -18
  32. package/dist/{provider-DmRizMZt.js → provider-CUUiwsg2.js} +3454 -2601
  33. package/dist/{relay-source-Blxj8xb4.js → relay-source-mhpMMRG-.js} +11 -21
  34. package/dist/{replies-4s_Vq_z9.js → replies-SnWyXcsL.js} +84 -27
  35. package/dist/{resolve-channels-yEqmM-3I.js → resolve-channels-BSpqC8jU.js} +39 -12
  36. package/dist/{resolve-users-BOrt5K-5.js → resolve-users-Dvz9pDe0.js} +28 -5
  37. package/dist/{runtime-api-QMb-Q5Ko.js → runtime-api-DBi7xc_g.js} +3 -3
  38. package/dist/runtime-api.js +11 -11
  39. package/dist/{scopes-BJSrkAp6.js → scopes-BSk4GZvZ.js} +1 -1
  40. package/dist/security-contract-api.js +1 -1
  41. package/dist/{send-DsLeZpST.js → send-CtnOxKCc.js} +249 -163
  42. package/dist/{send.runtime-gUdK6V3h.js → send.runtime-kYctMHSd.js} +1 -1
  43. package/dist/setup-entry.js +1 -4
  44. package/dist/setup-plugin-api.js +1 -1
  45. package/dist/{setup-surface-BeNzrMrJ.js → setup-surface-BPFfp6So.js} +6 -6
  46. package/dist/{shared-DgIN3Dcr.js → shared-DJBaslN7.js} +45 -45
  47. package/dist/{block-text-DkHaoGem.js → slack-client-kind-BXyOOICI.js} +17 -2
  48. package/dist/{slash-dispatch.runtime-zPkyl7dB.js → slash-dispatch.runtime-Cf2FTrnf.js} +2 -2
  49. package/dist/{streaming-compat-3_X-21wW.js → streaming-compat-B-e0mqM0.js} +3 -8
  50. package/dist/{target-parsing-N6lHogYZ.js → target-parsing-BnMD2ZqZ.js} +40 -2
  51. package/dist/{targets-KmjP2E8s.js → targets-Cx5W_n3W.js} +1 -1
  52. package/node_modules/@slack/bolt/node_modules/@types/node/README.md +1 -1
  53. package/node_modules/@slack/bolt/node_modules/@types/node/crypto.d.ts +1 -1
  54. package/node_modules/@slack/bolt/node_modules/@types/node/package.json +2 -2
  55. package/node_modules/@slack/socket-mode/node_modules/@types/node/README.md +1 -1
  56. package/node_modules/@slack/socket-mode/node_modules/@types/node/crypto.d.ts +1 -1
  57. package/node_modules/@slack/socket-mode/node_modules/@types/node/package.json +2 -2
  58. package/node_modules/@slack/web-api/node_modules/@types/node/README.md +1 -1
  59. package/node_modules/@slack/web-api/node_modules/@types/node/crypto.d.ts +1 -1
  60. package/node_modules/@slack/web-api/node_modules/@types/node/package.json +2 -2
  61. package/node_modules/undici/lib/cache/memory-cache-store.js +54 -9
  62. package/node_modules/undici/lib/cache/sqlite-cache-store.js +7 -1
  63. package/node_modules/undici/lib/core/request.js +12 -1
  64. package/node_modules/undici/lib/dispatcher/client-h1.js +11 -2
  65. package/node_modules/undici/lib/handler/cache-handler.js +246 -64
  66. package/node_modules/undici/lib/handler/cache-revalidation-handler.js +3 -3
  67. package/node_modules/undici/lib/handler/retry-handler.js +24 -0
  68. package/node_modules/undici/lib/interceptor/cache.js +159 -52
  69. package/node_modules/undici/lib/util/cache.js +324 -62
  70. package/node_modules/undici/lib/util/date.js +23 -6
  71. package/node_modules/undici/lib/web/cookies/util.js +79 -9
  72. package/node_modules/undici/package.json +1 -1
  73. package/openclaw.plugin.json +280 -33
  74. package/package.json +9 -11
  75. package/skills/slack/SKILL.md +10 -68
  76. package/dist/account-reply-mode-DrUXogvO.js +0 -14
  77. package/dist/action-runtime.runtime-DQErZYpz.js +0 -2
  78. package/dist/actions.runtime-BIRU_G4M.js +0 -2
  79. package/dist/channel.setup-CLP5oTvh.js +0 -56
  80. package/dist/preflight-audio.runtime-D5Bgags0.js +0 -3
  81. package/dist/slash-plugin-commands.runtime-Dosfcx3X.js +0 -2
  82. package/node_modules/p-map/index.d.ts +0 -155
  83. package/node_modules/p-map/index.js +0 -285
  84. package/node_modules/p-map/license +0 -9
  85. package/node_modules/p-map/package.json +0 -57
  86. package/node_modules/p-map/readme.md +0 -190
  87. package/dist/{security-audit-DR097212.js → security-audit-B_yKr7MW.js} +1 -1
@@ -1,14 +1,18 @@
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-BlzxuYle.js";
2
+ import { a as parseSlackTarget, n as formatSlackTarget, t as canonicalizeSlackApiTargetId } from "./target-parsing-BnMD2ZqZ.js";
3
+ import { t as slackContextTargetsMatch } from "./targets-Cx5W_n3W.js";
3
4
  import { t as isSlackPluginAccountConfigured } from "./account-configured-BwfXBo_F.js";
4
5
  import { normalizeAccountId } from "openclaw/plugin-sdk/account-resolution";
5
- import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringifiedOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
6
+ import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringifiedOptionalString, readNonBlankString } from "openclaw/plugin-sdk/string-coerce-runtime";
6
7
  import { normalizeMessageChannel } from "openclaw/plugin-sdk/routing";
7
8
  import { FormatCapabilityProfile, chunkTextForOutbound, markdownToIR, renderMarkdownIRChunksWithinLimit, renderMarkdownWithMarkers } from "openclaw/plugin-sdk/text-chunking";
8
- import { createNativeApprovalChannelRouteGates, doesApprovalRequestMatchChannelAccount, resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
9
+ import { isSingleUseReplyToMode } from "openclaw/plugin-sdk/reply-reference";
10
+ import { buildChannelKeyCandidates } from "openclaw/plugin-sdk/channel-targets";
11
+ import { createNativeApprovalChannelRouteGates, doesApprovalRequestSelectChannelAccount, resolveApprovalKind, resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
9
12
  import { createChannelApprovalAuth, resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
10
13
  import { createChannelExecApprovalProfile, isChannelExecApprovalClientEnabledFromConfig, isChannelExecApprovalTargetRecipient, matchesApprovalRequestFilters } from "openclaw/plugin-sdk/approval-client-runtime";
11
14
  import { channelRouteTargetsMatchExact, stringifyRouteThreadId } from "openclaw/plugin-sdk/channel-route";
15
+ import { resolveGlobalMap } from "openclaw/plugin-sdk/global-singleton";
12
16
  import { normalizeMessagePresentation, reduceLegacyInteractiveReply, renderMessagePresentationChartFallbackText, renderMessagePresentationFallbackText, renderMessagePresentationTableFallbackText, resolveMessagePresentationButtonAction, resolveMessagePresentationOptionAction } from "openclaw/plugin-sdk/interactive-runtime";
13
17
  import { parseExecApprovalCommandText } from "openclaw/plugin-sdk/approval-reply-runtime";
14
18
  import { buildApprovalResolutionRef } from "openclaw/plugin-sdk/approval-reference-runtime";
@@ -17,8 +21,23 @@ import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
17
21
  import { eastAsianWidthType } from "get-east-asian-width";
18
22
  import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
19
23
  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";
24
+ import { normalizeHyphenSlug, normalizeStringEntries as normalizeStringEntries$1, normalizeStringEntriesLower } from "openclaw/plugin-sdk/string-normalization-runtime";
21
25
  import { resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
26
+ //#region extensions/slack/src/action-threading.ts
27
+ const SLACK_PRIVATE_ACTION_DELIVERY_RESULT = Symbol("slack.action.delivery-result");
28
+ function resolveSlackAutoThreadId(params) {
29
+ const context = params.toolContext;
30
+ if (!context?.currentChannelId && !context?.currentMessagingTarget) return;
31
+ if (!slackContextTargetsMatch(params.to, context)) return;
32
+ if (!context.currentThreadTs) {
33
+ if (context.sameChannelThreadRequired) throw new Error("Slack thread context is required for same-channel replies from a threaded Slack turn. Set topLevel=true or threadId=null to post at the channel root.");
34
+ return;
35
+ }
36
+ if (context.replyToMode !== "all" && !isSingleUseReplyToMode(context.replyToMode ?? "off")) return;
37
+ if (isSingleUseReplyToMode(context.replyToMode ?? "off") && context.hasRepliedRef?.value) return;
38
+ return context.currentThreadTs;
39
+ }
40
+ //#endregion
22
41
  //#region extensions/slack/src/exec-approvals.ts
23
42
  function normalizeSlackUserLikeId(value) {
24
43
  const upper = value.toUpperCase();
@@ -60,13 +79,7 @@ const slackExecApprovalProfile = createChannelExecApprovalProfile({
60
79
  resolveConfig: (params) => resolveSlackAccount(params).config.execApprovals,
61
80
  resolveApprovers: getSlackExecApprovalApprovers,
62
81
  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
- })
82
+ isTargetRecipient: isSlackExecApprovalTargetRecipient
70
83
  });
71
84
  const isSlackExecApprovalClientEnabled = slackExecApprovalProfile.isClientEnabled;
72
85
  const isSlackExecApprovalAuthorizedSender = slackExecApprovalProfile.isAuthorizedSender;
@@ -96,18 +109,40 @@ const slackApproval = createChannelApprovalAuth({
96
109
  const getSlackApprovalApprovers = slackApproval.resolveApprovers;
97
110
  const isSlackApprovalAuthorizedSender = slackApproval.isAuthorizedSender;
98
111
  //#endregion
112
+ //#region extensions/slack/src/installation-identity-state.ts
113
+ const slackInstallationStates = resolveGlobalMap(Symbol.for("openclaw.slack.installation-identities"), "close-and-restart");
114
+ function registerSlackInstallationState(accountId, kind) {
115
+ const normalizedAccountId = normalizeAccountId(accountId);
116
+ const owner = Symbol(`slack-installation:${normalizedAccountId}`);
117
+ slackInstallationStates.set(normalizedAccountId, {
118
+ kind,
119
+ owner
120
+ });
121
+ return {
122
+ update: (nextKind) => {
123
+ if (slackInstallationStates.get(normalizedAccountId)?.owner === owner) slackInstallationStates.set(normalizedAccountId, {
124
+ kind: nextKind,
125
+ owner
126
+ });
127
+ },
128
+ release: () => {
129
+ if (slackInstallationStates.get(normalizedAccountId)?.owner === owner) slackInstallationStates.delete(normalizedAccountId);
130
+ }
131
+ };
132
+ }
133
+ function getSlackInstallationKind(accountId) {
134
+ return slackInstallationStates.get(normalizeAccountId(accountId))?.kind;
135
+ }
136
+ function isSlackWorkspaceInstallation(accountId) {
137
+ return getSlackInstallationKind(accountId) === "workspace";
138
+ }
139
+ //#endregion
99
140
  //#region extensions/slack/src/approval-native-gates.ts
100
141
  const DEFAULT_APPROVAL_FORWARDING_MODE = "session";
101
142
  const SLACK_DM_CHANNEL_ID_RE = /^D[A-Z0-9]{8,}$/i;
102
143
  const SLACK_USER_ID_RE = /^[UW][A-Z0-9]{8,}$/i;
103
- function resolveSlackApprovalKind(request) {
104
- const isExec = "command" in request.request;
105
- if (isExec === ("title" in request.request && "description" in request.request)) throw new Error("Slack approval request payload does not identify exactly one owner");
106
- return isExec ? "exec" : "plugin";
107
- }
108
144
  function isSlackApprovalTransportEnabled(params) {
109
- const account = resolveSlackAccount(params);
110
- return account.config.enterpriseOrgInstall !== true && isSlackPluginAccountConfigured(account);
145
+ return isSlackPluginAccountConfigured(resolveSlackAccount(params));
111
146
  }
112
147
  function resolveSlackNativeApprovalConfig(params) {
113
148
  return resolveSlackAccount(params).config.execApprovals;
@@ -140,7 +175,10 @@ function resolveTurnSourceSlackOriginTarget(request) {
140
175
  }) });
141
176
  if (!parsed) return null;
142
177
  return {
143
- to: `${parsed.kind}:${parsed.id}`,
178
+ to: formatSlackTarget({
179
+ ...parsed,
180
+ explicitKind: true
181
+ }),
144
182
  threadId: stringifyRouteThreadId(request.request.turnSourceThreadId)
145
183
  };
146
184
  }
@@ -160,7 +198,11 @@ function resolveSlackFallbackOriginTarget(request) {
160
198
  const parsed = parseSlackTarget(sessionTarget.id, { defaultKind: "channel" });
161
199
  if (!parsed) return null;
162
200
  return {
163
- to: `${parsed.kind}:${canonicalizeSlackApiTargetId(parsed.kind, parsed.id)}`,
201
+ to: formatSlackTarget({
202
+ ...parsed,
203
+ id: canonicalizeSlackApiTargetId(parsed.kind, parsed.id),
204
+ explicitKind: true
205
+ }),
164
206
  threadId: sessionTarget.threadId
165
207
  };
166
208
  }
@@ -177,6 +219,7 @@ function isSlackDmChannelToUserRoutePair(a, b) {
177
219
  const left = parseComparableSlackTarget(a);
178
220
  const right = parseComparableSlackTarget(b);
179
221
  if (!left || !right) return false;
222
+ if (left.teamId?.toLowerCase() !== right.teamId?.toLowerCase()) return false;
180
223
  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
224
  }
182
225
  function slackTargetsMatch(a, b) {
@@ -201,7 +244,10 @@ function normalizeSlackForwardTarget(target) {
201
244
  const parsed = parseSlackTarget(to, { defaultKind: SLACK_USER_ID_RE.test(to) ? "user" : "channel" });
202
245
  if (!parsed) return null;
203
246
  return {
204
- to: `${parsed.kind}:${parsed.id}`,
247
+ to: formatSlackTarget({
248
+ ...parsed,
249
+ explicitKind: true
250
+ }),
205
251
  accountId: normalizeOptionalString(target.accountId),
206
252
  threadId: stringifyRouteThreadId(target.threadId)
207
253
  };
@@ -235,21 +281,18 @@ function isSlackPluginNativeApprovalClientEnabled(params) {
235
281
  return isSlackPluginNativeApprovalClientConfigEnabled(params) || isSlackPluginForwardingRoutePotentiallyEnabled(params);
236
282
  }
237
283
  function shouldHandleSlackPluginViaNativeClientConfig(params) {
238
- if (!doesApprovalRequestMatchChannelAccount({
239
- cfg: params.cfg,
240
- request: params.request,
284
+ if (!doesApprovalRequestSelectChannelAccount({
285
+ ...params,
241
286
  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
287
+ defaultAccountId: resolveDefaultSlackAccountId(params.cfg),
288
+ eligibleAccountIds: listSlackNativeApprovalEligibleAccountIds({
289
+ ...params,
290
+ approvalKind: "plugin"
291
+ })
248
292
  })) return false;
249
- return matchesSlackNativeApprovalFilters({
250
- request: params.request,
251
- agentFilter: config?.agentFilter,
252
- sessionFilter: config?.sessionFilter
293
+ return isSlackNativeApprovalAccountEligible({
294
+ ...params,
295
+ approvalKind: "plugin"
253
296
  });
254
297
  }
255
298
  function matchesSlackNativeApprovalFilters(params) {
@@ -259,6 +302,25 @@ function matchesSlackNativeApprovalFilters(params) {
259
302
  sessionFilter: params.sessionFilter
260
303
  });
261
304
  }
305
+ function isSlackNativeApprovalAccountEligible(params) {
306
+ const config = resolveSlackNativeApprovalConfig(params);
307
+ const approverCount = params.approvalKind === "exec" ? getSlackExecApprovalApprovers(params).length : getSlackApprovalApprovers(params).length;
308
+ return isSlackApprovalTransportEnabled(params) && isChannelExecApprovalClientEnabledFromConfig({
309
+ enabled: config?.enabled,
310
+ approverCount
311
+ }) && matchesSlackNativeApprovalFilters({
312
+ request: params.request,
313
+ agentFilter: config?.agentFilter,
314
+ sessionFilter: config?.sessionFilter
315
+ });
316
+ }
317
+ function listSlackNativeApprovalEligibleAccountIds(params) {
318
+ const accountId = params.accountId ?? resolveDefaultSlackAccountId(params.cfg);
319
+ return isSlackNativeApprovalAccountEligible({
320
+ ...params,
321
+ accountId
322
+ }) ? [accountId] : [];
323
+ }
262
324
  function isAnyForwardedSlackExplicitTargetEligible(params) {
263
325
  return (resolvePluginApprovalForwardingConfig(params.cfg)?.targets ?? []).some((target) => isForwardedSlackExplicitTargetEligible({
264
326
  ...params,
@@ -292,24 +354,32 @@ function isSlackAnyNativeApprovalClientEnabled(params) {
292
354
  });
293
355
  }
294
356
  function shouldHandleSlackNativeApprovalRequest(params) {
295
- if ((params.approvalKind ?? resolveSlackApprovalKind(params.request)) === "plugin") return shouldHandleSlackPluginViaNativeClientConfig(params) || shouldHandleSlackPluginViaForwarding(params);
296
- if (!doesApprovalRequestMatchChannelAccount({
297
- cfg: params.cfg,
298
- request: params.request,
357
+ if (getSlackInstallationKind(resolveSlackAccount(params).accountId) === "enterprise" && !resolveEnterpriseApprovalTeamId(params.request)) return false;
358
+ if (resolveApprovalKind(params.request, params.approvalKind) === "plugin") return shouldHandleSlackPluginViaNativeClientConfig(params) || shouldHandleSlackPluginViaForwarding(params);
359
+ const turnSourceChannel = normalizeMessageChannel(params.request.request.turnSourceChannel);
360
+ if (turnSourceChannel && turnSourceChannel !== "slack") return false;
361
+ if (!doesApprovalRequestSelectChannelAccount({
362
+ ...params,
299
363
  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
364
+ defaultAccountId: resolveDefaultSlackAccountId(params.cfg),
365
+ eligibleAccountIds: listSlackNativeApprovalEligibleAccountIds({
366
+ ...params,
367
+ approvalKind: "exec"
368
+ })
306
369
  })) return false;
307
- return matchesSlackNativeApprovalFilters({
308
- request: params.request,
309
- agentFilter: config?.agentFilter,
310
- sessionFilter: config?.sessionFilter
370
+ return isSlackNativeApprovalAccountEligible({
371
+ ...params,
372
+ approvalKind: "exec"
311
373
  });
312
374
  }
375
+ function resolveEnterpriseApprovalTeamId(request) {
376
+ try {
377
+ const target = resolveTurnSourceSlackOriginTarget(request);
378
+ return target ? parseSlackTarget(target.to)?.teamId : void 0;
379
+ } catch {
380
+ return;
381
+ }
382
+ }
313
383
  //#endregion
314
384
  //#region extensions/slack/src/blocks-input.ts
315
385
  const SLACK_MAX_BLOCKS = 50;
@@ -368,6 +438,7 @@ const SLACK_PRESENTATION_CAPABILITIES = {
368
438
  //#endregion
369
439
  //#region extensions/slack/src/approval-actions.ts
370
440
  const SLACK_APPROVAL_VALUE_PREFIX = "openclaw:approval:v1:";
441
+ const SLACK_APPROVAL_HEADER_BLOCK_ID = "openclaw_approval_header";
371
442
  function isApprovalDecision(value) {
372
443
  return value === "allow-once" || value === "allow-always" || value === "deny";
373
444
  }
@@ -500,42 +571,36 @@ function renderSlackMessagePresentationFallbackText(params) {
500
571
  //#region extensions/slack/src/data-table.ts
501
572
  const SLACK_DATA_TABLE_COLUMNS_MAX = 20;
502
573
  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
574
  function countCharacters(value) {
510
575
  return Array.from(value).length;
511
576
  }
512
577
  function readRichTextLeaf(record) {
513
578
  if (record.type === "text" && typeof record.text === "string") return record.text;
514
- const text = readNonEmptyString$1(record.text);
579
+ const text = readNonBlankString(record.text);
515
580
  if (text) return text;
516
581
  switch (record.type) {
517
- case "link": return readNonEmptyString$1(record.url) ?? "";
582
+ case "link": return readNonBlankString(record.url) ?? "";
518
583
  case "user": {
519
- const userId = readNonEmptyString$1(record.user_id);
584
+ const userId = readNonBlankString(record.user_id);
520
585
  return userId ? `<@${userId}>` : "";
521
586
  }
522
587
  case "channel": {
523
- const channelId = readNonEmptyString$1(record.channel_id);
588
+ const channelId = readNonBlankString(record.channel_id);
524
589
  return channelId ? `<#${channelId}>` : "";
525
590
  }
526
591
  case "usergroup": {
527
- const usergroupId = readNonEmptyString$1(record.usergroup_id);
592
+ const usergroupId = readNonBlankString(record.usergroup_id);
528
593
  return usergroupId ? `<!subteam^${usergroupId}>` : "";
529
594
  }
530
595
  case "broadcast": {
531
- const range = readNonEmptyString$1(record.range);
596
+ const range = readNonBlankString(record.range);
532
597
  return range ? `<!${range}>` : "";
533
598
  }
534
599
  case "emoji": {
535
- const name = readNonEmptyString$1(record.name);
600
+ const name = readNonBlankString(record.name);
536
601
  return name ? `:${name}:` : "";
537
602
  }
538
- case "date": return readNonEmptyString$1(record.fallback) ?? "";
603
+ case "date": return readNonBlankString(record.fallback) ?? "";
539
604
  default: return "";
540
605
  }
541
606
  }
@@ -543,7 +608,7 @@ function readRichTextElements(value, separator = "") {
543
608
  if (!Array.isArray(value)) return "";
544
609
  const parts = [];
545
610
  for (const rawElement of value) {
546
- const element = asRecord$3(rawElement);
611
+ const element = asOptionalRecord(rawElement);
547
612
  if (!element) continue;
548
613
  if (Array.isArray(element.elements)) {
549
614
  const rendered = readRichTextElements(element.elements, element.type === "rich_text_list" ? "\n" : "");
@@ -556,7 +621,7 @@ function readRichTextElements(value, separator = "") {
556
621
  return parts.join(separator);
557
622
  }
558
623
  function readSlackBasicTableCell(value) {
559
- const cell = asRecord$3(value);
624
+ const cell = asOptionalRecord(value);
560
625
  if (!cell) return "";
561
626
  if (cell.type === "raw_text") return typeof cell.text === "string" ? cell.text : "";
562
627
  if (cell.type === "raw_number") {
@@ -567,7 +632,7 @@ function readSlackBasicTableCell(value) {
567
632
  return cell.type === "rich_text" ? readRichTextElements(cell.elements, "\n") : "";
568
633
  }
569
634
  function parseSlackBasicTableRows(value) {
570
- const block = asRecord$3(value);
635
+ const block = asOptionalRecord(value);
571
636
  if (block?.type !== "table" || !Array.isArray(block.rows)) return;
572
637
  if (block.rows.length < 1 || block.rows.length > SLACK_DATA_TABLE_ROWS_MAX) return;
573
638
  let characterCount = 0;
@@ -582,15 +647,15 @@ function parseSlackBasicTableRows(value) {
582
647
  return rows.some((row) => row.some((cell) => cell.length > 0)) ? rows : void 0;
583
648
  }
584
649
  function readSlackDataTableCell(value, allowRichText) {
585
- const cell = asRecord$3(value);
650
+ const cell = asOptionalRecord(value);
586
651
  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));
652
+ if (cell.type === "raw_text") return readNonBlankString(cell.text);
653
+ if (cell.type === "raw_number") return typeof cell.value === "number" && Number.isFinite(cell.value) ? readNonBlankString(cell.text) : void 0;
654
+ if (allowRichText && cell.type === "rich_text") return readNonBlankString(readRichTextElements(cell.elements));
590
655
  }
591
656
  function parseSlackDataTable(value, options = {}) {
592
- const block = asRecord$3(value);
593
- const caption = readNonEmptyString$1(block?.caption);
657
+ const block = asOptionalRecord(value);
658
+ const caption = readNonBlankString(block?.caption);
594
659
  if (block?.type !== "data_table" || !caption || !Array.isArray(block.rows)) return;
595
660
  if (block.rows.length < 2) return;
596
661
  const rawHeader = block.rows[0];
@@ -614,7 +679,7 @@ function parseSlackDataTable(value, options = {}) {
614
679
  }
615
680
  /** Detect current native table blocks without depending on unreleased Slack SDK types. */
616
681
  function hasSlackDataTableBlock(blocks) {
617
- return blocks?.some((block) => asRecord$3(block)?.type === "data_table") ?? false;
682
+ return blocks?.some((block) => asOptionalRecord(block)?.type === "data_table") ?? false;
618
683
  }
619
684
  function countSlackDataTableCellCharacters(value) {
620
685
  return parseSlackDataTable(value, { enforceNativeLimits: true })?.cellCharacterCount;
@@ -678,7 +743,7 @@ function buildSlackDataTableBlock(block, options = {}) {
678
743
  }
679
744
  /** Extract a deterministic accessible summary from a native Slack table block. */
680
745
  function renderSlackDataTableFallbackText(value) {
681
- const block = asRecord$3(value);
746
+ const block = asOptionalRecord(value);
682
747
  if (block?.type !== "data_table") return;
683
748
  const parsed = parseSlackDataTable(block);
684
749
  if (parsed) return renderMessagePresentationTableFallbackText({
@@ -687,7 +752,7 @@ function renderSlackDataTableFallbackText(value) {
687
752
  headers: parsed.headers,
688
753
  rows: parsed.rows
689
754
  });
690
- return readNonEmptyString$1(block.caption)?.trim();
755
+ return readNonBlankString(block.caption)?.trim();
691
756
  }
692
757
  function escapeCompactFallbackCell(value) {
693
758
  return value.replaceAll("\\", "\\\\").replaceAll(" ", "\\t").replaceAll("\r", "\\r").replaceAll("\n", "\\n");
@@ -708,10 +773,10 @@ function renderSlackTableMrkdwnFallbackText(value) {
708
773
  }
709
774
  /** Render each native table cell once for bounded, formatting-disabled delivery. */
710
775
  function renderSlackDataTableCompactPlainTextFallback(value) {
711
- const block = asRecord$3(value);
776
+ const block = asOptionalRecord(value);
712
777
  if (block?.type !== "data_table") return;
713
778
  const parsed = parseSlackDataTable(block);
714
- if (!parsed) return readNonEmptyString$1(block.caption)?.trim();
779
+ if (!parsed) return readNonBlankString(block.caption)?.trim();
715
780
  return [
716
781
  `${escapeCompactFallbackCell(parsed.caption)} (table)`,
717
782
  parsed.headers.map(escapeCompactFallbackCell).join(" "),
@@ -720,7 +785,7 @@ function renderSlackDataTableCompactPlainTextFallback(value) {
720
785
  }
721
786
  /** Render a native table as mrkdwn without activating raw cell control tokens. */
722
787
  function renderSlackDataTableMrkdwnFallbackText(value) {
723
- const block = asRecord$3(value);
788
+ const block = asOptionalRecord(value);
724
789
  if (block?.type !== "data_table") return;
725
790
  const parsed = parseSlackDataTable(block);
726
791
  if (parsed) return renderSlackMessagePresentationTableFallbackText({
@@ -729,7 +794,7 @@ function renderSlackDataTableMrkdwnFallbackText(value) {
729
794
  headers: parsed.headers,
730
795
  rows: parsed.rows
731
796
  });
732
- const caption = readNonEmptyString$1(block.caption)?.trim();
797
+ const caption = readNonBlankString(block.caption)?.trim();
733
798
  return caption ? escapeSlackMrkdwn(caption) : void 0;
734
799
  }
735
800
  //#endregion
@@ -739,12 +804,9 @@ const SLACK_CHART_LABEL_MAX = 20;
739
804
  const SLACK_CHART_AXIS_LABEL_MAX = 50;
740
805
  const SLACK_CHART_SERIES_MAX = 12;
741
806
  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
807
  /** Detect native chart blocks without depending on unreleased Slack SDK types. */
746
808
  function hasSlackDataVisualizationBlock(blocks) {
747
- return blocks?.some((block) => asRecord$2(block)?.type === "data_visualization") ?? false;
809
+ return blocks?.some((block) => asOptionalRecord(block)?.type === "data_visualization") ?? false;
748
810
  }
749
811
  function isStringWithin(value, maxLength) {
750
812
  return typeof value === "string" && value.trim().length > 0 && Array.from(value).length <= maxLength;
@@ -791,7 +853,7 @@ function buildSlackDataVisualizationBlock(block) {
791
853
  };
792
854
  }
793
855
  function readSlackChartDatum(value) {
794
- const record = asRecord$2(value);
856
+ const record = asOptionalRecord(value);
795
857
  const label = record?.label;
796
858
  const datumValue = record?.value;
797
859
  return typeof label === "string" && typeof datumValue === "number" ? {
@@ -800,9 +862,9 @@ function readSlackChartDatum(value) {
800
862
  } : void 0;
801
863
  }
802
864
  function parseSlackDataVisualizationBlock(value) {
803
- const block = asRecord$2(value);
865
+ const block = asOptionalRecord(value);
804
866
  const title = block?.title;
805
- const chart = asRecord$2(block?.chart);
867
+ const chart = asOptionalRecord(block?.chart);
806
868
  if (block?.type !== "data_visualization" || typeof title !== "string" || !chart) return;
807
869
  if (chart.type === "pie") {
808
870
  if (!Array.isArray(chart.segments)) return;
@@ -817,12 +879,12 @@ function parseSlackDataVisualizationBlock(value) {
817
879
  return normalizedBlock?.type === "chart" ? normalizedBlock : void 0;
818
880
  }
819
881
  if (chart.type !== "bar" && chart.type !== "area" && chart.type !== "line") return;
820
- const axisConfig = asRecord$2(chart.axis_config);
882
+ const axisConfig = asOptionalRecord(chart.axis_config);
821
883
  const categories = axisConfig?.categories;
822
884
  if (!Array.isArray(categories) || !categories.every((category) => typeof category === "string")) return;
823
885
  if (!Array.isArray(chart.series)) return;
824
886
  const series = chart.series.map((rawSeries) => {
825
- const seriesRecord = asRecord$2(rawSeries);
887
+ const seriesRecord = asOptionalRecord(rawSeries);
826
888
  if (typeof seriesRecord?.name !== "string" || !Array.isArray(seriesRecord.data)) return;
827
889
  const data = seriesRecord.data.map(readSlackChartDatum);
828
890
  if (data.some((datum) => !datum) || data.length !== categories.length) return;
@@ -847,7 +909,7 @@ function parseSlackDataVisualizationBlock(value) {
847
909
  }
848
910
  /** Extract a deterministic accessible summary from a native Slack chart block. */
849
911
  function renderSlackDataVisualizationFallbackText(value) {
850
- const block = asRecord$2(value);
912
+ const block = asOptionalRecord(value);
851
913
  if (block?.type !== "data_visualization") return;
852
914
  const parsed = parseSlackDataVisualizationBlock(block);
853
915
  if (parsed) return renderMessagePresentationChartFallbackText(parsed);
@@ -855,7 +917,7 @@ function renderSlackDataVisualizationFallbackText(value) {
855
917
  }
856
918
  /** Render a native chart as mrkdwn without activating raw data control tokens. */
857
919
  function renderSlackDataVisualizationMrkdwnFallbackText(value) {
858
- const block = asRecord$2(value);
920
+ const block = asOptionalRecord(value);
859
921
  if (block?.type !== "data_visualization") return;
860
922
  const parsed = parseSlackDataVisualizationBlock(block);
861
923
  if (parsed) return renderSlackMessagePresentationChartFallbackText(parsed);
@@ -898,15 +960,24 @@ async function resolveSlackQuestionAction(params) {
898
960
  //#region extensions/slack/src/reply-action-ids.ts
899
961
  const SLACK_REPLY_BUTTON_ACTION_ID = "openclaw:reply_button";
900
962
  const SLACK_REPLY_LINK_ACTION_ID = "openclaw:reply_link";
963
+ const SLACK_SESSION_LINK_ACTION_ID = "openclaw:session_link";
901
964
  const SLACK_REPLY_SELECT_ACTION_ID = "openclaw:reply_select";
902
965
  const SLACK_CALLBACK_BUTTON_ACTION_ID = "openclaw:callback_button";
903
966
  const SLACK_CALLBACK_SELECT_ACTION_ID = "openclaw:callback_select";
904
967
  const SLACK_APPROVAL_BUTTON_ACTION_ID = "openclaw:approval_button";
905
968
  const SLACK_APPROVAL_SELECT_ACTION_ID = "openclaw:approval_select";
906
969
  const SLACK_QUESTION_BUTTON_ACTION_ID = "openclaw:question_button";
970
+ const SLACK_QUESTION_FINALIZATION_BLOCKS = Symbol("slackQuestionFinalizationBlocks");
907
971
  function isSlackQuestionActionId(actionId) {
908
972
  return actionId === "openclaw:question_button" || actionId.startsWith(`openclaw:question_button:`);
909
973
  }
974
+ /** Read only question control identities from the blocks actually sent to Slack. */
975
+ function resolveSlackQuestionActionIds(blocks) {
976
+ return (blocks ?? []).flatMap((block) => {
977
+ if (block.type !== "actions") return [];
978
+ return (block.elements ?? []).flatMap(({ action_id }) => action_id && isSlackQuestionActionId(action_id) ? [action_id] : []);
979
+ });
980
+ }
910
981
  function isSlackApprovalActionId(actionId) {
911
982
  return actionId === "openclaw:approval_button" || actionId === "openclaw:approval_select" || actionId.startsWith(`openclaw:approval_button:`) || actionId.startsWith(`openclaw:approval_select:`);
912
983
  }
@@ -1351,6 +1422,7 @@ function buildSlackPresentationSelectBlock(block, selectIndex) {
1351
1422
  //#endregion
1352
1423
  //#region extensions/slack/src/limits.ts
1353
1424
  const SLACK_TEXT_LIMIT = 8e3;
1425
+ const SLACK_MESSAGE_TEXT_RECOMMENDED_LIMIT = 4e3;
1354
1426
  const SLACK_EDIT_TEXT_MAX_BYTES = 4e3;
1355
1427
  const SLACK_MESSAGE_TEXT_HARD_LIMIT = 4e4;
1356
1428
  //#endregion
@@ -1394,54 +1466,46 @@ const SLACK_SELECT_ELEMENT_TYPES = /* @__PURE__ */ new Set([
1394
1466
  "channels_select",
1395
1467
  "multi_channels_select"
1396
1468
  ]);
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
1469
  function readTextObject(value, options = {}) {
1406
- const record = asRecord$1(value);
1470
+ const record = asOptionalRecord(value);
1407
1471
  if (!record) return;
1408
- const text = readNonEmptyString(record?.text);
1472
+ const text = normalizeOptionalString(record?.text);
1409
1473
  if (!text) return;
1410
1474
  return record.type === "plain_text" && options.nativeDataFormat !== "plain" ? escapeSlackMrkdwn(text) : text;
1411
1475
  }
1412
1476
  function readTextValue(value, options = {}) {
1413
- return readNonEmptyString(value) ?? readTextObject(value, options);
1477
+ return normalizeOptionalString(value) ?? readTextObject(value, options);
1414
1478
  }
1415
1479
  function renderSlackRichTextLeaf(element) {
1416
1480
  switch (element.type) {
1417
1481
  case "text": return typeof element.text === "string" ? escapeSlackMrkdwn(element.text) : "";
1418
- case "link": return escapeSlackMrkdwn(readNonEmptyString(element.text) ?? readNonEmptyString(element.url) ?? "");
1482
+ case "link": return escapeSlackMrkdwn(normalizeOptionalString(element.text) ?? normalizeOptionalString(element.url) ?? "");
1419
1483
  case "user": {
1420
- const userId = readNonEmptyString(element.user_id);
1484
+ const userId = normalizeOptionalString(element.user_id);
1421
1485
  return userId ? escapeSlackMrkdwn(`<@${userId}>`) : "";
1422
1486
  }
1423
1487
  case "channel": {
1424
- const channelId = readNonEmptyString(element.channel_id);
1488
+ const channelId = normalizeOptionalString(element.channel_id);
1425
1489
  return channelId ? escapeSlackMrkdwn(`<#${channelId}>`) : "";
1426
1490
  }
1427
1491
  case "usergroup": {
1428
- const usergroupId = readNonEmptyString(element.usergroup_id);
1492
+ const usergroupId = normalizeOptionalString(element.usergroup_id);
1429
1493
  return usergroupId ? escapeSlackMrkdwn(`<!subteam^${usergroupId}>`) : "";
1430
1494
  }
1431
1495
  case "broadcast": {
1432
- const range = readNonEmptyString(element.range);
1496
+ const range = normalizeOptionalString(element.range);
1433
1497
  return range ? escapeSlackMrkdwn(`<!${range}>`) : "";
1434
1498
  }
1435
1499
  case "emoji": {
1436
- const name = readNonEmptyString(element.name);
1500
+ const name = normalizeOptionalString(element.name);
1437
1501
  return name ? `:${name}:` : "";
1438
1502
  }
1439
- case "date": return escapeSlackMrkdwn(readNonEmptyString(element.fallback) ?? "");
1503
+ case "date": return escapeSlackMrkdwn(normalizeOptionalString(element.fallback) ?? "");
1440
1504
  default: return "";
1441
1505
  }
1442
1506
  }
1443
1507
  function renderSlackRichTextElement(value) {
1444
- const element = asRecord$1(value);
1508
+ const element = asOptionalRecord(value);
1445
1509
  if (!element) return "";
1446
1510
  switch (element.type) {
1447
1511
  case "rich_text_section":
@@ -1456,25 +1520,25 @@ function renderSlackRichTextElements(value, separator) {
1456
1520
  return value.map(renderSlackRichTextElement).filter(Boolean).join(separator);
1457
1521
  }
1458
1522
  function readImageText(block) {
1459
- const altText = readNonEmptyString(block.alt_text);
1523
+ const altText = normalizeOptionalString(block.alt_text);
1460
1524
  return (altText ? escapeSlackMrkdwn(altText) : void 0) ?? readTextObject(block.title);
1461
1525
  }
1462
1526
  function readVideoText(block, options = {}) {
1463
- const altText = readNonEmptyString(block.alt_text);
1527
+ const altText = normalizeOptionalString(block.alt_text);
1464
1528
  return readTextObject(block.title, options) ?? (altText ? escapeSlackMrkdwn(altText) : void 0);
1465
1529
  }
1466
1530
  function readContextText(block, options = {}) {
1467
1531
  if (!Array.isArray(block.elements)) return;
1468
1532
  const parts = block.elements.map((element) => {
1469
- const record = asRecord$1(element);
1470
- const altText = readNonEmptyString(record?.alt_text);
1533
+ const record = asOptionalRecord(element);
1534
+ const altText = normalizeOptionalString(record?.alt_text);
1471
1535
  return readTextObject(record, options) ?? (altText ? escapeSlackMrkdwn(altText) : void 0);
1472
1536
  }).filter((part) => Boolean(part));
1473
1537
  return parts.length > 0 ? parts.join(" ") : void 0;
1474
1538
  }
1475
1539
  function readControlElementText(value, options = {}) {
1476
- const element = asRecord$1(value);
1477
- const type = readNonEmptyString(element?.type);
1540
+ const element = asOptionalRecord(value);
1541
+ const type = normalizeOptionalString(element?.type);
1478
1542
  if (type === "button" || type === "workflow_button") return readTextValue(element?.text, options);
1479
1543
  if (type && SLACK_SELECT_ELEMENT_TYPES.has(type)) return readTextObject(element?.placeholder, options);
1480
1544
  }
@@ -1501,10 +1565,10 @@ function readActionsText(block, options = {}) {
1501
1565
  }
1502
1566
  /** Read only user-visible text from one Slack block. */
1503
1567
  function renderSlackBlockFallbackText(raw, options = {}) {
1504
- const block = asRecord$1(raw);
1568
+ const block = asOptionalRecord(raw);
1505
1569
  if (!block) return;
1506
1570
  switch (block.type) {
1507
- case "rich_text": return readNonEmptyString(renderSlackRichTextElements(block.elements, "\n"));
1571
+ case "rich_text": return normalizeOptionalString(renderSlackRichTextElements(block.elements, "\n"));
1508
1572
  case "header": return readTextObject(block.text, options);
1509
1573
  case "section": return readSectionText(block, options);
1510
1574
  case "image": return readImageText(block) ?? "Shared an image";
@@ -1773,19 +1837,6 @@ function protectSlackAssistantTranscriptRoleHeaders(text) {
1773
1837
  if (!slackProjectionHasRoleHeader(tokenProjection) && !slackProjectionHasRoleHeader(fallbackProjection)) return text;
1774
1838
  return `${SLACK_ASSISTANT_TRANSCRIPT_PREFIX}${text}`;
1775
1839
  }
1776
- function hardSliceSlackToken(token, limit) {
1777
- const chunks = [];
1778
- let chunk = "";
1779
- for (const character of token) {
1780
- if (chunk && chunk.length + character.length > limit) {
1781
- chunks.push(chunk);
1782
- chunk = "";
1783
- }
1784
- chunk += character;
1785
- }
1786
- if (chunk) chunks.push(chunk);
1787
- return chunks;
1788
- }
1789
1840
  function buildSlackRenderOptions() {
1790
1841
  return {
1791
1842
  annotationMarkers: { assistant_transcript_role: {
@@ -1859,8 +1910,8 @@ function chunkSlackMrkdwnText(text, limit) {
1859
1910
  flush();
1860
1911
  const marker = wrapper();
1861
1912
  if (activeMarker && isAllowedSlackAngleToken(token)) {
1862
- if (marker) chunks.push(...hardSliceSlackToken(token, contentLimit).map((fragment) => `${marker}${fragment}${marker}`));
1863
- else chunks.push(...hardSliceSlackToken(escapeSlackMrkdwnSegment(token), limit));
1913
+ if (marker) chunks.push(...chunkTextForOutbound(token, Math.max(1, Math.floor(contentLimit)), { preserveWhitespace: true }).map((fragment) => `${marker}${fragment}${marker}`));
1914
+ else chunks.push(...chunkTextForOutbound(escapeSlackMrkdwnSegment(token), Math.max(1, Math.floor(limit)), { preserveWhitespace: true }));
1864
1915
  continue;
1865
1916
  }
1866
1917
  chunks.push(...token.length <= limit ? [token] : chunkTextForOutbound(token, limit));
@@ -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,55 @@ 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
  }
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
+ });
2474
+ }
2392
2475
  //#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");
2476
+ //#region extensions/slack/src/monitor/workspace-routing.ts
2477
+ function resolveSlackEnterpriseMainDmSessionKey(params) {
2478
+ const accountId = encodeURIComponent(params.accountId).toLowerCase();
2479
+ const teamId = encodeURIComponent(params.eventScope.teamId).toLowerCase();
2480
+ return `${params.baseSessionKey}:account:${accountId}:team:${teamId}`;
2481
+ }
2482
+ function qualifySlackRoutePeerId(params) {
2483
+ if (!params.eventScope) return params.id;
2484
+ return `team:${encodeURIComponent(params.eventScope.teamId)}:${params.kind}:${encodeURIComponent(params.id)}`;
2485
+ }
2486
+ function qualifySlackConversationId(conversationId, eventScope) {
2487
+ return eventScope ? `team:${encodeURIComponent(eventScope.teamId)}:${conversationId}` : conversationId;
2396
2488
  }
2397
2489
  //#endregion
2398
2490
  //#region extensions/slack/src/group-policy.ts
2491
+ function buildSlackChannelIdCandidates(channelId, teamId, options) {
2492
+ const trimmedId = channelId?.trim();
2493
+ if (!trimmedId) return [];
2494
+ const lowercaseId = trimmedId.toLowerCase();
2495
+ const uppercaseId = trimmedId.toUpperCase();
2496
+ const exactTeamId = teamId || void 0;
2497
+ const lowercaseTeamId = exactTeamId?.toLowerCase();
2498
+ const uppercaseTeamId = exactTeamId?.toUpperCase();
2499
+ const scopedCandidates = buildChannelKeyCandidates(exactTeamId ? `team:${exactTeamId}:channel:${trimmedId}` : void 0, lowercaseTeamId ? `team:${lowercaseTeamId}:channel:${lowercaseId}` : void 0, uppercaseTeamId ? `team:${uppercaseTeamId}:channel:${uppercaseId}` : void 0);
2500
+ if (exactTeamId && options?.allowUnscoped !== true) return scopedCandidates;
2501
+ return buildChannelKeyCandidates(...scopedCandidates, trimmedId, lowercaseId, uppercaseId, `channel:${trimmedId}`, `channel:${lowercaseId}`, `channel:${uppercaseId}`);
2502
+ }
2399
2503
  function buildSlackChannelPolicyScope(params) {
2400
2504
  const channels = params.channels ?? {};
2401
2505
  const tree = { scopes: channels };
@@ -2413,16 +2517,11 @@ function buildSlackChannelPolicyScope(params) {
2413
2517
  function resolveSlackGroupPolicyScope(params) {
2414
2518
  const accountId = normalizeAccountId(params.accountId ?? resolveDefaultSlackAccountId(params.cfg));
2415
2519
  const channels = mergeSlackAccountConfig(params.cfg, accountId).channels;
2416
- const channelId = params.groupId?.trim();
2417
2520
  const channelName = params.groupChannel?.replace(/^#/, "");
2521
+ const allowUnscoped = getSlackInstallationKind(accountId) !== "enterprise";
2418
2522
  return buildSlackChannelPolicyScope({
2419
2523
  channels,
2420
- candidates: [
2421
- channelId,
2422
- channelName ? `#${channelName}` : void 0,
2423
- channelName,
2424
- normalizeHyphenSlug(channelName)
2425
- ].filter((candidate) => Boolean(candidate))
2524
+ candidates: buildChannelKeyCandidates(...buildSlackChannelIdCandidates(params.groupId, params.groupSpace, { allowUnscoped }), channelName ? `#${channelName}` : void 0, channelName, normalizeHyphenSlug(channelName))
2426
2525
  });
2427
2526
  }
2428
2527
  function resolveSlackGroupRequireMention(params) {
@@ -2439,4 +2538,4 @@ function resolveSlackGroupToolPolicy(params) {
2439
2538
  });
2440
2539
  }
2441
2540
  //#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 };
2541
+ export { SLACK_REPLY_SELECT_ACTION_ID as $, isSlackInvalidBlocksResponse as A, isSlackApprovalAuthorizedSender as At, SLACK_EDIT_TEXT_MAX_BYTES as B, SLACK_MALFORMED_NATIVE_DATA_FALLBACK as C, shouldHandleSlackNativeApprovalRequest as Ct, createSlackNativeDataBaseTextConsumer as D, isSlackWorkspaceInstallation as Dt, buildSlackNativeDataAccessibilityText as E, getSlackInstallationKind as Et, normalizeSlackOutboundText as F, resolveSlackExecApprovalTarget as Ft, buildSlackPresentationBlocks as G, SLACK_MESSAGE_TEXT_RECOMMENDED_LIMIT as H, buildSlackBlocksFallbackText as I, shouldSuppressLocalSlackExecApprovalPrompt as It, truncateSlackText as J, canRenderSlackPresentation as K, buildSlackCompleteBlocksFallbackText as L, SLACK_PRIVATE_ACTION_DELIVERY_RESULT as Lt, stripSlackNativeDataBlocks as M, isSlackExecApprovalAuthorizedSender as Mt, chunkSlackMrkdwnText as N, isSlackExecApprovalClientEnabled as Nt, hasSlackNativeDataBlock as O, registerSlackInstallationState as Ot, markdownToSlackMrkdwnChunks as P, normalizeSlackApproverId as Pt, SLACK_REPLY_LINK_ACTION_ID as Q, renderSlackBlockFallbackText as R, resolveSlackAutoThreadId as Rt, resolveSlackReplyRenderPlan as S, resolveTurnSourceSlackOriginTarget as St, appendSlackNativeDataPlainTextFallback as T, slackTargetsMatch as Tt, SLACK_TEXT_LIMIT as U, SLACK_MESSAGE_TEXT_HARD_LIMIT as V, buildSlackInteractiveBlocks as W, SLACK_QUESTION_FINALIZATION_BLOCKS as X, truncateSlackTextByUtf8Bytes as Y, SLACK_REPLY_BUTTON_ACTION_ID as Z, hasSlackReplyStructuredContent as _, normalizeSlackForwardTarget as _t, qualifySlackConversationId as a, decodeSlackQuestionAction as at, resolveSlackReplyBlocks as b, resolveSessionSlackOriginTarget as bt, allowListMatches as c, escapeSlackMrkdwn as ct, normalizeSlackAllowOwnerEntry as d, SLACK_PRESENTATION_CAPABILITIES as dt, SLACK_SESSION_LINK_ACTION_ID as et, normalizeSlackSlug as f, SLACK_MAX_BLOCKS as ft, assertSlackDetachedTargetAllowed as g, isSlackAnyNativeApprovalClientEnabled as gt, resolveSlackUserAllowed as h, hasSlackPluginApprovers as ht, resolveSlackGroupToolPolicy as i, resolveSlackQuestionActionIds as it, isSlackNativeResponseUrlRejection as j, getSlackExecApprovalApprovers as jt, isSlackInvalidBlocksError as k, getSlackApprovalApprovers as kt, normalizeAllowList as l, SLACK_APPROVAL_HEADER_BLOCK_ID as lt, resolveSlackUserAllowListForTeam as m, validateSlackBlocksArray as mt, buildSlackChannelPolicyScope as n, isSlackCallbackActionId as nt, qualifySlackRoutePeerId as o, resolveSlackQuestionAction as ot, resolveSlackAllowListMatch as p, parseSlackBlocksInput as pt, countSlackTextUtf8Bytes as q, resolveSlackGroupRequireMention as r, isSlackQuestionActionId as rt, resolveSlackEnterpriseMainDmSessionKey as s, renderSlackMessagePresentationFallbackText as st, buildSlackChannelIdCandidates as t, isSlackApprovalActionId as tt, normalizeAllowListLower as u, decodeSlackApprovalAction as ut, parseSlackReplyBlockSegments as v, normalizeSlackOriginTarget as vt, appendSlackNativeDataFallbackText as w, shouldHandleSlackPluginViaForwardingSession as wt, resolveSlackReplyDeliveryMessages as x, resolveSlackFallbackOriginTarget as xt, resolveSlackReplyBlockResolution as y, resolveEnterpriseApprovalTeamId as yt, resolveSlackAuthoredTextPlacement as z };