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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/accounts.runtime-KvftRRek.js +2 -0
  2. package/dist/{action-runtime-DoQGk_8D.js → action-runtime-p6M2ULpv.js} +205 -29
  3. package/dist/action-runtime.runtime-DQstDkHG.js +2 -0
  4. package/dist/{actions-DMpDmKEA.js → actions-DkTcWmpT.js} +87 -138
  5. package/dist/{actions.runtime-C103_BPW.js → actions.runtime-BSxpwgUc.js} +1 -1
  6. package/dist/api.js +14 -13
  7. package/dist/{approval-handler.runtime-CIUbOt5T.js → approval-handler.runtime-UGbnz9OC.js} +3 -3
  8. package/dist/{channel-BjlsaGHn.js → channel-B0_j6C6z.js} +116 -140
  9. package/dist/channel-config-api.js +1 -1
  10. package/dist/channel-plugin-api.js +1 -1
  11. package/dist/channel-type-BpeTezY4.js +109 -0
  12. package/dist/{channel.setup-Dq6syTNU.js → channel.setup-BQFut21T.js} +2 -2
  13. package/dist/{client-DGxwSL7f.js → client-D2F6m8CP.js} +38 -2
  14. package/dist/config-api-Df9a1B8C.js +3 -0
  15. package/dist/{config-schema-BzqzFW__.js → config-schema-DYI01PV2.js} +20 -37
  16. package/dist/cursor-pages-DSmTg0su.js +13 -0
  17. package/dist/{directory-config-BZuS8141.js → directory-config-DRkFujwG.js} +2 -2
  18. package/dist/directory-contract-api.js +1 -1
  19. package/dist/{directory-live-BNsxCgqP.js → directory-live-DX7yFsM-.js} +2 -2
  20. package/dist/{doctor-contract-XL6HeNCb.js → doctor-contract-DddfkMlF.js} +25 -38
  21. package/dist/doctor-contract-api.js +1 -1
  22. package/dist/group-policy-C-b2ZnSl.js +2332 -0
  23. package/dist/{interactive-replies-CXpT-vA_.js → interactive-replies-BilWUdcI.js} +5 -1
  24. package/dist/interactive-replies-api.js +1 -1
  25. package/dist/{message-tool-api-BgWKyu2r.js → message-tool-api-CA9jZrCY.js} +1 -1
  26. package/dist/message-tool-api.js +1 -1
  27. package/dist/{monitor-Ux-wzUWI.js → monitor-BN_uuvrQ.js} +3 -3
  28. package/dist/outbound-adapter-ECl0go7d.js +228 -0
  29. package/dist/{pipeline.runtime-rpVpay59.js → pipeline.runtime-f0eJJoJL.js} +492 -178
  30. package/dist/{policy-Dj-puVPb.js → policy-f8b0edSi.js} +1 -1
  31. package/dist/preflight-audio.runtime-D5Bgags0.js +3 -0
  32. package/dist/{probe-CuwRDE5j.js → probe-CfALk0l4.js} +2 -2
  33. package/dist/{provider-C1-DFSpw.js → provider-Ds-y-imP.js} +658 -192
  34. package/dist/{relay-source-D2jv1iGg.js → relay-source-B-6LFUdz.js} +6 -17
  35. package/dist/replies-BMvf3ihj.js +420 -0
  36. package/dist/{resolve-channels-CstkChBE.js → resolve-channels-DOMgNCWb.js} +17 -6
  37. package/dist/{resolve-users-C3NP2c-Q.js → resolve-users-aL6-Ssee.js} +11 -6
  38. package/dist/{runtime-BOk7xkOl.js → runtime-CauEvswP.js} +1 -1
  39. package/dist/{runtime-api-CEmqYxR-.js → runtime-api-CVb6CQjX.js} +3 -3
  40. package/dist/runtime-api.js +11 -11
  41. package/dist/runtime-setter-api.js +1 -1
  42. package/dist/{scopes-DCWVD5-8.js → scopes-Dw8wbU0j.js} +2 -2
  43. package/dist/{secret-contract-C3ChHo5A.js → secret-contract-CMohXFWS.js} +18 -113
  44. package/dist/secret-contract-api.js +1 -1
  45. package/dist/{send-DKDXoeoV.js → send-NEfJ0GvM.js} +662 -325
  46. package/dist/{send.runtime-GPRa63l1.js → send.runtime-BkKpitPu.js} +1 -1
  47. package/dist/setup-plugin-api.js +1 -1
  48. package/dist/{setup-surface-Dv2fIJNB.js → setup-surface-BN2WeG5c.js} +3 -3
  49. package/dist/{shared-CosfcFri.js → shared-BLnB9R1T.js} +7 -5
  50. package/dist/{slash-dispatch.runtime-CcMbbT3_.js → slash-dispatch.runtime-C0Otuf_T.js} +1 -1
  51. package/dist/{streaming-compat-DjlgH-Be.js → streaming-compat-3_X-21wW.js} +1 -0
  52. package/dist/{target-parsing-3jYH8LfB.js → target-parsing-N6lHogYZ.js} +16 -3
  53. package/dist/{targets-Dll4NJgs.js → targets-KmjP2E8s.js} +1 -1
  54. package/node_modules/p-map/index.d.ts +155 -0
  55. package/node_modules/p-map/index.js +284 -0
  56. package/node_modules/p-map/license +9 -0
  57. package/node_modules/p-map/package.json +57 -0
  58. package/node_modules/p-map/readme.md +190 -0
  59. package/npm-shrinkwrap.json +16 -3
  60. package/openclaw.plugin.json +16 -0
  61. package/package.json +6 -4
  62. package/dist/accounts.runtime-DWvaCb-X.js +0 -2
  63. package/dist/action-runtime.runtime-BABj3aM-.js +0 -2
  64. package/dist/config-api-DnT9ohtE.js +0 -2
  65. package/dist/format-YCNoNkM2.js +0 -107
  66. package/dist/outbound-adapter-DVoBfBW5.js +0 -272
  67. package/dist/replies-DpF38e1y.js +0 -287
  68. package/dist/reply-blocks-BGQ6zSqM.js +0 -877
  69. package/dist/resolve-allowlist-common-CP8SH0Zf.js +0 -43
@@ -1,13 +1,13 @@
1
1
  import { n as registerSlackHttpHandler, r as normalizeSlackWebhookPath } from "./registry-bbjH7IHX.js";
2
- import { a as resolveSlackAccount, d as formatSlackBotTokenIdentityWarning, f as resolveSlackAppToken, o as resolveSlackAccountAllowFrom, p as resolveSlackBotToken, s as resolveSlackAccountDmPolicy } from "./accounts-BOJJiHSr.js";
3
- import { B as isSlackExecApprovalAuthorizedSender, D as isSlackAnyNativeApprovalClientEnabled, R as isSlackApprovalAuthorizedSender, a as SLACK_TEXT_LIMIT, b as truncateSlackText, c as formatSlackError, d as normalizeAllowListLower, f as normalizeSlackAllowOwnerEntry, l as allowListMatches, m as resolveSlackAllowListMatch, p as normalizeSlackSlug, u as normalizeAllowList } from "./reply-blocks-BGQ6zSqM.js";
4
- import { n as isSlackInteractiveRepliesEnabled, t as compileSlackInteractiveReplies } from "./interactive-replies-CXpT-vA_.js";
5
- import { c as resolveSlackWebClientOptions } from "./client-DGxwSL7f.js";
6
- import { t as getOptionalSlackRuntime } from "./runtime-BOk7xkOl.js";
7
- import { i as setSlackDefaultSendIdentity } from "./send-DKDXoeoV.js";
8
- import { t as resolveSlackChannelAllowlist } from "./resolve-channels-CstkChBE.js";
9
- import { t as resolveSlackUserAllowlist } from "./resolve-users-C3NP2c-Q.js";
10
- import { n as resolveSlackChannelConfig, r as resolveSlackChannelLabel, t as isSlackChannelAllowedByPolicy } from "./policy-Dj-puVPb.js";
2
+ import { a as resolveSlackAccount, d as formatSlackBotTokenIdentityWarning, f as resolveSlackAppToken, i as resolveDefaultSlackAccountId, o as resolveSlackAccountAllowFrom, p as resolveSlackBotToken, s as resolveSlackAccountDmPolicy } from "./accounts-BOJJiHSr.js";
3
+ import { $ as isSlackAnyNativeApprovalClientEnabled, F as SLACK_TEXT_LIMIT, K as escapeSlackMrkdwn, U as isSlackApprovalActionId, W as isSlackCallbackActionId, a as allowListMatches, c as normalizeSlackAllowOwnerEntry, dt as isSlackExecApprovalAuthorizedSender, l as normalizeSlackSlug, lt as isSlackApprovalAuthorizedSender, o as normalizeAllowList, q as decodeSlackApprovalAction, r as formatSlackError, s as normalizeAllowListLower, u as resolveSlackAllowListMatch, z as truncateSlackText } from "./group-policy-C-b2ZnSl.js";
4
+ import { n as isSlackInteractiveRepliesEnabled, t as compileSlackInteractiveReplies } from "./interactive-replies-BilWUdcI.js";
5
+ import { o as getSlackListenerUploadCompletionClient, u as resolveSlackWebClientOptions } from "./client-D2F6m8CP.js";
6
+ import { t as getOptionalSlackRuntime } from "./runtime-CauEvswP.js";
7
+ import { i as setSlackDefaultSendIdentity } from "./send-NEfJ0GvM.js";
8
+ import { t as resolveSlackChannelAllowlist } from "./resolve-channels-DOMgNCWb.js";
9
+ import { t as resolveSlackUserAllowlist } from "./resolve-users-aL6-Ssee.js";
10
+ import { n as resolveSlackChannelConfig, r as resolveSlackChannelLabel, t as isSlackChannelAllowedByPolicy } from "./policy-f8b0edSi.js";
11
11
  import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
12
12
  import { asOptionalRecord, isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, normalizeStringEntriesLower, normalizeUniqueTrimmedStringList } from "openclaw/plugin-sdk/string-coerce-runtime";
