@openclaw/slack 2026.5.12-beta.7

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 (88) hide show
  1. package/dist/account-inspect-D7AZNs8C.js +77 -0
  2. package/dist/account-inspect-api.js +10 -0
  3. package/dist/accounts-ClAPP5ry.js +139 -0
  4. package/dist/accounts.runtime-DDVcLJUI.js +2 -0
  5. package/dist/action-runtime-e2UhRsNx.js +350 -0
  6. package/dist/action-runtime.runtime-BFcqMbOm.js +2 -0
  7. package/dist/actions-CYLFK-Zy.js +292 -0
  8. package/dist/actions.runtime-CO3OaTLb.js +2 -0
  9. package/dist/allow-list-BPnnlRPL.js +82 -0
  10. package/dist/api.js +21 -0
  11. package/dist/approval-handler.runtime-CmeRr9qA.js +256 -0
  12. package/dist/blocks-input-CwTFVImV.js +29 -0
  13. package/dist/blocks-render-BIDw-Pom.js +161 -0
  14. package/dist/channel-DRjHBTDB.js +1020 -0
  15. package/dist/channel-api-B_nZwosg.js +20 -0
  16. package/dist/channel-config-api.js +2 -0
  17. package/dist/channel-entry.js +22 -0
  18. package/dist/channel-plugin-api.js +2 -0
  19. package/dist/channel.setup-Cayn7afd.js +73 -0
  20. package/dist/client-CPe4GmDR.js +103 -0
  21. package/dist/config-api-B_jq4NJW.js +2 -0
  22. package/dist/config-schema-D9B5LB_L.js +167 -0
  23. package/dist/configured-state.js +11 -0
  24. package/dist/contract-api.js +5 -0
  25. package/dist/directory-config-B3JiHeB7.js +54 -0
  26. package/dist/directory-contract-api.js +2 -0
  27. package/dist/directory-live-Bf16GwDh.js +133 -0
  28. package/dist/doctor-contract-KUjHnkQm.js +147 -0
  29. package/dist/doctor-contract-api.js +2 -0
  30. package/dist/errors-BYFHR24f.js +109 -0
  31. package/dist/exec-approvals-7xUNgLi9.js +58 -0
  32. package/dist/group-policy-CyLUK6My.js +41 -0
  33. package/dist/http-routes-api.js +2 -0
  34. package/dist/inbound-contract-test-api.js +3 -0
  35. package/dist/index.js +33 -0
  36. package/dist/interactive-replies-api.js +2 -0
  37. package/dist/interactive-replies-qAIfuBor.js +173 -0
  38. package/dist/magic-string.es-BMaGRRZ1.js +1011 -0
  39. package/dist/media-D1XCd1uP.js +469 -0
  40. package/dist/message-tool-api-6lowf9zE.js +104 -0
  41. package/dist/message-tool-api.js +2 -0
  42. package/dist/monitor-a97o17G6.js +13 -0
  43. package/dist/mrkdwn-Cax-eSfK.js +6 -0
  44. package/dist/outbound-adapter-B_5sEhCg.js +174 -0
  45. package/dist/outbound-payload-test-api.js +2 -0
  46. package/dist/outbound-payload.test-harness-CVCamg1x.js +13558 -0
  47. package/dist/pipeline.runtime-DT0hLnq2.js +1379 -0
  48. package/dist/plugin-routes-DtTPmga1.js +20 -0
  49. package/dist/prepare-D3YqV8jB.js +1482 -0
  50. package/dist/prepare.test-helpers-DVcjRhfG.js +49 -0
  51. package/dist/probe-3eZf1FjI.js +42 -0
  52. package/dist/provider-D7uAN3Fq.js +3235 -0
  53. package/dist/registry-CeaoNfoP.js +39 -0
  54. package/dist/replies-Xe_jMR6o.js +139 -0
  55. package/dist/reply-blocks-Z5l6_R6H.js +14 -0
  56. package/dist/resolve-allowlist-common-Bk3clYPK.js +43 -0
  57. package/dist/resolve-channels-BRYqyNVJ.js +81 -0
  58. package/dist/resolve-users-Bd_SdP8j.js +113 -0
  59. package/dist/rolldown-runtime-CiIaOW0V.js +13 -0
  60. package/dist/room-context-0vovmZPU.js +787 -0
  61. package/dist/runtime-Bo-KHM-F.js +8 -0
  62. package/dist/runtime-api-Dd1xIV5v.js +9 -0
  63. package/dist/runtime-api.js +14 -0
  64. package/dist/runtime-setter-api.js +2 -0
  65. package/dist/scopes-CDevO8jg.js +74 -0
  66. package/dist/secret-contract-Bo6lbSkh.js +141 -0
  67. package/dist/secret-contract-api.js +2 -0
  68. package/dist/security-audit-BtHGnD3d.js +51 -0
  69. package/dist/security-contract-api.js +2 -0
  70. package/dist/send-D_A9kL-C.js +721 -0
  71. package/dist/send.runtime-BRE_ncCU.js +2 -0
  72. package/dist/send.runtime-_l76lUuL.js +2 -0
  73. package/dist/setup-core-B9NetDkM.js +320 -0
  74. package/dist/setup-entry.js +15 -0
  75. package/dist/setup-plugin-api.js +2 -0
  76. package/dist/setup-surface-D88QBVOW.js +128 -0
  77. package/dist/shared-D8U42xFL.js +208 -0
  78. package/dist/slash-commands.runtime-22kgyst2.js +19 -0
  79. package/dist/slash-dispatch.runtime-BJgT0jwV.js +32 -0
  80. package/dist/slash-plugin-commands.runtime-CF-n3MeP.js +2 -0
  81. package/dist/slash-skill-commands.runtime-BMs0VjTe.js +7 -0
  82. package/dist/streaming-compat-RkZgTmQ2.js +43 -0
  83. package/dist/target-parsing-CQmv-iSm.js +55 -0
  84. package/dist/targets-B1tYCAr6.js +2 -0
  85. package/dist/test-api.js +8 -0
  86. package/dist/thread-ts-C2x7c5PP.js +24 -0
  87. package/openclaw.plugin.json +2405 -0
  88. package/package.json +84 -0