13
13
  import { normalizeAccountId, normalizeMainKey, resolveAgentRoute, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
@@ -20,9 +20,10 @@ import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plu
20
20
  import { parseExecApprovalCommandText } from "openclaw/plugin-sdk/approval-reply-runtime";
21
21
  import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
22
22
  import { addAllowlistUserEntriesFromConfigEntry, buildAllowlistResolutionSummary, formatAllowlistMatchMeta, mergeAllowlist, patchAllowlistUsersInConfigEntries, summarizeMapping } from "openclaw/plugin-sdk/allow-from";
23
- import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
23
+ import { collectErrorGraphCandidates, formatErrorMessage, isApprovalNotFoundError } from "openclaw/plugin-sdk/error-runtime";
24
24
  import { asDateTimestampMs, parseFiniteNumber, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
25
25
  import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
26
+ import { buildUntrustedChannelMetadata } from "openclaw/plugin-sdk/security-runtime";
26
27
  import { createDedupeCache, createPersistentDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
27
28
  import { buildPluginBindingResolvedText, parsePluginBindingApprovalCustomId, recordInboundSession, resolveConversationLabel as resolveConversationLabel$1, resolvePluginConversationBindingApproval, resolveRuntimeConversationBindingRoute, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
28
29
  import { resolveNativeCommandsEnabled, resolveNativeSkillsEnabled } from "openclaw/plugin-sdk/native-command-config-runtime";
@@ -45,7 +46,6 @@ import { createInteractiveConversationBindingHelpers, dispatchPluginInteractiveH
45
46
  import { createChannelIngressResolver, defineStableChannelIngressIdentity, readChannelIngressStoreAllowFromForDmPolicy } from "openclaw/plugin-sdk/channel-ingress-runtime";
46
47
  import { createChannelInboundDebouncer, shouldDebounceTextInbound } from "openclaw/plugin-sdk/channel-inbound";
47
48
  import { generateSecureToken } from "openclaw/plugin-sdk/secure-random-runtime";
48
- import { buildUntrustedChannelMetadata } from "openclaw/plugin-sdk/security-runtime";
49
49
  //#region extensions/slack/src/monitor/commands.ts
50
50
  /**
51
51
  * Strip Slack mentions (<@U123>, <@U123|name>) so command detection works on
@@ -96,6 +96,9 @@ function resolveSlackChatType(channelType) {
96
96
  //#endregion
97
97
  //#region extensions/slack/src/monitor/context.ts
98
98
  const SLACK_ASSISTANT_THREAD_CONTEXT_METADATA_EVENT = "assistant_thread_context";
99
+ const SLACK_CHANNEL_CACHE_MAX_ENTRIES = 1024;
100
+ const SLACK_CHANNEL_DENIAL_WARNING_TTL_MS = 5 * 6e4;
101
+ const SLACK_CHANNEL_DENIAL_WARNING_MAX_ENTRIES = 1024;
99
102
  function buildSlackAssistantThreadMetadata(context) {
100
103
  const eventPayload = {};
101
104
  if (context.channelId) eventPayload.channel_id = context.channelId;
@@ -134,23 +137,61 @@ function createSlackMonitorContext(params) {
134
137
  ttlMs: 6e4,
135
138
  maxSize: 500
136
139
  });
140
+ const channelDenialWarnings = createDedupeCache({
141
+ ttlMs: SLACK_CHANNEL_DENIAL_WARNING_TTL_MS,
142
+ maxSize: SLACK_CHANNEL_DENIAL_WARNING_MAX_ENTRIES
143
+ });
137
144
  const assistantThreadContexts = /* @__PURE__ */ new Map();
138
145
  let lastAssistantContextCleanupAt = Date.now();
139
146
  const allowFrom = normalizeAllowList(params.allowFrom);
140
147
  const groupDmChannels = normalizeAllowList(params.groupDmChannels);
141
- const groupDmChannelsLower = normalizeAllowListLower(groupDmChannels);
148
+ const groupDmChannelsLower = new Set(normalizeAllowListLower(groupDmChannels).map((entry) => entry.replace(/^channel:/, "")));
142
149
  const defaultRequireMention = params.defaultRequireMention ?? true;
143
150
  const hasChannelAllowlistConfig = Object.keys(params.channelsConfig ?? {}).length > 0;
144
151
  const channelsConfigKeys = Object.keys(params.channelsConfig ?? {});
145
- const markMessageSeen = (channelId, ts) => {
152
+ const scopedKey = (key, eventScope) => eventScope ? `${params.accountId}:${eventScope.teamId}:${key}` : key;
153
+ const readCachedChannel = (cacheKey) => {
154
+ const cached = channelCache.get(cacheKey);
155
+ if (cached) {
156
+ channelCache.delete(cacheKey);
157
+ channelCache.set(cacheKey, cached);
158
+ }
159
+ return cached;
160
+ };
161
+ const writeCachedChannel = (cacheKey, entry) => {
162
+ channelCache.delete(cacheKey);
163
+ channelCache.set(cacheKey, entry);
164
+ pruneMapToMaxSize(channelCache, SLACK_CHANNEL_CACHE_MAX_ENTRIES);
165
+ };
166
+ const rememberSlackChannelType = (channelId, channelType, eventScope) => {
167
+ const id = normalizeOptionalString(channelId);
168
+ const normalizedType = normalizeOptionalString(channelType)?.toLowerCase();
169
+ if (!id || normalizedType !== "im" && normalizedType !== "mpim" && normalizedType !== "channel" && normalizedType !== "group") return;
170
+ const cacheKey = scopedKey(id, eventScope);
171
+ const cached = readCachedChannel(cacheKey);
172
+ const type = normalizeSlackChannelType(normalizedType, id);
173
+ if (cached?.info.type === type) return;
174
+ writeCachedChannel(cacheKey, {
175
+ info: {
176
+ ...cached?.info,
177
+ type
178
+ },
179
+ metadataLoaded: cached?.metadataLoaded ?? false
180
+ });
181
+ };
182
+ const recallSlackChannelType = (channelId, eventScope) => {
183
+ const id = normalizeOptionalString(channelId);
184
+ return id ? readCachedChannel(scopedKey(id, eventScope))?.info.type : void 0;
185
+ };
186
+ const markMessageSeen = (channelId, ts, eventScope) => {
146
187
  if (!channelId || !ts) return false;
147
- return seenMessages.check(`${channelId}:${ts}`);
188
+ return seenMessages.check(scopedKey(`${channelId}:${ts}`, eventScope));
148
189
  };
149
- const releaseSeenMessage = (channelId, ts) => {
190
+ const releaseSeenMessage = (channelId, ts, eventScope) => {
150
191
  if (!channelId || !ts) return;
151
- seenMessages.delete(`${channelId}:${ts}`);
192
+ seenMessages.delete(scopedKey(`${channelId}:${ts}`, eventScope));
152
193
  };
153
- const assistantContextKey = (channelId, threadTs) => `${channelId}:${threadTs}`;
194
+ const assistantContextKey = (channelId, threadTs, eventScope) => scopedKey(`${channelId}:${threadTs}`, eventScope);
154
195
  const cleanupAssistantThreadContexts = () => {
155
196
  const now = Date.now();
156
197
  if (now - lastAssistantContextCleanupAt < SLACK_ASSISTANT_CONTEXT_CLEANUP_INTERVAL_MS) return;
@@ -158,9 +199,9 @@ function createSlackMonitorContext(params) {
158
199
  const cutoff = now - SLACK_ASSISTANT_CONTEXT_TTL_MS;
159
200
  for (const [key, entry] of assistantThreadContexts) if (entry.updatedAt < cutoff) assistantThreadContexts.delete(key);
160
201
  };
161
- const getSlackAssistantThreadContext = (channelId, threadTs) => {
202
+ const getSlackAssistantThreadContext = (channelId, threadTs, eventScope) => {
162
203
  if (!channelId || !threadTs) return;
163
- const key = assistantContextKey(channelId, threadTs);
204
+ const key = assistantContextKey(channelId, threadTs, eventScope);
164
205
  const entry = assistantThreadContexts.get(key);
165
206
  if (!entry) return;
166
207
  if (Date.now() - entry.updatedAt > SLACK_ASSISTANT_CONTEXT_TTL_MS) {
@@ -169,9 +210,9 @@ function createSlackMonitorContext(params) {
169
210
  }
170
211
  return entry;
171
212
  };
172
- const saveSlackAssistantThreadContext = (context) => {
213
+ const saveSlackAssistantThreadContext = (context, eventScope) => {
173
214
  cleanupAssistantThreadContexts();
174
- assistantThreadContexts.set(assistantContextKey(context.assistantChannelId, context.threadTs), {
215
+ assistantThreadContexts.set(assistantContextKey(context.assistantChannelId, context.threadTs, eventScope), {
175
216
  ...context,
176
217
  updatedAt: Date.now()
177
218
  });
@@ -179,7 +220,7 @@ function createSlackMonitorContext(params) {
179
220
  const resolveSlackSystemEventSessionKey = (p) => {
180
221
  const channelId = normalizeOptionalString(p.channelId) ?? "";
181
222
  const senderId = normalizeOptionalString(p.senderId) ?? "";
182
- const channelType = normalizeSlackChannelType(p.channelType, channelId);
223
+ const channelType = normalizeSlackChannelType(p.channelType ?? recallSlackChannelType(channelId), channelId);
183
224
  const isDirectMessage = channelType === "im";
184
225
  if (!channelId && (!isDirectMessage || !senderId)) return params.mainKey;
185
226
  const isGroup = channelType === "mpim";
@@ -237,39 +278,47 @@ function createSlackMonitorContext(params) {
237
278
  parentSessionKey: normalizeOptionalString(p.threadTs) && params.threadInheritParent ? legacySessionKey : void 0
238
279
  }).sessionKey;
239
280
  };
240
- const resolveChannelName = async (channelId) => {
241
- const cached = channelCache.get(channelId);
242
- if (cached) return cached;
281
+ const resolveChannelName = async (channelId, eventScope) => {
282
+ const cacheKey = scopedKey(channelId, eventScope);
283
+ const cached = readCachedChannel(cacheKey);
284
+ if (cached?.metadataLoaded) return cached.info;
243
285
  try {
244
- const info = await params.app.client.conversations.info({
286
+ const info = await (eventScope?.client ?? params.app.client).conversations.info({
245
287
  token: params.botToken,
246
288
  channel: channelId
247
289
  });
248
290
  const name = info.channel && "name" in info.channel ? info.channel.name : void 0;
249
291
  const channel = info.channel ?? void 0;
292
+ const type = channel?.is_im ? "im" : channel?.is_mpim ? "mpim" : channel?.is_channel ? "channel" : channel?.is_group ? "group" : void 0;
293
+ const topic = channel && "topic" in channel ? channel.topic?.value ?? void 0 : void 0;
294
+ const purpose = channel && "purpose" in channel ? channel.purpose?.value ?? void 0 : void 0;
250
295
  const entry = {
251
- name,
252
- type: channel?.is_im ? "im" : channel?.is_mpim ? "mpim" : channel?.is_channel ? "channel" : channel?.is_group ? "group" : void 0,
253
- topic: channel && "topic" in channel ? channel.topic?.value ?? void 0 : void 0,
254
- purpose: channel && "purpose" in channel ? channel.purpose?.value ?? void 0 : void 0
296
+ info: {
297
+ name,
298
+ type: cached?.info.type ?? type,
299
+ topic,
300
+ purpose
301
+ },
302
+ metadataLoaded: true
255
303
  };
256
- channelCache.set(channelId, entry);
257
- return entry;
304
+ writeCachedChannel(cacheKey, entry);
305
+ return entry.info;
258
306
  } catch {
259
- return {};
307
+ return cached?.info ?? {};
260
308
  }
261
309
  };
262
- const resolveUserName = async (userId) => {
263
- const cached = userCache.get(userId);
310
+ const resolveUserName = async (userId, eventScope) => {
311
+ const cacheKey = scopedKey(userId, eventScope);
312
+ const cached = userCache.get(cacheKey);
264
313
  if (cached) return cached;
265
314
  try {
266
- const info = await params.app.client.users.info({
315
+ const info = await (eventScope?.client ?? params.app.client).users.info({
267
316
  token: params.botToken,
268
317
  user: userId
269
318
  });
270
319
  const profile = info.user?.profile;
271
320
  const entry = { name: profile?.display_name || profile?.real_name || info.user?.name || void 0 };
272
- userCache.set(userId, entry);
321
+ userCache.set(cacheKey, entry);
273
322
  return entry;
274
323
  } catch {
275
324
  return {};
@@ -278,7 +327,7 @@ function createSlackMonitorContext(params) {
278
327
  const setSlackThreadStatus = async (p) => {
279
328
  if (!p.threadTs) return;
280
329
  try {
281
- await params.app.client.assistant.threads.setStatus({
330
+ await (p.eventScope?.client ?? params.app.client).assistant.threads.setStatus({
282
331
  token: params.botToken,
283
332
  channel_id: p.channelId,
284
333
  thread_ts: p.threadTs,
@@ -323,7 +372,7 @@ function createSlackMonitorContext(params) {
323
372
  p.channelName,
324
373
  p.channelName ? normalizeSlackSlug(p.channelName) : void 0
325
374
  ].filter((value) => Boolean(value)).map((value) => normalizeLowercaseStringOrEmpty(value));
326
- if (!(groupDmChannelsLower.includes("*") || candidates.some((candidate) => groupDmChannelsLower.includes(candidate)))) return false;
375
+ if (!(groupDmChannelsLower.has("*") || candidates.some((candidate) => groupDmChannelsLower.has(candidate)))) return false;
327
376
  }
328
377
  if (isRoom && p.channelId) {
329
378
  const channelConfig = resolveSlackChannelConfig({
@@ -337,19 +386,33 @@ function createSlackMonitorContext(params) {
337
386
  const channelMatchMeta = formatAllowlistMatchMeta(channelConfig);
338
387
  const channelAllowed = channelConfig?.allowed !== false;
339
388
  const channelAllowlistConfigured = hasChannelAllowlistConfig;
340
- if (!isSlackChannelAllowedByPolicy({
389
+ const allowedByPolicy = isSlackChannelAllowedByPolicy({
341
390
  groupPolicy: params.groupPolicy,
342
391
  channelAllowlistConfigured,
343
392
  channelAllowed
344
- })) {
393
+ });
394
+ const explicitlyDisabled = params.groupPolicy !== "disabled" && channelConfig?.allowed === false && channelConfig.matchSource !== void 0;
395
+ if (!allowedByPolicy || params.groupPolicy === "open" && explicitlyDisabled) {
396
+ if (explicitlyDisabled) {
397
+ const reason = "channel_not_allowed";
398
+ const warningKey = `${params.accountId}:${p.channelId}:${reason}`;
399
+ if (!channelDenialWarnings.peek(warningKey)) {
400
+ channelDenialWarnings.check(warningKey);
401
+ logger.warn({
402
+ provider: "slack",
403
+ accountId: params.accountId,
404
+ channelId: p.channelId,
405
+ reason,
406
+ cause: "channel_disabled",
407
+ groupPolicy: params.groupPolicy,
408
+ matchSource: channelConfig.matchSource,
409
+ matchKey: channelConfig.matchKey
410
+ }, "Slack channel denied by configuration");
411
+ }
412
+ }
345
413
  logVerbose(`slack: drop channel ${p.channelId} (groupPolicy=${params.groupPolicy}, ${channelMatchMeta})`);
346
414
  return false;
347
415
  }
348
- const hasExplicitConfig = Boolean(channelConfig?.matchSource);
349
- if (!channelAllowed && (params.groupPolicy !== "open" || hasExplicitConfig)) {
350
- logVerbose(`slack: drop channel ${p.channelId} (${channelMatchMeta})`);
351
- return false;
352
- }
353
416
  logVerbose(`slack: allow channel ${p.channelId} (${channelMatchMeta})`);
354
417
  }
355
418
  return true;
@@ -380,6 +443,10 @@ function createSlackMonitorContext(params) {
380
443
  botId: params.botId,
381
444
  teamId: params.teamId,
382
445
  apiAppId: params.apiAppId,
446
+ installationIdentity: params.installationIdentity ?? {
447
+ kind: "degraded",
448
+ reason: "auth_test_failed"
449
+ },
383
450
  historyLimit: params.historyLimit,
384
451
  dmHistoryLimit: Math.max(0, params.dmHistoryLimit ?? 0),
385
452
  channelHistories,
@@ -415,6 +482,8 @@ function createSlackMonitorContext(params) {
415
482
  resolveSlackSystemEventSessionKey,
416
483
  isChannelAllowed,
417
484
  resolveChannelName,
485
+ rememberSlackChannelType,
486
+ recallSlackChannelType,
418
487
  resolveUserName,
419
488
  setSlackThreadStatus,
420
489
  getSlackAssistantThreadContext,
@@ -423,6 +492,121 @@ function createSlackMonitorContext(params) {
423
492
  };
424
493
  }
425
494
  //#endregion
495
+ //#region extensions/slack/src/monitor/enterprise-install.ts
496
+ const SLACK_CHANNEL_ID_RE = /^[CDG][A-Z0-9]{8,}$/;
497
+ const SLACK_USER_ID_RE = /^[UW][A-Z0-9]{8,}$/;
498
+ function isStableSlackChannelEntry(value, options) {
499
+ if (typeof value !== "string") return false;
500
+ const normalized = value.trim();
501
+ if (normalized === "*") return options?.allowWildcard === true;
502
+ if (/^channel:([CDG][A-Z0-9]{8,})$/.exec(normalized)?.[1]) return true;
503
+ return SLACK_CHANNEL_ID_RE.test(normalized);
504
+ }
505
+ function isStableSlackAllowlistUserEntry(value) {
506
+ if (typeof value !== "string") return false;
507
+ const normalized = value.trim();
508
+ if (normalized === "*") return true;
509
+ const prefixed = /^(?:slack|user):([UW][A-Z0-9]{8,})$/.exec(normalized);
510
+ return Boolean(prefixed?.[1]) || SLACK_USER_ID_RE.test(normalized);
511
+ }
512
+ function isStableSlackToolsBySenderEntry(value) {
513
+ if (typeof value !== "string") return false;
514
+ const normalized = value.trim();
515
+ if (normalized === "*") return true;
516
+ const prefixed = /^(?:id:|channel:slack:)([UW][A-Z0-9]{8,})$/.exec(normalized);
517
+ return Boolean(prefixed?.[1]) || SLACK_USER_ID_RE.test(normalized);
518
+ }
519
+ function assertStableEntries(params) {
520
+ const invalid = params.values?.find((value) => !params.predicate(value));
521
+ if (invalid !== void 0) throw new Error(`Slack Enterprise Grid org installs require stable Slack IDs in ${params.path}; invalid entry ${JSON.stringify(invalid)}`);
522
+ }
523
+ /** Validate every policy surface that would otherwise require name resolution. */
524
+ function assertEnterpriseSlackPolicyConfig(params) {
525
+ const { config, accountId } = params;
526
+ if (config.dangerouslyAllowNameMatching === true) throw new Error(`Slack Enterprise Grid org account "${accountId}" cannot use dangerouslyAllowNameMatching`);
527
+ if (config.mentionPatterns?.allowIn !== void 0 || config.mentionPatterns?.denyIn !== void 0) throw new Error(`Slack Enterprise Grid org account "${accountId}" cannot use mentionPatterns.allowIn or mentionPatterns.denyIn because Slack channel IDs are not workspace-qualified`);
528
+ assertStableEntries({
529
+ values: config.allowFrom,
530
+ path: `channels.slack.accounts.${accountId}.allowFrom`,
531
+ predicate: isStableSlackAllowlistUserEntry
532
+ });
533
+ assertStableEntries({
534
+ values: config.dm?.allowFrom,
535
+ path: `channels.slack.accounts.${accountId}.dm.allowFrom`,
536
+ predicate: isStableSlackAllowlistUserEntry
537
+ });
538
+ assertStableEntries({
539
+ values: config.dm?.groupChannels,
540
+ path: `channels.slack.accounts.${accountId}.dm.groupChannels`,
541
+ predicate: (value) => isStableSlackChannelEntry(value)
542
+ });
543
+ if (config.reactionNotifications === "allowlist") assertStableEntries({
544
+ values: config.reactionAllowlist,
545
+ path: `channels.slack.accounts.${accountId}.reactionAllowlist`,
546
+ predicate: isStableSlackAllowlistUserEntry
547
+ });
548
+ for (const [channelKey, channel] of Object.entries(config.channels ?? {})) {
549
+ if (!isStableSlackChannelEntry(channelKey, { allowWildcard: true })) throw new Error(`Slack Enterprise Grid org installs require stable Slack channel IDs; invalid channels key ${JSON.stringify(channelKey)}`);
550
+ assertStableEntries({
551
+ values: channel?.users,
552
+ path: `channels.slack.accounts.${accountId}.channels.${channelKey}.users`,
553
+ predicate: isStableSlackAllowlistUserEntry
554
+ });
555
+ assertStableEntries({
556
+ values: Object.keys(channel?.toolsBySender ?? {}),
557
+ path: `channels.slack.accounts.${accountId}.channels.${channelKey}.toolsBySender`,
558
+ predicate: isStableSlackToolsBySenderEntry
559
+ });
560
+ }
561
+ }
562
+ /** Prevent account-wide user authorization state from crossing workspace boundaries. */
563
+ function assertEnterpriseSlackDmPolicy(params) {
564
+ if (!params.dmEnabled || params.dmPolicy === "disabled") return;
565
+ if (params.dmPolicy === "open" && params.allowFrom?.includes("*")) return;
566
+ throw new Error(`Slack Enterprise Grid org account "${params.accountId}" supports DMs only with dm.enabled=false, dmPolicy="disabled", or dmPolicy="open" with effective allowFrom containing "*"; dmPolicy=${JSON.stringify(params.dmPolicy)} and allowFrom=${JSON.stringify(params.allowFrom ?? [])} would share per-user authorization across workspaces`);
567
+ }
568
+ function assertNoEnterpriseSlackBindings(params) {
569
+ const defaultAccountId = resolveDefaultSlackAccountId(params.cfg);
570
+ if (params.cfg.bindings?.find((binding) => {
571
+ if (binding.match.channel.trim().toLowerCase() !== "slack") return false;
572
+ const accountId = binding.match.accountId?.trim();
573
+ return accountId === "*" || accountId === params.accountId || !accountId && params.accountId === defaultAccountId;
574
+ })) throw new Error(`Slack Enterprise Grid org account "${params.accountId}" cannot use configured Slack bindings`);
575
+ }
576
+ function resolveSlackInstallationIdentity(params) {
577
+ const auth = params.auth;
578
+ if (!auth) {
579
+ if (params.enterpriseOrgInstall) throw new Error(`Slack enterpriseOrgInstall=true requires a successful auth.test (${formatSlackError(params.authError)})`);
580
+ return {
581
+ kind: "degraded",
582
+ reason: "auth_test_failed"
583
+ };
584
+ }
585
+ const isEnterpriseInstall = auth.is_enterprise_install === true;
586
+ if (isEnterpriseInstall !== params.enterpriseOrgInstall) throw new Error(isEnterpriseInstall ? "Slack auth.test detected an org-wide installation; set enterpriseOrgInstall=true" : "Slack enterpriseOrgInstall=true requires an org-wide bot installation");
587
+ const apiAppId = normalizeOptionalString(auth.app_id);
588
+ const enterpriseId = normalizeOptionalString(auth.enterprise_id);
589
+ if (params.enterpriseOrgInstall) {
590
+ if (!enterpriseId) throw new Error("Slack org-wide auth.test returned no enterprise_id");
591
+ const transportApiAppId = normalizeOptionalString(params.transportApiAppId);
592
+ if (apiAppId && transportApiAppId && apiAppId !== transportApiAppId) throw new Error(`Slack token mismatch: bot token app_id=${apiAppId} but transport app_id=${transportApiAppId}`);
593
+ const effectiveApiAppId = apiAppId ?? transportApiAppId;
594
+ return {
595
+ kind: "enterprise",
596
+ ...effectiveApiAppId ? { apiAppId: effectiveApiAppId } : {},
597
+ enterpriseId
598
+ };
599
+ }
600
+ const teamId = normalizeOptionalString(auth.team_id);
601
+ if (!teamId) throw new Error("Slack workspace auth.test returned no team_id");
602
+ return {
603
+ kind: "workspace",
604
+ teamId,
605
+ ...apiAppId ? { apiAppId } : {},
606
+ ...enterpriseId ? { enterpriseId } : {}
607
+ };
608
+ }
609
+ //#endregion
426
610
  //#region extensions/slack/src/monitor/events/assistant.ts
427
611
  const DEFAULT_ASSISTANT_PROMPTS = [
428
612
  {
@@ -557,7 +741,12 @@ function migrateSlackChannelsInPlace(channels, oldChannelId, newChannelId) {
557
741
  migrated: false,
558
742
  skippedExisting: true
559
743
  };
560
- channels[newChannelId] = channels[oldChannelId];
744
+ const channelConfig = channels[oldChannelId];
745
+ if (!channelConfig) return {
746
+ migrated: false,
747
+ skippedExisting: false
748
+ };
749
+ channels[newChannelId] = channelConfig;
561
750
  delete channels[oldChannelId];
562
751
  return {
563
752
  migrated: true,
@@ -702,7 +891,7 @@ function registerSlackChannelEvents(params) {
702
891
  }
703
892
  //#endregion
704
893
  //#region extensions/slack/src/monitor/events/home.ts
705
- function buildSlackHomeView() {
894
+ function buildSlackHomeView(slashCommandName) {
706
895
  return {
707
896
  type: "home",
708
897
  callback_id: "openclaw:home",
@@ -718,7 +907,7 @@ function buildSlackHomeView() {
718
907
  type: "section",
719
908
  text: {
720
909
  type: "mrkdwn",
721
- text: "Send a DM, mention OpenClaw in a channel, or use `/openclaw` to start a session."
910
+ text: slashCommandName ? `Send a DM, mention OpenClaw in a channel, or use \`/${slashCommandName}\` to start a session.` : "Send a DM or mention OpenClaw in a channel to start a session."
722
911
  }
723
912
  },
724
913
  {
@@ -732,7 +921,7 @@ function buildSlackHomeView() {
732
921
  };
733
922
  }
734
923
  function registerSlackHomeEvents(params) {
735
- const { ctx, trackEvent } = params;
924
+ const { ctx, slashCommandName, trackEvent } = params;
736
925
  ctx.app.event("app_home_opened", async ({ event, body }) => {
737
926
  try {
738
927
  if (ctx.shouldDropMismatchedSlackEvent(body)) return;
@@ -742,7 +931,7 @@ function registerSlackHomeEvents(params) {
742
931
  await ctx.app.client.views.publish({
743
932
  token: ctx.botToken,
744
933
  user_id: payload.user,
745
- view: buildSlackHomeView()
934
+ view: buildSlackHomeView(slashCommandName)
746
935
  });
747
936
  } catch (err) {
748
937
  ctx.runtime.error?.(danger(`slack app home handler failed: ${formatErrorMessage(err)}`));
@@ -783,7 +972,7 @@ async function dispatchSlackPluginInteractiveHandler(params) {
783
972
  }
784
973
  //#endregion
785
974
  //#region extensions/slack/src/monitor/auth.ts
786
- let slackChannelMembersCache = /* @__PURE__ */ new WeakMap();
975
+ const slackChannelMembersCache = /* @__PURE__ */ new WeakMap();
787
976
  const DEFAULT_CHANNEL_MEMBERS_CACHE_TTL_MS = 6e4;
788
977
  const CHANNEL_MEMBERS_CACHE_MAX = 512;
789
978
  const SLACK_CHANNEL_ID = "slack";
@@ -891,11 +1080,11 @@ async function resolveSlackEffectiveAllowFrom(ctx, options) {
891
1080
  }
892
1081
  return normalizeAllowListLower([...base, ...storeAllowFrom]);
893
1082
  }
894
- async function fetchSlackChannelMemberIds(ctx, channelId) {
1083
+ async function fetchSlackChannelMemberIds(ctx, channelId, eventScope) {
895
1084
  const members = /* @__PURE__ */ new Set();
896
1085
  let cursor;
897
1086
  do {
898
- const response = await ctx.app.client.conversations.members({
1087
+ const response = await (eventScope?.client ?? ctx.app.client).conversations.members({
899
1088
  token: ctx.botToken,
900
1089
  channel: channelId,
901
1090
  limit: 999,
@@ -907,9 +1096,9 @@ async function fetchSlackChannelMemberIds(ctx, channelId) {
907
1096
  } while (cursor);
908
1097
  return members;
909
1098
  }
910
- async function resolveSlackChannelMemberIds(ctx, channelId) {
1099
+ async function resolveSlackChannelMemberIds(ctx, channelId, eventScope) {
911
1100
  const cache = getChannelMembersCache(ctx);
912
- const key = `${ctx.accountId}:${channelId}`;
1101
+ const key = `${ctx.accountId}:${eventScope ? `${eventScope.teamId}:` : ""}${channelId}`;
913
1102
  const ttlMs = readSlackCacheTtlMs("OPENCLAW_SLACK_CHANNEL_MEMBERS_CACHE_TTL_MS", DEFAULT_CHANNEL_MEMBERS_CACHE_TTL_MS);
914
1103
  const rawNowMs = Date.now();
915
1104
  const nowMs = asDateTimestampMs(rawNowMs);
@@ -919,7 +1108,7 @@ async function resolveSlackChannelMemberIds(ctx, channelId) {
919
1108
  cache.delete(key);
920
1109
  }
921
1110
  if (cached?.pending) return await cached.pending;
922
- const pending = fetchSlackChannelMemberIds(ctx, channelId);
1111
+ const pending = fetchSlackChannelMemberIds(ctx, channelId, eventScope);
923
1112
  const pendingExpiresAtMs = ttlMs > 0 ? resolveExpiresAtMsFromDurationMs(ttlMs, { nowMs: rawNowMs }) : void 0;
924
1113
  cache.set(key, {
925
1114
  expiresAtMs: pendingExpiresAtMs ?? 0,
@@ -963,7 +1152,7 @@ async function authorizeSlackBotRoomMessage(params) {
963
1152
  return false;
964
1153
  }
965
1154
  try {
966
- const channelMemberIds = await resolveSlackChannelMemberIds(params.ctx, params.channelId);
1155
+ const channelMemberIds = await resolveSlackChannelMemberIds(params.ctx, params.channelId, params.eventScope);
967
1156
  if (explicitOwnerIds.some((ownerId) => channelMemberIds.has(ownerId))) return true;
968
1157
  logVerbose(`slack: drop bot message ${params.senderId} in ${params.channelId} (no owner present)`);
969
1158
  } catch (error) {
@@ -979,8 +1168,10 @@ function slackIngressConversationKind(channelType) {
979
1168
  }
980
1169
  async function resolveSlackCommandIngress(params) {
981
1170
  const isDirectMessage = params.channelType === "im";
1171
+ const isGroupDm = params.channelType === "mpim";
982
1172
  const channelUsers = normalizeAllowListLower(params.channelUsers);
983
- const channelUsersConfigured = !isDirectMessage && channelUsers.length > 0;
1173
+ const channelUsersConfigured = !isDirectMessage && !isGroupDm && channelUsers.length > 0;
1174
+ const groupAllowFrom = isGroupDm ? params.ownerAllowFromLower : channelUsersConfigured ? channelUsers : [];
984
1175
  return await createSlackIngressResolver(params.ctx).message({
985
1176
  subject: createSlackIngressSubject({
986
1177
  senderId: params.senderId,
@@ -996,7 +1187,7 @@ async function resolveSlackCommandIngress(params) {
996
1187
  mayPair: false
997
1188
  },
998
1189
  dmPolicy: isDirectMessage ? "open" : "disabled",
999
- groupPolicy: channelUsersConfigured ? "allowlist" : "open",
1190
+ groupPolicy: isGroupDm || channelUsersConfigured ? "allowlist" : "open",
1000
1191
  policy: {
1001
1192
  groupAllowFromFallbackToAllowFrom: false,
1002
1193
  mutableIdentifierMatching: params.ctx.allowNameMatching ? "enabled" : "disabled",
@@ -1004,7 +1195,7 @@ async function resolveSlackCommandIngress(params) {
1004
1195
  },
1005
1196
  mentionFacts: params.mentionFacts,
1006
1197
  allowFrom: isDirectMessage ? ["*"] : params.ownerAllowFromLower,
1007
- groupAllowFrom: channelUsersConfigured ? channelUsers : [],
1198
+ groupAllowFrom,
1008
1199
  command: {
1009
1200
  allowTextCommands: params.allowTextCommands,
1010
1201
  hasControlCommand: params.hasControlCommand,
@@ -1015,12 +1206,14 @@ async function resolveSlackCommandIngress(params) {
1015
1206
  }
1016
1207
  async function decideSlackSystemIngress(params) {
1017
1208
  const isDirectMessage = params.channelType === "im";
1209
+ const isGroupDm = params.channelType === "mpim";
1018
1210
  const channelUsers = normalizeAllowListLower(params.channelUsers);
1019
- const channelUsersConfigured = !isDirectMessage && channelUsers.length > 0;
1211
+ const channelUsersConfigured = !isDirectMessage && !isGroupDm && channelUsers.length > 0;
1020
1212
  const ownerAllowFrom = params.interactiveEvent && channelUsersConfigured ? params.ownerAllowFromLower.filter((entry) => entry !== "*") : params.ownerAllowFromLower;
1021
1213
  const hasAnyCommandAllowlist = ownerAllowFrom.length > 0 || channelUsersConfigured;
1022
1214
  const groupAllowFrom = (() => {
1023
1215
  if (isDirectMessage) return [];
1216
+ if (isGroupDm) return ownerAllowFrom;
1024
1217
  if (params.interactiveEvent && hasAnyCommandAllowlist) return channelUsersConfigured ? channelUsers : [];
1025
1218
  if (channelUsersConfigured) return channelUsers;
1026
1219
  return params.channelId ? ["*"] : wildcardWhenOpen(params.ownerAllowFromLower);
@@ -1040,7 +1233,7 @@ async function decideSlackSystemIngress(params) {
1040
1233
  mayPair: false
1041
1234
  },
1042
1235
  dmPolicy: isDirectMessage ? "open" : "disabled",
1043
- groupPolicy: params.interactiveEvent && hasAnyCommandAllowlist ? "open" : channelUsersConfigured || !params.channelId && params.ownerAllowFromLower.length > 0 ? "allowlist" : "open",
1236
+ groupPolicy: isGroupDm ? "allowlist" : params.interactiveEvent && hasAnyCommandAllowlist ? "open" : channelUsersConfigured || !params.channelId && params.ownerAllowFromLower.length > 0 ? "allowlist" : "open",
1044
1237
  policy: {
1045
1238
  groupAllowFromFallbackToAllowFrom: false,
1046
1239
  mutableIdentifierMatching: params.ctx.allowNameMatching ? "enabled" : "disabled"
@@ -1149,11 +1342,6 @@ async function authorizeSlackSystemEventSender(params) {
1149
1342
  };
1150
1343
  }
1151
1344
  //#endregion
1152
- //#region extensions/slack/src/monitor/mrkdwn.ts
1153
- function escapeSlackMrkdwn(value) {
1154
- return value.replaceAll("\\", "\\\\").replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replace(/([*_`~])/g, "\\$1");
1155
- }
1156
- //#endregion
1157
1345
  //#region extensions/slack/src/monitor/events/interactions.block-actions.ts
1158
1346
  function readOptionValues(options) {
1159
1347
  if (!Array.isArray(options)) return;
@@ -1235,9 +1423,6 @@ function summarizeAction(action) {
1235
1423
  workflowId: typed.workflow?.workflow_id
1236
1424
  };
1237
1425
  }
1238
- function isBulkActionsBlock(block) {
1239
- return block.type === "actions" && Array.isArray(block.elements) && block.elements.length > 0 && block.elements.every((el) => typeof el.action_id === "string" && el.action_id.includes("_all_"));
1240
- }
1241
1426
  function formatInteractionSelectionLabel(params) {
1242
1427
  if (params.summary.actionType === "button" && params.buttonText?.trim()) return params.buttonText.trim();
1243
1428
  if (params.summary.selectedLabels?.length) {
@@ -1267,12 +1452,15 @@ function buildSlackPluginInteractionData(params) {
1267
1452
  const actionId = normalizeOptionalString(params.actionId) ?? "";
1268
1453
  if (!actionId) return null;
1269
1454
  const payload = normalizeOptionalString(params.summary.value) || params.summary.selectedValues?.map((value) => normalizeOptionalString(value)).find(Boolean) || "";
1270
- if (actionId === "openclaw:reply_button" || actionId === "openclaw:reply_select" || actionId.startsWith(`openclaw:reply_button:`) || actionId.startsWith(`openclaw:reply_select:`)) return payload || null;
1455
+ if (actionId === "openclaw:reply_button" || actionId === "openclaw:reply_select" || isSlackCallbackActionId(actionId) || actionId.startsWith(`openclaw:reply_button:`) || actionId.startsWith(`openclaw:reply_select:`)) return payload || null;
1271
1456
  return payload ? `${actionId}:${payload}` : actionId;
1272
1457
  }
1273
1458
  function isSlackReplyActionId(actionId) {
1274
1459
  return actionId === "openclaw:reply_button" || actionId === "openclaw:reply_select" || actionId.startsWith(`openclaw:reply_button:`) || actionId.startsWith(`openclaw:reply_select:`);
1275
1460
  }
1461
+ function readSlackApprovalAction(parsed) {
1462
+ return decodeSlackApprovalAction(normalizeOptionalString(parsed.actionSummary.value) ?? parsed.actionSummary.selectedValues?.map((entry) => normalizeOptionalString(entry)).find((entry) => Boolean(entry)));
1463
+ }
1276
1464
  function isSlackReplyLinkAction(parsed) {
1277
1465
  if (parsed.actionId === "openclaw:reply_link" || parsed.actionId.startsWith(`openclaw:reply_link:`)) return true;
1278
1466
  const legacyUrl = normalizeOptionalString(parsed.typedAction.url);
@@ -1328,6 +1516,37 @@ async function updateSlackInteractionMessage(params) {
1328
1516
  ...params.blocks ? { blocks: params.blocks } : {}
1329
1517
  });
1330
1518
  }
1519
+ function resolveSlackApprovalTerminalLabel(approval) {
1520
+ if (approval.status === "allowed") return approval.decision === "allow-always" ? "Allowed always" : "Allowed once";
1521
+ if (approval.status === "denied") return "Denied";
1522
+ if (approval.status === "expired") return "Expired";
1523
+ return "Cancelled";
1524
+ }
1525
+ function removeSlackApprovalControls(blocks) {
1526
+ return blocks.flatMap((block) => {
1527
+ if (!block || typeof block !== "object" || Array.isArray(block)) return [block];
1528
+ const typedBlock = block;
1529
+ if (typedBlock.type !== "actions" || !Array.isArray(typedBlock.elements)) return [block];
1530
+ const elements = typedBlock.elements.filter((element) => typeof element.action_id !== "string" || !isSlackApprovalActionId(element.action_id));
1531
+ return elements.length > 0 ? [{
1532
+ ...block,
1533
+ elements
1534
+ }] : [];
1535
+ });
1536
+ }
1537
+ function buildSlackApprovalTerminalBlocks(params) {
1538
+ const blocks = removeSlackApprovalControls(params.blocks ?? []).filter((block) => {
1539
+ const text = block.text?.text;
1540
+ return !(block.type === "section" && typeof text === "string" && /^\*(?:Exec|Plugin) approval required\*/u.test(text));
1541
+ });
1542
+ return [{
1543
+ type: "section",
1544
+ text: {
1545
+ type: "mrkdwn",
1546
+ text: `*${params.prefix}: ${params.label}*`
1547
+ }
1548
+ }, ...blocks];
1549
+ }
1331
1550
  async function authorizeSlackBlockAction(params) {
1332
1551
  const auth = await authorizeSlackSystemEventSender({
1333
1552
  ctx: params.ctx,
@@ -1361,9 +1580,7 @@ async function handleSlackPluginBindingApproval(params) {
1361
1580
  await respondEphemeral(params.respond, buildPluginBindingResolvedText(resolved));
1362
1581
  return true;
1363
1582
  }
1364
- async function handleSlackExecApprovalInteraction(params) {
1365
- const approval = parseExecApprovalCommandText(params.pluginInteractionData);
1366
- if (!approval) return false;
1583
+ async function handleSlackApprovalInteraction(params) {
1367
1584
  const pluginApprovalAuthorizedSender = isSlackApprovalAuthorizedSender({
1368
1585
  cfg: params.ctx.cfg,
1369
1586
  accountId: params.ctx.accountId,
@@ -1374,38 +1591,95 @@ async function handleSlackExecApprovalInteraction(params) {
1374
1591
  accountId: params.ctx.accountId,
1375
1592
  senderId: params.parsed.userId
1376
1593
  });
1377
- const isPluginApproval = approval.approvalId.startsWith("plugin:");
1378
- const resolveUnprefixedAsPlugin = !isPluginApproval && !execApprovalAuthorizedSender && pluginApprovalAuthorizedSender;
1379
- const authorized = isPluginApproval ? pluginApprovalAuthorizedSender : execApprovalAuthorizedSender || resolveUnprefixedAsPlugin;
1380
- const allowPluginFallback = !isPluginApproval && execApprovalAuthorizedSender && pluginApprovalAuthorizedSender;
1381
- if (!authorized) {
1382
- params.ctx.runtime.log?.(`slack:interaction drop exec approval user=${params.parsed.userId} (not authorized)`);
1594
+ if (!(params.approval.approvalKind === "plugin" ? pluginApprovalAuthorizedSender : execApprovalAuthorizedSender)) {
1595
+ params.ctx.runtime.log?.(`slack:interaction drop ${params.approval.approvalKind} approval user=${params.parsed.userId} (not authorized)`);
1383
1596
  await respondEphemeral(params.respond, "You are not authorized to approve this request.");
1384
1597
  return true;
1385
1598
  }
1386
1599
  try {
1387
- await resolveApprovalOverGateway({
1600
+ const result = await resolveApprovalOverGateway({
1388
1601
  cfg: params.ctx.cfg,
1389
- approvalId: approval.approvalId,
1390
- decision: approval.decision,
1602
+ approvalId: params.approval.approvalId,
1603
+ approvalKind: params.approval.approvalKind,
1604
+ decision: params.approval.decision,
1391
1605
  senderId: params.parsed.userId,
1392
- allowPluginFallback,
1393
- ...resolveUnprefixedAsPlugin ? { resolveMethod: "plugin" } : {},
1394
1606
  clientDisplayName: `Slack approval (${params.parsed.userId.trim() || "unknown"})`
1395
1607
  });
1608
+ const terminalLabel = resolveSlackApprovalTerminalLabel(result.approval);
1609
+ const prefix = result.applied ? "Resolved" : "Already resolved";
1610
+ let terminalized = false;
1611
+ try {
1612
+ const terminalText = `${prefix}: ${terminalLabel}`;
1613
+ await updateSlackInteractionMessage({
1614
+ ctx: params.ctx,
1615
+ channelId: params.parsed.channelId,
1616
+ messageTs: params.parsed.messageTs,
1617
+ text: truncateSlackText(terminalText, 4e3),
1618
+ blocks: buildSlackApprovalTerminalBlocks({
1619
+ blocks: params.parsed.typedBody.message?.blocks,
1620
+ label: terminalLabel,
1621
+ prefix
1622
+ })
1623
+ });
1624
+ terminalized = true;
1625
+ } catch {}
1626
+ if (!terminalized || !result.applied) await respondEphemeral(params.respond, result.applied ? `Approval resolved: ${terminalLabel}.` : `This approval was already resolved: ${terminalLabel}.`);
1396
1627
  } catch (error) {
1397
- params.ctx.runtime.log?.(`slack:interaction exec approval resolve failed id=${approval.approvalId}: ${String(error)}`);
1628
+ params.ctx.runtime.log?.(`slack:interaction approval resolve failed id=${params.approval.approvalId}: ${String(error)}`);
1629
+ if (isApprovalNotFoundError(error)) {
1630
+ await respondEphemeral(params.respond, "This approval is no longer pending.");
1631
+ return true;
1632
+ }
1633
+ await respondEphemeral(params.respond, "Could not reach the Gateway to resolve this approval. Try again.");
1398
1634
  throw error;
1399
1635
  }
1400
- try {
1401
- await updateSlackInteractionMessage({
1402
- ctx: params.ctx,
1403
- channelId: params.parsed.channelId,
1404
- messageTs: params.parsed.messageTs,
1405
- text: params.parsed.typedBody.message?.text ?? "",
1406
- blocks: []
1636
+ return true;
1637
+ }
1638
+ async function handleSlackLegacyApprovalInteraction(params) {
1639
+ const parsedApproval = parseExecApprovalCommandText(params.pluginInteractionData);
1640
+ if (!parsedApproval) return false;
1641
+ const pluginAuthorized = isSlackApprovalAuthorizedSender({
1642
+ cfg: params.ctx.cfg,
1643
+ accountId: params.ctx.accountId,
1644
+ senderId: params.parsed.userId
1645
+ });
1646
+ const execAuthorized = isSlackExecApprovalAuthorizedSender({
1647
+ cfg: params.ctx.cfg,
1648
+ accountId: params.ctx.accountId,
1649
+ senderId: params.parsed.userId
1650
+ });
1651
+ const resolveMethods = [];
1652
+ if (execAuthorized) resolveMethods.push("exec");
1653
+ if (pluginAuthorized) resolveMethods.push("plugin");
1654
+ if (resolveMethods.length === 0) {
1655
+ params.ctx.runtime.log?.(`slack:interaction drop legacy approval user=${params.parsed.userId} (not authorized)`);
1656
+ await respondEphemeral(params.respond, "You are not authorized to approve this request.");
1657
+ return true;
1658
+ }
1659
+ for (const [index, resolveMethod] of resolveMethods.entries()) try {
1660
+ await resolveApprovalOverGateway({
1661
+ cfg: params.ctx.cfg,
1662
+ approvalId: parsedApproval.approvalId,
1663
+ decision: parsedApproval.decision,
1664
+ senderId: params.parsed.userId,
1665
+ resolveMethod,
1666
+ clientDisplayName: `Slack approval (${params.parsed.userId.trim() || "unknown"})`
1407
1667
  });
1408
- } catch {}
1668
+ try {
1669
+ await updateSlackInteractionMessage({
1670
+ ctx: params.ctx,
1671
+ channelId: params.parsed.channelId,
1672
+ messageTs: params.parsed.messageTs,
1673
+ text: params.parsed.typedBody.message?.text ?? "",
1674
+ blocks: []
1675
+ });
1676
+ } catch {}
1677
+ return true;
1678
+ } catch (error) {
1679
+ if (index + 1 < resolveMethods.length && isApprovalNotFoundError(error)) continue;
1680
+ params.ctx.runtime.log?.(`slack:interaction legacy approval resolve failed id=${parsedApproval.approvalId}: ${String(error)}`);
1681
+ throw error;
1682
+ }
1409
1683
  return true;
1410
1684
  }
1411
1685
  async function dispatchSlackPluginInteraction(params) {
@@ -1572,7 +1846,7 @@ function buildSlackConfirmationBlocks(params) {
1572
1846
  summary: params.parsed.actionSummary,
1573
1847
  buttonText: params.parsed.typedActionWithText.text?.text
1574
1848
  });
1575
- let updatedBlocks = params.originalBlocks.map((block) => {
1849
+ return params.originalBlocks.map((block) => {
1576
1850
  const typedBlock = block;
1577
1851
  if (typedBlock.type === "actions" && typedBlock.block_id === params.parsed.blockId) return {
1578
1852
  type: "context",
@@ -1586,17 +1860,6 @@ function buildSlackConfirmationBlocks(params) {
1586
1860
  };
1587
1861
  return block;
1588
1862
  });
1589
- if (!updatedBlocks.some((block) => {
1590
- const typedBlock = block;
1591
- return typedBlock.type === "actions" && !isBulkActionsBlock(typedBlock);
1592
- })) updatedBlocks = updatedBlocks.filter((block, index) => {
1593
- const typedBlock = block;
1594
- if (isBulkActionsBlock(typedBlock)) return false;
1595
- if (typedBlock.type !== "divider") return true;
1596
- const next = updatedBlocks[index + 1];
1597
- return !next || !isBulkActionsBlock(next);
1598
- });
1599
- return updatedBlocks;
1600
1863
  }
1601
1864
  async function updateSlackLegacyBlockAction(params) {
1602
1865
  const originalBlocks = params.parsed.typedBody.message?.blocks;
@@ -1631,12 +1894,27 @@ async function handleSlackBlockAction(params) {
1631
1894
  if (!parsed) return;
1632
1895
  if (isSlackReplyLinkAction(parsed)) return;
1633
1896
  params.trackEvent?.();
1897
+ if (isSlackApprovalActionId(parsed.actionId)) {
1898
+ const approval = readSlackApprovalAction(parsed);
1899
+ if (!approval) {
1900
+ params.ctx.runtime.log?.(`slack:interaction drop malformed approval action user=${parsed.userId} channel=${parsed.channelId ?? "unknown"}`);
1901
+ await respondEphemeral(respond, "This approval action is invalid or expired.");
1902
+ return;
1903
+ }
1904
+ await handleSlackApprovalInteraction({
1905
+ ctx: params.ctx,
1906
+ parsed,
1907
+ approval,
1908
+ respond
1909
+ });
1910
+ return;
1911
+ }
1634
1912
  const pluginInteractionData = buildSlackPluginInteractionData({
1635
1913
  actionId: parsed.actionId,
1636
1914
  summary: parsed.actionSummary
1637
1915
  });
1638
1916
  if (pluginInteractionData && isSlackReplyActionId(parsed.actionId)) {
1639
- if (await handleSlackExecApprovalInteraction({
1917
+ if (await handleSlackLegacyApprovalInteraction({
1640
1918
  ctx: params.ctx,
1641
1919
  parsed,
1642
1920
  pluginInteractionData,
@@ -2293,6 +2571,66 @@ function registerSlackMemberEvents(params) {
2293
2571
  });
2294
2572
  }
2295
2573
  //#endregion
2574
+ //#region extensions/slack/src/monitor/event-scope.ts
2575
+ function resolveSlackEventScope(params) {
2576
+ const context = params.context ?? {};
2577
+ if (params.identity.kind !== "enterprise") return context.isEnterpriseInstall === true ? {
2578
+ ok: false,
2579
+ reason: "enterprise_event_for_workspace_account"
2580
+ } : { ok: true };
2581
+ const apiAppId = normalizeOptionalString((params.body && typeof params.body === "object" ? params.body : {}).api_app_id);
2582
+ if (!apiAppId) return {
2583
+ ok: false,
2584
+ reason: "missing_api_app_id"
2585
+ };
2586
+ if (params.identity.apiAppId && apiAppId !== params.identity.apiAppId) return {
2587
+ ok: false,
2588
+ reason: "wrong_app"
2589
+ };
2590
+ if (context.isEnterpriseInstall !== true) return {
2591
+ ok: false,
2592
+ reason: "not_enterprise_install"
2593
+ };
2594
+ const enterpriseId = normalizeOptionalString(context.enterpriseId);
2595
+ if (!enterpriseId) return {
2596
+ ok: false,
2597
+ reason: "missing_enterprise_id"
2598
+ };
2599
+ if (enterpriseId !== params.identity.enterpriseId) return {
2600
+ ok: false,
2601
+ reason: "wrong_enterprise"
2602
+ };
2603
+ const teamId = normalizeOptionalString(context.teamId);
2604
+ if (!teamId) return {
2605
+ ok: false,
2606
+ reason: "missing_team_id"
2607
+ };
2608
+ if (!/^T[A-Z0-9]+$/i.test(teamId)) return {
2609
+ ok: false,
2610
+ reason: "invalid_team_id"
2611
+ };
2612
+ if (!params.client) return {
2613
+ ok: false,
2614
+ reason: "missing_listener_client"
2615
+ };
2616
+ const uploadCompletionClient = getSlackListenerUploadCompletionClient({
2617
+ listenerClient: params.client,
2618
+ teamId,
2619
+ clientOptions: params.clientOptions
2620
+ });
2621
+ return {
2622
+ ok: true,
2623
+ scope: {
2624
+ apiAppId,
2625
+ enterpriseId,
2626
+ teamId,
2627
+ isEnterpriseInstall: true,
2628
+ client: params.client,
2629
+ ...uploadCompletionClient ? { uploadCompletionClient } : {}
2630
+ }
2631
+ };
2632
+ }
2633
+ //#endregion
2296
2634
  //#region extensions/slack/src/monitor/events/message-subtype-handlers.ts
2297
2635
  const SUBTYPE_HANDLER_REGISTRY = {
2298
2636
  message_changed: {
@@ -2340,6 +2678,9 @@ function formatSlackInboundLogLine(params) {
2340
2678
  function isSlackUserId(value) {
2341
2679
  return /^[UW][A-Z0-9]+$/.test(value);
2342
2680
  }
2681
+ function isBotAuthoredEnterpriseEvent(event) {
2682
+ return Boolean(normalizeOptionalString(event.bot_id)) || event.subtype === "bot_message";
2683
+ }
2343
2684
  function addUserCandidate(candidates, value, botUserId) {
2344
2685
  const id = normalizeOptionalString(value);
2345
2686
  if (!id || id === botUserId || !isSlackUserId(id)) return;
@@ -2396,21 +2737,57 @@ function resolveAssistantMessageChangedInbound(params) {
2396
2737
  event_ts: changed.event_ts,
2397
2738
  assistant_thread: asOptionalRecord(message.assistant_thread) ?? asOptionalRecord(changed.assistant_thread),
2398
2739
  files: Array.isArray(message.files) ? message.files : void 0,
2399
- attachments: Array.isArray(message.attachments) ? message.attachments : void 0
2740
+ attachments: Array.isArray(message.attachments) ? message.attachments : void 0,
2741
+ blocks: Array.isArray(message.blocks) ? message.blocks : void 0
2400
2742
  };
2401
2743
  }
2402
2744
  function registerSlackMessageEvents(params) {
2403
2745
  const { ctx, handleSlackMessage } = params;
2404
- const handleIncomingMessageEvent = async ({ event, body }) => {
2746
+ const resolveEventScope = (args) => {
2747
+ const resolved = resolveSlackEventScope({
2748
+ identity: ctx.installationIdentity,
2749
+ body: args.body,
2750
+ context: args.context,
2751
+ client: args.client,
2752
+ clientOptions: ctx.app.webClientOptions
2753
+ });
2754
+ if (!resolved.ok) {
2755
+ logVerbose(`slack: drop event (${resolved.reason})`);
2756
+ return null;
2757
+ }
2758
+ return resolved.scope;
2759
+ };
2760
+ const handleIncomingMessageEvent = async ({ event, body, context, client }) => {
2405
2761
  try {
2762
+ const eventScope = resolveEventScope({
2763
+ body,
2764
+ context,
2765
+ client
2766
+ });
2767
+ if (eventScope === null) return;
2406
2768
  if (ctx.shouldDropMismatchedSlackEvent(body)) return;
2407
2769
  const message = event;
2770
+ ctx.rememberSlackChannelType(message.channel, message.channel_type, eventScope);
2771
+ if (eventScope && isBotAuthoredEnterpriseEvent(message)) {
2772
+ logVerbose("slack: drop enterprise bot-authored message");
2773
+ return;
2774
+ }
2775
+ if (eventScope && message.subtype && message.subtype !== "file_share") {
2776
+ logVerbose(`slack: drop enterprise message subtype=${message.subtype}`);
2777
+ return;
2778
+ }
2408
2779
  const assistantChangedInbound = resolveAssistantMessageChangedInbound({
2409
2780
  event: message,
2410
2781
  ctx
2411
2782
  });
2412
2783
  if (assistantChangedInbound) {
2413
- await handleSlackMessage(assistantChangedInbound, { source: "message" });
2784
+ await handleSlackMessage(assistantChangedInbound, {
2785
+ source: "message",
2786
+ ...eventScope ? {
2787
+ eventScope,
2788
+ awaitDispatch: true
2789
+ } : {}
2790
+ });
2414
2791
  return;
2415
2792
  }
2416
2793
  if (message.subtype === "message_changed" && isSelfAttributedMessageChange({
@@ -2435,25 +2812,39 @@ function registerSlackMessageEvents(params) {
2435
2812
  });
2436
2813
  return;
2437
2814
  }
2438
- await handleSlackMessage(message, { source: "message" });
2815
+ await handleSlackMessage(message, {
2816
+ source: "message",
2817
+ ...eventScope ? {
2818
+ eventScope,
2819
+ awaitDispatch: true
2820
+ } : {}
2821
+ });
2439
2822
  } catch (err) {
2440
2823
  ctx.runtime.error?.(danger(`slack handler failed: ${formatErrorMessage(err)}`));
2441
2824
  }
2442
2825
  };
2443
- ctx.app.event("message", async ({ event, body }) => {
2444
- await handleIncomingMessageEvent({
2445
- event,
2446
- body
2447
- });
2826
+ ctx.app.event("message", async (args) => {
2827
+ await handleIncomingMessageEvent(args);
2448
2828
  });
2449
- ctx.app.event("app_mention", async ({ event, body }) => {
2829
+ ctx.app.event("app_mention", async (args) => {
2830
+ const { event, body, context, client } = args;
2450
2831
  try {
2832
+ const eventScope = resolveEventScope({
2833
+ body,
2834
+ context,
2835
+ client
2836
+ });
2837
+ if (eventScope === null) return;
2451
2838
  if (ctx.shouldDropMismatchedSlackEvent(body)) return;
2452
2839
  const mention = event;
2840
+ if (eventScope && isBotAuthoredEnterpriseEvent(mention)) {
2841
+ logVerbose("slack: drop enterprise bot-authored app_mention");
2842
+ return;
2843
+ }
2453
2844
  const channelType = normalizeSlackChannelType(mention.channel_type, mention.channel);
2454
2845
  if (channelType === "im" || channelType === "mpim") return;
2455
2846
  slackInboundLog.info(formatSlackInboundLogLine({
2456
- workspaceId: ctx.teamId,
2847
+ workspaceId: eventScope?.teamId ?? ctx.teamId,
2457
2848
  channelId: mention.channel,
2458
2849
  channelType: channelType ?? "channel",
2459
2850
  userId: normalizeOptionalString(mention.user) ?? "unknown",
@@ -2462,7 +2853,11 @@ function registerSlackMessageEvents(params) {
2462
2853
  }));
2463
2854
  await handleSlackMessage(mention, {
2464
2855
  source: "app_mention",
2465
- wasMentioned: true
2856
+ wasMentioned: true,
2857
+ ...eventScope ? {
2858
+ eventScope,
2859
+ awaitDispatch: true
2860
+ } : {}
2466
2861
  });
2467
2862
  } catch (err) {
2468
2863
  ctx.runtime.error?.(danger(`slack mention handler failed: ${formatErrorMessage(err)}`));
@@ -2591,6 +2986,7 @@ function registerSlackMonitorEvents(params) {
2591
2986
  ctx: params.ctx,
2592
2987
  handleSlackMessage: params.handleSlackMessage
2593
2988
  });
2989
+ if (params.ctx.installationIdentity.kind === "enterprise") return;
2594
2990
  registerSlackReactionEvents({
2595
2991
  ctx: params.ctx,
2596
2992
  trackEvent: params.trackEvent
@@ -2609,6 +3005,7 @@ function registerSlackMonitorEvents(params) {
2609
3005
  });
2610
3006
  registerSlackHomeEvents({
2611
3007
  ctx: params.ctx,
3008
+ slashCommandName: params.appHomeSlashCommandName,
2612
3009
  trackEvent: params.trackEvent
2613
3010
  });
2614
3011
  registerSlackInteractionEvents({
@@ -2640,12 +3037,12 @@ const deliveredMessages = createPersistentDedupeCache({
2640
3037
  }
2641
3038
  }
2642
3039
  });
2643
- function makeKey(accountId, channelId, ts) {
2644
- return `${accountId}:${channelId}:${ts}`;
3040
+ function makeKey(accountId, channelId, ts, teamId) {
3041
+ return `${accountId}:${teamId ? `${teamId}:` : ""}${channelId}:${ts}`;
2645
3042
  }
2646
3043
  async function hasSlackInboundMessageDelivery(params) {
2647
3044
  if (!params.accountId || !params.channelId || !params.ts) return false;
2648
- return await deliveredMessages.lookup(makeKey(params.accountId, params.channelId, params.ts));
3045
+ return await deliveredMessages.lookup(makeKey(params.accountId, params.channelId, params.ts, params.teamId));
2649
3046
  }
2650
3047
  async function recordSlackInboundMessageDeliveries(params) {
2651
3048
  if (!params.accountId || params.messages.length === 0) return;
@@ -2653,7 +3050,7 @@ async function recordSlackInboundMessageDeliveries(params) {
2653
3050
  const keys = /* @__PURE__ */ new Set();
2654
3051
  for (const message of params.messages) {
2655
3052
  if (!message.channel || !message.ts) continue;
2656
- keys.add(makeKey(params.accountId, message.channel, message.ts));
3053
+ keys.add(makeKey(params.accountId, message.channel, message.ts, params.teamId));
2657
3054
  }
2658
3055
  await Promise.all(Array.from(keys, (key) => deliveredMessages.register(key, { deliveredAt }, { at: deliveredAt })));
2659
3056
  }
@@ -2668,17 +3065,18 @@ function isSlackDirectMessageChannel(channelId) {
2668
3065
  function isTopLevelSlackMessage(message) {
2669
3066
  return !message.thread_ts && !message.parent_user_id;
2670
3067
  }
2671
- function buildTopLevelSlackConversationKey(message, accountId) {
3068
+ function buildTopLevelSlackConversationKey(message, accountId, teamId) {
2672
3069
  if (!isTopLevelSlackMessage(message)) return null;
2673
3070
  const senderId = resolveSlackSenderId(message);
2674
3071
  if (!senderId) return null;
2675
- return `slack:${accountId}:${message.channel}:${senderId}`;
3072
+ return `slack:${accountId}:${teamId ? `${teamId}:` : ""}${message.channel}:${senderId}`;
2676
3073
  }
2677
- function buildSlackDebounceKey(message, accountId) {
3074
+ function buildSlackDebounceKey(message, accountId, teamId) {
2678
3075
  const senderId = resolveSlackSenderId(message);
2679
3076
  if (!senderId) return null;
2680
3077
  const messageTs = message.ts ?? message.event_ts;
2681
- return `slack:${accountId}:${message.thread_ts ? `${message.channel}:${message.thread_ts}` : message.parent_user_id && messageTs ? `${message.channel}:maybe-thread:${messageTs}` : messageTs && !isSlackDirectMessageChannel(message.channel) ? `${message.channel}:${messageTs}` : message.channel}:${senderId}`;
3078
+ const threadKey = message.thread_ts ? `${message.channel}:${message.thread_ts}` : message.parent_user_id && messageTs ? `${message.channel}:maybe-thread:${messageTs}` : messageTs && !isSlackDirectMessageChannel(message.channel) ? `${message.channel}:${messageTs}` : message.channel;
3079
+ return `slack:${accountId}:${teamId ? `${teamId}:` : ""}${threadKey}:${senderId}`;
2682
3080
  }
2683
3081
  //#endregion
2684
3082
  //#region extensions/slack/src/monitor/thread-resolution.ts
@@ -2782,7 +3180,7 @@ function createSlackThreadTsResolver(params) {
2782
3180
  }
2783
3181
  //#endregion
2784
3182
  //#region extensions/slack/src/monitor/message-handler.ts
2785
- const loadSlackMessagePipeline = createLazyRuntimeModule(() => import("./pipeline.runtime-rpVpay59.js"));
3183
+ const loadSlackMessagePipeline = createLazyRuntimeModule(() => import("./pipeline.runtime-f0eJJoJL.js"));
2786
3184
  function createSlackDispatchCompletion() {
2787
3185
  let resolve;
2788
3186
  let reject;
@@ -2799,14 +3197,7 @@ const APP_MENTION_RETRY_TTL_MS = 6e4;
2799
3197
  const RETRYABLE_FLUSH_MAX_ATTEMPTS = 3;
2800
3198
  const RETRYABLE_FLUSH_RETRY_DELAY_MS = 1e3;
2801
3199
  const REPLY_SESSION_INIT_CONFLICT_MESSAGE_RE = /reply session initialization conflicted for \S+/u;
2802
- var SlackRetryableInboundError = class extends Error {
2803
- constructor(message, options) {
2804
- super(message, options);
2805
- this.name = "SlackRetryableInboundError";
2806
- }
2807
- };
2808
3200
  function isRetryableSlackInboundError(error) {
2809
- if (error instanceof SlackRetryableInboundError) return true;
2810
3201
  return collectErrorGraphCandidates(error, (current) => [current.cause, current.error]).some((candidate) => REPLY_SESSION_INIT_CONFLICT_MESSAGE_RE.test(formatErrorMessage(candidate)));
2811
3202
  }
2812
3203
  function shouldDebounceSlackMessage(message, cfg) {
@@ -2816,20 +3207,20 @@ function shouldDebounceSlackMessage(message, cfg) {
2816
3207
  hasMedia: Boolean(message.files && message.files.length > 0)
2817
3208
  });
2818
3209
  }
2819
- function buildSeenMessageKey(channelId, ts) {
3210
+ function buildSeenMessageKey(channelId, ts, teamId) {
2820
3211
  if (!channelId || !ts) return null;
2821
- return `${channelId}:${ts}`;
3212
+ return `${teamId ? `${teamId}:` : ""}${channelId}:${ts}`;
2822
3213
  }
2823
3214
  function createSlackMessageHandler(params) {
2824
3215
  const { ctx, account, trackEvent } = params;
2825
3216
  const { debounceMs, debouncer } = createChannelInboundDebouncer({
2826
3217
  cfg: ctx.cfg,
2827
3218
  channel: "slack",
2828
- buildKey: (entry) => buildSlackDebounceKey(entry.message, ctx.accountId),
2829
- shouldDebounce: (entry) => shouldDebounceSlackMessage(entry.message, ctx.cfg),
3219
+ buildKey: (entry) => buildSlackDebounceKey(entry.message, ctx.accountId, entry.opts.eventScope?.teamId),
3220
+ shouldDebounce: (entry) => !entry.opts.eventScope && shouldDebounceSlackMessage(entry.message, ctx.cfg),
2830
3221
  onFlush: async (entries) => {
2831
3222
  const retryEntries = (sourceError) => {
2832
- if (!isRetryableSlackInboundError(sourceError)) return false;
3223
+ if (!isRetryableSlackInboundError(sourceError) || entries.some((entry) => entry.opts.eventScope)) return false;
2833
3224
  const nextEntries = entries.map((entry) => {
2834
3225
  if (entry.opts.dispatchCompletion) return null;
2835
3226
  const retryAttempt = entry.opts.retryAttempt ?? 0;
@@ -2856,8 +3247,9 @@ function createSlackMessageHandler(params) {
2856
3247
  await (async () => {
2857
3248
  const last = entries.at(-1);
2858
3249
  if (!last) return;
2859
- const flushedKey = buildSlackDebounceKey(last.message, ctx.accountId);
2860
- const topLevelConversationKey = buildTopLevelSlackConversationKey(last.message, ctx.accountId);
3250
+ const teamId = last.opts.eventScope?.teamId;
3251
+ const flushedKey = buildSlackDebounceKey(last.message, ctx.accountId, teamId);
3252
+ const topLevelConversationKey = buildTopLevelSlackConversationKey(last.message, ctx.accountId, teamId);
2861
3253
  if (flushedKey && topLevelConversationKey) {
2862
3254
  const pendingKeys = pendingTopLevelDebounceKeys.get(topLevelConversationKey);
2863
3255
  if (pendingKeys) {
@@ -2871,7 +3263,7 @@ function createSlackMessageHandler(params) {
2871
3263
  ...last.message,
2872
3264
  text: combinedText
2873
3265
  };
2874
- const seenMessageKey = buildSeenMessageKey(last.message.channel, last.message.ts);
3266
+ const seenMessageKey = buildSeenMessageKey(last.message.channel, last.message.ts, last.opts.eventScope?.teamId);
2875
3267
  try {
2876
3268
  const { prepareSlackMessage, dispatchPreparedSlackMessage } = await loadSlackMessagePipeline();
2877
3269
  const { dispatchCompletion: _completion, awaitDispatch: _awaitDispatch, ...lastOpts } = last.opts;
@@ -2917,20 +3309,22 @@ function createSlackMessageHandler(params) {
2917
3309
  await dispatchPreparedSlackMessage(prepared);
2918
3310
  await recordSlackInboundMessageDeliveries({
2919
3311
  accountId: ctx.accountId,
3312
+ ...last.opts.eventScope ? { teamId: last.opts.eventScope.teamId } : {},
2920
3313
  messages: entries.map((entry) => entry.message)
2921
3314
  });
2922
3315
  } catch (error) {
2923
3316
  if (!isRetryableSlackInboundError(error)) await recordSlackInboundMessageDeliveries({
2924
3317
  accountId: ctx.accountId,
3318
+ ...last.opts.eventScope ? { teamId: last.opts.eventScope.teamId } : {},
2925
3319
  messages: entries.map((entry) => entry.message)
2926
3320
  });
2927
3321
  throw error;
2928
3322
  }
2929
3323
  } catch (error) {
2930
3324
  if (isRetryableSlackInboundError(error)) for (const entry of entries) {
2931
- const entrySeenKey = buildSeenMessageKey(entry.message.channel, entry.message.ts);
3325
+ const entrySeenKey = buildSeenMessageKey(entry.message.channel, entry.message.ts, entry.opts.eventScope?.teamId);
2932
3326
  if (entrySeenKey) appMentionDispatchedKeys.delete(entrySeenKey);
2933
- ctx.releaseSeenMessage(entry.message.channel, entry.message.ts);
3327
+ ctx.releaseSeenMessage(entry.message.channel, entry.message.ts, entry.opts.eventScope);
2934
3328
  }
2935
3329
  throw error;
2936
3330
  }
@@ -2980,25 +3374,28 @@ function createSlackMessageHandler(params) {
2980
3374
  async function enqueueSlackMessage(message, opts) {
2981
3375
  if (opts.source === "message" && message.type !== "message") return;
2982
3376
  if (opts.source === "message" && message.subtype && message.subtype !== "file_share" && message.subtype !== "bot_message" && message.subtype !== "thread_broadcast") return;
2983
- const seenMessageKey = buildSeenMessageKey(message.channel, message.ts);
3377
+ ctx.rememberSlackChannelType(message.channel, message.channel_type, opts.eventScope);
3378
+ const seenMessageKey = buildSeenMessageKey(message.channel, message.ts, opts.eventScope?.teamId);
2984
3379
  if (seenMessageKey && await hasSlackInboundMessageDelivery({
2985
3380
  accountId: ctx.accountId,
2986
3381
  channelId: message.channel,
2987
- ts: message.ts
3382
+ ts: message.ts,
3383
+ ...opts.eventScope ? { teamId: opts.eventScope.teamId } : {}
2988
3384
  })) return;
2989
- const wasSeen = seenMessageKey ? ctx.markMessageSeen(message.channel, message.ts) : false;
3385
+ const wasSeen = seenMessageKey ? ctx.markMessageSeen(message.channel, message.ts, opts.eventScope) : false;
2990
3386
  if (seenMessageKey && opts.source === "message" && !wasSeen) rememberAppMentionRetryKey(seenMessageKey);
2991
3387
  if (seenMessageKey && wasSeen) {
2992
3388
  if (opts.source !== "app_mention" || !consumeAppMentionRetryKey(seenMessageKey)) return;
2993
3389
  }
2994
3390
  trackEvent?.();
2995
- const resolvedMessage = await threadTsResolver.resolve({
3391
+ const resolvedMessage = await (opts.eventScope ? createSlackThreadTsResolver({ client: opts.eventScope.client }) : threadTsResolver).resolve({
2996
3392
  message,
2997
3393
  source: opts.source
2998
3394
  });
2999
- const debounceKey = buildSlackDebounceKey(resolvedMessage, ctx.accountId);
3000
- const conversationKey = buildTopLevelSlackConversationKey(resolvedMessage, ctx.accountId);
3001
- const canDebounce = debounceMs > 0 && shouldDebounceSlackMessage(resolvedMessage, ctx.cfg);
3395
+ const teamId = opts.eventScope?.teamId;
3396
+ const debounceKey = buildSlackDebounceKey(resolvedMessage, ctx.accountId, teamId);
3397
+ const conversationKey = buildTopLevelSlackConversationKey(resolvedMessage, ctx.accountId, teamId);
3398
+ const canDebounce = !opts.eventScope && debounceMs > 0 && shouldDebounceSlackMessage(resolvedMessage, ctx.cfg);
3002
3399
  if (!canDebounce && conversationKey) {
3003
3400
  const pendingKeys = pendingTopLevelDebounceKeys.get(conversationKey);
3004
3401
  if (pendingKeys && pendingKeys.size > 0) {
@@ -3389,8 +3786,11 @@ async function gracefulStopSlackApp(app) {
3389
3786
  }
3390
3787
  function formatSlackResolvedLabel(params) {
3391
3788
  const extras = params.extra?.filter(Boolean) ?? [];
3392
- const suffix = extras.length > 0 ? ` (id:${params.id}, ${extras.join(", ")})` : ` (id:${params.id})`;
3393
- return `${params.input}→${params.name ?? params.id}${suffix}`;
3789
+ const display = params.name ?? params.id;
3790
+ if (params.input === params.id && !params.name && extras.length === 0) return null;
3791
+ const details = [...params.input === params.id || display === params.id ? [] : [`id:${params.id}`], ...extras];
3792
+ const suffix = details.length > 0 ? ` (${details.join(", ")})` : "";
3793
+ return `${params.input}→${display}${suffix}`;
3394
3794
  }
3395
3795
  function formatSlackChannelResolved(entry) {
3396
3796
  const id = entry.id ?? entry.input;
@@ -3511,6 +3911,25 @@ function createSlackExternalArgMenuStore() {
3511
3911
  };
3512
3912
  }
3513
3913
  //#endregion
3914
+ //#region extensions/slack/src/monitor/response-url-budget.ts
3915
+ const SLACK_RESPONSE_URL_MAX_CALLS = 5;
3916
+ var SlackResponseAlreadyReportedError = class extends Error {};
3917
+ function isSlackResponseAlreadyReportedError(error) {
3918
+ return error instanceof SlackResponseAlreadyReportedError;
3919
+ }
3920
+ /** Count every response_url attempt, including requests Slack rejects. */
3921
+ function createSlackResponseUrlBudget(respond, maxCalls = SLACK_RESPONSE_URL_MAX_CALLS) {
3922
+ let remaining = maxCalls;
3923
+ return {
3924
+ remaining: () => remaining,
3925
+ respond: async (payload) => {
3926
+ if (remaining <= 0) throw new Error(`Slack response_url cannot be used more than ${String(maxCalls)} times.`);
3927
+ remaining -= 1;
3928
+ return await respond(payload);
3929
+ }
3930
+ };
3931
+ }
3932
+ //#endregion
3514
3933
  //#region extensions/slack/src/monitor/room-context.ts
3515
3934
  function resolveSlackRoomContextHints(params) {
3516
3935
  const untrustedChannelMetadata = params.isRoomish ? buildUntrustedChannelMetadata({
@@ -3541,7 +3960,7 @@ const SLACK_COMMAND_ARG_CONFIRM_TEXT_MAX = 300;
3541
3960
  const SLACK_HEADER_TEXT_MAX = 150;
3542
3961
  const SLACK_COMMAND_ARG_ACTION_BLOCKS_MAX = 47;
3543
3962
  const loadSlashCommandsRuntime = createLazyRuntimeModule(() => import("./slash-commands.runtime-BTTMlmwy.js"));
3544
- const loadSlashDispatchRuntime = createLazyRuntimeModule(() => import("./slash-dispatch.runtime-CcMbbT3_.js"));
3963
+ const loadSlashDispatchRuntime = createLazyRuntimeModule(() => import("./slash-dispatch.runtime-C0Otuf_T.js"));
3545
3964
  const loadSlackPluginCommandsRuntime = createLazyRuntimeModule(() => import("./slash-plugin-commands.runtime-Dosfcx3X.js"));
3546
3965
  const loadSlashSkillCommandsRuntime = createLazyRuntimeModule(() => import("./slash-skill-commands.runtime-CD1J2ODY.js"));
3547
3966
  function resolveSlackCommandMenuModelContext(params) {
@@ -3771,7 +4190,12 @@ async function registerSlackMonitorSlashCommands(params) {
3771
4190
  let supportsExternalArgMenus = typeof ctx.app.options === "function";
3772
4191
  const slashCommand = resolveSlackSlashCommandConfig(ctx.slashCommand ?? account.config.slashCommand);
3773
4192
  const handleSlashCommand = async (p) => {
3774
- const { command, ack, respond, body, prompt, commandArgs, commandDefinition } = p;
4193
+ const { command, ack, respond: respondWithoutBudget, body, prompt, commandArgs, commandDefinition } = p;
4194
+ const responseBudget = p.responseTransport === "web-api" ? {
4195
+ respond: respondWithoutBudget,
4196
+ remaining: () => void 0
4197
+ } : createSlackResponseUrlBudget(respondWithoutBudget);
4198
+ const respond = responseBudget.respond;
3775
4199
  const cfg = getRuntimeConfigSnapshot() ?? ctx.cfg;
3776
4200
  try {
3777
4201
  if (ctx.shouldDropMismatchedSlackEvent?.(body)) {
@@ -3887,7 +4311,7 @@ async function registerSlackMonitorSlashCommands(params) {
3887
4311
  modeWhenAccessGroupsOff: "configured"
3888
4312
  });
3889
4313
  const senderGate = slashIngress.senderAccess.gate;
3890
- if (isRoom && senderGate?.allowed === false) {
4314
+ if (isRoomish && senderGate?.allowed === false) {
3891
4315
  await respond({
3892
4316
  text: "You are not authorized to use this command here.",
3893
4317
  response_type: "ephemeral"
@@ -4062,15 +4486,19 @@ async function registerSlackMonitorSlashCommands(params) {
4062
4486
  cfg,
4063
4487
  channel: "slack",
4064
4488
  accountId: route.accountId
4065
- })
4489
+ }),
4490
+ responseBudget
4066
4491
  });
4067
4492
  };
4493
+ const pendingSlashReplies = [];
4068
4494
  const { counts } = await dispatchReplyWithDispatcher({
4069
4495
  ctx: ctxPayload,
4070
4496
  cfg,
4071
4497
  dispatcherOptions: {
4072
4498
  ...replyPipeline,
4073
- deliver: async (payload) => deliverSlashPayloads([payload]),
4499
+ deliver: async (payload) => {
4500
+ pendingSlashReplies.push(payload);
4501
+ },
4074
4502
  onError: (err, info) => {
4075
4503
  runtime.error?.(danger(`slack slash ${info.kind} reply failed: ${formatSlackError(err)}`));
4076
4504
  }
@@ -4080,10 +4508,11 @@ async function registerSlackMonitorSlashCommands(params) {
4080
4508
  onModelSelected
4081
4509
  }
4082
4510
  });
4083
- if (counts.final + counts.tool + counts.block === 0) await deliverSlashPayloads([]);
4511
+ if (pendingSlashReplies.length > 0) await deliverSlashPayloads(pendingSlashReplies);
4512
+ else if (counts.final + counts.tool + counts.block === 0) await deliverSlashPayloads([]);
4084
4513
  } catch (err) {
4085
4514
  runtime.error?.(danger(`slack slash handler failed: ${formatErrorMessage(err)}`));
4086
- await respond({
4515
+ if (!isSlackResponseAlreadyReportedError(err) && responseBudget.remaining() !== 0) await respond({
4087
4516
  text: "Sorry, something went wrong handling that command.",
4088
4517
  response_type: "ephemeral"
4089
4518
  });
@@ -4117,7 +4546,16 @@ async function registerSlackMonitorSlashCommands(params) {
4117
4546
  nativeCommands.push(pluginCommand);
4118
4547
  }
4119
4548
  }
4120
- if (nativeCommands.length > 0) {
4549
+ if (slashCommand.enabled) ctx.app.command(buildSlackSlashCommandMatcher(slashCommand.name), async ({ command, ack, respond, body }) => {
4550
+ await handleSlashCommand({
4551
+ command,
4552
+ ack,
4553
+ respond,
4554
+ body,
4555
+ prompt: command.text?.trim() ?? ""
4556
+ });
4557
+ });
4558
+ else if (nativeCommands.length > 0) {
4121
4559
  if (!slashCommandsRuntime) throw new Error("Missing commands runtime for native Slack commands.");
4122
4560
  for (const command of nativeCommands) ctx.app.command(`/${command.name}`, async ({ command: cmd, ack, respond, body }) => {
4123
4561
  const commandDefinition = slashCommandsRuntime.findCommandByNativeName(command.name, "slack");
@@ -4134,17 +4572,12 @@ async function registerSlackMonitorSlashCommands(params) {
4134
4572
  commandDefinition: commandDefinition ?? void 0
4135
4573
  });
4136
4574
  });
4137
- } else if (slashCommand.enabled) ctx.app.command(buildSlackSlashCommandMatcher(slashCommand.name), async ({ command, ack, respond, body }) => {
4138
- await handleSlashCommand({
4139
- command,
4140
- ack,
4141
- respond,
4142
- body,
4143
- prompt: command.text?.trim() ?? ""
4144
- });
4145
- });
4146
- else logVerbose("slack: slash commands disabled");
4147
- if (nativeCommands.length === 0 || !supportsInteractiveArgMenus) return;
4575
+ } else logVerbose("slack: slash commands disabled");
4576
+ const registration = nativeCommands.length > 0 ? { mode: "native" } : slashCommand.enabled ? {
4577
+ mode: "single",
4578
+ name: slashCommand.name
4579
+ } : { mode: "disabled" };
4580
+ if (nativeCommands.length === 0 || !supportsInteractiveArgMenus) return registration;
4148
4581
  const registerArgOptions = () => {
4149
4582
  const appWithOptions = ctx.app;
4150
4583
  if (typeof appWithOptions.options !== "function") return;
@@ -4190,21 +4623,24 @@ async function registerSlackMonitorSlashCommands(params) {
4190
4623
  }
4191
4624
  const registerArgAction = (actionId) => {
4192
4625
  ctx.app.action(actionId, async (args) => {
4193
- const { ack, body, respond } = args;
4626
+ const { ack, body } = args;
4627
+ const respond = args.respond;
4194
4628
  const action = args.action;
4195
4629
  await ack();
4196
4630
  if (ctx.shouldDropMismatchedSlackEvent?.(body)) {
4197
4631
  runtime.log?.("slack: drop slash arg action payload (mismatched app/team)");
4198
4632
  return;
4199
4633
  }
4200
- const respondFn = respond ?? (async (payload) => {
4634
+ const respondFn = respond ?? (async (message) => {
4201
4635
  if (!body.channel?.id || !body.user?.id) return;
4636
+ const payload = typeof message === "string" ? { text: message } : message;
4202
4637
  await ctx.app.client.chat.postEphemeral({
4203
4638
  token: ctx.botToken,
4204
4639
  channel: body.channel.id,
4205
4640
  user: body.user.id,
4206
- text: payload.text,
4207
- blocks: payload.blocks
4641
+ text: payload.text ?? "",
4642
+ ...payload.blocks ? { blocks: payload.blocks } : {},
4643
+ ...typeof payload.mrkdwn === "boolean" ? { mrkdwn: payload.mrkdwn } : {}
4208
4644
  });
4209
4645
  });
4210
4646
  const parsed = parseSlackCommandArgValue(action?.value ?? action?.selected_option?.value);
@@ -4240,6 +4676,7 @@ async function registerSlackMonitorSlashCommands(params) {
4240
4676
  command: commandPayload,
4241
4677
  ack: async () => {},
4242
4678
  respond: respondFn,
4679
+ responseTransport: respond ? "response-url" : "web-api",
4243
4680
  body,
4244
4681
  prompt,
4245
4682
  commandArgs,
@@ -4248,6 +4685,7 @@ async function registerSlackMonitorSlashCommands(params) {
4248
4685
  });
4249
4686
  };
4250
4687
  registerArgAction(SLACK_COMMAND_ARG_ACTION_LISTENER);
4688
+ return registration;
4251
4689
  }
4252
4690
  //#endregion
4253
4691
  //#region extensions/slack/src/monitor/provider.ts
@@ -4262,7 +4700,7 @@ async function getSlackBoltInterop() {
4262
4700
  }
4263
4701
  return slackBoltInterop;
4264
4702
  }
4265
- const loadSlackRelaySource = createLazyRuntimeModule(() => import("./relay-source-D2jv1iGg.js"));
4703
+ const loadSlackRelaySource = createLazyRuntimeModule(() => import("./relay-source-B-6LFUdz.js"));
4266
4704
  const SLACK_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
4267
4705
  const SLACK_WEBHOOK_BODY_TIMEOUT_MS = 3e4;
4268
4706
  function resolveStableSlackUserIdEntry(raw) {
@@ -4336,6 +4774,19 @@ async function monitorSlackProvider(opts = {}) {
4336
4774
  const sessionScope = sessionCfg?.scope ?? "per-sender";
4337
4775
  const mainKey = normalizeMainKey(sessionCfg?.mainKey);
4338
4776
  const slackMode = opts.mode ?? account.config.mode ?? "socket";
4777
+ const enterpriseOrgInstall = account.config.enterpriseOrgInstall === true;
4778
+ if (enterpriseOrgInstall && slackMode === "relay") throw new Error(`Slack Enterprise Grid org account "${account.accountId}" requires direct socket or HTTP delivery; relay mode is unsupported`);
4779
+ if (enterpriseOrgInstall && account.config.execApprovals?.enabled === true) throw new Error(`Slack Enterprise Grid org account "${account.accountId}" does not support Slack-native exec approvals`);
4780
+ if (enterpriseOrgInstall) {
4781
+ assertEnterpriseSlackPolicyConfig({
4782
+ config: account.config,
4783
+ accountId: account.accountId
4784
+ });
4785
+ assertNoEnterpriseSlackBindings({
4786
+ cfg,
4787
+ accountId: account.accountId
4788
+ });
4789
+ }
4339
4790
  const slackWebhookPath = normalizeSlackWebhookPath(account.config.webhookPath);
4340
4791
  const signingSecret = normalizeResolvedSecretInputString({
4341
4792
  value: account.config.signingSecret,
@@ -4363,6 +4814,12 @@ async function monitorSlackProvider(opts = {}) {
4363
4814
  cfg,
4364
4815
  accountId: account.accountId
4365
4816
  });
4817
+ if (enterpriseOrgInstall) assertEnterpriseSlackDmPolicy({
4818
+ accountId: account.accountId,
4819
+ dmEnabled,
4820
+ dmPolicy,
4821
+ allowFrom
4822
+ });
4366
4823
  const groupDmEnabled = dmConfig?.groupEnabled ?? false;
4367
4824
  const groupDmChannels = dmConfig?.groupChannels;
4368
4825
  let channelsConfig = slackCfg.channels;
@@ -4432,24 +4889,22 @@ async function monitorSlackProvider(opts = {}) {
4432
4889
  }) : () => {};
4433
4890
  let botUserId = "";
4434
4891
  let botId = "";
4435
- let teamId = "";
4436
- let apiAppId = "";
4437
4892
  const expectedApiAppIdFromAppToken = slackMode === "socket" ? parseApiAppIdFromAppToken(appToken) : void 0;
4438
4893
  let authTestFailed = false;
4439
4894
  let authTestError;
4440
4895
  let authIdentityWarning;
4896
+ let authTestIdentity;
4441
4897
  try {
4442
4898
  const auth = await app.client.auth.test();
4443
4899
  const authUserId = normalizeOptionalString(auth.user_id) ?? "";
4444
4900
  botId = normalizeOptionalString(auth.bot_id) ?? "";
4445
4901
  botUserId = botId ? authUserId : "";
4446
- teamId = auth.team_id ?? "";
4447
- apiAppId = auth.api_app_id ?? "";
4902
+ authTestIdentity = auth;
4448
4903
  authIdentityWarning = formatSlackBotTokenIdentityWarning({
4449
4904
  auth,
4450
4905
  accountId: account.accountId
4451
4906
  });
4452
- if (!authUserId) {
4907
+ if (!authUserId && !enterpriseOrgInstall) {
4453
4908
  authTestFailed = true;
4454
4909
  authTestError = "auth.test returned no user_id";
4455
4910
  }
@@ -4457,9 +4912,17 @@ async function monitorSlackProvider(opts = {}) {
4457
4912
  authTestFailed = true;
4458
4913
  authTestError = err instanceof Error ? err.message : String(err);
4459
4914
  }
4915
+ const installationIdentity = resolveSlackInstallationIdentity({
4916
+ enterpriseOrgInstall,
4917
+ auth: authTestFailed ? void 0 : authTestIdentity,
4918
+ authError: authTestError,
4919
+ transportApiAppId: expectedApiAppIdFromAppToken
4920
+ });
4921
+ const teamId = installationIdentity.kind === "workspace" ? installationIdentity.teamId : "";
4922
+ const apiAppId = installationIdentity.kind === "degraded" ? "" : installationIdentity.apiAppId ?? "";
4460
4923
  if (authTestFailed) runtime.log?.(warn(`[${account.accountId}] slack auth.test failed at boot (${authTestError ?? "unknown error"}); explicit bot-mention detection will be disabled until restart with a valid bot token; required-mention channels will fail closed without another trusted activation signal`));
4461
4924
  if (authIdentityWarning) runtime.log?.(warn(authIdentityWarning));
4462
- if (apiAppId && expectedApiAppIdFromAppToken && apiAppId !== expectedApiAppIdFromAppToken) runtime.error?.(`slack token mismatch: bot token api_app_id=${apiAppId} but app token looks like api_app_id=${expectedApiAppIdFromAppToken}`);
4925
+ if (apiAppId && expectedApiAppIdFromAppToken && apiAppId !== expectedApiAppIdFromAppToken) runtime.error?.(`slack token mismatch: bot token app_id=${apiAppId} but app token looks like app_id=${expectedApiAppIdFromAppToken}`);
4463
4926
  const ctx = createSlackMonitorContext({
4464
4927
  cfg,
4465
4928
  accountId: account.accountId,
@@ -4471,6 +4934,7 @@ async function monitorSlackProvider(opts = {}) {
4471
4934
  botId,
4472
4935
  teamId,
4473
4936
  apiAppId,
4937
+ installationIdentity,
4474
4938
  historyLimit,
4475
4939
  dmHistoryLimit,
4476
4940
  sessionScope,
@@ -4509,7 +4973,7 @@ async function monitorSlackProvider(opts = {}) {
4509
4973
  account,
4510
4974
  trackEvent
4511
4975
  });
4512
- if (isSlackAnyNativeApprovalClientEnabled({
4976
+ if (installationIdentity.kind !== "enterprise" && isSlackAnyNativeApprovalClientEnabled({
4513
4977
  cfg,
4514
4978
  accountId: account.accountId
4515
4979
  })) registerChannelRuntimeContext({
@@ -4523,15 +4987,16 @@ async function monitorSlackProvider(opts = {}) {
4523
4987
  },
4524
4988
  abortSignal: opts.abortSignal
4525
4989
  });
4526
- registerSlackMonitorEvents({
4990
+ const commandRegistration = installationIdentity.kind === "enterprise" ? { mode: "disabled" } : await registerSlackMonitorSlashCommands({
4527
4991
  ctx,
4528
4992
  account,
4529
- handleSlackMessage,
4530
4993
  trackEvent
4531
4994
  });
4532
- await registerSlackMonitorSlashCommands({
4995
+ registerSlackMonitorEvents({
4533
4996
  ctx,
4534
4997
  account,
4998
+ handleSlackMessage,
4999
+ appHomeSlashCommandName: commandRegistration.mode === "single" ? commandRegistration.name : void 0,
4535
5000
  trackEvent
4536
5001
  });
4537
5002
  if (slackMode === "http" && slackHttpHandler) unregisterHttpHandler = registerSlackHttpHandler({
@@ -4540,7 +5005,7 @@ async function monitorSlackProvider(opts = {}) {
4540
5005
  log: runtime.log,
4541
5006
  accountId: account.accountId
4542
5007
  });
4543
- if (resolveToken) (async () => {
5008
+ if (resolveToken && installationIdentity.kind !== "enterprise") (async () => {
4544
5009
  if (opts.abortSignal?.aborted) return;
4545
5010
  if (channelsConfig && Object.keys(channelsConfig).length > 0) try {
4546
5011
  const entries = Object.keys(channelsConfig).filter((key) => key !== "*");
@@ -4559,7 +5024,8 @@ async function monitorSlackProvider(opts = {}) {
4559
5024
  unresolved.push(entry.input);
4560
5025
  continue;
4561
5026
  }
4562
- mapping.push(formatSlackChannelResolved(entry));
5027
+ const resolvedLabel = formatSlackChannelResolved(entry);
5028
+ if (resolvedLabel) mapping.push(resolvedLabel);
4563
5029
  const existing = nextChannels[entry.id] ?? {};
4564
5030
  nextChannels[entry.id] = {
4565
5031
  ...source,
@@ -4721,4 +5187,4 @@ async function monitorSlackProvider(opts = {}) {
4721
5187
  }
4722
5188
  const resolveSlackRuntimeGroupPolicy = resolveOpenProviderRuntimeGroupPolicy$1;
4723
5189
  //#endregion
4724
- export { stripSlackMentionsForCommandDetection as C, buildSlackSlashCommandMatcher as S, readSessionUpdatedAt as _, SLACK_SOCKET_RECONNECT_POLICY as a, resolveStorePath$1 as b, recordInboundSession as c, resolveSlackCommandIngress as d, resolveSlackEffectiveAllowFrom as f, resolveSlackChatType as g, normalizeSlackChannelType as h, authorizeSlackDirectMessage as i, resolveConversationLabel$1 as l, parseSlackAssistantThreadMetadata as m, resolveSlackRuntimeGroupPolicy as n, formatUnknownError as o, buildSlackAssistantThreadMetadata as p, resolveSlackRoomContextHints as r, escapeSlackMrkdwn as s, monitorSlackProvider as t, authorizeSlackBotRoomMessage as u, resolveChannelContextVisibilityMode as v, updateLastRoute as x, resolveChannelResetConfig as y };
5190
+ export { buildSlackSlashCommandMatcher as C, updateLastRoute as S, resolveSlackChatType as _, createSlackResponseUrlBudget as a, resolveChannelResetConfig as b, formatUnknownError as c, authorizeSlackBotRoomMessage as d, resolveSlackCommandIngress as f, normalizeSlackChannelType as g, parseSlackAssistantThreadMetadata as h, SlackResponseAlreadyReportedError as i, recordInboundSession as l, buildSlackAssistantThreadMetadata as m, resolveSlackRuntimeGroupPolicy as n, authorizeSlackDirectMessage as o, resolveSlackEffectiveAllowFrom as p, resolveSlackRoomContextHints as r, SLACK_SOCKET_RECONNECT_POLICY as s, monitorSlackProvider as t, resolveConversationLabel$1 as u, readSessionUpdatedAt as v, stripSlackMentionsForCommandDetection as w, resolveStorePath$1 as x, resolveChannelContextVisibilityMode as y };