@@ -0,0 +1,3235 @@
1
+ import { a as resolveSlackAccount, d as resolveSlackBotToken, o as resolveSlackAccountAllowFrom, s as resolveSlackAccountDmPolicy, u as resolveSlackAppToken } from "./accounts-ClAPP5ry.js";
2
+ import { i as isSlackExecApprovalClientEnabled, n as isSlackExecApprovalApprover, r as isSlackExecApprovalAuthorizedSender } from "./exec-approvals-7xUNgLi9.js";
3
+ import "./blocks-render-BIDw-Pom.js";
4
+ import { i as truncateSlackText, r as SLACK_TEXT_LIMIT } from "./thread-ts-C2x7c5PP.js";
5
+ import { c as resolveSlackWebClientOptions } from "./client-CPe4GmDR.js";
6
+ import { n as normalizeAllowList } from "./allow-list-BPnnlRPL.js";
7
+ import "./blocks-input-CwTFVImV.js";
8
+ import { n as registerSlackHttpHandler, r as normalizeSlackWebhookPath } from "./registry-CeaoNfoP.js";
9
+ import { t as formatSlackError } from "./errors-BYFHR24f.js";
10
+ import { t as resolveSlackChannelAllowlist } from "./resolve-channels-BRYqyNVJ.js";
11
+ import { t as resolveSlackUserAllowlist } from "./resolve-users-Bd_SdP8j.js";
12
+ import { C as resolveOpenProviderRuntimeGroupPolicy, D as buildSlackSlashCommandMatcher, E as warnMissingProviderGroupPolicyFallbackOnce, O as resolveSlackSlashCommandConfig, S as resolveDefaultGroupPolicy, _ as resolveSlackChannelLabel, d as resolveSlackEffectiveAllowFrom, f as createSlackMonitorContext, g as resolveSlackChannelConfig, h as resolveSlackChatType, i as parsePluginBindingApprovalCustomId, k as stripSlackMentionsForCommandDetection, l as authorizeSlackSystemEventSender, m as normalizeSlackChannelType, n as authorizeSlackDirectMessage, p as isSlackChannelAllowedByPolicy, r as buildPluginBindingResolvedText, s as resolvePluginConversationBindingApproval, t as resolveSlackRoomContextHints, u as resolveSlackCommandIngress, v as getRuntimeConfig$1, y as isDangerousNameMatchingEnabled } from "./room-context-0vovmZPU.js";
13
+ import { t as escapeSlackMrkdwn } from "./mrkdwn-Cax-eSfK.js";
14
+ import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
15
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
16
+ import { normalizeAccountId, normalizeMainKey } from "openclaw/plugin-sdk/routing";
17
+ import { createChannelMessageReplyPipeline } from "openclaw/plugin-sdk/channel-message";
18
+ import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
19
+ import { addAllowlistUserEntriesFromConfigEntry, buildAllowlistResolutionSummary, mergeAllowlist, patchAllowlistUsersInConfigEntries, summarizeMapping } from "openclaw/plugin-sdk/allow-from";
20
+ import { computeBackoff, createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, sleepWithAbort, warn } from "openclaw/plugin-sdk/runtime-env";
21
+ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
22
+ import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
23
+ import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
24
+ import { chunkItems } from "openclaw/plugin-sdk/text-chunking";
25
+ import { resolveNativeCommandsEnabled, resolveNativeSkillsEnabled } from "openclaw/plugin-sdk/native-command-config-runtime";
26
+ import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
27
+ import { DEFAULT_GROUP_HISTORY_LIMIT } from "openclaw/plugin-sdk/reply-history";
28
+ import { installRequestBodyLimitGuard } from "openclaw/plugin-sdk/webhook-request-guards";
29
+ import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
30
+ import { loadSessionStore, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
31
+ import { resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
32
+ import { resolveChannelConfigWrites } from "openclaw/plugin-sdk/channel-config-writes";
33
+ import { replaceConfigFile } from "openclaw/plugin-sdk/config-mutation";
34
+ import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
35
+ import { resolveApprovalOverGateway } from "openclaw/plugin-sdk/approval-gateway-runtime";
36
+ import { parseExecApprovalCommandText } from "openclaw/plugin-sdk/approval-reply-runtime";
37
+ import { formatCommandArgMenuTitle, resolveCommandAuthorization, resolveNativeCommandSessionTargets, resolveStoredModelOverride } from "openclaw/plugin-sdk/command-auth-native";
38
+ import { requestHeartbeat } from "openclaw/plugin-sdk/heartbeat-runtime";
39
+ import { createInteractiveConversationBindingHelpers, dispatchPluginInteractiveHandler } from "openclaw/plugin-sdk/plugin-runtime";
40
+ import { createChannelInboundDebouncer, shouldDebounceTextInbound } from "openclaw/plugin-sdk/channel-inbound";
41
+ import { generateSecureToken } from "openclaw/plugin-sdk/secure-random-runtime";
42
+ //#region extensions/slack/src/channel-migration.ts
43
+ function resolveAccountChannels(cfg, accountId) {
44
+ if (!accountId) return {};
45
+ const normalized = normalizeAccountId(accountId);
46
+ const accounts = cfg.channels?.slack?.accounts;
47
+ if (!accounts || typeof accounts !== "object") return {};
48
+ const exact = accounts[normalized];
49
+ if (exact?.channels) return { channels: exact.channels };
50
+ const matchKey = Object.keys(accounts).find((key) => normalizeLowercaseStringOrEmpty(key) === normalizeLowercaseStringOrEmpty(normalized));
51
+ return { channels: matchKey ? accounts[matchKey]?.channels : void 0 };
52
+ }
53
+ function migrateSlackChannelsInPlace(channels, oldChannelId, newChannelId) {
54
+ if (!channels) return {
55
+ migrated: false,
56
+ skippedExisting: false
57
+ };
58
+ if (oldChannelId === newChannelId) return {
59
+ migrated: false,
60
+ skippedExisting: false
61
+ };
62
+ if (!Object.hasOwn(channels, oldChannelId)) return {
63
+ migrated: false,
64
+ skippedExisting: false
65
+ };
66
+ if (Object.hasOwn(channels, newChannelId)) return {
67
+ migrated: false,
68
+ skippedExisting: true
69
+ };
70
+ channels[newChannelId] = channels[oldChannelId];
71
+ delete channels[oldChannelId];
72
+ return {
73
+ migrated: true,
74
+ skippedExisting: false
75
+ };
76
+ }
77
+ function migrateSlackChannelConfig(params) {
78
+ const scopes = [];
79
+ let migrated = false;
80
+ let skippedExisting = false;
81
+ const accountChannels = resolveAccountChannels(params.cfg, params.accountId).channels;
82
+ if (accountChannels) {
83
+ const result = migrateSlackChannelsInPlace(accountChannels, params.oldChannelId, params.newChannelId);
84
+ if (result.migrated) {
85
+ migrated = true;
86
+ scopes.push("account");
87
+ }
88
+ if (result.skippedExisting) skippedExisting = true;
89
+ }
90
+ const globalChannels = params.cfg.channels?.slack?.channels;
91
+ if (globalChannels) {
92
+ const result = migrateSlackChannelsInPlace(globalChannels, params.oldChannelId, params.newChannelId);
93
+ if (result.migrated) {
94
+ migrated = true;
95
+ scopes.push("global");
96
+ }
97
+ if (result.skippedExisting) skippedExisting = true;
98
+ }
99
+ return {
100
+ migrated,
101
+ skippedExisting,
102
+ scopes
103
+ };
104
+ }
105
+ //#endregion
106
+ //#region extensions/slack/src/monitor/events/channels.ts
107
+ function registerSlackChannelEvents(params) {
108
+ const { ctx, trackEvent } = params;
109
+ const enqueueChannelSystemEvent = (params) => {
110
+ if (!ctx.isChannelAllowed({
111
+ channelId: params.channelId,
112
+ channelName: params.channelName,
113
+ channelType: "channel"
114
+ })) return;
115
+ const label = resolveSlackChannelLabel({
116
+ channelId: params.channelId,
117
+ channelName: params.channelName
118
+ });
119
+ const sessionKey = ctx.resolveSlackSystemEventSessionKey({
120
+ channelId: params.channelId,
121
+ channelType: "channel"
122
+ });
123
+ enqueueSystemEvent(`Slack channel ${params.kind}: ${label}.`, {
124
+ sessionKey,
125
+ contextKey: `slack:channel:${params.kind}:${params.channelId ?? params.channelName ?? "unknown"}`
126
+ });
127
+ };
128
+ ctx.app.event("channel_created", async ({ event, body }) => {
129
+ try {
130
+ if (ctx.shouldDropMismatchedSlackEvent(body)) return;
131
+ trackEvent?.();
132
+ const payload = event;
133
+ const channelId = payload.channel?.id;
134
+ const channelName = payload.channel?.name;
135
+ enqueueChannelSystemEvent({
136
+ kind: "created",
137
+ channelId,
138
+ channelName
139
+ });
140
+ } catch (err) {
141
+ ctx.runtime.error?.(danger(`slack channel created handler failed: ${formatErrorMessage(err)}`));
142
+ }
143
+ });
144
+ ctx.app.event("channel_rename", async ({ event, body }) => {
145
+ try {
146
+ if (ctx.shouldDropMismatchedSlackEvent(body)) return;
147
+ trackEvent?.();
148
+ const payload = event;
149
+ const channelId = payload.channel?.id;
150
+ enqueueChannelSystemEvent({
151
+ kind: "renamed",
152
+ channelId,
153
+ channelName: payload.channel?.name_normalized ?? payload.channel?.name
154
+ });
155
+ } catch (err) {
156
+ ctx.runtime.error?.(danger(`slack channel rename handler failed: ${formatErrorMessage(err)}`));
157
+ }
158
+ });
159
+ ctx.app.event("channel_id_changed", async ({ event, body }) => {
160
+ try {
161
+ if (ctx.shouldDropMismatchedSlackEvent(body)) return;
162
+ trackEvent?.();
163
+ const payload = event;
164
+ const oldChannelId = payload.old_channel_id;
165
+ const newChannelId = payload.new_channel_id;
166
+ if (!oldChannelId || !newChannelId) return;
167
+ const label = resolveSlackChannelLabel({
168
+ channelId: newChannelId,
169
+ channelName: (await ctx.resolveChannelName(newChannelId))?.name
170
+ });
171
+ ctx.runtime.log?.(warn(`[slack] Channel ID changed: ${oldChannelId} → ${newChannelId} (${label})`));
172
+ if (!resolveChannelConfigWrites({
173
+ cfg: ctx.cfg,
174
+ channelId: "slack",
175
+ accountId: ctx.accountId
176
+ })) {
177
+ ctx.runtime.log?.(warn("[slack] Config writes disabled; skipping channel config migration."));
178
+ return;
179
+ }
180
+ const currentConfig = getRuntimeConfig();
181
+ const migration = migrateSlackChannelConfig({
182
+ cfg: currentConfig,
183
+ accountId: ctx.accountId,
184
+ oldChannelId,
185
+ newChannelId
186
+ });
187
+ if (migration.migrated) {
188
+ migrateSlackChannelConfig({
189
+ cfg: ctx.cfg,
190
+ accountId: ctx.accountId,
191
+ oldChannelId,
192
+ newChannelId
193
+ });
194
+ await replaceConfigFile({
195
+ nextConfig: currentConfig,
196
+ afterWrite: { mode: "auto" }
197
+ });
198
+ ctx.runtime.log?.(warn("[slack] Channel config migrated and saved successfully."));
199
+ } else if (migration.skippedExisting) ctx.runtime.log?.(warn(`[slack] Channel config already exists for ${newChannelId}; leaving ${oldChannelId} unchanged`));
200
+ else ctx.runtime.log?.(warn(`[slack] No config found for old channel ID ${oldChannelId}; migration logged only`));
201
+ } catch (err) {
202
+ ctx.runtime.error?.(danger(`slack channel_id_changed handler failed: ${formatErrorMessage(err)}`));
203
+ }
204
+ });
205
+ }
206
+ //#endregion
207
+ //#region extensions/slack/src/monitor/events/home.ts
208
+ function buildSlackHomeView() {
209
+ return {
210
+ type: "home",
211
+ callback_id: "openclaw:home",
212
+ blocks: [
213
+ {
214
+ type: "header",
215
+ text: {
216
+ type: "plain_text",
217
+ text: "OpenClaw"
218
+ }
219
+ },
220
+ {
221
+ type: "section",
222
+ text: {
223
+ type: "mrkdwn",
224
+ text: "Send a DM, mention OpenClaw in a channel, or use `/openclaw` to start a session."
225
+ }
226
+ },
227
+ {
228
+ type: "context",
229
+ elements: [{
230
+ type: "mrkdwn",
231
+ text: "This Home tab is safe to show to any workspace member who opens the app."
232
+ }]
233
+ }
234
+ ]
235
+ };
236
+ }
237
+ function registerSlackHomeEvents(params) {
238
+ const { ctx, trackEvent } = params;
239
+ ctx.app.event("app_home_opened", async ({ event, body }) => {
240
+ try {
241
+ if (ctx.shouldDropMismatchedSlackEvent(body)) return;
242
+ trackEvent?.();
243
+ const payload = event;
244
+ if (!payload.user || payload.tab === "messages") return;
245
+ await ctx.app.client.views.publish({
246
+ token: ctx.botToken,
247
+ user_id: payload.user,
248
+ view: buildSlackHomeView()
249
+ });
250
+ } catch (err) {
251
+ ctx.runtime.error?.(danger(`slack app home handler failed: ${formatErrorMessage(err)}`));
252
+ }
253
+ });
254
+ }
255
+ //#endregion
256
+ //#region extensions/slack/src/interactive-dispatch.ts
257
+ async function dispatchSlackPluginInteractiveHandler(params) {
258
+ return await dispatchPluginInteractiveHandler({
259
+ channel: "slack",
260
+ data: params.data,
261
+ dedupeId: params.interactionId,
262
+ onMatched: params.onMatched,
263
+ invoke: ({ registration, namespace, payload }) => registration.handler({
264
+ ...params.ctx,
265
+ channel: "slack",
266
+ interaction: {
267
+ ...params.ctx.interaction,
268
+ data: params.data,
269
+ namespace,
270
+ payload
271
+ },
272
+ respond: params.respond,
273
+ ...createInteractiveConversationBindingHelpers({
274
+ registration,
275
+ senderId: params.ctx.senderId,
276
+ conversation: {
277
+ channel: "slack",
278
+ accountId: params.ctx.accountId,
279
+ conversationId: params.ctx.conversationId,
280
+ parentConversationId: params.ctx.parentConversationId,
281
+ threadId: params.ctx.threadId
282
+ }
283
+ })
284
+ })
285
+ });
286
+ }
287
+ //#endregion
288
+ //#region extensions/slack/src/monitor/events/interactions.block-actions.ts
289
+ function readOptionValues(options) {
290
+ if (!Array.isArray(options)) return;
291
+ const values = options.map((option) => option && typeof option === "object" ? option.value : null).filter((value) => typeof value === "string" && value.trim().length > 0);
292
+ return values.length > 0 ? values : void 0;
293
+ }
294
+ function readOptionLabels(options) {
295
+ if (!Array.isArray(options)) return;
296
+ const labels = options.map((option) => option && typeof option === "object" ? option.text?.text ?? null : null).filter((label) => typeof label === "string" && label.trim().length > 0);
297
+ return labels.length > 0 ? labels : void 0;
298
+ }
299
+ function uniqueNonEmptyStrings(values) {
300
+ const unique = [];
301
+ const seen = /* @__PURE__ */ new Set();
302
+ for (const entry of values) {
303
+ if (typeof entry !== "string") continue;
304
+ const trimmed = entry.trim();
305
+ if (!trimmed || seen.has(trimmed)) continue;
306
+ seen.add(trimmed);
307
+ unique.push(trimmed);
308
+ }
309
+ return unique;
310
+ }
311
+ function collectRichTextFragments(value, out) {
312
+ if (!value || typeof value !== "object") return;
313
+ const typed = value;
314
+ if (typeof typed.text === "string" && typed.text.trim().length > 0) out.push(typed.text.trim());
315
+ if (Array.isArray(typed.elements)) for (const child of typed.elements) collectRichTextFragments(child, out);
316
+ }
317
+ function summarizeRichTextPreview(value) {
318
+ const fragments = [];
319
+ collectRichTextFragments(value, fragments);
320
+ if (fragments.length === 0) return;
321
+ const joined = fragments.join(" ").replace(/\s+/g, " ").trim();
322
+ if (!joined) return;
323
+ const max = 120;
324
+ return joined.length <= max ? joined : `${joined.slice(0, max - 1)}…`;
325
+ }
326
+ function readInteractionAction(raw) {
327
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
328
+ return raw;
329
+ }
330
+ function summarizeAction(action) {
331
+ const typed = action;
332
+ const actionType = typed.type;
333
+ const selectedUsers = uniqueNonEmptyStrings([...typed.selected_user ? [typed.selected_user] : [], ...Array.isArray(typed.selected_users) ? typed.selected_users : []]);
334
+ const selectedChannels = uniqueNonEmptyStrings([...typed.selected_channel ? [typed.selected_channel] : [], ...Array.isArray(typed.selected_channels) ? typed.selected_channels : []]);
335
+ const selectedConversations = uniqueNonEmptyStrings([...typed.selected_conversation ? [typed.selected_conversation] : [], ...Array.isArray(typed.selected_conversations) ? typed.selected_conversations : []]);
336
+ const selectedValues = uniqueNonEmptyStrings([
337
+ ...typed.selected_option?.value ? [typed.selected_option.value] : [],
338
+ ...readOptionValues(typed.selected_options) ?? [],
339
+ ...selectedUsers,
340
+ ...selectedChannels,
341
+ ...selectedConversations
342
+ ]);
343
+ const selectedLabels = uniqueNonEmptyStrings([...typed.selected_option?.text?.text ? [typed.selected_option.text.text] : [], ...readOptionLabels(typed.selected_options) ?? []]);
344
+ const inputValue = typeof typed.value === "string" ? typed.value : void 0;
345
+ const inputNumber = actionType === "number_input" && inputValue != null ? Number.parseFloat(inputValue) : void 0;
346
+ const parsedNumber = Number.isFinite(inputNumber) ? inputNumber : void 0;
347
+ const inputEmail = actionType === "email_text_input" && inputValue?.includes("@") ? inputValue : void 0;
348
+ let inputUrl;
349
+ if (actionType === "url_text_input" && inputValue) try {
350
+ inputUrl = new URL(inputValue).toString();
351
+ } catch {
352
+ inputUrl = void 0;
353
+ }
354
+ const richTextValue = actionType === "rich_text_input" ? typed.rich_text_value : void 0;
355
+ const richTextPreview = summarizeRichTextPreview(richTextValue);
356
+ return {
357
+ actionType,
358
+ inputKind: actionType === "number_input" ? "number" : actionType === "email_text_input" ? "email" : actionType === "url_text_input" ? "url" : actionType === "rich_text_input" ? "rich_text" : inputValue != null ? "text" : void 0,
359
+ value: typed.value,
360
+ selectedValues: selectedValues.length > 0 ? selectedValues : void 0,
361
+ selectedUsers: selectedUsers.length > 0 ? selectedUsers : void 0,
362
+ selectedChannels: selectedChannels.length > 0 ? selectedChannels : void 0,
363
+ selectedConversations: selectedConversations.length > 0 ? selectedConversations : void 0,
364
+ selectedLabels: selectedLabels.length > 0 ? selectedLabels : void 0,
365
+ selectedDate: typed.selected_date,
366
+ selectedTime: typed.selected_time,
367
+ selectedDateTime: typeof typed.selected_date_time === "number" ? typed.selected_date_time : void 0,
368
+ inputValue,
369
+ inputNumber: parsedNumber,
370
+ inputEmail,
371
+ inputUrl,
372
+ richTextValue,
373
+ richTextPreview,
374
+ workflowTriggerUrl: typed.workflow?.trigger_url,
375
+ workflowId: typed.workflow?.workflow_id
376
+ };
377
+ }
378
+ function isBulkActionsBlock(block) {
379
+ 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_"));
380
+ }
381
+ function formatInteractionSelectionLabel(params) {
382
+ if (params.summary.actionType === "button" && params.buttonText?.trim()) return params.buttonText.trim();
383
+ if (params.summary.selectedLabels?.length) {
384
+ if (params.summary.selectedLabels.length <= 3) return params.summary.selectedLabels.join(", ");
385
+ return `${params.summary.selectedLabels.slice(0, 3).join(", ")} +${params.summary.selectedLabels.length - 3}`;
386
+ }
387
+ if (params.summary.selectedValues?.length) {
388
+ if (params.summary.selectedValues.length <= 3) return params.summary.selectedValues.join(", ");
389
+ return `${params.summary.selectedValues.slice(0, 3).join(", ")} +${params.summary.selectedValues.length - 3}`;
390
+ }
391
+ if (params.summary.selectedDate) return params.summary.selectedDate;
392
+ if (params.summary.selectedTime) return params.summary.selectedTime;
393
+ if (typeof params.summary.selectedDateTime === "number") return (/* @__PURE__ */ new Date(params.summary.selectedDateTime * 1e3)).toISOString();
394
+ if (params.summary.richTextPreview) return params.summary.richTextPreview;
395
+ if (params.summary.value?.trim()) return params.summary.value.trim();
396
+ return params.actionId;
397
+ }
398
+ function formatInteractionConfirmationText(params) {
399
+ const userId = normalizeOptionalString(params.userId);
400
+ const actor = userId ? ` by <@${userId}>` : "";
401
+ return `:white_check_mark: *${escapeSlackMrkdwn(params.selectedLabel)}* selected${actor}`;
402
+ }
403
+ function buildSlackPluginInteractionData(params) {
404
+ const actionId = normalizeOptionalString(params.actionId) ?? "";
405
+ if (!actionId) return null;
406
+ const payload = normalizeOptionalString(params.summary.value) || params.summary.selectedValues?.map((value) => normalizeOptionalString(value)).find(Boolean) || "";
407
+ if (actionId === "openclaw:reply_button" || actionId === "openclaw:reply_select" || actionId.startsWith(`openclaw:reply_button:`) || actionId.startsWith(`openclaw:reply_select:`)) return payload || null;
408
+ return payload ? `${actionId}:${payload}` : actionId;
409
+ }
410
+ function isSlackReplyActionId(actionId) {
411
+ return actionId === "openclaw:reply_button" || actionId === "openclaw:reply_select" || actionId.startsWith(`openclaw:reply_button:`) || actionId.startsWith(`openclaw:reply_select:`);
412
+ }
413
+ function buildSlackPluginInteractionId(params) {
414
+ const primaryValue = normalizeOptionalString(params.summary.value) || params.summary.selectedValues?.map((value) => normalizeOptionalString(value)).find(Boolean) || "";
415
+ return [
416
+ normalizeOptionalString(params.userId) ?? "",
417
+ normalizeOptionalString(params.channelId) ?? "",
418
+ normalizeOptionalString(params.messageTs) ?? "",
419
+ normalizeOptionalString(params.triggerId) ?? "",
420
+ normalizeOptionalString(params.actionId) ?? "",
421
+ primaryValue
422
+ ].join(":");
423
+ }
424
+ function parseSlackBlockAction(params) {
425
+ const typedBody = params.body;
426
+ const typedAction = readInteractionAction(params.action);
427
+ if (!typedAction) {
428
+ params.log?.(`slack:interaction malformed action payload channel=${typedBody.channel?.id ?? typedBody.container?.channel_id ?? "unknown"} user=${typedBody.user?.id ?? "unknown"}`);
429
+ return null;
430
+ }
431
+ const typedActionWithText = typedAction;
432
+ return {
433
+ typedBody,
434
+ typedAction,
435
+ typedActionWithText,
436
+ actionId: typeof typedActionWithText.action_id === "string" ? typedActionWithText.action_id : "unknown",
437
+ blockId: typedActionWithText.block_id,
438
+ userId: typedBody.user?.id ?? "unknown",
439
+ channelId: typedBody.channel?.id ?? typedBody.container?.channel_id,
440
+ messageTs: typedBody.message?.ts ?? typedBody.container?.message_ts,
441
+ threadTs: typedBody.container?.thread_ts,
442
+ actionSummary: summarizeAction(typedAction)
443
+ };
444
+ }
445
+ async function respondEphemeral(respond, text) {
446
+ if (!respond) return;
447
+ try {
448
+ await respond({
449
+ text,
450
+ response_type: "ephemeral"
451
+ });
452
+ } catch {}
453
+ }
454
+ async function updateSlackInteractionMessage(params) {
455
+ if (!params.channelId || !params.messageTs) return;
456
+ await params.ctx.app.client.chat.update({
457
+ channel: params.channelId,
458
+ ts: params.messageTs,
459
+ text: params.text,
460
+ ...params.blocks ? { blocks: params.blocks } : {}
461
+ });
462
+ }
463
+ async function authorizeSlackBlockAction(params) {
464
+ const auth = await authorizeSlackSystemEventSender({
465
+ ctx: params.ctx,
466
+ senderId: params.parsed.userId,
467
+ channelId: params.parsed.channelId,
468
+ expectedSenderId: params.parsed.userId,
469
+ interactiveEvent: true
470
+ });
471
+ if (auth.allowed) return auth;
472
+ params.ctx.runtime.log?.(`slack:interaction drop action=${params.parsed.actionId} user=${params.parsed.userId} channel=${params.parsed.channelId ?? "unknown"} reason=${auth.reason ?? "unauthorized"}`);
473
+ await respondEphemeral(params.respond, "You are not authorized to use this control.");
474
+ return { allowed: false };
475
+ }
476
+ async function handleSlackPluginBindingApproval(params) {
477
+ const pluginBindingApproval = parsePluginBindingApprovalCustomId(params.pluginInteractionData);
478
+ if (!pluginBindingApproval) return false;
479
+ const resolved = await resolvePluginConversationBindingApproval({
480
+ approvalId: pluginBindingApproval.approvalId,
481
+ decision: pluginBindingApproval.decision,
482
+ senderId: params.parsed.userId
483
+ });
484
+ try {
485
+ await updateSlackInteractionMessage({
486
+ ctx: params.ctx,
487
+ channelId: params.parsed.channelId,
488
+ messageTs: params.parsed.messageTs,
489
+ text: params.parsed.typedBody.message?.text ?? "",
490
+ blocks: []
491
+ });
492
+ } catch {}
493
+ await respondEphemeral(params.respond, buildPluginBindingResolvedText(resolved));
494
+ return true;
495
+ }
496
+ async function handleSlackExecApprovalInteraction(params) {
497
+ const approval = parseExecApprovalCommandText(params.pluginInteractionData);
498
+ if (!approval) return false;
499
+ const pluginApprovalAuthorizedSender = isSlackExecApprovalApprover({
500
+ cfg: params.ctx.cfg,
501
+ accountId: params.ctx.accountId,
502
+ senderId: params.parsed.userId
503
+ });
504
+ const execApprovalAuthorizedSender = isSlackExecApprovalAuthorizedSender({
505
+ cfg: params.ctx.cfg,
506
+ accountId: params.ctx.accountId,
507
+ senderId: params.parsed.userId
508
+ });
509
+ if (!(approval.approvalId.startsWith("plugin:") ? pluginApprovalAuthorizedSender : execApprovalAuthorizedSender || pluginApprovalAuthorizedSender)) {
510
+ params.ctx.runtime.log?.(`slack:interaction drop exec approval user=${params.parsed.userId} (not authorized)`);
511
+ await respondEphemeral(params.respond, "You are not authorized to approve this request.");
512
+ return true;
513
+ }
514
+ try {
515
+ await resolveApprovalOverGateway({
516
+ cfg: params.ctx.cfg,
517
+ approvalId: approval.approvalId,
518
+ decision: approval.decision,
519
+ senderId: params.parsed.userId,
520
+ allowPluginFallback: pluginApprovalAuthorizedSender,
521
+ clientDisplayName: `Slack approval (${params.parsed.userId.trim() || "unknown"})`
522
+ });
523
+ } catch (error) {
524
+ params.ctx.runtime.log?.(`slack:interaction exec approval resolve failed id=${approval.approvalId}: ${String(error)}`);
525
+ throw error;
526
+ }
527
+ try {
528
+ await updateSlackInteractionMessage({
529
+ ctx: params.ctx,
530
+ channelId: params.parsed.channelId,
531
+ messageTs: params.parsed.messageTs,
532
+ text: params.parsed.typedBody.message?.text ?? "",
533
+ blocks: []
534
+ });
535
+ } catch {}
536
+ return true;
537
+ }
538
+ async function dispatchSlackPluginInteraction(params) {
539
+ const pluginInteractionId = buildSlackPluginInteractionId({
540
+ userId: params.parsed.userId,
541
+ channelId: params.parsed.channelId,
542
+ messageTs: params.parsed.messageTs,
543
+ triggerId: params.parsed.typedBody.trigger_id,
544
+ actionId: params.parsed.actionId,
545
+ summary: params.parsed.actionSummary
546
+ });
547
+ if (await handleSlackPluginBindingApproval({
548
+ ctx: params.ctx,
549
+ parsed: params.parsed,
550
+ pluginInteractionData: params.pluginInteractionData,
551
+ respond: params.respond
552
+ })) return true;
553
+ const pluginResult = await dispatchSlackPluginInteractiveHandler({
554
+ data: params.pluginInteractionData,
555
+ interactionId: pluginInteractionId,
556
+ ctx: {
557
+ accountId: params.ctx.accountId,
558
+ interactionId: pluginInteractionId,
559
+ conversationId: params.parsed.channelId ?? "",
560
+ parentConversationId: void 0,
561
+ threadId: params.parsed.threadTs,
562
+ senderId: params.parsed.userId,
563
+ senderUsername: void 0,
564
+ auth: params.auth,
565
+ interaction: {
566
+ kind: params.parsed.actionSummary.actionType === "button" ? "button" : "select",
567
+ actionId: params.parsed.actionId,
568
+ blockId: params.parsed.blockId,
569
+ messageTs: params.parsed.messageTs,
570
+ threadTs: params.parsed.threadTs,
571
+ value: params.parsed.actionSummary.value,
572
+ selectedValues: params.parsed.actionSummary.selectedValues,
573
+ selectedLabels: params.parsed.actionSummary.selectedLabels,
574
+ triggerId: params.parsed.typedBody.trigger_id,
575
+ responseUrl: params.parsed.typedBody.response_url
576
+ }
577
+ },
578
+ respond: {
579
+ acknowledge: async () => {},
580
+ reply: async ({ text, responseType }) => {
581
+ if (!text) return;
582
+ await params.respond?.({
583
+ text,
584
+ response_type: responseType ?? "ephemeral"
585
+ });
586
+ },
587
+ followUp: async ({ text, responseType }) => {
588
+ if (!text) return;
589
+ await params.respond?.({
590
+ text,
591
+ response_type: responseType ?? "ephemeral"
592
+ });
593
+ },
594
+ editMessage: async ({ text, blocks }) => {
595
+ await updateSlackInteractionMessage({
596
+ ctx: params.ctx,
597
+ channelId: params.parsed.channelId,
598
+ messageTs: params.parsed.messageTs,
599
+ text: text ?? params.parsed.typedBody.message?.text ?? "",
600
+ blocks: Array.isArray(blocks) ? blocks : void 0
601
+ });
602
+ }
603
+ }
604
+ });
605
+ return pluginResult.matched && pluginResult.handled;
606
+ }
607
+ async function resolveSlackBlockActionCommandAuthorized(params) {
608
+ const commandsAllowFrom = params.ctx.cfg.commands?.allowFrom;
609
+ if (commandsAllowFrom != null && typeof commandsAllowFrom === "object" && (Array.isArray(commandsAllowFrom.slack) || Array.isArray(commandsAllowFrom["*"]))) return resolveCommandAuthorization({
610
+ ctx: {
611
+ Provider: "slack",
612
+ Surface: "slack",
613
+ OriginatingChannel: "slack",
614
+ AccountId: params.ctx.accountId,
615
+ ChatType: params.auth.channelType === "im" ? "direct" : "group",
616
+ From: params.parsed.channelId ? `slack:${params.parsed.channelId}` : "slack",
617
+ SenderId: params.parsed.userId
618
+ },
619
+ cfg: params.ctx.cfg,
620
+ commandAuthorized: false
621
+ }).isAuthorizedSender;
622
+ const isDirectMessage = params.auth.channelType === "im";
623
+ const isRoom = params.auth.channelType === "channel" || params.auth.channelType === "group";
624
+ const allowFromLower = await resolveSlackEffectiveAllowFrom(params.ctx, { includePairingStore: isDirectMessage });
625
+ const senderName = (await params.ctx.resolveUserName(params.parsed.userId).catch(() => void 0))?.name;
626
+ let channelUsers = [];
627
+ if (isRoom && params.parsed.channelId) {
628
+ const channelConfig = resolveSlackChannelConfig({
629
+ channelId: params.parsed.channelId,
630
+ channelName: params.auth.channelName,
631
+ channels: params.ctx.channelsConfig,
632
+ channelKeys: params.ctx.channelsConfigKeys,
633
+ defaultRequireMention: params.ctx.defaultRequireMention,
634
+ allowNameMatching: params.ctx.allowNameMatching
635
+ });
636
+ channelUsers = Array.isArray(channelConfig?.users) ? channelConfig.users : [];
637
+ }
638
+ return (await resolveSlackCommandIngress({
639
+ ctx: params.ctx,
640
+ senderId: params.parsed.userId,
641
+ senderName,
642
+ channelType: params.auth.channelType ?? "channel",
643
+ channelId: params.parsed.channelId ?? "slack-interaction",
644
+ ownerAllowFromLower: allowFromLower,
645
+ channelUsers,
646
+ allowTextCommands: false,
647
+ hasControlCommand: true,
648
+ eventKind: "button",
649
+ modeWhenAccessGroupsOff: "configured"
650
+ })).commandAccess.authorized;
651
+ }
652
+ function enqueueSlackBlockActionEvent(params) {
653
+ const eventPayload = {
654
+ interactionType: "block_action",
655
+ actionId: params.parsed.actionId,
656
+ blockId: params.parsed.blockId,
657
+ ...params.parsed.actionSummary,
658
+ userId: params.parsed.userId,
659
+ teamId: params.parsed.typedBody.team?.id,
660
+ triggerId: params.parsed.typedBody.trigger_id,
661
+ responseUrl: params.parsed.typedBody.response_url,
662
+ channelId: params.parsed.channelId,
663
+ messageTs: params.parsed.messageTs,
664
+ threadTs: params.parsed.threadTs
665
+ };
666
+ params.ctx.runtime.log?.(`slack:interaction action=${params.parsed.actionId} type=${params.parsed.actionSummary.actionType ?? "unknown"} user=${params.parsed.userId} channel=${params.parsed.channelId}`);
667
+ const sessionKey = params.ctx.resolveSlackSystemEventSessionKey({
668
+ channelId: params.parsed.channelId,
669
+ channelType: params.auth.channelType,
670
+ senderId: params.parsed.userId,
671
+ threadTs: params.parsed.threadTs
672
+ });
673
+ const contextParts = [
674
+ "slack:interaction",
675
+ params.parsed.channelId,
676
+ params.parsed.messageTs,
677
+ params.parsed.actionId
678
+ ].filter(Boolean);
679
+ if (enqueueSystemEvent(params.formatSystemEvent(eventPayload), {
680
+ sessionKey,
681
+ contextKey: contextParts.join(":"),
682
+ deliveryContext: {
683
+ channel: "slack",
684
+ to: params.auth.channelType === "im" ? `user:${params.parsed.userId}` : params.parsed.channelId ? `channel:${params.parsed.channelId}` : void 0,
685
+ accountId: params.ctx.accountId,
686
+ threadId: params.parsed.threadTs
687
+ },
688
+ trusted: false
689
+ })) requestHeartbeat({
690
+ source: "hook",
691
+ intent: "immediate",
692
+ reason: "hook:slack-interaction",
693
+ sessionKey,
694
+ heartbeat: { target: "last" }
695
+ });
696
+ }
697
+ function buildSlackConfirmationBlocks(params) {
698
+ const selectedLabel = formatInteractionSelectionLabel({
699
+ actionId: params.parsed.actionId,
700
+ summary: params.parsed.actionSummary,
701
+ buttonText: params.parsed.typedActionWithText.text?.text
702
+ });
703
+ let updatedBlocks = params.originalBlocks.map((block) => {
704
+ const typedBlock = block;
705
+ if (typedBlock.type === "actions" && typedBlock.block_id === params.parsed.blockId) return {
706
+ type: "context",
707
+ elements: [{
708
+ type: "mrkdwn",
709
+ text: formatInteractionConfirmationText({
710
+ selectedLabel,
711
+ userId: params.parsed.userId
712
+ })
713
+ }]
714
+ };
715
+ return block;
716
+ });
717
+ if (!updatedBlocks.some((block) => {
718
+ const typedBlock = block;
719
+ return typedBlock.type === "actions" && !isBulkActionsBlock(typedBlock);
720
+ })) updatedBlocks = updatedBlocks.filter((block, index) => {
721
+ const typedBlock = block;
722
+ if (isBulkActionsBlock(typedBlock)) return false;
723
+ if (typedBlock.type !== "divider") return true;
724
+ const next = updatedBlocks[index + 1];
725
+ return !next || !isBulkActionsBlock(next);
726
+ });
727
+ return updatedBlocks;
728
+ }
729
+ async function updateSlackLegacyBlockAction(params) {
730
+ const originalBlocks = params.parsed.typedBody.message?.blocks;
731
+ if (!Array.isArray(originalBlocks) || !params.parsed.channelId || !params.parsed.messageTs || !params.parsed.blockId) return;
732
+ try {
733
+ await updateSlackInteractionMessage({
734
+ ctx: params.ctx,
735
+ channelId: params.parsed.channelId,
736
+ messageTs: params.parsed.messageTs,
737
+ text: params.parsed.typedBody.message?.text ?? "",
738
+ blocks: buildSlackConfirmationBlocks({
739
+ parsed: params.parsed,
740
+ originalBlocks
741
+ })
742
+ });
743
+ } catch {
744
+ await respondEphemeral(params.respond, `Button "${params.parsed.actionId}" clicked!`);
745
+ }
746
+ }
747
+ async function handleSlackBlockAction(params) {
748
+ const { ack, body, action, respond } = params.args;
749
+ await ack();
750
+ if (params.ctx.shouldDropMismatchedSlackEvent?.(body)) {
751
+ params.ctx.runtime.log?.("slack:interaction drop block action payload (mismatched app/team)");
752
+ return;
753
+ }
754
+ const parsed = parseSlackBlockAction({
755
+ body,
756
+ action,
757
+ log: params.ctx.runtime.log
758
+ });
759
+ if (!parsed) return;
760
+ params.trackEvent?.();
761
+ const pluginInteractionData = buildSlackPluginInteractionData({
762
+ actionId: parsed.actionId,
763
+ summary: parsed.actionSummary
764
+ });
765
+ if (pluginInteractionData && isSlackReplyActionId(parsed.actionId)) {
766
+ if (await handleSlackExecApprovalInteraction({
767
+ ctx: params.ctx,
768
+ parsed,
769
+ pluginInteractionData,
770
+ respond
771
+ })) return;
772
+ }
773
+ const auth = await authorizeSlackBlockAction({
774
+ ctx: params.ctx,
775
+ parsed,
776
+ respond
777
+ });
778
+ if (!auth.allowed) return;
779
+ if (pluginInteractionData && isSlackReplyActionId(parsed.actionId)) {
780
+ if (await handleSlackPluginBindingApproval({
781
+ ctx: params.ctx,
782
+ parsed,
783
+ pluginInteractionData,
784
+ respond
785
+ })) return;
786
+ } else if (pluginInteractionData) {
787
+ const isAuthorizedSender = await resolveSlackBlockActionCommandAuthorized({
788
+ ctx: params.ctx,
789
+ parsed,
790
+ auth
791
+ });
792
+ if (await dispatchSlackPluginInteraction({
793
+ ctx: params.ctx,
794
+ parsed,
795
+ pluginInteractionData,
796
+ auth: { isAuthorizedSender },
797
+ respond
798
+ })) return;
799
+ }
800
+ enqueueSlackBlockActionEvent({
801
+ ctx: params.ctx,
802
+ parsed,
803
+ auth,
804
+ formatSystemEvent: params.formatSystemEvent
805
+ });
806
+ await updateSlackLegacyBlockAction({
807
+ ctx: params.ctx,
808
+ parsed,
809
+ respond
810
+ });
811
+ }
812
+ function registerSlackBlockActionHandler(params) {
813
+ if (typeof params.ctx.app.action !== "function") return;
814
+ params.ctx.app.action(/.+/, async (args) => {
815
+ await handleSlackBlockAction({
816
+ ctx: params.ctx,
817
+ trackEvent: params.trackEvent,
818
+ args,
819
+ formatSystemEvent: params.formatSystemEvent
820
+ });
821
+ });
822
+ }
823
+ //#endregion
824
+ //#region extensions/slack/src/modal-metadata.ts
825
+ function parseSlackModalPrivateMetadata(raw) {
826
+ if (typeof raw !== "string" || raw.trim().length === 0) return {};
827
+ try {
828
+ const parsed = JSON.parse(raw);
829
+ return {
830
+ sessionKey: normalizeOptionalString(parsed.sessionKey),
831
+ channelId: normalizeOptionalString(parsed.channelId),
832
+ channelType: normalizeOptionalString(parsed.channelType),
833
+ userId: normalizeOptionalString(parsed.userId)
834
+ };
835
+ } catch {
836
+ return {};
837
+ }
838
+ }
839
+ //#endregion
840
+ //#region extensions/slack/src/monitor/events/interactions.modal.ts
841
+ function resolveModalSessionRouting(params) {
842
+ const metadata = params.metadata;
843
+ if (metadata.sessionKey) return {
844
+ sessionKey: metadata.sessionKey,
845
+ channelId: metadata.channelId,
846
+ channelType: metadata.channelType
847
+ };
848
+ if (metadata.channelId) return {
849
+ sessionKey: params.ctx.resolveSlackSystemEventSessionKey({
850
+ channelId: metadata.channelId,
851
+ channelType: metadata.channelType,
852
+ senderId: params.userId
853
+ }),
854
+ channelId: metadata.channelId,
855
+ channelType: metadata.channelType
856
+ };
857
+ return { sessionKey: params.ctx.resolveSlackSystemEventSessionKey({}) };
858
+ }
859
+ function summarizeSlackViewLifecycleContext(view) {
860
+ const rootViewId = view.root_view_id;
861
+ const previousViewId = view.previous_view_id;
862
+ return {
863
+ rootViewId,
864
+ previousViewId,
865
+ externalId: view.external_id,
866
+ viewHash: view.hash,
867
+ isStackedView: Boolean(previousViewId)
868
+ };
869
+ }
870
+ function resolveSlackModalEventBase(params) {
871
+ const metadata = parseSlackModalPrivateMetadata(params.body.view?.private_metadata);
872
+ const callbackId = params.body.view?.callback_id ?? "unknown";
873
+ const userId = params.body.user?.id ?? "unknown";
874
+ const viewId = params.body.view?.id;
875
+ const inputs = params.summarizeViewState(params.body.view?.state?.values);
876
+ const sessionRouting = resolveModalSessionRouting({
877
+ ctx: params.ctx,
878
+ metadata,
879
+ userId
880
+ });
881
+ return {
882
+ callbackId,
883
+ userId,
884
+ expectedUserId: metadata.userId,
885
+ viewId,
886
+ sessionRouting,
887
+ payload: {
888
+ actionId: `view:${callbackId}`,
889
+ callbackId,
890
+ viewId,
891
+ userId,
892
+ teamId: params.body.team?.id,
893
+ ...summarizeSlackViewLifecycleContext({
894
+ root_view_id: params.body.view?.root_view_id,
895
+ previous_view_id: params.body.view?.previous_view_id,
896
+ external_id: params.body.view?.external_id,
897
+ hash: params.body.view?.hash
898
+ }),
899
+ privateMetadata: params.body.view?.private_metadata,
900
+ routedChannelId: sessionRouting.channelId,
901
+ routedChannelType: sessionRouting.channelType,
902
+ inputs
903
+ }
904
+ };
905
+ }
906
+ async function emitSlackModalLifecycleEvent(params) {
907
+ const { callbackId, userId, expectedUserId, viewId, sessionRouting, payload } = resolveSlackModalEventBase({
908
+ ctx: params.ctx,
909
+ body: params.body,
910
+ summarizeViewState: params.summarizeViewState
911
+ });
912
+ const isViewClosed = params.interactionType === "view_closed";
913
+ const isCleared = params.body.is_cleared === true;
914
+ const eventPayload = isViewClosed ? {
915
+ interactionType: params.interactionType,
916
+ ...payload,
917
+ isCleared
918
+ } : {
919
+ interactionType: params.interactionType,
920
+ ...payload
921
+ };
922
+ if (isViewClosed) params.ctx.runtime.log?.(`slack:interaction view_closed callback=${callbackId} user=${userId} cleared=${isCleared}`);
923
+ else params.ctx.runtime.log?.(`slack:interaction view_submission callback=${callbackId} user=${userId} inputs=${payload.inputs.length}`);
924
+ if (!expectedUserId) {
925
+ params.ctx.runtime.log?.(`slack:interaction drop modal callback=${callbackId} user=${userId} reason=missing-expected-user`);
926
+ return;
927
+ }
928
+ const auth = await authorizeSlackSystemEventSender({
929
+ ctx: params.ctx,
930
+ senderId: userId,
931
+ channelId: sessionRouting.channelId,
932
+ channelType: sessionRouting.channelType,
933
+ expectedSenderId: expectedUserId,
934
+ interactiveEvent: true
935
+ });
936
+ if (!auth.allowed) {
937
+ params.ctx.runtime.log?.(`slack:interaction drop modal callback=${callbackId} user=${userId} reason=${auth.reason ?? "unauthorized"}`);
938
+ return;
939
+ }
940
+ enqueueSystemEvent(params.formatSystemEvent(eventPayload), {
941
+ sessionKey: sessionRouting.sessionKey,
942
+ contextKey: [
943
+ params.contextPrefix,
944
+ callbackId,
945
+ viewId,
946
+ userId
947
+ ].filter(Boolean).join(":")
948
+ });
949
+ }
950
+ function registerModalLifecycleHandler(params) {
951
+ params.register(params.matcher, async ({ ack, body }) => {
952
+ await ack();
953
+ if (params.ctx.shouldDropMismatchedSlackEvent?.(body)) {
954
+ params.ctx.runtime.log?.(`slack:interaction drop ${params.interactionType} payload (mismatched app/team)`);
955
+ return;
956
+ }
957
+ params.trackEvent?.();
958
+ await emitSlackModalLifecycleEvent({
959
+ ctx: params.ctx,
960
+ body,
961
+ interactionType: params.interactionType,
962
+ contextPrefix: params.contextPrefix,
963
+ summarizeViewState: params.summarizeViewState,
964
+ formatSystemEvent: params.formatSystemEvent
965
+ });
966
+ });
967
+ }
968
+ //#endregion
969
+ //#region extensions/slack/src/monitor/events/interactions.ts
970
+ const OPENCLAW_ACTION_PREFIX = "openclaw:";
971
+ const SLACK_INTERACTION_EVENT_PREFIX = "Slack interaction: ";
972
+ const REDACTED_INTERACTION_VALUE = "[redacted]";
973
+ const SLACK_INTERACTION_EVENT_MAX_CHARS = 2400;
974
+ const SLACK_INTERACTION_STRING_MAX_CHARS = 160;
975
+ const SLACK_INTERACTION_ARRAY_MAX_ITEMS = 64;
976
+ const SLACK_INTERACTION_COMPACT_INPUTS_MAX_ITEMS = 3;
977
+ const SLACK_INTERACTION_REDACTED_KEYS = new Set([
978
+ "triggerId",
979
+ "responseUrl",
980
+ "workflowTriggerUrl",
981
+ "privateMetadata",
982
+ "viewHash"
983
+ ]);
984
+ function sanitizeSlackInteractionPayloadValue(value, key) {
985
+ if (value === void 0) return;
986
+ if (key && SLACK_INTERACTION_REDACTED_KEYS.has(key)) {
987
+ if (typeof value !== "string" || value.trim().length === 0) return;
988
+ return REDACTED_INTERACTION_VALUE;
989
+ }
990
+ if (typeof value === "string") return truncateSlackText(value, SLACK_INTERACTION_STRING_MAX_CHARS);
991
+ if (Array.isArray(value)) {
992
+ const sanitized = value.slice(0, SLACK_INTERACTION_ARRAY_MAX_ITEMS).map((entry) => sanitizeSlackInteractionPayloadValue(entry)).filter((entry) => entry !== void 0);
993
+ if (value.length > SLACK_INTERACTION_ARRAY_MAX_ITEMS) sanitized.push(`…+${value.length - SLACK_INTERACTION_ARRAY_MAX_ITEMS} more`);
994
+ return sanitized;
995
+ }
996
+ if (!value || typeof value !== "object") return value;
997
+ const output = {};
998
+ for (const [entryKey, entryValue] of Object.entries(value)) {
999
+ const sanitized = sanitizeSlackInteractionPayloadValue(entryValue, entryKey);
1000
+ if (sanitized === void 0) continue;
1001
+ if (typeof sanitized === "string" && sanitized.length === 0) continue;
1002
+ if (Array.isArray(sanitized) && sanitized.length === 0) continue;
1003
+ output[entryKey] = sanitized;
1004
+ }
1005
+ return output;
1006
+ }
1007
+ function buildCompactSlackInteractionPayload(payload) {
1008
+ const rawInputs = Array.isArray(payload.inputs) ? payload.inputs : [];
1009
+ const compactInputs = rawInputs.slice(0, SLACK_INTERACTION_COMPACT_INPUTS_MAX_ITEMS).flatMap((entry) => {
1010
+ if (!entry || typeof entry !== "object") return [];
1011
+ const typed = entry;
1012
+ return [{
1013
+ actionId: typed.actionId,
1014
+ blockId: typed.blockId,
1015
+ actionType: typed.actionType,
1016
+ inputKind: typed.inputKind,
1017
+ selectedValues: typed.selectedValues,
1018
+ selectedLabels: typed.selectedLabels,
1019
+ inputValue: typed.inputValue,
1020
+ inputNumber: typed.inputNumber,
1021
+ selectedDate: typed.selectedDate,
1022
+ selectedTime: typed.selectedTime,
1023
+ selectedDateTime: typed.selectedDateTime,
1024
+ richTextPreview: typed.richTextPreview
1025
+ }];
1026
+ });
1027
+ return {
1028
+ interactionType: payload.interactionType,
1029
+ actionId: payload.actionId,
1030
+ callbackId: payload.callbackId,
1031
+ actionType: payload.actionType,
1032
+ userId: payload.userId,
1033
+ teamId: payload.teamId,
1034
+ channelId: payload.channelId ?? payload.routedChannelId,
1035
+ messageTs: payload.messageTs,
1036
+ threadTs: payload.threadTs,
1037
+ viewId: payload.viewId,
1038
+ isCleared: payload.isCleared,
1039
+ selectedValues: payload.selectedValues,
1040
+ selectedLabels: payload.selectedLabels,
1041
+ selectedDate: payload.selectedDate,
1042
+ selectedTime: payload.selectedTime,
1043
+ selectedDateTime: payload.selectedDateTime,
1044
+ workflowId: payload.workflowId,
1045
+ routedChannelType: payload.routedChannelType,
1046
+ inputs: compactInputs.length > 0 ? compactInputs : void 0,
1047
+ inputsOmitted: rawInputs.length > SLACK_INTERACTION_COMPACT_INPUTS_MAX_ITEMS ? rawInputs.length - SLACK_INTERACTION_COMPACT_INPUTS_MAX_ITEMS : void 0,
1048
+ payloadTruncated: true
1049
+ };
1050
+ }
1051
+ function formatSlackInteractionSystemEvent(payload) {
1052
+ const toEventText = (value) => `${SLACK_INTERACTION_EVENT_PREFIX}${JSON.stringify(value)}`;
1053
+ const sanitizedPayload = sanitizeSlackInteractionPayloadValue(payload) ?? {};
1054
+ let eventText = toEventText(sanitizedPayload);
1055
+ if (eventText.length <= SLACK_INTERACTION_EVENT_MAX_CHARS) return eventText;
1056
+ eventText = toEventText(sanitizeSlackInteractionPayloadValue(buildCompactSlackInteractionPayload(sanitizedPayload)));
1057
+ if (eventText.length <= SLACK_INTERACTION_EVENT_MAX_CHARS) return eventText;
1058
+ return toEventText({
1059
+ interactionType: sanitizedPayload.interactionType,
1060
+ actionId: sanitizedPayload.actionId ?? "unknown",
1061
+ userId: sanitizedPayload.userId,
1062
+ channelId: sanitizedPayload.channelId ?? sanitizedPayload.routedChannelId,
1063
+ payloadTruncated: true
1064
+ });
1065
+ }
1066
+ function summarizeViewState(values) {
1067
+ if (!values || typeof values !== "object") return [];
1068
+ const entries = [];
1069
+ for (const [blockId, blockValue] of Object.entries(values)) {
1070
+ if (!blockValue || typeof blockValue !== "object") continue;
1071
+ for (const [actionId, rawAction] of Object.entries(blockValue)) {
1072
+ if (!rawAction || typeof rawAction !== "object") continue;
1073
+ const actionSummary = summarizeAction(rawAction);
1074
+ entries.push({
1075
+ blockId,
1076
+ actionId,
1077
+ ...actionSummary
1078
+ });
1079
+ }
1080
+ }
1081
+ return entries;
1082
+ }
1083
+ function registerSlackInteractionEvents(params) {
1084
+ const { ctx, trackEvent } = params;
1085
+ registerSlackBlockActionHandler({
1086
+ ctx,
1087
+ trackEvent,
1088
+ formatSystemEvent: formatSlackInteractionSystemEvent
1089
+ });
1090
+ if (typeof ctx.app.view !== "function") return;
1091
+ const modalMatcher = new RegExp(`^${OPENCLAW_ACTION_PREFIX}`);
1092
+ registerModalLifecycleHandler({
1093
+ register: (matcher, handler) => ctx.app.view(matcher, handler),
1094
+ matcher: modalMatcher,
1095
+ ctx,
1096
+ trackEvent,
1097
+ interactionType: "view_submission",
1098
+ contextPrefix: "slack:interaction:view",
1099
+ summarizeViewState,
1100
+ formatSystemEvent: formatSlackInteractionSystemEvent
1101
+ });
1102
+ const viewClosed = ctx.app.viewClosed;
1103
+ if (typeof viewClosed !== "function") return;
1104
+ registerModalLifecycleHandler({
1105
+ register: viewClosed,
1106
+ matcher: modalMatcher,
1107
+ ctx,
1108
+ trackEvent,
1109
+ interactionType: "view_closed",
1110
+ contextPrefix: "slack:interaction:view-closed",
1111
+ summarizeViewState,
1112
+ formatSystemEvent: formatSlackInteractionSystemEvent
1113
+ });
1114
+ }
1115
+ //#endregion
1116
+ //#region extensions/slack/src/monitor/events/system-event-context.ts
1117
+ async function authorizeAndResolveSlackSystemEventContext(params) {
1118
+ const { ctx, senderId, channelId, channelType, eventKind } = params;
1119
+ const auth = await authorizeSlackSystemEventSender({
1120
+ ctx,
1121
+ senderId,
1122
+ channelId,
1123
+ channelType
1124
+ });
1125
+ if (!auth.allowed) {
1126
+ logVerbose(`slack: drop ${eventKind} sender ${senderId ?? "unknown"} channel=${channelId ?? "unknown"} reason=${auth.reason ?? "unauthorized"}`);
1127
+ return;
1128
+ }
1129
+ return {
1130
+ channelLabel: resolveSlackChannelLabel({
1131
+ channelId,
1132
+ channelName: auth.channelName
1133
+ }),
1134
+ sessionKey: ctx.resolveSlackSystemEventSessionKey({
1135
+ channelId,
1136
+ channelType: auth.channelType,
1137
+ senderId
1138
+ })
1139
+ };
1140
+ }
1141
+ //#endregion
1142
+ //#region extensions/slack/src/monitor/events/members.ts
1143
+ function registerSlackMemberEvents(params) {
1144
+ const { ctx, trackEvent } = params;
1145
+ const handleMemberChannelEvent = async (params) => {
1146
+ try {
1147
+ if (ctx.shouldDropMismatchedSlackEvent(params.body)) return;
1148
+ trackEvent?.();
1149
+ const payload = params.event;
1150
+ const channelId = payload.channel;
1151
+ const channelInfo = channelId ? await ctx.resolveChannelName(channelId) : {};
1152
+ const channelType = payload.channel_type ?? channelInfo?.type;
1153
+ const ingressContext = await authorizeAndResolveSlackSystemEventContext({
1154
+ ctx,
1155
+ senderId: payload.user,
1156
+ channelId,
1157
+ channelType,
1158
+ eventKind: `member-${params.verb}`
1159
+ });
1160
+ if (!ingressContext) return;
1161
+ enqueueSystemEvent(`Slack: ${(payload.user ? await ctx.resolveUserName(payload.user) : {})?.name ?? payload.user ?? "someone"} ${params.verb} ${ingressContext.channelLabel}.`, {
1162
+ sessionKey: ingressContext.sessionKey,
1163
+ contextKey: `slack:member:${params.verb}:${channelId ?? "unknown"}:${payload.user ?? "unknown"}`
1164
+ });
1165
+ } catch (err) {
1166
+ ctx.runtime.error?.(danger(`slack ${params.verb} handler failed: ${formatErrorMessage(err)}`));
1167
+ }
1168
+ };
1169
+ ctx.app.event("member_joined_channel", async ({ event, body }) => {
1170
+ await handleMemberChannelEvent({
1171
+ verb: "joined",
1172
+ event,
1173
+ body
1174
+ });
1175
+ });
1176
+ ctx.app.event("member_left_channel", async ({ event, body }) => {
1177
+ await handleMemberChannelEvent({
1178
+ verb: "left",
1179
+ event,
1180
+ body
1181
+ });
1182
+ });
1183
+ }
1184
+ //#endregion
1185
+ //#region extensions/slack/src/monitor/events/message-subtype-handlers.ts
1186
+ const SUBTYPE_HANDLER_REGISTRY = {
1187
+ message_changed: {
1188
+ subtype: "message_changed",
1189
+ eventKind: "message_changed",
1190
+ describe: (channelLabel) => `Slack message edited in ${channelLabel}.`,
1191
+ contextKey: (event) => {
1192
+ const changed = event;
1193
+ return `slack:message:changed:${changed.channel ?? "unknown"}:${changed.message?.ts ?? changed.previous_message?.ts ?? changed.event_ts ?? "unknown"}`;
1194
+ },
1195
+ resolveSenderId: (event) => {
1196
+ const changed = event;
1197
+ return changed.message?.user ?? changed.previous_message?.user ?? changed.message?.bot_id ?? changed.previous_message?.bot_id;
1198
+ },
1199
+ resolveChannelId: (event) => event.channel,
1200
+ resolveChannelType: () => void 0
1201
+ },
1202
+ message_deleted: {
1203
+ subtype: "message_deleted",
1204
+ eventKind: "message_deleted",
1205
+ describe: (channelLabel) => `Slack message deleted in ${channelLabel}.`,
1206
+ contextKey: (event) => {
1207
+ const deleted = event;
1208
+ return `slack:message:deleted:${deleted.channel ?? "unknown"}:${deleted.deleted_ts ?? deleted.event_ts ?? "unknown"}`;
1209
+ },
1210
+ resolveSenderId: (event) => {
1211
+ const deleted = event;
1212
+ return deleted.previous_message?.user ?? deleted.previous_message?.bot_id;
1213
+ },
1214
+ resolveChannelId: (event) => event.channel,
1215
+ resolveChannelType: () => void 0
1216
+ }
1217
+ };
1218
+ function resolveSlackMessageSubtypeHandler(event) {
1219
+ const subtype = event.subtype;
1220
+ if (subtype !== "message_changed" && subtype !== "message_deleted") return;
1221
+ return SUBTYPE_HANDLER_REGISTRY[subtype];
1222
+ }
1223
+ //#endregion
1224
+ //#region extensions/slack/src/monitor/events/messages.ts
1225
+ function asRecord$1(value) {
1226
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1227
+ }
1228
+ function asString(value) {
1229
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
1230
+ }
1231
+ function isSlackUserId(value) {
1232
+ return /^[UW][A-Z0-9]+$/.test(value);
1233
+ }
1234
+ function addUserCandidate(candidates, value, botUserId) {
1235
+ const id = asString(value);
1236
+ if (!id || id === botUserId || !isSlackUserId(id)) return;
1237
+ candidates.add(id);
1238
+ }
1239
+ function collectMetadataUserCandidates(candidates, value, botUserId) {
1240
+ const payload = asRecord$1(asRecord$1(value)?.event_payload);
1241
+ if (!payload) return;
1242
+ for (const key of [
1243
+ "user",
1244
+ "user_id",
1245
+ "actor_user_id",
1246
+ "author_user_id",
1247
+ "slack_user_id"
1248
+ ]) addUserCandidate(candidates, payload[key], botUserId);
1249
+ }
1250
+ function resolveAssistantMessageChangedSender(params) {
1251
+ const candidates = /* @__PURE__ */ new Set();
1252
+ collectMetadataUserCandidates(candidates, params.message?.metadata, params.botUserId);
1253
+ return candidates.size === 1 ? [...candidates][0] : void 0;
1254
+ }
1255
+ function isSelfAttributedMessageChange(params) {
1256
+ const topUser = asString(params.event.user);
1257
+ const messageUser = asString(params.message?.user);
1258
+ const messageBotId = asString(params.message?.bot_id);
1259
+ return Boolean(params.ctx.botUserId) && (topUser === params.ctx.botUserId || messageUser === params.ctx.botUserId) || Boolean(params.ctx.botId) && messageBotId === params.ctx.botId;
1260
+ }
1261
+ function resolveAssistantMessageChangedInbound(params) {
1262
+ if (params.event.subtype !== "message_changed") return;
1263
+ const changed = params.event;
1264
+ const message = asRecord$1(changed.message);
1265
+ if (!message || !isSelfAttributedMessageChange({
1266
+ event: changed,
1267
+ message,
1268
+ ctx: params.ctx
1269
+ })) return;
1270
+ if (normalizeSlackChannelType(asString(changed.channel_type), changed.channel) !== "im") return;
1271
+ const senderId = resolveAssistantMessageChangedSender({
1272
+ message,
1273
+ botUserId: params.ctx.botUserId
1274
+ });
1275
+ if (!senderId) return;
1276
+ return {
1277
+ type: "message",
1278
+ channel: changed.channel ?? params.event.channel,
1279
+ channel_type: "im",
1280
+ user: senderId,
1281
+ text: asString(message.text),
1282
+ ts: asString(message.ts) ?? asString(changed.event_ts),
1283
+ thread_ts: asString(message.thread_ts),
1284
+ event_ts: changed.event_ts,
1285
+ files: Array.isArray(message.files) ? message.files : void 0,
1286
+ attachments: Array.isArray(message.attachments) ? message.attachments : void 0
1287
+ };
1288
+ }
1289
+ function registerSlackMessageEvents(params) {
1290
+ const { ctx, handleSlackMessage } = params;
1291
+ const handleIncomingMessageEvent = async ({ event, body }) => {
1292
+ try {
1293
+ if (ctx.shouldDropMismatchedSlackEvent(body)) return;
1294
+ const message = event;
1295
+ const assistantChangedInbound = resolveAssistantMessageChangedInbound({
1296
+ event: message,
1297
+ ctx
1298
+ });
1299
+ if (assistantChangedInbound) {
1300
+ await handleSlackMessage(assistantChangedInbound, { source: "message" });
1301
+ return;
1302
+ }
1303
+ if (message.subtype === "message_changed" && isSelfAttributedMessageChange({
1304
+ event: message,
1305
+ message: asRecord$1(message.message),
1306
+ ctx
1307
+ })) return;
1308
+ const subtypeHandler = resolveSlackMessageSubtypeHandler(message);
1309
+ if (subtypeHandler) {
1310
+ const channelId = subtypeHandler.resolveChannelId(message);
1311
+ const ingressContext = await authorizeAndResolveSlackSystemEventContext({
1312
+ ctx,
1313
+ senderId: subtypeHandler.resolveSenderId(message),
1314
+ channelId,
1315
+ channelType: subtypeHandler.resolveChannelType(message),
1316
+ eventKind: subtypeHandler.eventKind
1317
+ });
1318
+ if (!ingressContext) return;
1319
+ enqueueSystemEvent(subtypeHandler.describe(ingressContext.channelLabel), {
1320
+ sessionKey: ingressContext.sessionKey,
1321
+ contextKey: subtypeHandler.contextKey(message)
1322
+ });
1323
+ return;
1324
+ }
1325
+ await handleSlackMessage(message, { source: "message" });
1326
+ } catch (err) {
1327
+ ctx.runtime.error?.(danger(`slack handler failed: ${formatErrorMessage(err)}`));
1328
+ }
1329
+ };
1330
+ ctx.app.event("message", async ({ event, body }) => {
1331
+ await handleIncomingMessageEvent({
1332
+ event,
1333
+ body
1334
+ });
1335
+ });
1336
+ ctx.app.event("app_mention", async ({ event, body }) => {
1337
+ try {
1338
+ if (ctx.shouldDropMismatchedSlackEvent(body)) return;
1339
+ const mention = event;
1340
+ const channelType = normalizeSlackChannelType(mention.channel_type, mention.channel);
1341
+ if (channelType === "im" || channelType === "mpim") return;
1342
+ await handleSlackMessage(mention, {
1343
+ source: "app_mention",
1344
+ wasMentioned: true
1345
+ });
1346
+ } catch (err) {
1347
+ ctx.runtime.error?.(danger(`slack mention handler failed: ${formatErrorMessage(err)}`));
1348
+ }
1349
+ });
1350
+ }
1351
+ //#endregion
1352
+ //#region extensions/slack/src/monitor/events/pins.ts
1353
+ async function handleSlackPinEvent(params) {
1354
+ const { ctx, trackEvent, body, event, action, contextKeySuffix, errorLabel } = params;
1355
+ try {
1356
+ if (ctx.shouldDropMismatchedSlackEvent(body)) return;
1357
+ trackEvent?.();
1358
+ const payload = event;
1359
+ const channelId = payload.channel_id;
1360
+ const ingressContext = await authorizeAndResolveSlackSystemEventContext({
1361
+ ctx,
1362
+ senderId: payload.user,
1363
+ channelId,
1364
+ eventKind: "pin"
1365
+ });
1366
+ if (!ingressContext) return;
1367
+ const userLabel = (payload.user ? await ctx.resolveUserName(payload.user) : {})?.name ?? payload.user ?? "someone";
1368
+ const itemType = payload.item?.type ?? "item";
1369
+ const messageId = payload.item?.message?.ts ?? payload.event_ts;
1370
+ enqueueSystemEvent(`Slack: ${userLabel} ${action} a ${itemType} in ${ingressContext.channelLabel}.`, {
1371
+ sessionKey: ingressContext.sessionKey,
1372
+ contextKey: `slack:pin:${contextKeySuffix}:${channelId ?? "unknown"}:${messageId ?? "unknown"}`
1373
+ });
1374
+ } catch (err) {
1375
+ ctx.runtime.error?.(danger(`slack ${errorLabel} handler failed: ${formatErrorMessage(err)}`));
1376
+ }
1377
+ }
1378
+ function registerSlackPinEvents(params) {
1379
+ const { ctx, trackEvent } = params;
1380
+ ctx.app.event("pin_added", async ({ event, body }) => {
1381
+ await handleSlackPinEvent({
1382
+ ctx,
1383
+ trackEvent,
1384
+ body,
1385
+ event,
1386
+ action: "pinned",
1387
+ contextKeySuffix: "added",
1388
+ errorLabel: "pin added"
1389
+ });
1390
+ });
1391
+ ctx.app.event("pin_removed", async ({ event, body }) => {
1392
+ await handleSlackPinEvent({
1393
+ ctx,
1394
+ trackEvent,
1395
+ body,
1396
+ event,
1397
+ action: "unpinned",
1398
+ contextKeySuffix: "removed",
1399
+ errorLabel: "pin removed"
1400
+ });
1401
+ });
1402
+ }
1403
+ //#endregion
1404
+ //#region extensions/slack/src/monitor/events/reactions.ts
1405
+ function registerSlackReactionEvents(params) {
1406
+ const { ctx, trackEvent } = params;
1407
+ const handleReactionEvent = async (event, action) => {
1408
+ try {
1409
+ const item = event.item;
1410
+ if (!item || item.type !== "message") return;
1411
+ trackEvent?.();
1412
+ const ingressContext = await authorizeAndResolveSlackSystemEventContext({
1413
+ ctx,
1414
+ senderId: event.user,
1415
+ channelId: item.channel,
1416
+ eventKind: "reaction"
1417
+ });
1418
+ if (!ingressContext) return;
1419
+ const actorInfoPromise = event.user ? ctx.resolveUserName(event.user) : Promise.resolve(void 0);
1420
+ const authorInfoPromise = event.item_user ? ctx.resolveUserName(event.item_user) : Promise.resolve(void 0);
1421
+ const [actorInfo, authorInfo] = await Promise.all([actorInfoPromise, authorInfoPromise]);
1422
+ const actorLabel = actorInfo?.name ?? event.user;
1423
+ const emojiLabel = event.reaction ?? "emoji";
1424
+ const authorLabel = authorInfo?.name ?? event.item_user;
1425
+ const baseText = `Slack reaction ${action}: :${emojiLabel}: by ${actorLabel} in ${ingressContext.channelLabel} msg ${item.ts}`;
1426
+ enqueueSystemEvent(authorLabel ? `${baseText} from ${authorLabel}` : baseText, {
1427
+ sessionKey: ingressContext.sessionKey,
1428
+ contextKey: `slack:reaction:${action}:${item.channel}:${item.ts}:${event.user}:${emojiLabel}`
1429
+ });
1430
+ } catch (err) {
1431
+ ctx.runtime.error?.(danger(`slack reaction handler failed: ${formatErrorMessage(err)}`));
1432
+ }
1433
+ };
1434
+ ctx.app.event("reaction_added", async ({ event, body }) => {
1435
+ if (ctx.shouldDropMismatchedSlackEvent(body)) return;
1436
+ await handleReactionEvent(event, "added");
1437
+ });
1438
+ ctx.app.event("reaction_removed", async ({ event, body }) => {
1439
+ if (ctx.shouldDropMismatchedSlackEvent(body)) return;
1440
+ await handleReactionEvent(event, "removed");
1441
+ });
1442
+ }
1443
+ //#endregion
1444
+ //#region extensions/slack/src/monitor/events.ts
1445
+ function registerSlackMonitorEvents(params) {
1446
+ registerSlackMessageEvents({
1447
+ ctx: params.ctx,
1448
+ handleSlackMessage: params.handleSlackMessage
1449
+ });
1450
+ registerSlackReactionEvents({
1451
+ ctx: params.ctx,
1452
+ trackEvent: params.trackEvent
1453
+ });
1454
+ registerSlackMemberEvents({
1455
+ ctx: params.ctx,
1456
+ trackEvent: params.trackEvent
1457
+ });
1458
+ registerSlackChannelEvents({
1459
+ ctx: params.ctx,
1460
+ trackEvent: params.trackEvent
1461
+ });
1462
+ registerSlackPinEvents({
1463
+ ctx: params.ctx,
1464
+ trackEvent: params.trackEvent
1465
+ });
1466
+ registerSlackHomeEvents({
1467
+ ctx: params.ctx,
1468
+ trackEvent: params.trackEvent
1469
+ });
1470
+ registerSlackInteractionEvents({
1471
+ ctx: params.ctx,
1472
+ trackEvent: params.trackEvent
1473
+ });
1474
+ }
1475
+ //#endregion
1476
+ //#region extensions/slack/src/monitor/message-handler/debounce-key.ts
1477
+ function resolveSlackSenderId(message) {
1478
+ return message.user ?? message.bot_id ?? null;
1479
+ }
1480
+ function isSlackDirectMessageChannel(channelId) {
1481
+ return channelId.startsWith("D");
1482
+ }
1483
+ function isTopLevelSlackMessage(message) {
1484
+ return !message.thread_ts && !message.parent_user_id;
1485
+ }
1486
+ function buildTopLevelSlackConversationKey(message, accountId) {
1487
+ if (!isTopLevelSlackMessage(message)) return null;
1488
+ const senderId = resolveSlackSenderId(message);
1489
+ if (!senderId) return null;
1490
+ return `slack:${accountId}:${message.channel}:${senderId}`;
1491
+ }
1492
+ function buildSlackDebounceKey(message, accountId) {
1493
+ const senderId = resolveSlackSenderId(message);
1494
+ if (!senderId) return null;
1495
+ const messageTs = message.ts ?? message.event_ts;
1496
+ 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}`;
1497
+ }
1498
+ //#endregion
1499
+ //#region extensions/slack/src/monitor/thread-resolution.ts
1500
+ const DEFAULT_THREAD_TS_CACHE_TTL_MS = 6e4;
1501
+ const DEFAULT_THREAD_TS_CACHE_MAX = 500;
1502
+ const normalizeThreadTs = (threadTs) => {
1503
+ const trimmed = threadTs?.trim();
1504
+ return trimmed ? trimmed : void 0;
1505
+ };
1506
+ const markAmbiguousThreadReply = (message) => ({
1507
+ ...message,
1508
+ _ambiguousThreadReply: true
1509
+ });
1510
+ async function resolveThreadTsFromHistory(params) {
1511
+ try {
1512
+ const response = await params.client.conversations.history({
1513
+ channel: params.channelId,
1514
+ latest: params.messageTs,
1515
+ oldest: params.messageTs,
1516
+ inclusive: true,
1517
+ limit: 1
1518
+ });
1519
+ return normalizeThreadTs((response.messages?.find((entry) => entry.ts === params.messageTs) ?? response.messages?.[0])?.thread_ts);
1520
+ } catch (err) {
1521
+ if (shouldLogVerbose()) logVerbose(`slack inbound: failed to resolve thread_ts via conversations.history for channel=${params.channelId} ts=${params.messageTs}: ${formatSlackError(err)}`);
1522
+ return;
1523
+ }
1524
+ }
1525
+ function createSlackThreadTsResolver(params) {
1526
+ const ttlMs = Math.max(0, params.cacheTtlMs ?? DEFAULT_THREAD_TS_CACHE_TTL_MS);
1527
+ const maxSize = Math.max(0, params.maxSize ?? DEFAULT_THREAD_TS_CACHE_MAX);
1528
+ const cache = /* @__PURE__ */ new Map();
1529
+ const inflight = /* @__PURE__ */ new Map();
1530
+ const getCached = (key, now) => {
1531
+ const entry = cache.get(key);
1532
+ if (!entry) return;
1533
+ if (ttlMs > 0 && now - entry.updatedAt > ttlMs) {
1534
+ cache.delete(key);
1535
+ return;
1536
+ }
1537
+ cache.delete(key);
1538
+ cache.set(key, {
1539
+ ...entry,
1540
+ updatedAt: now
1541
+ });
1542
+ return entry.threadTs;
1543
+ };
1544
+ const setCached = (key, threadTs, now) => {
1545
+ cache.delete(key);
1546
+ cache.set(key, {
1547
+ threadTs,
1548
+ updatedAt: now
1549
+ });
1550
+ pruneMapToMaxSize(cache, maxSize);
1551
+ };
1552
+ return { resolve: async (request) => {
1553
+ const { message } = request;
1554
+ if (!message.parent_user_id || message.thread_ts || !message.ts) return message;
1555
+ const cacheKey = `${message.channel}:${message.ts}`;
1556
+ const cached = getCached(cacheKey, Date.now());
1557
+ if (cached !== void 0) return cached ? {
1558
+ ...message,
1559
+ thread_ts: cached
1560
+ } : markAmbiguousThreadReply(message);
1561
+ if (shouldLogVerbose()) logVerbose(`slack inbound: missing thread_ts for thread reply channel=${message.channel} ts=${message.ts} source=${request.source}`);
1562
+ let pending = inflight.get(cacheKey);
1563
+ if (!pending) {
1564
+ pending = resolveThreadTsFromHistory({
1565
+ client: params.client,
1566
+ channelId: message.channel,
1567
+ messageTs: message.ts
1568
+ });
1569
+ inflight.set(cacheKey, pending);
1570
+ }
1571
+ let resolved;
1572
+ try {
1573
+ resolved = await pending;
1574
+ } finally {
1575
+ inflight.delete(cacheKey);
1576
+ }
1577
+ setCached(cacheKey, resolved ?? null, Date.now());
1578
+ if (resolved) {
1579
+ if (shouldLogVerbose()) logVerbose(`slack inbound: resolved missing thread_ts channel=${message.channel} ts=${message.ts} -> thread_ts=${resolved}`);
1580
+ return {
1581
+ ...message,
1582
+ thread_ts: resolved
1583
+ };
1584
+ }
1585
+ if (shouldLogVerbose()) logVerbose(`slack inbound: could not resolve missing thread_ts channel=${message.channel} ts=${message.ts}; marking reply ambiguous`);
1586
+ return markAmbiguousThreadReply(message);
1587
+ } };
1588
+ }
1589
+ //#endregion
1590
+ //#region extensions/slack/src/monitor/message-handler.ts
1591
+ let slackMessagePipelinePromise;
1592
+ function loadSlackMessagePipeline() {
1593
+ slackMessagePipelinePromise ??= import("./pipeline.runtime-DT0hLnq2.js");
1594
+ return slackMessagePipelinePromise;
1595
+ }
1596
+ const APP_MENTION_RETRY_TTL_MS = 6e4;
1597
+ var SlackRetryableInboundError = class extends Error {
1598
+ constructor(message, options) {
1599
+ super(message, options);
1600
+ this.name = "SlackRetryableInboundError";
1601
+ }
1602
+ };
1603
+ function shouldDebounceSlackMessage(message, cfg) {
1604
+ return shouldDebounceTextInbound({
1605
+ text: stripSlackMentionsForCommandDetection(message.text ?? ""),
1606
+ cfg,
1607
+ hasMedia: Boolean(message.files && message.files.length > 0)
1608
+ });
1609
+ }
1610
+ function buildSeenMessageKey(channelId, ts) {
1611
+ if (!channelId || !ts) return null;
1612
+ return `${channelId}:${ts}`;
1613
+ }
1614
+ function createSlackMessageHandler(params) {
1615
+ const { ctx, account, trackEvent } = params;
1616
+ const { debounceMs, debouncer } = createChannelInboundDebouncer({
1617
+ cfg: ctx.cfg,
1618
+ channel: "slack",
1619
+ buildKey: (entry) => buildSlackDebounceKey(entry.message, ctx.accountId),
1620
+ shouldDebounce: (entry) => shouldDebounceSlackMessage(entry.message, ctx.cfg),
1621
+ onFlush: async (entries) => {
1622
+ const last = entries.at(-1);
1623
+ if (!last) return;
1624
+ const flushedKey = buildSlackDebounceKey(last.message, ctx.accountId);
1625
+ const topLevelConversationKey = buildTopLevelSlackConversationKey(last.message, ctx.accountId);
1626
+ if (flushedKey && topLevelConversationKey) {
1627
+ const pendingKeys = pendingTopLevelDebounceKeys.get(topLevelConversationKey);
1628
+ if (pendingKeys) {
1629
+ pendingKeys.delete(flushedKey);
1630
+ if (pendingKeys.size === 0) pendingTopLevelDebounceKeys.delete(topLevelConversationKey);
1631
+ }
1632
+ }
1633
+ const combinedText = entries.length === 1 ? last.message.text ?? "" : entries.map((entry) => entry.message.text ?? "").filter(Boolean).join("\n");
1634
+ const combinedMentioned = entries.some((entry) => Boolean(entry.opts.wasMentioned));
1635
+ const syntheticMessage = {
1636
+ ...last.message,
1637
+ text: combinedText
1638
+ };
1639
+ const seenMessageKey = buildSeenMessageKey(last.message.channel, last.message.ts);
1640
+ try {
1641
+ const { prepareSlackMessage, dispatchPreparedSlackMessage } = await loadSlackMessagePipeline();
1642
+ const prepared = await prepareSlackMessage({
1643
+ ctx,
1644
+ account,
1645
+ message: syntheticMessage,
1646
+ opts: {
1647
+ ...last.opts,
1648
+ wasMentioned: combinedMentioned || last.opts.wasMentioned
1649
+ }
1650
+ });
1651
+ if (!prepared) return;
1652
+ if (seenMessageKey) {
1653
+ pruneAppMentionRetryKeys(Date.now());
1654
+ if (last.opts.source === "app_mention") appMentionDispatchedKeys.set(seenMessageKey, Date.now() + APP_MENTION_RETRY_TTL_MS);
1655
+ else if (last.opts.source === "message" && appMentionDispatchedKeys.has(seenMessageKey)) {
1656
+ appMentionDispatchedKeys.delete(seenMessageKey);
1657
+ appMentionRetryKeys.delete(seenMessageKey);
1658
+ return;
1659
+ }
1660
+ appMentionRetryKeys.delete(seenMessageKey);
1661
+ }
1662
+ if (entries.length > 1) {
1663
+ const ids = entries.map((entry) => entry.message.ts).filter(Boolean);
1664
+ if (ids.length > 0) {
1665
+ prepared.ctxPayload.MessageSids = ids;
1666
+ prepared.ctxPayload.MessageSidFirst = ids[0];
1667
+ prepared.ctxPayload.MessageSidLast = ids[ids.length - 1];
1668
+ }
1669
+ }
1670
+ await dispatchPreparedSlackMessage(prepared);
1671
+ } catch (error) {
1672
+ if (error instanceof SlackRetryableInboundError) {
1673
+ if (seenMessageKey) appMentionDispatchedKeys.delete(seenMessageKey);
1674
+ ctx.releaseSeenMessage(last.message.channel, last.message.ts);
1675
+ }
1676
+ throw error;
1677
+ }
1678
+ },
1679
+ onError: (err) => {
1680
+ ctx.runtime.error?.(`slack inbound debounce flush failed: ${formatErrorMessage(err)}`);
1681
+ }
1682
+ });
1683
+ const threadTsResolver = createSlackThreadTsResolver({ client: ctx.app.client });
1684
+ const pendingTopLevelDebounceKeys = /* @__PURE__ */ new Map();
1685
+ const appMentionRetryKeys = /* @__PURE__ */ new Map();
1686
+ const appMentionDispatchedKeys = /* @__PURE__ */ new Map();
1687
+ const pruneAppMentionRetryKeys = (now) => {
1688
+ for (const [key, expiresAt] of appMentionRetryKeys) if (expiresAt <= now) appMentionRetryKeys.delete(key);
1689
+ for (const [key, expiresAt] of appMentionDispatchedKeys) if (expiresAt <= now) appMentionDispatchedKeys.delete(key);
1690
+ };
1691
+ const rememberAppMentionRetryKey = (key) => {
1692
+ const now = Date.now();
1693
+ pruneAppMentionRetryKeys(now);
1694
+ appMentionRetryKeys.set(key, now + APP_MENTION_RETRY_TTL_MS);
1695
+ };
1696
+ const consumeAppMentionRetryKey = (key) => {
1697
+ pruneAppMentionRetryKeys(Date.now());
1698
+ if (!appMentionRetryKeys.has(key)) return false;
1699
+ appMentionRetryKeys.delete(key);
1700
+ return true;
1701
+ };
1702
+ return async (message, opts) => {
1703
+ if (opts.source === "message" && message.type !== "message") return;
1704
+ if (opts.source === "message" && message.subtype && message.subtype !== "file_share" && message.subtype !== "bot_message" && message.subtype !== "thread_broadcast") return;
1705
+ const seenMessageKey = buildSeenMessageKey(message.channel, message.ts);
1706
+ const wasSeen = seenMessageKey ? ctx.markMessageSeen(message.channel, message.ts) : false;
1707
+ if (seenMessageKey && opts.source === "message" && !wasSeen) rememberAppMentionRetryKey(seenMessageKey);
1708
+ if (seenMessageKey && wasSeen) {
1709
+ if (opts.source !== "app_mention" || !consumeAppMentionRetryKey(seenMessageKey)) return;
1710
+ }
1711
+ trackEvent?.();
1712
+ const resolvedMessage = await threadTsResolver.resolve({
1713
+ message,
1714
+ source: opts.source
1715
+ });
1716
+ const debounceKey = buildSlackDebounceKey(resolvedMessage, ctx.accountId);
1717
+ const conversationKey = buildTopLevelSlackConversationKey(resolvedMessage, ctx.accountId);
1718
+ const canDebounce = debounceMs > 0 && shouldDebounceSlackMessage(resolvedMessage, ctx.cfg);
1719
+ if (!canDebounce && conversationKey) {
1720
+ const pendingKeys = pendingTopLevelDebounceKeys.get(conversationKey);
1721
+ if (pendingKeys && pendingKeys.size > 0) {
1722
+ const keysToFlush = Array.from(pendingKeys);
1723
+ for (const pendingKey of keysToFlush) await debouncer.flushKey(pendingKey);
1724
+ }
1725
+ }
1726
+ if (canDebounce && debounceKey && conversationKey) {
1727
+ const pendingKeys = pendingTopLevelDebounceKeys.get(conversationKey) ?? /* @__PURE__ */ new Set();
1728
+ pendingKeys.add(debounceKey);
1729
+ pendingTopLevelDebounceKeys.set(conversationKey, pendingKeys);
1730
+ }
1731
+ await debouncer.enqueue({
1732
+ message: resolvedMessage,
1733
+ opts
1734
+ });
1735
+ };
1736
+ }
1737
+ //#endregion
1738
+ //#region extensions/slack/src/monitor/reconnect-policy.ts
1739
+ const SLACK_AUTH_ERROR_RE = /account_inactive|invalid_auth|token_revoked|token_expired|not_authed|org_login_required|team_access_not_granted|missing_scope|cannot_find_service|invalid_token/i;
1740
+ const NO_ERROR_DETAIL = "no error detail";
1741
+ const SLACK_SOCKET_RECONNECT_POLICY = {
1742
+ initialMs: 2e3,
1743
+ maxMs: 3e4,
1744
+ factor: 1.8,
1745
+ jitter: .25,
1746
+ maxAttempts: 12
1747
+ };
1748
+ function getSocketEmitter(app) {
1749
+ const receiver = app.receiver;
1750
+ const client = receiver && typeof receiver === "object" ? receiver.client : void 0;
1751
+ if (!client || typeof client !== "object") return null;
1752
+ const on = client.on;
1753
+ const off = client.off;
1754
+ if (typeof on !== "function" || typeof off !== "function") return null;
1755
+ return {
1756
+ on: (event, listener) => on.call(client, event, listener),
1757
+ off: (event, listener) => off.call(client, event, listener)
1758
+ };
1759
+ }
1760
+ function waitForSlackSocketDisconnect(app, abortSignal) {
1761
+ return new Promise((resolve) => {
1762
+ const emitter = getSocketEmitter(app);
1763
+ if (!emitter) {
1764
+ abortSignal?.addEventListener("abort", () => resolve({ event: "disconnect" }), { once: true });
1765
+ return;
1766
+ }
1767
+ const disconnectListener = () => resolveOnce({ event: "disconnect" });
1768
+ const startFailListener = (error) => resolveOnce({
1769
+ event: "unable_to_socket_mode_start",
1770
+ error
1771
+ });
1772
+ const errorListener = (error) => resolveOnce({
1773
+ event: "error",
1774
+ error
1775
+ });
1776
+ const abortListener = () => resolveOnce({ event: "disconnect" });
1777
+ const cleanup = () => {
1778
+ emitter.off("disconnected", disconnectListener);
1779
+ emitter.off("unable_to_socket_mode_start", startFailListener);
1780
+ emitter.off("error", errorListener);
1781
+ abortSignal?.removeEventListener("abort", abortListener);
1782
+ };
1783
+ const resolveOnce = (value) => {
1784
+ cleanup();
1785
+ resolve(value);
1786
+ };
1787
+ emitter.on("disconnected", disconnectListener);
1788
+ emitter.on("unable_to_socket_mode_start", startFailListener);
1789
+ emitter.on("error", errorListener);
1790
+ abortSignal?.addEventListener("abort", abortListener, { once: true });
1791
+ });
1792
+ }
1793
+ /**
1794
+ * Detect non-recoverable Slack API / auth errors that should NOT be retried.
1795
+ * These indicate permanent credential problems (revoked bot, deactivated account, etc.)
1796
+ * and retrying will never succeed — continuing to retry blocks the entire gateway.
1797
+ */
1798
+ function isNonRecoverableSlackAuthError(error) {
1799
+ return SLACK_AUTH_ERROR_RE.test(formatUnknownError(error, ""));
1800
+ }
1801
+ function formatUnknownError(error, fallback = NO_ERROR_DETAIL) {
1802
+ return formatSlackError(error, fallback);
1803
+ }
1804
+ //#endregion
1805
+ //#region extensions/slack/src/monitor/provider-support.ts
1806
+ const OPENCLAW_SLACK_CLIENT_PING_TIMEOUT_MS = 15e3;
1807
+ const OPENCLAW_SLACK_SOCKET_START_FAILED_EVENT = "unable_to_socket_mode_start";
1808
+ const OPENCLAW_SLACK_NATIVE_RECONNECT_OBSERVER_KEY = "__openclawNativeReconnectFailureObserver";
1809
+ const SLACK_SOCKET_PONG_TIMEOUT_WARNING_PREFIX = "A pong wasn't received from the server";
1810
+ const SLACK_SOCKET_PING_TIMEOUT_WARNING_PREFIX = "A ping wasn't received from the server";
1811
+ const SLACK_SOCKET_LOG_LEVEL_IGNORED_WARNING_RE = /^The logLevel given to .+ was ignored as you also gave logger$/;
1812
+ function isConstructorFunction(value) {
1813
+ return typeof value === "function";
1814
+ }
1815
+ function installSlackNativeReconnectFailureObserver(receiver) {
1816
+ if (!receiver || typeof receiver !== "object") return;
1817
+ const client = Reflect.get(receiver, "client");
1818
+ if (!client || typeof client !== "object") return;
1819
+ if (Reflect.get(client, OPENCLAW_SLACK_NATIVE_RECONNECT_OBSERVER_KEY)) return;
1820
+ const delayReconnectAttempt = Reflect.get(client, "delayReconnectAttempt");
1821
+ const emit = Reflect.get(client, "emit");
1822
+ if (typeof delayReconnectAttempt !== "function" || typeof emit !== "function") return;
1823
+ Reflect.set(client, OPENCLAW_SLACK_NATIVE_RECONNECT_OBSERVER_KEY, true);
1824
+ Reflect.set(client, "delayReconnectAttempt", function patchedDelayReconnectAttempt(callback) {
1825
+ if (typeof callback !== "function") return delayReconnectAttempt.call(this, callback);
1826
+ const nextFailureCount = Number(Reflect.get(this, "numOfConsecutiveReconnectionFailures") ?? 0) + 1;
1827
+ Reflect.set(this, "numOfConsecutiveReconnectionFailures", nextFailureCount);
1828
+ const pingTimeoutMs = Number(Reflect.get(this, "clientPingTimeoutMS"));
1829
+ const delayMs = (Number.isFinite(pingTimeoutMs) && pingTimeoutMs >= 0 ? pingTimeoutMs : OPENCLAW_SLACK_CLIENT_PING_TIMEOUT_MS) * nextFailureCount;
1830
+ const logger = Reflect.get(this, "logger");
1831
+ logger?.debug?.(`Before trying to reconnect, this client will wait for ${delayMs} milliseconds`);
1832
+ return new Promise((resolve, reject) => {
1833
+ setTimeout(() => {
1834
+ if (Reflect.get(this, "shuttingDown")) {
1835
+ logger?.debug?.("Client shutting down, will not attempt reconnect.");
1836
+ resolve(void 0);
1837
+ return;
1838
+ }
1839
+ logger?.debug?.("Continuing with reconnect...");
1840
+ emit.call(this, "reconnecting");
1841
+ Promise.resolve(callback.call(this)).then(resolve, (error) => {
1842
+ if (callback === Reflect.get(this, "start")) {
1843
+ emit.call(this, OPENCLAW_SLACK_SOCKET_START_FAILED_EVENT, error);
1844
+ resolve(void 0);
1845
+ return;
1846
+ }
1847
+ reject(error);
1848
+ });
1849
+ }, delayMs);
1850
+ });
1851
+ });
1852
+ }
1853
+ function resolveSlackBoltModule(value) {
1854
+ if (!value || typeof value !== "object") return null;
1855
+ const app = Reflect.get(value, "App");
1856
+ const httpReceiver = Reflect.get(value, "HTTPReceiver");
1857
+ const socketModeReceiver = Reflect.get(value, "SocketModeReceiver");
1858
+ if (!isConstructorFunction(app) || !isConstructorFunction(httpReceiver) || !isConstructorFunction(socketModeReceiver)) return null;
1859
+ return {
1860
+ App: app,
1861
+ HTTPReceiver: httpReceiver,
1862
+ SocketModeReceiver: socketModeReceiver
1863
+ };
1864
+ }
1865
+ function resolveSlackBoltInterop(params) {
1866
+ const { defaultImport, namespaceImport } = params;
1867
+ const nestedDefault = defaultImport && typeof defaultImport === "object" ? Reflect.get(defaultImport, "default") : void 0;
1868
+ const namespaceDefault = namespaceImport && typeof namespaceImport === "object" ? Reflect.get(namespaceImport, "default") : void 0;
1869
+ const namespaceReceiver = namespaceImport && typeof namespaceImport === "object" ? Reflect.get(namespaceImport, "HTTPReceiver") : void 0;
1870
+ const namespaceSocketModeReceiver = namespaceImport && typeof namespaceImport === "object" ? Reflect.get(namespaceImport, "SocketModeReceiver") : void 0;
1871
+ const directModule = resolveSlackBoltModule(defaultImport) ?? resolveSlackBoltModule(nestedDefault) ?? resolveSlackBoltModule(namespaceDefault) ?? resolveSlackBoltModule(namespaceImport);
1872
+ if (directModule) return directModule;
1873
+ if (isConstructorFunction(defaultImport) && isConstructorFunction(namespaceReceiver) && isConstructorFunction(namespaceSocketModeReceiver)) return {
1874
+ App: defaultImport,
1875
+ HTTPReceiver: namespaceReceiver,
1876
+ SocketModeReceiver: namespaceSocketModeReceiver
1877
+ };
1878
+ throw new TypeError("Unable to resolve @slack/bolt App/HTTPReceiver exports");
1879
+ }
1880
+ function publishSlackConnectedStatus(setStatus) {
1881
+ if (!setStatus) return;
1882
+ setStatus({
1883
+ connected: true,
1884
+ lastConnectedAt: Date.now(),
1885
+ healthState: "healthy",
1886
+ lastError: null
1887
+ });
1888
+ }
1889
+ function publishSlackDisconnectedStatus(setStatus, error) {
1890
+ if (!setStatus) return;
1891
+ const at = Date.now();
1892
+ const message = error ? formatUnknownError(error) : void 0;
1893
+ setStatus({
1894
+ connected: false,
1895
+ healthState: "disconnected",
1896
+ lastDisconnect: message ? {
1897
+ at,
1898
+ error: message
1899
+ } : { at },
1900
+ lastError: message ?? null
1901
+ });
1902
+ }
1903
+ function isSlackSocketHeartbeatTimeoutWarning(args) {
1904
+ return typeof args[0] === "string" && (args[0].startsWith(SLACK_SOCKET_PONG_TIMEOUT_WARNING_PREFIX) || args[0].startsWith(SLACK_SOCKET_PING_TIMEOUT_WARNING_PREFIX));
1905
+ }
1906
+ function isSlackSocketSelfInflictedLoggerWarning(args) {
1907
+ return typeof args[0] === "string" && SLACK_SOCKET_LOG_LEVEL_IGNORED_WARNING_RE.test(args[0]);
1908
+ }
1909
+ function formatSlackSdkLogArgs(args) {
1910
+ return args.map((arg) => formatUnknownError(arg, "")).filter(Boolean).join(" ");
1911
+ }
1912
+ function createSlackSocketModeLogger(sink = console) {
1913
+ let level = "info";
1914
+ let name = "socket-mode";
1915
+ const prefix = () => `socket-mode:${name}`;
1916
+ let lastMessage;
1917
+ const remember = (args) => {
1918
+ const message = formatSlackSdkLogArgs([prefix(), ...args]);
1919
+ if (message) lastMessage = message;
1920
+ };
1921
+ return {
1922
+ debug: () => {},
1923
+ info: () => {},
1924
+ warn: (...args) => {
1925
+ if (isSlackSocketHeartbeatTimeoutWarning(args) || isSlackSocketSelfInflictedLoggerWarning(args)) return;
1926
+ remember(args);
1927
+ sink.warn(prefix(), ...args);
1928
+ },
1929
+ error: (...args) => {
1930
+ remember(args);
1931
+ sink.error(prefix(), ...args);
1932
+ },
1933
+ setLevel: (nextLevel) => {
1934
+ level = nextLevel;
1935
+ },
1936
+ getLevel: () => level,
1937
+ setName: (nextName) => {
1938
+ name = nextName;
1939
+ },
1940
+ getLastMessage: () => lastMessage
1941
+ };
1942
+ }
1943
+ function asRecord(value) {
1944
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1945
+ }
1946
+ function shouldSkipOpenClawSlackSelfEvent(args) {
1947
+ const botId = args.context?.botId;
1948
+ const botUserId = args.context?.botUserId;
1949
+ const message = asRecord(args.message);
1950
+ if (message?.subtype === "bot_message" && botId && message.bot_id === botId) return true;
1951
+ const event = asRecord(args.event);
1952
+ if (event?.type === "message" && event.subtype === "message_changed" && event.user === botUserId) return false;
1953
+ const eventsWhichShouldBeKept = new Set(["member_joined_channel", "member_left_channel"]);
1954
+ return Boolean(botUserId && event && event.user === botUserId && typeof event.type === "string" && !eventsWhichShouldBeKept.has(event.type));
1955
+ }
1956
+ function createSlackBoltApp(params) {
1957
+ const socketModeLogger = createSlackSocketModeLogger();
1958
+ const socketModeReceiverOptions = {
1959
+ appToken: params.appToken ?? "",
1960
+ autoReconnectEnabled: true,
1961
+ clientPingTimeout: params.socketMode?.clientPingTimeout ?? OPENCLAW_SLACK_CLIENT_PING_TIMEOUT_MS,
1962
+ logger: socketModeLogger,
1963
+ installerOptions: { clientOptions: params.clientOptions }
1964
+ };
1965
+ if (params.socketMode?.serverPingTimeout !== void 0) socketModeReceiverOptions.serverPingTimeout = params.socketMode.serverPingTimeout;
1966
+ if (params.socketMode?.pingPongLoggingEnabled !== void 0) socketModeReceiverOptions.pingPongLoggingEnabled = params.socketMode.pingPongLoggingEnabled;
1967
+ const receiver = params.slackMode === "socket" ? new params.interop.SocketModeReceiver(socketModeReceiverOptions) : new params.interop.HTTPReceiver({
1968
+ signingSecret: params.signingSecret ?? "",
1969
+ endpoints: params.slackWebhookPath
1970
+ });
1971
+ if (params.slackMode === "socket") installSlackNativeReconnectFailureObserver(receiver);
1972
+ const app = new params.interop.App({
1973
+ token: params.botToken,
1974
+ receiver,
1975
+ clientOptions: params.clientOptions,
1976
+ ignoreSelf: false,
1977
+ tokenVerificationEnabled: false
1978
+ });
1979
+ app.use(async (args) => {
1980
+ if (shouldSkipOpenClawSlackSelfEvent(args)) return;
1981
+ await args.next();
1982
+ });
1983
+ return {
1984
+ app,
1985
+ receiver,
1986
+ socketModeLogger
1987
+ };
1988
+ }
1989
+ function createSlackSocketDisconnectWaiter(app, abortSignal) {
1990
+ const waiterAbortController = new AbortController();
1991
+ const relayAbort = () => waiterAbortController.abort();
1992
+ let latest;
1993
+ abortSignal?.addEventListener("abort", relayAbort, { once: true });
1994
+ return {
1995
+ promise: waitForSlackSocketDisconnect(app, waiterAbortController.signal).then((value) => {
1996
+ latest = value;
1997
+ return value;
1998
+ }),
1999
+ getLatest: () => latest,
2000
+ cancel: () => {
2001
+ waiterAbortController.abort();
2002
+ abortSignal?.removeEventListener("abort", relayAbort);
2003
+ },
2004
+ complete: () => {
2005
+ abortSignal?.removeEventListener("abort", relayAbort);
2006
+ }
2007
+ };
2008
+ }
2009
+ async function startSlackSocketAndWaitForDisconnect(params) {
2010
+ const disconnectWaiter = createSlackSocketDisconnectWaiter(params.app, params.abortSignal);
2011
+ try {
2012
+ await Promise.resolve(params.app.start());
2013
+ if (params.abortSignal?.aborted) {
2014
+ disconnectWaiter.cancel();
2015
+ return null;
2016
+ }
2017
+ params.onStarted?.();
2018
+ const disconnect = await disconnectWaiter.promise;
2019
+ disconnectWaiter.complete();
2020
+ return disconnect;
2021
+ } catch (err) {
2022
+ await Promise.resolve();
2023
+ const disconnect = disconnectWaiter.getLatest();
2024
+ disconnectWaiter.cancel();
2025
+ if ((err === void 0 || err === null || err === "") && disconnect?.error !== void 0) throw disconnect.error;
2026
+ if (err === void 0 || err === null || err === "") {
2027
+ const suffix = disconnect ? ` after ${disconnect.event}` : "";
2028
+ throw new Error(`Slack Socket Mode start failed${suffix} without error detail`, { cause: err });
2029
+ }
2030
+ throw err;
2031
+ }
2032
+ }
2033
+ function resolveSlackSocketShutdownClient(app) {
2034
+ if (!app || typeof app !== "object") return;
2035
+ const receiver = Reflect.get(app, "receiver");
2036
+ if (!receiver || typeof receiver !== "object") return;
2037
+ const client = Reflect.get(receiver, "client");
2038
+ if (!client || typeof client !== "object") return;
2039
+ return client;
2040
+ }
2041
+ async function gracefulStopSlackApp(app) {
2042
+ const socketClient = resolveSlackSocketShutdownClient(app);
2043
+ if (socketClient) socketClient.shuttingDown = true;
2044
+ await Promise.resolve(app.stop()).catch(() => void 0);
2045
+ }
2046
+ function formatSlackResolvedLabel(params) {
2047
+ const extras = params.extra?.filter(Boolean) ?? [];
2048
+ const suffix = extras.length > 0 ? ` (id:${params.id}, ${extras.join(", ")})` : ` (id:${params.id})`;
2049
+ return `${params.input}→${params.name ?? params.id}${suffix}`;
2050
+ }
2051
+ function formatSlackChannelResolved(entry) {
2052
+ const id = entry.id ?? entry.input;
2053
+ return formatSlackResolvedLabel({
2054
+ input: entry.input,
2055
+ id,
2056
+ name: entry.name,
2057
+ extra: entry.archived ? ["archived"] : []
2058
+ });
2059
+ }
2060
+ function formatSlackUserResolved(entry) {
2061
+ const id = entry.id ?? entry.input;
2062
+ return formatSlackResolvedLabel({
2063
+ input: entry.input,
2064
+ id,
2065
+ name: entry.name,
2066
+ extra: entry.note ? [entry.note] : []
2067
+ });
2068
+ }
2069
+ //#endregion
2070
+ //#region extensions/slack/src/monitor/external-arg-menu-store.ts
2071
+ const SLACK_EXTERNAL_ARG_MENU_TOKEN_BYTES = 18;
2072
+ const SLACK_EXTERNAL_ARG_MENU_TOKEN_LENGTH = Math.ceil(SLACK_EXTERNAL_ARG_MENU_TOKEN_BYTES * 8 / 6);
2073
+ const SLACK_EXTERNAL_ARG_MENU_TOKEN_PATTERN = new RegExp(`^[A-Za-z0-9_-]{${SLACK_EXTERNAL_ARG_MENU_TOKEN_LENGTH}}$`);
2074
+ const SLACK_EXTERNAL_ARG_MENU_TTL_MS = 600 * 1e3;
2075
+ const SLACK_EXTERNAL_ARG_MENU_PREFIX = "openclaw_cmdarg_ext:";
2076
+ function pruneSlackExternalArgMenuStore(store, now) {
2077
+ for (const [token, entry] of store.entries()) if (entry.expiresAt <= now) store.delete(token);
2078
+ }
2079
+ function createSlackExternalArgMenuToken(store) {
2080
+ let token = "";
2081
+ do
2082
+ token = generateSecureToken(SLACK_EXTERNAL_ARG_MENU_TOKEN_BYTES);
2083
+ while (store.has(token));
2084
+ return token;
2085
+ }
2086
+ function createSlackExternalArgMenuStore() {
2087
+ const store = /* @__PURE__ */ new Map();
2088
+ return {
2089
+ create(params, now = Date.now()) {
2090
+ pruneSlackExternalArgMenuStore(store, now);
2091
+ const token = createSlackExternalArgMenuToken(store);
2092
+ store.set(token, {
2093
+ choices: params.choices,
2094
+ userId: params.userId,
2095
+ expiresAt: now + SLACK_EXTERNAL_ARG_MENU_TTL_MS
2096
+ });
2097
+ return token;
2098
+ },
2099
+ readToken(raw) {
2100
+ if (typeof raw !== "string" || !raw.startsWith("openclaw_cmdarg_ext:")) return;
2101
+ const token = raw.slice(20).trim();
2102
+ return SLACK_EXTERNAL_ARG_MENU_TOKEN_PATTERN.test(token) ? token : void 0;
2103
+ },
2104
+ get(token, now = Date.now()) {
2105
+ pruneSlackExternalArgMenuStore(store, now);
2106
+ return store.get(token);
2107
+ }
2108
+ };
2109
+ }
2110
+ //#endregion
2111
+ //#region extensions/slack/src/monitor/slash.ts
2112
+ const SLACK_COMMAND_ARG_ACTION_ID = "openclaw_cmdarg";
2113
+ const SLACK_COMMAND_ARG_ACTION_LISTENER = /^openclaw_cmdarg/;
2114
+ const SLACK_COMMAND_ARG_VALUE_PREFIX = "cmdarg";
2115
+ const SLACK_COMMAND_ARG_BUTTON_ROW_SIZE = 5;
2116
+ const SLACK_COMMAND_ARG_OVERFLOW_MIN = 3;
2117
+ const SLACK_COMMAND_ARG_OVERFLOW_MAX = 5;
2118
+ const SLACK_COMMAND_ARG_SELECT_OPTIONS_MAX = 100;
2119
+ const SLACK_COMMAND_ARG_SELECT_OPTION_TEXT_MAX = 75;
2120
+ const SLACK_COMMAND_ARG_SELECT_OPTION_VALUE_MAX = 150;
2121
+ const SLACK_COMMAND_ARG_BUTTON_TEXT_MAX = 75;
2122
+ const SLACK_COMMAND_ARG_BUTTON_VALUE_MAX = 2e3;
2123
+ const SLACK_COMMAND_ARG_CONFIRM_TEXT_MAX = 300;
2124
+ const SLACK_HEADER_TEXT_MAX = 150;
2125
+ const SLACK_COMMAND_ARG_ACTION_BLOCKS_MAX = 47;
2126
+ let slashCommandsRuntimePromise = null;
2127
+ let slashDispatchRuntimePromise = null;
2128
+ let slackPluginCommandsRuntimePromise = null;
2129
+ let slashSkillCommandsRuntimePromise = null;
2130
+ function loadSlashCommandsRuntime() {
2131
+ slashCommandsRuntimePromise ??= import("./slash-commands.runtime-22kgyst2.js");
2132
+ return slashCommandsRuntimePromise;
2133
+ }
2134
+ function loadSlashDispatchRuntime() {
2135
+ slashDispatchRuntimePromise ??= import("./slash-dispatch.runtime-BJgT0jwV.js");
2136
+ return slashDispatchRuntimePromise;
2137
+ }
2138
+ function loadSlackPluginCommandsRuntime() {
2139
+ slackPluginCommandsRuntimePromise ??= import("./slash-plugin-commands.runtime-CF-n3MeP.js");
2140
+ return slackPluginCommandsRuntimePromise;
2141
+ }
2142
+ function loadSlashSkillCommandsRuntime() {
2143
+ slashSkillCommandsRuntimePromise ??= import("./slash-skill-commands.runtime-BMs0VjTe.js");
2144
+ return slashSkillCommandsRuntimePromise;
2145
+ }
2146
+ function resolveSlackCommandMenuModelContext(params) {
2147
+ if (!params.sessionKey.trim()) return {};
2148
+ try {
2149
+ const defaultModel = resolveDefaultModelForAgent({
2150
+ cfg: params.cfg,
2151
+ agentId: params.agentId
2152
+ });
2153
+ const store = loadSessionStore(resolveStorePath(params.cfg.session?.store, { agentId: params.agentId }));
2154
+ const entry = store[params.sessionKey];
2155
+ if (entry?.modelOverrideSource === "auto" && normalizeOptionalString(entry.modelOverride)) return {
2156
+ provider: defaultModel.provider,
2157
+ model: defaultModel.model
2158
+ };
2159
+ const override = resolveStoredModelOverride({
2160
+ sessionEntry: entry,
2161
+ sessionStore: store,
2162
+ sessionKey: params.sessionKey,
2163
+ defaultProvider: defaultModel.provider
2164
+ });
2165
+ if (override?.model) return {
2166
+ provider: override.provider || defaultModel.provider,
2167
+ model: override.model
2168
+ };
2169
+ const provider = normalizeOptionalString(entry?.providerOverride) ?? normalizeOptionalString(entry?.modelProvider);
2170
+ const model = normalizeOptionalString(entry?.modelOverride) ?? normalizeOptionalString(entry?.model);
2171
+ return {
2172
+ ...provider ? { provider } : {},
2173
+ ...model ? { model } : {}
2174
+ };
2175
+ } catch {
2176
+ return {};
2177
+ }
2178
+ }
2179
+ const slackExternalArgMenuStore = createSlackExternalArgMenuStore();
2180
+ function buildSlackArgMenuConfirm(params) {
2181
+ return {
2182
+ title: {
2183
+ type: "plain_text",
2184
+ text: "Confirm selection"
2185
+ },
2186
+ text: {
2187
+ type: "mrkdwn",
2188
+ text: truncateSlackText(`Run */${escapeSlackMrkdwn(params.command)}* with *${escapeSlackMrkdwn(params.arg)}* set to this value?`, SLACK_COMMAND_ARG_CONFIRM_TEXT_MAX)
2189
+ },
2190
+ confirm: {
2191
+ type: "plain_text",
2192
+ text: "Run command"
2193
+ },
2194
+ deny: {
2195
+ type: "plain_text",
2196
+ text: "Cancel"
2197
+ }
2198
+ };
2199
+ }
2200
+ function storeSlackExternalArgMenu(params) {
2201
+ return slackExternalArgMenuStore.create({
2202
+ choices: params.choices,
2203
+ userId: params.userId
2204
+ });
2205
+ }
2206
+ function readSlackExternalArgMenuToken(raw) {
2207
+ return slackExternalArgMenuStore.readToken(raw);
2208
+ }
2209
+ function encodeSlackCommandArgValue(parts) {
2210
+ return [
2211
+ SLACK_COMMAND_ARG_VALUE_PREFIX,
2212
+ encodeURIComponent(parts.command),
2213
+ encodeURIComponent(parts.arg),
2214
+ encodeURIComponent(parts.value),
2215
+ encodeURIComponent(parts.userId)
2216
+ ].join("|");
2217
+ }
2218
+ function parseSlackCommandArgValue(raw) {
2219
+ if (!raw) return null;
2220
+ const parts = raw.split("|");
2221
+ if (parts.length !== 5 || parts[0] !== SLACK_COMMAND_ARG_VALUE_PREFIX) return null;
2222
+ const [, command, arg, value, userId] = parts;
2223
+ if (!command || !arg || !value || !userId) return null;
2224
+ const decode = (text) => {
2225
+ try {
2226
+ return decodeURIComponent(text);
2227
+ } catch {
2228
+ return null;
2229
+ }
2230
+ };
2231
+ const decodedCommand = decode(command);
2232
+ const decodedArg = decode(arg);
2233
+ const decodedValue = decode(value);
2234
+ const decodedUserId = decode(userId);
2235
+ if (!decodedCommand || !decodedArg || !decodedValue || !decodedUserId) return null;
2236
+ return {
2237
+ command: decodedCommand,
2238
+ arg: decodedArg,
2239
+ value: decodedValue,
2240
+ userId: decodedUserId
2241
+ };
2242
+ }
2243
+ function buildSlackArgMenuOptions(choices) {
2244
+ return choices.map((choice) => ({
2245
+ text: {
2246
+ type: "plain_text",
2247
+ text: truncateSlackText(choice.label, SLACK_COMMAND_ARG_SELECT_OPTION_TEXT_MAX)
2248
+ },
2249
+ value: choice.value
2250
+ }));
2251
+ }
2252
+ function buildSlackCommandArgMenuBlocks(params) {
2253
+ const encodedChoices = params.choices.map((choice) => ({
2254
+ label: choice.label,
2255
+ value: encodeSlackCommandArgValue({
2256
+ command: params.command,
2257
+ arg: params.arg,
2258
+ value: choice.value,
2259
+ userId: params.userId
2260
+ })
2261
+ }));
2262
+ const canUseStaticSelect = encodedChoices.every((choice) => choice.value.length <= SLACK_COMMAND_ARG_SELECT_OPTION_VALUE_MAX);
2263
+ const canUseOverflow = canUseStaticSelect && encodedChoices.length >= SLACK_COMMAND_ARG_OVERFLOW_MIN && encodedChoices.length <= SLACK_COMMAND_ARG_OVERFLOW_MAX;
2264
+ const canUseExternalSelect = params.supportsExternalSelect && canUseStaticSelect && encodedChoices.length > SLACK_COMMAND_ARG_SELECT_OPTIONS_MAX;
2265
+ const rows = canUseOverflow ? [{
2266
+ type: "actions",
2267
+ elements: [{
2268
+ type: "overflow",
2269
+ action_id: SLACK_COMMAND_ARG_ACTION_ID,
2270
+ confirm: buildSlackArgMenuConfirm({
2271
+ command: params.command,
2272
+ arg: params.arg
2273
+ }),
2274
+ options: buildSlackArgMenuOptions(encodedChoices)
2275
+ }]
2276
+ }] : canUseExternalSelect ? [{
2277
+ type: "actions",
2278
+ block_id: `${SLACK_EXTERNAL_ARG_MENU_PREFIX}${params.createExternalMenuToken(encodedChoices)}`,
2279
+ elements: [{
2280
+ type: "external_select",
2281
+ action_id: SLACK_COMMAND_ARG_ACTION_ID,
2282
+ confirm: buildSlackArgMenuConfirm({
2283
+ command: params.command,
2284
+ arg: params.arg
2285
+ }),
2286
+ min_query_length: 0,
2287
+ placeholder: {
2288
+ type: "plain_text",
2289
+ text: `Search ${params.arg}`
2290
+ }
2291
+ }]
2292
+ }] : encodedChoices.length <= SLACK_COMMAND_ARG_BUTTON_ROW_SIZE || !canUseStaticSelect ? chunkItems(encodedChoices.filter((choice) => choice.value.length <= SLACK_COMMAND_ARG_BUTTON_VALUE_MAX), SLACK_COMMAND_ARG_BUTTON_ROW_SIZE).map((choices, rowIndex) => ({
2293
+ type: "actions",
2294
+ elements: choices.map((choice, colIndex) => ({
2295
+ type: "button",
2296
+ action_id: `${SLACK_COMMAND_ARG_ACTION_ID}_${rowIndex}_${colIndex}`,
2297
+ text: {
2298
+ type: "plain_text",
2299
+ text: truncateSlackText(choice.label, SLACK_COMMAND_ARG_BUTTON_TEXT_MAX)
2300
+ },
2301
+ value: choice.value,
2302
+ confirm: buildSlackArgMenuConfirm({
2303
+ command: params.command,
2304
+ arg: params.arg
2305
+ })
2306
+ }))
2307
+ })) : chunkItems(encodedChoices, SLACK_COMMAND_ARG_SELECT_OPTIONS_MAX).map((choices, index) => ({
2308
+ type: "actions",
2309
+ elements: [{
2310
+ type: "static_select",
2311
+ action_id: SLACK_COMMAND_ARG_ACTION_ID,
2312
+ confirm: buildSlackArgMenuConfirm({
2313
+ command: params.command,
2314
+ arg: params.arg
2315
+ }),
2316
+ placeholder: {
2317
+ type: "plain_text",
2318
+ text: index === 0 ? `Choose ${params.arg}` : `Choose ${params.arg} (${index + 1})`
2319
+ },
2320
+ options: buildSlackArgMenuOptions(choices)
2321
+ }]
2322
+ }));
2323
+ const headerText = truncateSlackText(`/${params.command}: choose ${params.arg}`, SLACK_HEADER_TEXT_MAX);
2324
+ const sectionText = truncateSlackText(params.title, 3e3);
2325
+ const contextText = truncateSlackText(`Select one option to continue /${params.command} (${params.arg})`, 3e3);
2326
+ const visibleRows = rows.slice(0, SLACK_COMMAND_ARG_ACTION_BLOCKS_MAX);
2327
+ return [
2328
+ {
2329
+ type: "header",
2330
+ text: {
2331
+ type: "plain_text",
2332
+ text: headerText
2333
+ }
2334
+ },
2335
+ {
2336
+ type: "section",
2337
+ text: {
2338
+ type: "mrkdwn",
2339
+ text: sectionText
2340
+ }
2341
+ },
2342
+ {
2343
+ type: "context",
2344
+ elements: [{
2345
+ type: "mrkdwn",
2346
+ text: contextText
2347
+ }]
2348
+ },
2349
+ ...visibleRows
2350
+ ];
2351
+ }
2352
+ async function registerSlackMonitorSlashCommands(params) {
2353
+ const { ctx, account, trackEvent } = params;
2354
+ const cfg = ctx.cfg;
2355
+ const runtime = ctx.runtime;
2356
+ const supportsInteractiveArgMenus = typeof ctx.app.action === "function";
2357
+ let supportsExternalArgMenus = typeof ctx.app.options === "function";
2358
+ const slashCommand = resolveSlackSlashCommandConfig(ctx.slashCommand ?? account.config.slashCommand);
2359
+ const handleSlashCommand = async (p) => {
2360
+ const { command, ack, respond, body, prompt, commandArgs, commandDefinition } = p;
2361
+ try {
2362
+ if (ctx.shouldDropMismatchedSlackEvent?.(body)) {
2363
+ await ack();
2364
+ runtime.log?.(`slack: drop slash command from user=${command.user_id ?? "unknown"} channel=${command.channel_id ?? "unknown"} (mismatched app/team)`);
2365
+ return;
2366
+ }
2367
+ trackEvent?.();
2368
+ if (!prompt.trim()) {
2369
+ await ack({
2370
+ text: "Message required.",
2371
+ response_type: "ephemeral"
2372
+ });
2373
+ return;
2374
+ }
2375
+ await ack();
2376
+ if (ctx.botUserId && command.user_id === ctx.botUserId) return;
2377
+ const channelInfo = await ctx.resolveChannelName(command.channel_id);
2378
+ const channelType = normalizeSlackChannelType(channelInfo?.type ?? (command.channel_name === "directmessage" ? "im" : void 0), command.channel_id);
2379
+ const chatType = resolveSlackChatType(channelType);
2380
+ const isDirectMessage = channelType === "im";
2381
+ const isGroupDm = channelType === "mpim";
2382
+ const isRoom = channelType === "channel" || channelType === "group";
2383
+ const isRoomish = isRoom || isGroupDm;
2384
+ if (!ctx.isChannelAllowed({
2385
+ channelId: command.channel_id,
2386
+ channelName: channelInfo?.name,
2387
+ channelType
2388
+ })) {
2389
+ await respond({
2390
+ text: "This channel is not allowed.",
2391
+ response_type: "ephemeral"
2392
+ });
2393
+ return;
2394
+ }
2395
+ const effectiveAllowFromLower = await resolveSlackEffectiveAllowFrom(ctx, { includePairingStore: isDirectMessage });
2396
+ let commandAuthorized = false;
2397
+ let channelConfig = null;
2398
+ if (isDirectMessage) {
2399
+ if (!await authorizeSlackDirectMessage({
2400
+ ctx,
2401
+ accountId: ctx.accountId,
2402
+ senderId: command.user_id,
2403
+ allowFromLower: effectiveAllowFromLower,
2404
+ resolveSenderName: ctx.resolveUserName,
2405
+ sendPairingReply: async (text) => {
2406
+ await respond({
2407
+ text,
2408
+ response_type: "ephemeral"
2409
+ });
2410
+ },
2411
+ onDisabled: async () => {
2412
+ await respond({
2413
+ text: "Slack DMs are disabled.",
2414
+ response_type: "ephemeral"
2415
+ });
2416
+ },
2417
+ onUnauthorized: async ({ allowMatchMeta }) => {
2418
+ logVerbose(`slack: blocked slash sender ${command.user_id} (dmPolicy=${ctx.dmPolicy}, ${allowMatchMeta})`);
2419
+ await respond({
2420
+ text: "You are not authorized to use this command.",
2421
+ response_type: "ephemeral"
2422
+ });
2423
+ },
2424
+ log: logVerbose
2425
+ })) return;
2426
+ }
2427
+ if (isRoom) {
2428
+ channelConfig = resolveSlackChannelConfig({
2429
+ channelId: command.channel_id,
2430
+ channelName: channelInfo?.name,
2431
+ channels: ctx.channelsConfig,
2432
+ channelKeys: ctx.channelsConfigKeys,
2433
+ defaultRequireMention: ctx.defaultRequireMention,
2434
+ allowNameMatching: ctx.allowNameMatching
2435
+ });
2436
+ if (ctx.useAccessGroups) {
2437
+ const channelAllowlistConfigured = (ctx.channelsConfigKeys?.length ?? 0) > 0;
2438
+ const channelAllowed = channelConfig?.allowed !== false;
2439
+ if (!isSlackChannelAllowedByPolicy({
2440
+ groupPolicy: ctx.groupPolicy,
2441
+ channelAllowlistConfigured,
2442
+ channelAllowed
2443
+ })) {
2444
+ await respond({
2445
+ text: "This channel is not allowed.",
2446
+ response_type: "ephemeral"
2447
+ });
2448
+ return;
2449
+ }
2450
+ const hasExplicitConfig = Boolean(channelConfig?.matchSource);
2451
+ if (!channelAllowed && (ctx.groupPolicy !== "open" || hasExplicitConfig)) {
2452
+ await respond({
2453
+ text: "This channel is not allowed.",
2454
+ response_type: "ephemeral"
2455
+ });
2456
+ return;
2457
+ }
2458
+ }
2459
+ }
2460
+ const senderName = (await ctx.resolveUserName(command.user_id))?.name ?? command.user_name ?? command.user_id;
2461
+ const slashIngress = await resolveSlackCommandIngress({
2462
+ ctx,
2463
+ senderId: command.user_id,
2464
+ senderName,
2465
+ channelType: channelType ?? "channel",
2466
+ channelId: command.channel_id,
2467
+ ownerAllowFromLower: effectiveAllowFromLower,
2468
+ channelUsers: isRoom ? channelConfig?.users : void 0,
2469
+ allowTextCommands: false,
2470
+ hasControlCommand: false,
2471
+ eventKind: "slash-command",
2472
+ modeWhenAccessGroupsOff: "configured"
2473
+ });
2474
+ const senderGate = slashIngress.senderAccess.gate;
2475
+ if (isRoom && senderGate?.allowed === false) {
2476
+ await respond({
2477
+ text: "You are not authorized to use this command here.",
2478
+ response_type: "ephemeral"
2479
+ });
2480
+ return;
2481
+ }
2482
+ commandAuthorized = slashIngress.commandAccess.authorized;
2483
+ if (isRoomish) {
2484
+ if (ctx.useAccessGroups && !commandAuthorized) {
2485
+ await respond({
2486
+ text: "You are not authorized to use this command.",
2487
+ response_type: "ephemeral"
2488
+ });
2489
+ return;
2490
+ }
2491
+ }
2492
+ let resolvedSlashRoute;
2493
+ const resolveSlashRoute = async () => {
2494
+ if (resolvedSlashRoute) return resolvedSlashRoute;
2495
+ const { resolveAgentRoute } = await loadSlashDispatchRuntime();
2496
+ resolvedSlashRoute = resolveAgentRoute({
2497
+ cfg,
2498
+ channel: "slack",
2499
+ accountId: account.accountId,
2500
+ teamId: ctx.teamId || void 0,
2501
+ peer: {
2502
+ kind: isDirectMessage ? "direct" : isRoom ? "channel" : "group",
2503
+ id: isDirectMessage ? command.user_id : command.channel_id
2504
+ }
2505
+ });
2506
+ return resolvedSlashRoute;
2507
+ };
2508
+ if (commandDefinition && supportsInteractiveArgMenus) {
2509
+ const { resolveCommandArgMenu } = await loadSlashCommandsRuntime();
2510
+ const menuRoute = !(commandArgs?.raw && !commandArgs.values) && commandDefinition.args?.some((arg) => typeof arg.choices === "function" && commandArgs?.values?.[arg.name] == null) ? await resolveSlashRoute() : void 0;
2511
+ const menu = resolveCommandArgMenu({
2512
+ command: commandDefinition,
2513
+ args: commandArgs,
2514
+ cfg,
2515
+ ...menuRoute ? resolveSlackCommandMenuModelContext({
2516
+ cfg,
2517
+ agentId: menuRoute.agentId,
2518
+ sessionKey: menuRoute.sessionKey
2519
+ }) : {}
2520
+ });
2521
+ if (menu) {
2522
+ const commandLabel = commandDefinition.nativeName ?? commandDefinition.key;
2523
+ const title = formatCommandArgMenuTitle({
2524
+ command: commandDefinition,
2525
+ menu
2526
+ });
2527
+ await respond({
2528
+ text: title,
2529
+ blocks: buildSlackCommandArgMenuBlocks({
2530
+ title,
2531
+ command: commandLabel,
2532
+ arg: menu.arg.name,
2533
+ choices: menu.choices,
2534
+ userId: command.user_id,
2535
+ supportsExternalSelect: supportsExternalArgMenus,
2536
+ createExternalMenuToken: (choices) => storeSlackExternalArgMenu({
2537
+ choices,
2538
+ userId: command.user_id
2539
+ })
2540
+ }),
2541
+ response_type: "ephemeral"
2542
+ });
2543
+ return;
2544
+ }
2545
+ }
2546
+ const channelName = channelInfo?.name;
2547
+ const roomLabel = channelName ? `#${channelName}` : `#${command.channel_id}`;
2548
+ const { deliverSlackSlashReplies, dispatchReplyWithDispatcher, finalizeInboundContext, recordInboundSessionMetaSafe, resolveAgentRoute, resolveChunkMode, resolveConversationLabel, resolveMarkdownTableMode } = await loadSlashDispatchRuntime();
2549
+ const route = resolvedSlashRoute ?? resolveAgentRoute({
2550
+ cfg,
2551
+ channel: "slack",
2552
+ accountId: account.accountId,
2553
+ teamId: ctx.teamId || void 0,
2554
+ peer: {
2555
+ kind: isDirectMessage ? "direct" : isRoom ? "channel" : "group",
2556
+ id: isDirectMessage ? command.user_id : command.channel_id
2557
+ }
2558
+ });
2559
+ const { untrustedChannelMetadata, groupSystemPrompt } = resolveSlackRoomContextHints({
2560
+ isRoomish,
2561
+ channelInfo,
2562
+ channelConfig
2563
+ });
2564
+ const { sessionKey, commandTargetSessionKey } = resolveNativeCommandSessionTargets({
2565
+ agentId: route.agentId,
2566
+ sessionPrefix: slashCommand.sessionPrefix,
2567
+ userId: command.user_id,
2568
+ targetSessionKey: route.sessionKey,
2569
+ lowercaseSessionKey: true
2570
+ });
2571
+ const ctxPayload = finalizeInboundContext({
2572
+ Body: prompt,
2573
+ BodyForAgent: prompt,
2574
+ RawBody: prompt,
2575
+ CommandBody: prompt,
2576
+ CommandArgs: commandArgs,
2577
+ From: isDirectMessage ? `slack:${command.user_id}` : isRoom ? `slack:channel:${command.channel_id}` : `slack:group:${command.channel_id}`,
2578
+ To: `slash:${command.user_id}`,
2579
+ ChatType: chatType,
2580
+ ConversationLabel: resolveConversationLabel({
2581
+ ChatType: chatType,
2582
+ SenderName: senderName,
2583
+ GroupSubject: isRoomish ? roomLabel : void 0,
2584
+ From: isDirectMessage ? `slack:${command.user_id}` : isRoom ? `slack:channel:${command.channel_id}` : `slack:group:${command.channel_id}`
2585
+ }) ?? (isDirectMessage ? senderName : roomLabel),
2586
+ GroupSubject: isRoomish ? roomLabel : void 0,
2587
+ GroupSpace: ctx.teamId || void 0,
2588
+ GroupSystemPrompt: groupSystemPrompt,
2589
+ UntrustedContext: untrustedChannelMetadata ? [untrustedChannelMetadata] : void 0,
2590
+ SenderName: senderName,
2591
+ SenderId: command.user_id,
2592
+ Provider: "slack",
2593
+ Surface: "slack",
2594
+ WasMentioned: true,
2595
+ MessageSid: command.trigger_id,
2596
+ Timestamp: Date.now(),
2597
+ SessionKey: sessionKey,
2598
+ CommandTargetSessionKey: commandTargetSessionKey,
2599
+ AccountId: route.accountId,
2600
+ CommandSource: "native",
2601
+ CommandAuthorized: commandAuthorized,
2602
+ OriginatingChannel: "slack",
2603
+ OriginatingTo: `user:${command.user_id}`
2604
+ });
2605
+ await recordInboundSessionMetaSafe({
2606
+ cfg,
2607
+ agentId: route.agentId,
2608
+ sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
2609
+ ctx: ctxPayload,
2610
+ onError: (err) => runtime.error?.(danger(`slack slash: failed updating session meta: ${formatErrorMessage(err)}`))
2611
+ });
2612
+ const { onModelSelected, ...replyPipeline } = createChannelMessageReplyPipeline({
2613
+ cfg,
2614
+ agentId: route.agentId,
2615
+ channel: "slack",
2616
+ accountId: route.accountId
2617
+ });
2618
+ const deliverSlashPayloads = async (replies) => {
2619
+ await deliverSlackSlashReplies({
2620
+ replies,
2621
+ respond,
2622
+ ephemeral: slashCommand.ephemeral,
2623
+ textLimit: ctx.textLimit,
2624
+ chunkMode: resolveChunkMode(cfg, "slack", route.accountId),
2625
+ tableMode: resolveMarkdownTableMode({
2626
+ cfg,
2627
+ channel: "slack",
2628
+ accountId: route.accountId
2629
+ })
2630
+ });
2631
+ };
2632
+ const { counts } = await dispatchReplyWithDispatcher({
2633
+ ctx: ctxPayload,
2634
+ cfg,
2635
+ dispatcherOptions: {
2636
+ ...replyPipeline,
2637
+ deliver: async (payload) => deliverSlashPayloads([payload]),
2638
+ onError: (err, info) => {
2639
+ runtime.error?.(danger(`slack slash ${info.kind} reply failed: ${formatSlackError(err)}`));
2640
+ }
2641
+ },
2642
+ replyOptions: {
2643
+ skillFilter: channelConfig?.skills,
2644
+ onModelSelected
2645
+ }
2646
+ });
2647
+ if (counts.final + counts.tool + counts.block === 0) await deliverSlashPayloads([]);
2648
+ } catch (err) {
2649
+ runtime.error?.(danger(`slack slash handler failed: ${formatErrorMessage(err)}`));
2650
+ await respond({
2651
+ text: "Sorry, something went wrong handling that command.",
2652
+ response_type: "ephemeral"
2653
+ });
2654
+ }
2655
+ };
2656
+ const nativeEnabled = resolveNativeCommandsEnabled({
2657
+ providerId: "slack",
2658
+ providerSetting: account.config.commands?.native,
2659
+ globalSetting: cfg.commands?.native
2660
+ });
2661
+ const nativeSkillsEnabled = resolveNativeSkillsEnabled({
2662
+ providerId: "slack",
2663
+ providerSetting: account.config.commands?.nativeSkills,
2664
+ globalSetting: cfg.commands?.nativeSkills
2665
+ });
2666
+ let nativeCommands = [];
2667
+ let slashCommandsRuntime = null;
2668
+ if (nativeEnabled) {
2669
+ slashCommandsRuntime = await loadSlashCommandsRuntime();
2670
+ const skillCommands = nativeSkillsEnabled ? (await loadSlashSkillCommandsRuntime()).listSkillCommandsForAgents({ cfg }) : [];
2671
+ nativeCommands = slashCommandsRuntime.listNativeCommandSpecsForConfig(cfg, {
2672
+ skillCommands,
2673
+ provider: "slack"
2674
+ });
2675
+ const existingNativeNames = new Set(nativeCommands.map((c) => normalizeLowercaseStringOrEmpty(c.name)).filter(Boolean));
2676
+ const { listProviderPluginCommandSpecs } = await loadSlackPluginCommandsRuntime();
2677
+ for (const pluginCommand of listProviderPluginCommandSpecs("slack")) {
2678
+ const normalizedName = normalizeLowercaseStringOrEmpty(pluginCommand.name);
2679
+ if (!normalizedName || existingNativeNames.has(normalizedName)) continue;
2680
+ existingNativeNames.add(normalizedName);
2681
+ nativeCommands.push(pluginCommand);
2682
+ }
2683
+ }
2684
+ if (nativeCommands.length > 0) {
2685
+ if (!slashCommandsRuntime) throw new Error("Missing commands runtime for native Slack commands.");
2686
+ for (const command of nativeCommands) ctx.app.command(`/${command.name}`, async ({ command: cmd, ack, respond, body }) => {
2687
+ const commandDefinition = slashCommandsRuntime.findCommandByNativeName(command.name, "slack");
2688
+ const rawText = cmd.text?.trim() ?? "";
2689
+ const commandArgs = commandDefinition ? slashCommandsRuntime.parseCommandArgs(commandDefinition, rawText) : rawText ? { raw: rawText } : void 0;
2690
+ await handleSlashCommand({
2691
+ command: cmd,
2692
+ ack,
2693
+ respond,
2694
+ body,
2695
+ prompt: commandDefinition ? slashCommandsRuntime.buildCommandTextFromArgs(commandDefinition, commandArgs) : rawText ? `/${command.name} ${rawText}` : `/${command.name}`,
2696
+ commandArgs,
2697
+ commandDefinition: commandDefinition ?? void 0
2698
+ });
2699
+ });
2700
+ } else if (slashCommand.enabled) ctx.app.command(buildSlackSlashCommandMatcher(slashCommand.name), async ({ command, ack, respond, body }) => {
2701
+ await handleSlashCommand({
2702
+ command,
2703
+ ack,
2704
+ respond,
2705
+ body,
2706
+ prompt: command.text?.trim() ?? ""
2707
+ });
2708
+ });
2709
+ else logVerbose("slack: slash commands disabled");
2710
+ if (nativeCommands.length === 0 || !supportsInteractiveArgMenus) return;
2711
+ const registerArgOptions = () => {
2712
+ const appWithOptions = ctx.app;
2713
+ if (typeof appWithOptions.options !== "function") return;
2714
+ appWithOptions.options(SLACK_COMMAND_ARG_ACTION_ID, async ({ ack, body }) => {
2715
+ if (ctx.shouldDropMismatchedSlackEvent?.(body)) {
2716
+ await ack({ options: [] });
2717
+ runtime.log?.("slack: drop slash arg options payload (mismatched app/team)");
2718
+ return;
2719
+ }
2720
+ trackEvent?.();
2721
+ const typedBody = body;
2722
+ const token = readSlackExternalArgMenuToken(typedBody.actions?.[0]?.block_id ?? typedBody.block_id);
2723
+ if (!token) {
2724
+ await ack({ options: [] });
2725
+ return;
2726
+ }
2727
+ const entry = slackExternalArgMenuStore.get(token);
2728
+ if (!entry) {
2729
+ await ack({ options: [] });
2730
+ return;
2731
+ }
2732
+ const requesterUserId = typedBody.user?.id?.trim();
2733
+ if (!requesterUserId || requesterUserId !== entry.userId) {
2734
+ await ack({ options: [] });
2735
+ return;
2736
+ }
2737
+ const query = normalizeLowercaseStringOrEmpty(typedBody.value);
2738
+ await ack({ options: entry.choices.filter((choice) => !query || normalizeLowercaseStringOrEmpty(choice.label).includes(query)).slice(0, SLACK_COMMAND_ARG_SELECT_OPTIONS_MAX).map((choice) => ({
2739
+ text: {
2740
+ type: "plain_text",
2741
+ text: choice.label.slice(0, 75)
2742
+ },
2743
+ value: choice.value
2744
+ })) });
2745
+ });
2746
+ };
2747
+ try {
2748
+ registerArgOptions();
2749
+ } catch (err) {
2750
+ supportsExternalArgMenus = false;
2751
+ logVerbose(`slack: external arg-menu registration failed, falling back to static menus: ${formatErrorMessage(err)}`);
2752
+ }
2753
+ const registerArgAction = (actionId) => {
2754
+ ctx.app.action(actionId, async (args) => {
2755
+ const { ack, body, respond } = args;
2756
+ const action = args.action;
2757
+ await ack();
2758
+ if (ctx.shouldDropMismatchedSlackEvent?.(body)) {
2759
+ runtime.log?.("slack: drop slash arg action payload (mismatched app/team)");
2760
+ return;
2761
+ }
2762
+ const respondFn = respond ?? (async (payload) => {
2763
+ if (!body.channel?.id || !body.user?.id) return;
2764
+ await ctx.app.client.chat.postEphemeral({
2765
+ token: ctx.botToken,
2766
+ channel: body.channel.id,
2767
+ user: body.user.id,
2768
+ text: payload.text,
2769
+ blocks: payload.blocks
2770
+ });
2771
+ });
2772
+ const parsed = parseSlackCommandArgValue(action?.value ?? action?.selected_option?.value);
2773
+ if (!parsed) {
2774
+ await respondFn({
2775
+ text: "Sorry, that button is no longer valid.",
2776
+ response_type: "ephemeral"
2777
+ });
2778
+ return;
2779
+ }
2780
+ if (body.user?.id && parsed.userId !== body.user.id) {
2781
+ await respondFn({
2782
+ text: "That menu is for another user.",
2783
+ response_type: "ephemeral"
2784
+ });
2785
+ return;
2786
+ }
2787
+ const { buildCommandTextFromArgs, findCommandByNativeName } = await loadSlashCommandsRuntime();
2788
+ const commandDefinition = findCommandByNativeName(parsed.command, "slack");
2789
+ const commandArgs = { values: { [parsed.arg]: parsed.value } };
2790
+ const prompt = commandDefinition ? buildCommandTextFromArgs(commandDefinition, commandArgs) : `/${parsed.command} ${parsed.value}`;
2791
+ const user = body.user;
2792
+ const userName = user && "name" in user && user.name ? user.name : user && "username" in user && user.username ? user.username : user?.id ?? "";
2793
+ const triggerId = "trigger_id" in body ? body.trigger_id : void 0;
2794
+ await handleSlashCommand({
2795
+ command: {
2796
+ user_id: user?.id ?? "",
2797
+ user_name: userName,
2798
+ channel_id: body.channel?.id ?? "",
2799
+ channel_name: body.channel?.name ?? body.channel?.id ?? "",
2800
+ trigger_id: triggerId
2801
+ },
2802
+ ack: async () => {},
2803
+ respond: respondFn,
2804
+ body,
2805
+ prompt,
2806
+ commandArgs,
2807
+ commandDefinition: commandDefinition ?? void 0
2808
+ });
2809
+ });
2810
+ };
2811
+ registerArgAction(SLACK_COMMAND_ARG_ACTION_LISTENER);
2812
+ }
2813
+ //#endregion
2814
+ //#region extensions/slack/src/monitor/provider.ts
2815
+ let slackBoltInterop;
2816
+ async function getSlackBoltInterop() {
2817
+ if (!slackBoltInterop) {
2818
+ const slackBoltModule = await import("@slack/bolt");
2819
+ slackBoltInterop = resolveSlackBoltInterop({
2820
+ defaultImport: slackBoltModule.default,
2821
+ namespaceImport: slackBoltModule
2822
+ });
2823
+ }
2824
+ return slackBoltInterop;
2825
+ }
2826
+ const SLACK_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
2827
+ const SLACK_WEBHOOK_BODY_TIMEOUT_MS = 3e4;
2828
+ function resolveStableSlackUserIdEntry(raw) {
2829
+ const trimmed = raw.trim();
2830
+ if (!trimmed) return;
2831
+ const mention = /^<@([A-Z][A-Z0-9]+)>$/i.exec(trimmed);
2832
+ if (mention) return mention[1]?.toUpperCase();
2833
+ const prefixed = /^(?:slack:|user:)([A-Z][A-Z0-9]+)$/i.exec(trimmed);
2834
+ if (prefixed) return prefixed[1]?.toUpperCase();
2835
+ return /^[UW][A-Z0-9]+$/i.test(trimmed) ? trimmed.toUpperCase() : void 0;
2836
+ }
2837
+ function resolveStableSlackUserAllowlistEntries(entries) {
2838
+ const resolved = [];
2839
+ for (const input of entries) {
2840
+ const id = resolveStableSlackUserIdEntry(input);
2841
+ if (id) resolved.push({
2842
+ input,
2843
+ resolved: true,
2844
+ id
2845
+ });
2846
+ }
2847
+ return resolved;
2848
+ }
2849
+ function formatSlackSocketReconnectMessage(params) {
2850
+ const maxAttempts = params.maxAttempts > 0 ? String(params.maxAttempts) : "∞";
2851
+ const suffix = params.error ? ` (${formatUnknownError(params.error)})` : "";
2852
+ return `slack socket disconnected (${params.event}); reconnecting in ${Math.round(params.delayMs / 1e3)}s (attempt ${params.attempt}/${maxAttempts})${suffix}`;
2853
+ }
2854
+ function formatSlackSocketStartRetryMessage(params) {
2855
+ const maxAttempts = params.maxAttempts > 0 ? String(params.maxAttempts) : "∞";
2856
+ const reason = formatUnknownError(params.error, "Slack Socket Mode start failed without error detail");
2857
+ const sdkContext = params.sdkContext?.trim() ? `; last SDK log: ${params.sdkContext.trim()}` : "";
2858
+ return `slack socket mode failed to start; retry ${params.attempt}/${maxAttempts} in ${Math.round(params.delayMs / 1e3)}s reason="${reason}${sdkContext}"`;
2859
+ }
2860
+ function parseApiAppIdFromAppToken(raw) {
2861
+ const token = raw?.trim();
2862
+ if (!token) return;
2863
+ return /^xapp-\d-([a-z0-9]+)-/i.exec(token)?.[1]?.toUpperCase();
2864
+ }
2865
+ async function monitorSlackProvider(opts = {}) {
2866
+ const cfg = opts.config ?? getRuntimeConfig$1();
2867
+ const runtime = opts.runtime ?? createNonExitingRuntime();
2868
+ let account = resolveSlackAccount({
2869
+ cfg,
2870
+ accountId: opts.accountId
2871
+ });
2872
+ if (!account.enabled) {
2873
+ runtime.log?.(`[${account.accountId}] slack account disabled; monitor startup skipped`);
2874
+ if (opts.abortSignal?.aborted) return;
2875
+ await new Promise((resolve) => {
2876
+ opts.abortSignal?.addEventListener("abort", () => resolve(), { once: true });
2877
+ });
2878
+ return;
2879
+ }
2880
+ const historyLimit = Math.max(0, account.config.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT);
2881
+ const dmHistoryLimit = Math.max(0, account.config.dmHistoryLimit ?? 0);
2882
+ const sessionCfg = cfg.session;
2883
+ const sessionScope = sessionCfg?.scope ?? "per-sender";
2884
+ const mainKey = normalizeMainKey(sessionCfg?.mainKey);
2885
+ const slackMode = opts.mode ?? account.config.mode ?? "socket";
2886
+ const slackWebhookPath = normalizeSlackWebhookPath(account.config.webhookPath);
2887
+ const signingSecret = normalizeResolvedSecretInputString({
2888
+ value: account.config.signingSecret,
2889
+ path: `channels.slack.accounts.${account.accountId}.signingSecret`
2890
+ });
2891
+ const botToken = resolveSlackBotToken(opts.botToken ?? account.botToken);
2892
+ const appToken = resolveSlackAppToken(opts.appToken ?? account.appToken);
2893
+ if (!botToken || slackMode !== "http" && !appToken) {
2894
+ const missing = slackMode === "http" ? `Slack bot token missing for account "${account.accountId}" (set channels.slack.accounts.${account.accountId}.botToken or SLACK_BOT_TOKEN for default).` : `Slack bot + app tokens missing for account "${account.accountId}" (set channels.slack.accounts.${account.accountId}.botToken/appToken or SLACK_BOT_TOKEN/SLACK_APP_TOKEN for default).`;
2895
+ throw new Error(missing);
2896
+ }
2897
+ if (slackMode === "http" && !signingSecret) throw new Error(`Slack signing secret missing for account "${account.accountId}" (set channels.slack.signingSecret or channels.slack.accounts.${account.accountId}.signingSecret).`);
2898
+ const slackCfg = account.config;
2899
+ const dmConfig = slackCfg.dm;
2900
+ const dmEnabled = dmConfig?.enabled ?? true;
2901
+ const dmPolicy = resolveSlackAccountDmPolicy({
2902
+ cfg,
2903
+ accountId: account.accountId
2904
+ }) ?? "pairing";
2905
+ let allowFrom = resolveSlackAccountAllowFrom({
2906
+ cfg,
2907
+ accountId: account.accountId
2908
+ });
2909
+ const groupDmEnabled = dmConfig?.groupEnabled ?? false;
2910
+ const groupDmChannels = dmConfig?.groupChannels;
2911
+ let channelsConfig = slackCfg.channels;
2912
+ const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg);
2913
+ const { groupPolicy, providerMissingFallbackApplied } = resolveOpenProviderRuntimeGroupPolicy({
2914
+ providerConfigPresent: cfg.channels?.slack !== void 0,
2915
+ groupPolicy: slackCfg.groupPolicy,
2916
+ defaultGroupPolicy
2917
+ });
2918
+ warnMissingProviderGroupPolicyFallbackOnce({
2919
+ providerMissingFallbackApplied,
2920
+ providerKey: "slack",
2921
+ accountId: account.accountId,
2922
+ log: (message) => runtime.log?.(warn(message))
2923
+ });
2924
+ const resolveToken = account.userToken || botToken;
2925
+ const useAccessGroups = cfg.commands?.useAccessGroups !== false;
2926
+ const reactionMode = slackCfg.reactionNotifications ?? "own";
2927
+ const reactionAllowlist = slackCfg.reactionAllowlist ?? [];
2928
+ const replyToMode = slackCfg.replyToMode ?? "off";
2929
+ const threadHistoryScope = slackCfg.thread?.historyScope ?? "thread";
2930
+ const threadInheritParent = slackCfg.thread?.inheritParent ?? false;
2931
+ const threadRequireExplicitMention = slackCfg.thread?.requireExplicitMention ?? false;
2932
+ const slashCommand = resolveSlackSlashCommandConfig(opts.slashCommand ?? slackCfg.slashCommand);
2933
+ const allowNameMatching = isDangerousNameMatchingEnabled(slackCfg);
2934
+ const textLimit = resolveTextChunkLimit(cfg, "slack", account.accountId, { fallbackLimit: SLACK_TEXT_LIMIT });
2935
+ const ackReactionScope = cfg.messages?.ackReactionScope ?? "group-mentions";
2936
+ const typingReaction = slackCfg.typingReaction?.trim() ?? "";
2937
+ const mediaMaxBytes = (opts.mediaMaxMb ?? slackCfg.mediaMaxMb ?? 20) * 1024 * 1024;
2938
+ const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
2939
+ const clientOptions = resolveSlackWebClientOptions();
2940
+ const { app, receiver, socketModeLogger } = createSlackBoltApp({
2941
+ interop: await getSlackBoltInterop(),
2942
+ slackMode,
2943
+ botToken,
2944
+ appToken: appToken ?? void 0,
2945
+ signingSecret: signingSecret ?? void 0,
2946
+ slackWebhookPath,
2947
+ clientOptions,
2948
+ ...slackCfg.socketMode ? { socketMode: slackCfg.socketMode } : {}
2949
+ });
2950
+ const gracefulStop = async () => {
2951
+ await gracefulStopSlackApp(app);
2952
+ };
2953
+ const slackHttpHandler = slackMode === "http" && receiver ? async (req, res) => {
2954
+ const httpReceiver = receiver;
2955
+ const guard = installRequestBodyLimitGuard(req, res, {
2956
+ maxBytes: SLACK_WEBHOOK_MAX_BODY_BYTES,
2957
+ timeoutMs: SLACK_WEBHOOK_BODY_TIMEOUT_MS,
2958
+ responseFormat: "text"
2959
+ });
2960
+ if (guard.isTripped()) return;
2961
+ try {
2962
+ await Promise.resolve(httpReceiver.requestListener(req, res));
2963
+ } catch (err) {
2964
+ if (!guard.isTripped()) throw err;
2965
+ } finally {
2966
+ guard.dispose();
2967
+ }
2968
+ } : null;
2969
+ let unregisterHttpHandler = null;
2970
+ let botUserId = "";
2971
+ let botId = "";
2972
+ let teamId = "";
2973
+ let apiAppId = "";
2974
+ const expectedApiAppIdFromAppToken = parseApiAppIdFromAppToken(appToken);
2975
+ try {
2976
+ const auth = await app.client.auth.test({ token: botToken });
2977
+ botUserId = auth.user_id ?? "";
2978
+ botId = auth.bot_id ?? "";
2979
+ teamId = auth.team_id ?? "";
2980
+ apiAppId = auth.api_app_id ?? "";
2981
+ } catch {}
2982
+ 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}`);
2983
+ const ctx = createSlackMonitorContext({
2984
+ cfg,
2985
+ accountId: account.accountId,
2986
+ botToken,
2987
+ app,
2988
+ runtime,
2989
+ botUserId,
2990
+ botId,
2991
+ teamId,
2992
+ apiAppId,
2993
+ historyLimit,
2994
+ dmHistoryLimit,
2995
+ sessionScope,
2996
+ mainKey,
2997
+ dmEnabled,
2998
+ dmPolicy,
2999
+ allowFrom,
3000
+ allowNameMatching,
3001
+ groupDmEnabled,
3002
+ groupDmChannels,
3003
+ defaultRequireMention: slackCfg.requireMention,
3004
+ channelsConfig,
3005
+ groupPolicy,
3006
+ useAccessGroups,
3007
+ reactionMode,
3008
+ reactionAllowlist,
3009
+ replyToMode,
3010
+ threadHistoryScope,
3011
+ threadInheritParent,
3012
+ threadRequireExplicitMention,
3013
+ slashCommand,
3014
+ textLimit,
3015
+ ackReactionScope,
3016
+ typingReaction,
3017
+ mediaMaxBytes,
3018
+ removeAckAfterReply
3019
+ });
3020
+ const trackEvent = opts.setStatus ? () => {
3021
+ opts.setStatus({
3022
+ lastEventAt: Date.now(),
3023
+ lastInboundAt: Date.now()
3024
+ });
3025
+ } : void 0;
3026
+ const handleSlackMessage = createSlackMessageHandler({
3027
+ ctx,
3028
+ account,
3029
+ trackEvent
3030
+ });
3031
+ if (isSlackExecApprovalClientEnabled({
3032
+ cfg,
3033
+ accountId: account.accountId
3034
+ })) registerChannelRuntimeContext({
3035
+ channelRuntime: opts.channelRuntime,
3036
+ channelId: "slack",
3037
+ accountId: account.accountId,
3038
+ capability: CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY,
3039
+ context: {
3040
+ app,
3041
+ config: slackCfg.execApprovals ?? {}
3042
+ },
3043
+ abortSignal: opts.abortSignal
3044
+ });
3045
+ registerSlackMonitorEvents({
3046
+ ctx,
3047
+ account,
3048
+ handleSlackMessage,
3049
+ trackEvent
3050
+ });
3051
+ await registerSlackMonitorSlashCommands({
3052
+ ctx,
3053
+ account,
3054
+ trackEvent
3055
+ });
3056
+ if (slackMode === "http" && slackHttpHandler) unregisterHttpHandler = registerSlackHttpHandler({
3057
+ path: slackWebhookPath,
3058
+ handler: slackHttpHandler,
3059
+ log: runtime.log,
3060
+ accountId: account.accountId
3061
+ });
3062
+ if (resolveToken) (async () => {
3063
+ if (opts.abortSignal?.aborted) return;
3064
+ if (channelsConfig && Object.keys(channelsConfig).length > 0) try {
3065
+ const entries = Object.keys(channelsConfig).filter((key) => key !== "*");
3066
+ if (entries.length > 0) {
3067
+ const resolved = await resolveSlackChannelAllowlist({
3068
+ token: resolveToken,
3069
+ entries
3070
+ });
3071
+ const nextChannels = { ...channelsConfig };
3072
+ const mapping = [];
3073
+ const unresolved = [];
3074
+ for (const entry of resolved) {
3075
+ const source = channelsConfig?.[entry.input];
3076
+ if (!source) continue;
3077
+ if (!entry.resolved || !entry.id) {
3078
+ unresolved.push(entry.input);
3079
+ continue;
3080
+ }
3081
+ mapping.push(formatSlackChannelResolved(entry));
3082
+ const existing = nextChannels[entry.id] ?? {};
3083
+ nextChannels[entry.id] = {
3084
+ ...source,
3085
+ ...existing
3086
+ };
3087
+ }
3088
+ channelsConfig = nextChannels;
3089
+ ctx.channelsConfig = nextChannels;
3090
+ summarizeMapping("slack channels", mapping, unresolved, runtime);
3091
+ }
3092
+ } catch (err) {
3093
+ runtime.log?.(`slack channel resolve failed; using config entries. ${formatUnknownError(err)}`);
3094
+ }
3095
+ const allowEntries = normalizeStringEntries(allowFrom).filter((entry) => entry !== "*");
3096
+ if (allowEntries.length > 0) {
3097
+ const stableResolvedUsers = resolveStableSlackUserAllowlistEntries(allowEntries);
3098
+ if (stableResolvedUsers.length > 0) {
3099
+ const { mapping, additions } = buildAllowlistResolutionSummary(stableResolvedUsers, { formatResolved: formatSlackUserResolved });
3100
+ allowFrom = mergeAllowlist({
3101
+ existing: allowFrom,
3102
+ additions
3103
+ });
3104
+ ctx.allowFrom = normalizeAllowList(allowFrom);
3105
+ summarizeMapping("slack users", mapping, [], runtime);
3106
+ }
3107
+ if (allowNameMatching) try {
3108
+ const { mapping, unresolved, additions } = buildAllowlistResolutionSummary(await resolveSlackUserAllowlist({
3109
+ token: resolveToken,
3110
+ entries: allowEntries
3111
+ }), { formatResolved: formatSlackUserResolved });
3112
+ allowFrom = mergeAllowlist({
3113
+ existing: allowFrom,
3114
+ additions
3115
+ });
3116
+ ctx.allowFrom = normalizeAllowList(allowFrom);
3117
+ summarizeMapping("slack users", mapping, unresolved, runtime);
3118
+ } catch (err) {
3119
+ runtime.log?.(`slack user resolve failed; using config entries. ${formatUnknownError(err)}`);
3120
+ }
3121
+ }
3122
+ if (channelsConfig && Object.keys(channelsConfig).length > 0) {
3123
+ const userEntries = /* @__PURE__ */ new Set();
3124
+ for (const channel of Object.values(channelsConfig)) addAllowlistUserEntriesFromConfigEntry(userEntries, channel);
3125
+ if (userEntries.size > 0) {
3126
+ const stableResolvedUsers = resolveStableSlackUserAllowlistEntries(Array.from(userEntries));
3127
+ if (stableResolvedUsers.length > 0) {
3128
+ const { resolvedMap, mapping } = buildAllowlistResolutionSummary(stableResolvedUsers, { formatResolved: formatSlackUserResolved });
3129
+ const nextChannels = patchAllowlistUsersInConfigEntries({
3130
+ entries: channelsConfig,
3131
+ resolvedMap
3132
+ });
3133
+ channelsConfig = nextChannels;
3134
+ ctx.channelsConfig = nextChannels;
3135
+ summarizeMapping("slack channel users", mapping, [], runtime);
3136
+ }
3137
+ if (allowNameMatching) try {
3138
+ const { resolvedMap, mapping, unresolved } = buildAllowlistResolutionSummary(await resolveSlackUserAllowlist({
3139
+ token: resolveToken,
3140
+ entries: Array.from(userEntries)
3141
+ }), { formatResolved: formatSlackUserResolved });
3142
+ const nextChannels = patchAllowlistUsersInConfigEntries({
3143
+ entries: channelsConfig,
3144
+ resolvedMap
3145
+ });
3146
+ channelsConfig = nextChannels;
3147
+ ctx.channelsConfig = nextChannels;
3148
+ summarizeMapping("slack channel users", mapping, unresolved, runtime);
3149
+ } catch (err) {
3150
+ runtime.log?.(`slack channel user resolve failed; using config entries. ${formatUnknownError(err)}`);
3151
+ }
3152
+ }
3153
+ }
3154
+ })();
3155
+ const stopOnAbort = () => {
3156
+ if (opts.abortSignal?.aborted && slackMode === "socket") gracefulStop();
3157
+ };
3158
+ opts.abortSignal?.addEventListener("abort", stopOnAbort, { once: true });
3159
+ try {
3160
+ if (slackMode === "socket") {
3161
+ let reconnectAttempts = 0;
3162
+ let hasLoggedSocketConnected = false;
3163
+ while (!opts.abortSignal?.aborted) try {
3164
+ const disconnect = await startSlackSocketAndWaitForDisconnect({
3165
+ app,
3166
+ abortSignal: opts.abortSignal,
3167
+ onStarted: () => {
3168
+ reconnectAttempts = 0;
3169
+ publishSlackConnectedStatus(opts.setStatus);
3170
+ if (!hasLoggedSocketConnected) {
3171
+ hasLoggedSocketConnected = true;
3172
+ runtime.log?.("slack socket mode connected");
3173
+ }
3174
+ }
3175
+ });
3176
+ if (!disconnect) break;
3177
+ if (opts.abortSignal?.aborted) break;
3178
+ publishSlackDisconnectedStatus(opts.setStatus, disconnect.error);
3179
+ if (disconnect.error && isNonRecoverableSlackAuthError(disconnect.error)) {
3180
+ runtime.error?.(`slack socket mode disconnected due to non-recoverable auth error — skipping channel (${formatUnknownError(disconnect.error)})`);
3181
+ throw disconnect.error instanceof Error ? disconnect.error : new Error(formatUnknownError(disconnect.error));
3182
+ }
3183
+ reconnectAttempts += 1;
3184
+ if (SLACK_SOCKET_RECONNECT_POLICY.maxAttempts > 0 && reconnectAttempts >= SLACK_SOCKET_RECONNECT_POLICY.maxAttempts) throw new Error(`Slack socket mode reconnect max attempts reached (${reconnectAttempts}/${SLACK_SOCKET_RECONNECT_POLICY.maxAttempts}) after ${disconnect.event}`);
3185
+ const delayMs = computeBackoff(SLACK_SOCKET_RECONNECT_POLICY, reconnectAttempts);
3186
+ runtime.log?.(warn(formatSlackSocketReconnectMessage({
3187
+ event: disconnect.event,
3188
+ attempt: reconnectAttempts,
3189
+ maxAttempts: SLACK_SOCKET_RECONNECT_POLICY.maxAttempts,
3190
+ delayMs,
3191
+ error: disconnect.error
3192
+ })));
3193
+ await gracefulStop();
3194
+ try {
3195
+ await sleepWithAbort(delayMs, opts.abortSignal);
3196
+ } catch {
3197
+ break;
3198
+ }
3199
+ } catch (err) {
3200
+ if (isNonRecoverableSlackAuthError(err)) {
3201
+ runtime.error?.(`slack socket mode failed to start due to non-recoverable auth error — skipping channel (${formatUnknownError(err)})`);
3202
+ throw err;
3203
+ }
3204
+ reconnectAttempts += 1;
3205
+ if (SLACK_SOCKET_RECONNECT_POLICY.maxAttempts > 0 && reconnectAttempts >= SLACK_SOCKET_RECONNECT_POLICY.maxAttempts) throw err;
3206
+ const delayMs = computeBackoff(SLACK_SOCKET_RECONNECT_POLICY, reconnectAttempts);
3207
+ runtime.error?.(formatSlackSocketStartRetryMessage({
3208
+ attempt: reconnectAttempts,
3209
+ maxAttempts: SLACK_SOCKET_RECONNECT_POLICY.maxAttempts,
3210
+ delayMs,
3211
+ error: err,
3212
+ sdkContext: socketModeLogger.getLastMessage()
3213
+ }));
3214
+ try {
3215
+ await sleepWithAbort(delayMs, opts.abortSignal);
3216
+ } catch {
3217
+ break;
3218
+ }
3219
+ continue;
3220
+ }
3221
+ } else {
3222
+ runtime.log?.(`slack http mode listening at ${slackWebhookPath}`);
3223
+ if (!opts.abortSignal?.aborted) await new Promise((resolve) => {
3224
+ opts.abortSignal?.addEventListener("abort", () => resolve(), { once: true });
3225
+ });
3226
+ }
3227
+ } finally {
3228
+ opts.abortSignal?.removeEventListener("abort", stopOnAbort);
3229
+ unregisterHttpHandler?.();
3230
+ await gracefulStop();
3231
+ }
3232
+ }
3233
+ const resolveSlackRuntimeGroupPolicy = resolveOpenProviderRuntimeGroupPolicy;
3234
+ //#endregion
3235
+ export { resolveSlackRuntimeGroupPolicy as n, monitorSlackProvider as t };