@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,469 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-CiIaOW0V.js";
2
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
3
+ import { logVerbose as logVerbose$1 } from "openclaw/plugin-sdk/runtime-env";
4
+ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
5
+ import { normalizeHostname } from "openclaw/plugin-sdk/host-runtime";
6
+ import { resolveRequestUrl } from "openclaw/plugin-sdk/request-url";
7
+ import { fetchWithRuntimeDispatcher } from "openclaw/plugin-sdk/runtime-fetch";
8
+ import { fetchRemoteMedia, saveMediaBuffer } from "openclaw/plugin-sdk/media-runtime";
9
+ import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
10
+ //#region extensions/slack/src/file-reference.ts
11
+ function formatSlackFileReference(file) {
12
+ const name = normalizeOptionalString(file?.name) ?? "file";
13
+ const fileId = normalizeOptionalString(file?.id);
14
+ return fileId ? `${name} (fileId: ${fileId})` : name;
15
+ }
16
+ function formatSlackFileReferenceList(files) {
17
+ if (!files?.length) return "file";
18
+ return files.map((file) => formatSlackFileReference(file)).join(", ");
19
+ }
20
+ //#endregion
21
+ //#region extensions/slack/src/monitor/media-types.ts
22
+ const MAX_SLACK_MEDIA_FILES = 8;
23
+ //#endregion
24
+ //#region extensions/slack/src/monitor/thread.ts
25
+ const THREAD_STARTER_CACHE = /* @__PURE__ */ new Map();
26
+ const THREAD_STARTER_CACHE_TTL_MS = 360 * 6e4;
27
+ const THREAD_STARTER_CACHE_MAX = 2e3;
28
+ function evictThreadStarterCache() {
29
+ const now = Date.now();
30
+ for (const [cacheKey, entry] of THREAD_STARTER_CACHE.entries()) if (now - entry.cachedAt > THREAD_STARTER_CACHE_TTL_MS) THREAD_STARTER_CACHE.delete(cacheKey);
31
+ pruneMapToMaxSize(THREAD_STARTER_CACHE, THREAD_STARTER_CACHE_MAX);
32
+ }
33
+ function formatSlackFilePlaceholder(files) {
34
+ return `[attached: ${formatSlackFileReferenceList(files)}]`;
35
+ }
36
+ async function resolveSlackThreadStarter(params) {
37
+ evictThreadStarterCache();
38
+ const cacheKey = `${params.channelId}:${params.threadTs}`;
39
+ const cached = THREAD_STARTER_CACHE.get(cacheKey);
40
+ if (cached && Date.now() - cached.cachedAt <= THREAD_STARTER_CACHE_TTL_MS) return cached.value;
41
+ if (cached) THREAD_STARTER_CACHE.delete(cacheKey);
42
+ try {
43
+ const message = (await params.client.conversations.replies({
44
+ channel: params.channelId,
45
+ ts: params.threadTs,
46
+ limit: 1,
47
+ inclusive: true
48
+ }))?.messages?.[0];
49
+ const text = (message?.text ?? "").trim();
50
+ const files = message?.files?.length ? message.files : void 0;
51
+ if (!message || !text && !files) return null;
52
+ const starter = {
53
+ text: text || formatSlackFilePlaceholder(files),
54
+ userId: message.user,
55
+ botId: message.bot_id,
56
+ ts: message.ts,
57
+ files
58
+ };
59
+ if (THREAD_STARTER_CACHE.has(cacheKey)) THREAD_STARTER_CACHE.delete(cacheKey);
60
+ THREAD_STARTER_CACHE.set(cacheKey, {
61
+ value: starter,
62
+ cachedAt: Date.now()
63
+ });
64
+ evictThreadStarterCache();
65
+ return starter;
66
+ } catch (err) {
67
+ logVerbose$1(`slack thread starter fetch failed channel=${params.channelId} ts=${params.threadTs}: ${formatErrorMessage(err)}`);
68
+ return null;
69
+ }
70
+ }
71
+ function resetSlackThreadStarterCacheForTest() {
72
+ THREAD_STARTER_CACHE.clear();
73
+ }
74
+ /**
75
+ * Fetches the most recent messages in a Slack thread (excluding the current message).
76
+ * Used to populate thread context when a new thread session starts.
77
+ *
78
+ * Uses cursor pagination and keeps only the latest N retained messages so long threads
79
+ * still produce up-to-date context without unbounded memory growth.
80
+ */
81
+ async function resolveSlackThreadHistory(params) {
82
+ const maxMessages = params.limit ?? 20;
83
+ if (!Number.isFinite(maxMessages) || maxMessages <= 0) return [];
84
+ const fetchLimit = 200;
85
+ const retained = [];
86
+ let cursor;
87
+ try {
88
+ do {
89
+ const response = await params.client.conversations.replies({
90
+ channel: params.channelId,
91
+ ts: params.threadTs,
92
+ limit: fetchLimit,
93
+ inclusive: true,
94
+ ...cursor ? { cursor } : {}
95
+ });
96
+ for (const msg of response.messages ?? []) {
97
+ if (!msg.text?.trim() && !msg.files?.length) continue;
98
+ if (params.currentMessageTs && msg.ts === params.currentMessageTs) continue;
99
+ retained.push(msg);
100
+ }
101
+ if (retained.length > maxMessages) retained.splice(0, retained.length - maxMessages);
102
+ const next = response.response_metadata?.next_cursor;
103
+ cursor = typeof next === "string" && next.trim().length > 0 ? next.trim() : void 0;
104
+ } while (cursor);
105
+ return retained.map((msg) => ({
106
+ text: msg.text?.trim() ? msg.text : formatSlackFilePlaceholder(msg.files),
107
+ userId: msg.user,
108
+ botId: msg.bot_id,
109
+ ts: msg.ts,
110
+ files: msg.files
111
+ }));
112
+ } catch (err) {
113
+ logVerbose$1(`slack thread history fetch failed channel=${params.channelId} ts=${params.threadTs}: ${formatErrorMessage(err)}`);
114
+ return [];
115
+ }
116
+ }
117
+ //#endregion
118
+ //#region extensions/slack/src/monitor/media.ts
119
+ var media_exports = /* @__PURE__ */ __exportAll({
120
+ MAX_SLACK_MEDIA_FILES: () => 8,
121
+ SLACK_MEDIA_READ_IDLE_TIMEOUT_MS: () => SLACK_MEDIA_READ_IDLE_TIMEOUT_MS,
122
+ SLACK_MEDIA_TOTAL_TIMEOUT_MS: () => SLACK_MEDIA_TOTAL_TIMEOUT_MS,
123
+ fetchWithSlackAuth: () => fetchWithSlackAuth,
124
+ resetSlackThreadStarterCacheForTest: () => resetSlackThreadStarterCacheForTest,
125
+ resolveSlackAttachmentContent: () => resolveSlackAttachmentContent,
126
+ resolveSlackMedia: () => resolveSlackMedia,
127
+ resolveSlackThreadHistory: () => resolveSlackThreadHistory,
128
+ resolveSlackThreadStarter: () => resolveSlackThreadStarter
129
+ });
130
+ function isSlackHostname(hostname) {
131
+ const normalized = normalizeHostname(hostname);
132
+ if (!normalized) return false;
133
+ return [
134
+ "slack.com",
135
+ "slack-edge.com",
136
+ "slack-files.com"
137
+ ].some((suffix) => normalized === suffix || normalized.endsWith(`.${suffix}`));
138
+ }
139
+ function assertSlackFileUrl(rawUrl) {
140
+ let parsed;
141
+ try {
142
+ parsed = new URL(rawUrl);
143
+ } catch {
144
+ throw new Error(`Invalid Slack file URL: ${rawUrl}`);
145
+ }
146
+ if (parsed.protocol !== "https:") throw new Error(`Refusing Slack file URL with non-HTTPS protocol: ${parsed.protocol}`);
147
+ if (!isSlackHostname(parsed.hostname)) throw new Error(`Refusing to send Slack token to non-Slack host "${parsed.hostname}" (url: ${rawUrl})`);
148
+ return parsed;
149
+ }
150
+ function createSlackAuthHeaders(token) {
151
+ return { Authorization: `Bearer ${token}` };
152
+ }
153
+ function createSlackMediaRequest(url, token) {
154
+ return {
155
+ url: assertSlackFileUrl(url).href,
156
+ requestInit: { headers: createSlackAuthHeaders(token) }
157
+ };
158
+ }
159
+ function isMockedFetch(fetchImpl) {
160
+ if (typeof fetchImpl !== "function") return false;
161
+ return typeof fetchImpl.mock === "object";
162
+ }
163
+ function createSlackMediaFetch() {
164
+ return async (input, init) => {
165
+ const url = resolveRequestUrl(input);
166
+ if (!url) throw new Error("Unsupported fetch input: expected string, URL, or Request");
167
+ const parsed = assertSlackFileUrl(url);
168
+ return ("dispatcher" in (init ?? {}) && !isMockedFetch(globalThis.fetch) ? fetchWithRuntimeDispatcher : globalThis.fetch)(parsed.href, {
169
+ ...init,
170
+ redirect: "manual"
171
+ });
172
+ };
173
+ }
174
+ function resolveSlackFetchForRuntime() {
175
+ return isMockedFetch(globalThis.fetch) ? globalThis.fetch : fetchWithRuntimeDispatcher;
176
+ }
177
+ /**
178
+ * Fetches a URL with Authorization header while keeping same-origin redirects
179
+ * authenticated and dropping auth once the redirect crosses origins.
180
+ */
181
+ async function fetchWithSlackAuth(url, token) {
182
+ const parsed = assertSlackFileUrl(url);
183
+ const authHeaders = createSlackAuthHeaders(token);
184
+ const fetchImpl = resolveSlackFetchForRuntime();
185
+ const initialRes = await fetchImpl(parsed.href, {
186
+ headers: authHeaders,
187
+ redirect: "manual"
188
+ });
189
+ if (initialRes.status < 300 || initialRes.status >= 400) return initialRes;
190
+ const redirectUrl = initialRes.headers.get("location");
191
+ if (!redirectUrl) return initialRes;
192
+ let resolvedUrl;
193
+ try {
194
+ resolvedUrl = new URL(redirectUrl, parsed.href);
195
+ } catch {
196
+ return initialRes;
197
+ }
198
+ if (resolvedUrl.protocol !== "https:") return initialRes;
199
+ if (resolvedUrl.origin === parsed.origin) return fetchImpl(resolvedUrl.toString(), {
200
+ headers: authHeaders,
201
+ redirect: "follow"
202
+ });
203
+ return fetchImpl(resolvedUrl.toString(), { redirect: "follow" });
204
+ }
205
+ const SLACK_MEDIA_SSRF_POLICY = {
206
+ allowedHostnames: [
207
+ "*.slack.com",
208
+ "*.slack-edge.com",
209
+ "*.slack-files.com"
210
+ ],
211
+ hostnameAllowlist: [
212
+ "*.slack.com",
213
+ "*.slack-edge.com",
214
+ "*.slack-files.com"
215
+ ],
216
+ allowRfc2544BenchmarkRange: true
217
+ };
218
+ const SLACK_MEDIA_READ_IDLE_TIMEOUT_MS = 6e4;
219
+ const SLACK_MEDIA_TOTAL_TIMEOUT_MS = 12e4;
220
+ function mergeAbortSignals(signals) {
221
+ const activeSignals = signals.filter((signal) => Boolean(signal));
222
+ if (activeSignals.length === 0) return;
223
+ if (activeSignals.length === 1) return activeSignals[0];
224
+ if (typeof AbortSignal.any === "function") return AbortSignal.any(activeSignals);
225
+ const controller = new AbortController();
226
+ for (const signal of activeSignals) if (signal.aborted) {
227
+ controller.abort();
228
+ return controller.signal;
229
+ }
230
+ const abort = () => {
231
+ controller.abort();
232
+ for (const signal of activeSignals) signal.removeEventListener("abort", abort);
233
+ };
234
+ for (const signal of activeSignals) signal.addEventListener("abort", abort, { once: true });
235
+ return controller.signal;
236
+ }
237
+ async function fetchSlackMedia(params) {
238
+ const timeoutAbortController = params.totalTimeoutMs ? new AbortController() : void 0;
239
+ const signal = mergeAbortSignals([
240
+ params.abortSignal,
241
+ params.options.requestInit?.signal ?? void 0,
242
+ timeoutAbortController?.signal
243
+ ]);
244
+ let timedOut = false;
245
+ let timeoutHandle = null;
246
+ const fetchPromise = fetchRemoteMedia({
247
+ ...params.options,
248
+ readIdleTimeoutMs: params.readIdleTimeoutMs ?? 6e4,
249
+ ...signal ? { requestInit: {
250
+ ...params.options.requestInit,
251
+ signal
252
+ } } : {}
253
+ }).catch((error) => {
254
+ if (timedOut) return new Promise(() => {});
255
+ throw error;
256
+ });
257
+ try {
258
+ if (!params.totalTimeoutMs) return await fetchPromise;
259
+ const timeoutPromise = new Promise((_, reject) => {
260
+ timeoutHandle = setTimeout(() => {
261
+ timedOut = true;
262
+ timeoutAbortController?.abort();
263
+ reject(/* @__PURE__ */ new Error(`slack media download timed out after ${params.totalTimeoutMs}ms`));
264
+ }, params.totalTimeoutMs);
265
+ timeoutHandle.unref?.();
266
+ });
267
+ return await Promise.race([fetchPromise, timeoutPromise]);
268
+ } finally {
269
+ if (timeoutHandle) clearTimeout(timeoutHandle);
270
+ }
271
+ }
272
+ /**
273
+ * Slack voice messages (audio clips, huddle recordings) carry a `subtype` of
274
+ * `"slack_audio"` but are served with a `video/*` MIME type (e.g. `video/mp4`,
275
+ * `video/webm`). Override the primary type to `audio/` so the
276
+ * media-understanding pipeline routes them to transcription.
277
+ */
278
+ function resolveSlackMediaMimetype(file, fetchedContentType) {
279
+ const mime = fetchedContentType ?? file.mimetype;
280
+ if (file.subtype === "slack_audio" && mime?.startsWith("video/")) return mime.replace("video/", "audio/");
281
+ return mime;
282
+ }
283
+ function looksLikeHtmlBuffer(buffer) {
284
+ const head = normalizeLowercaseStringOrEmpty(buffer.subarray(0, 512).toString("utf-8").replace(/^\s+/, ""));
285
+ return head.startsWith("<!doctype html") || head.startsWith("<html");
286
+ }
287
+ const MAX_SLACK_MEDIA_CONCURRENCY = 3;
288
+ const MAX_SLACK_FORWARDED_ATTACHMENTS = 8;
289
+ async function fetchFreshSlackFileUrl(params) {
290
+ if (!params.file.id || !params.client) return null;
291
+ try {
292
+ const freshFile = (await params.client.files.info({ file: params.file.id })).file;
293
+ const freshUrl = freshFile?.url_private_download ?? freshFile?.url_private;
294
+ if (freshUrl) {
295
+ logVerbose$1(`slack: refreshed file URL via files.info for file id=${params.file.id}`);
296
+ return freshUrl;
297
+ }
298
+ logVerbose$1(`slack: files.info returned no private URL for file id=${params.file.id}`);
299
+ return null;
300
+ } catch (error) {
301
+ logVerbose$1(`slack: files.info failed for file id=${params.file.id}: ${formatErrorMessage(error)}`);
302
+ return null;
303
+ }
304
+ }
305
+ async function downloadSlackMediaFile(params) {
306
+ const { url: slackUrl, requestInit } = createSlackMediaRequest(params.url, params.token);
307
+ const fetched = await fetchSlackMedia({
308
+ options: {
309
+ url: slackUrl,
310
+ fetchImpl: createSlackMediaFetch(),
311
+ requestInit,
312
+ filePathHint: params.file.name,
313
+ maxBytes: params.maxBytes,
314
+ ssrfPolicy: SLACK_MEDIA_SSRF_POLICY
315
+ },
316
+ readIdleTimeoutMs: params.readIdleTimeoutMs,
317
+ totalTimeoutMs: params.totalTimeoutMs ?? 12e4,
318
+ abortSignal: params.abortSignal
319
+ });
320
+ if (fetched.buffer.byteLength > params.maxBytes) return null;
321
+ const fileMime = normalizeOptionalLowercaseString(params.file.mimetype);
322
+ const fileName = normalizeLowercaseStringOrEmpty(params.file.name);
323
+ if (!(fileMime === "text/html" || fileName.endsWith(".html") || fileName.endsWith(".htm"))) {
324
+ if (normalizeOptionalLowercaseString(fetched.contentType?.split(";")[0]) === "text/html" || looksLikeHtmlBuffer(fetched.buffer)) return null;
325
+ }
326
+ const effectiveMime = resolveSlackMediaMimetype(params.file, fetched.contentType);
327
+ const saved = await saveMediaBuffer(fetched.buffer, effectiveMime, "inbound", params.maxBytes);
328
+ const label = fetched.fileName ?? params.file.name;
329
+ const contentType = effectiveMime ?? saved.contentType;
330
+ return {
331
+ path: saved.path,
332
+ ...contentType ? { contentType } : {},
333
+ placeholder: `[Slack file: ${formatSlackFileReference({
334
+ ...params.file,
335
+ name: label
336
+ })}]`
337
+ };
338
+ }
339
+ function isForwardedSlackAttachment(attachment) {
340
+ return attachment.is_share === true;
341
+ }
342
+ function resolveForwardedAttachmentImageUrl(attachment) {
343
+ const rawUrl = attachment.image_url?.trim();
344
+ if (!rawUrl) return null;
345
+ try {
346
+ const parsed = new URL(rawUrl);
347
+ if (parsed.protocol !== "https:" || !isSlackHostname(parsed.hostname)) return null;
348
+ return parsed.toString();
349
+ } catch {
350
+ return null;
351
+ }
352
+ }
353
+ async function mapLimit(items, limit, fn) {
354
+ if (items.length === 0) return [];
355
+ const results = [];
356
+ results.length = items.length;
357
+ let nextIndex = 0;
358
+ const workerCount = Math.max(1, Math.min(limit, items.length));
359
+ await Promise.all(Array.from({ length: workerCount }, async () => {
360
+ while (true) {
361
+ const idx = nextIndex++;
362
+ if (idx >= items.length) return;
363
+ results[idx] = await fn(items[idx]);
364
+ }
365
+ }));
366
+ return results;
367
+ }
368
+ /**
369
+ * Downloads all files attached to a Slack message and returns them as an array.
370
+ * Returns `null` when no files could be downloaded.
371
+ */
372
+ async function resolveSlackMedia(params) {
373
+ const files = params.files ?? [];
374
+ const results = (await mapLimit(files.length > 8 ? files.slice(0, 8) : files, MAX_SLACK_MEDIA_CONCURRENCY, async (file) => {
375
+ const eventUrl = file.url_private_download ?? file.url_private;
376
+ const url = eventUrl ?? await fetchFreshSlackFileUrl({
377
+ file,
378
+ client: params.client
379
+ });
380
+ if (!url) return null;
381
+ const result = await downloadSlackMediaFile({
382
+ file,
383
+ url,
384
+ token: params.token,
385
+ maxBytes: params.maxBytes,
386
+ readIdleTimeoutMs: params.readIdleTimeoutMs,
387
+ totalTimeoutMs: params.totalTimeoutMs,
388
+ abortSignal: params.abortSignal
389
+ }).catch(() => null);
390
+ if (result || !eventUrl) return result;
391
+ const freshUrl = await fetchFreshSlackFileUrl({
392
+ file,
393
+ client: params.client
394
+ });
395
+ if (!freshUrl) return null;
396
+ return await downloadSlackMediaFile({
397
+ file,
398
+ url: freshUrl,
399
+ token: params.token,
400
+ maxBytes: params.maxBytes,
401
+ readIdleTimeoutMs: params.readIdleTimeoutMs,
402
+ totalTimeoutMs: params.totalTimeoutMs,
403
+ abortSignal: params.abortSignal
404
+ }).catch(() => null);
405
+ })).filter((entry) => Boolean(entry));
406
+ return results.length > 0 ? results : null;
407
+ }
408
+ /** Extracts text and media from forwarded-message attachments. Returns null when empty. */
409
+ async function resolveSlackAttachmentContent(params) {
410
+ const attachments = params.attachments;
411
+ if (!attachments || attachments.length === 0) return null;
412
+ const forwardedAttachments = attachments.filter((attachment) => isForwardedSlackAttachment(attachment)).slice(0, MAX_SLACK_FORWARDED_ATTACHMENTS);
413
+ if (forwardedAttachments.length === 0) return null;
414
+ const textBlocks = [];
415
+ const allMedia = [];
416
+ for (const att of forwardedAttachments) {
417
+ const text = att.text?.trim() || att.fallback?.trim();
418
+ if (text) {
419
+ const author = att.author_name;
420
+ const heading = author ? `[Forwarded message from ${author}]` : "[Forwarded message]";
421
+ textBlocks.push(`${heading}\n${text}`);
422
+ }
423
+ const imageUrl = resolveForwardedAttachmentImageUrl(att);
424
+ if (imageUrl) try {
425
+ const { url: slackUrl, requestInit } = createSlackMediaRequest(imageUrl, params.token);
426
+ const fetched = await fetchSlackMedia({
427
+ options: {
428
+ url: slackUrl,
429
+ fetchImpl: createSlackMediaFetch(),
430
+ requestInit,
431
+ maxBytes: params.maxBytes,
432
+ ssrfPolicy: SLACK_MEDIA_SSRF_POLICY
433
+ },
434
+ readIdleTimeoutMs: params.readIdleTimeoutMs,
435
+ totalTimeoutMs: params.totalTimeoutMs ?? 12e4,
436
+ abortSignal: params.abortSignal
437
+ });
438
+ if (fetched.buffer.byteLength <= params.maxBytes) {
439
+ const saved = await saveMediaBuffer(fetched.buffer, fetched.contentType, "inbound", params.maxBytes);
440
+ const label = fetched.fileName ?? "forwarded image";
441
+ allMedia.push({
442
+ path: saved.path,
443
+ contentType: fetched.contentType ?? saved.contentType,
444
+ placeholder: `[Forwarded image: ${label}]`
445
+ });
446
+ }
447
+ } catch {}
448
+ if (att.files && att.files.length > 0) {
449
+ const fileMedia = await resolveSlackMedia({
450
+ files: att.files,
451
+ client: params.client,
452
+ token: params.token,
453
+ maxBytes: params.maxBytes,
454
+ readIdleTimeoutMs: params.readIdleTimeoutMs,
455
+ totalTimeoutMs: params.totalTimeoutMs,
456
+ abortSignal: params.abortSignal
457
+ });
458
+ if (fileMedia) allMedia.push(...fileMedia);
459
+ }
460
+ }
461
+ const combinedText = textBlocks.join("\n\n");
462
+ if (!combinedText && allMedia.length === 0) return null;
463
+ return {
464
+ text: combinedText,
465
+ media: allMedia
466
+ };
467
+ }
468
+ //#endregion
469
+ export { MAX_SLACK_MEDIA_FILES as a, resolveSlackThreadStarter as i, resolveSlackMedia as n, formatSlackFileReference as o, resolveSlackThreadHistory as r, media_exports as t };
@@ -0,0 +1,104 @@
1
+ import { a as resolveSlackAccount, t as listEnabledSlackAccounts } from "./accounts-ClAPP5ry.js";
2
+ import { n as isSlackInteractiveRepliesEnabled } from "./interactive-replies-qAIfuBor.js";
3
+ import { createActionGate } from "openclaw/plugin-sdk/channel-actions";
4
+ import { extractToolSend } from "openclaw/plugin-sdk/tool-send";
5
+ import { Type } from "typebox";
6
+ //#region extensions/slack/src/message-actions.ts
7
+ function listSlackMessageActions(cfg, accountId) {
8
+ const accounts = (accountId ? [resolveSlackAccount({
9
+ cfg,
10
+ accountId
11
+ })] : listEnabledSlackAccounts(cfg)).filter((account) => account.enabled && account.botTokenSource !== "none");
12
+ if (accounts.length === 0) return [];
13
+ const isActionEnabled = (key, defaultValue = true) => {
14
+ for (const account of accounts) if (createActionGate(account.actions ?? cfg.channels?.slack?.actions)(key, defaultValue)) return true;
15
+ return false;
16
+ };
17
+ const actions = new Set(["send"]);
18
+ if (isActionEnabled("reactions")) {
19
+ actions.add("react");
20
+ actions.add("reactions");
21
+ }
22
+ if (isActionEnabled("messages")) {
23
+ actions.add("read");
24
+ actions.add("edit");
25
+ actions.add("delete");
26
+ actions.add("download-file");
27
+ actions.add("upload-file");
28
+ }
29
+ if (isActionEnabled("pins")) {
30
+ actions.add("pin");
31
+ actions.add("unpin");
32
+ actions.add("list-pins");
33
+ }
34
+ if (isActionEnabled("memberInfo")) actions.add("member-info");
35
+ if (isActionEnabled("emojiList")) actions.add("emoji-list");
36
+ return Array.from(actions);
37
+ }
38
+ function extractSlackToolSend(args) {
39
+ return extractToolSend(args, "sendMessage");
40
+ }
41
+ //#endregion
42
+ //#region extensions/slack/src/message-tool-api.ts
43
+ const SLACK_MESSAGE_ID_ACTIONS = [
44
+ "react",
45
+ "reactions",
46
+ "edit",
47
+ "delete",
48
+ "pin",
49
+ "unpin"
50
+ ];
51
+ function createSlackFileActionSchema() {
52
+ return { fileId: Type.Optional(Type.String({ description: "Slack file id, starting with \"F\" (for example F0B0LTT8M36). Required for action=\"download-file\". Read it from inbound Slack file metadata at event.files[].id. This is not the Slack message timestamp/messageId." })) };
53
+ }
54
+ function createSlackMessageIdActionSchema() {
55
+ const description = "Slack message timestamp/message id (for example \"1777423717.666499\"). Used by react, reactions, edit, delete, pin, and unpin actions. Not used by download-file, which requires fileId from event.files[].id.";
56
+ return {
57
+ messageId: Type.Optional(Type.String({ description })),
58
+ message_id: Type.Optional(Type.String({ description: `${description} Alias for messageId.` }))
59
+ };
60
+ }
61
+ function createSlackSendActionSchema() {
62
+ return {
63
+ topLevel: Type.Optional(Type.Boolean({ description: "Slack-only opt-out for action=\"send\" from a threaded same-channel context. Set true to post a new parent-channel message instead of inheriting the current Slack thread. `threadId: null` is accepted as the same top-level request." })),
64
+ replyBroadcast: Type.Optional(Type.Boolean({ description: "Slack-only opt-in for action=\"send\" thread replies. Set true with threadId or replyTo on text/block sends to also broadcast the reply to the parent channel. Not supported for media or upload-file." }))
65
+ };
66
+ }
67
+ function createSlackTopLevelActionSchema() {
68
+ return { topLevel: Type.Optional(Type.Boolean({ description: "Slack-only opt-out from threaded same-channel context. Set true to post at the channel root instead of inheriting the current Slack thread." })) };
69
+ }
70
+ function describeSlackMessageTool({ cfg, accountId }) {
71
+ const actions = listSlackMessageActions(cfg, accountId);
72
+ const capabilities = /* @__PURE__ */ new Set();
73
+ const schema = [];
74
+ if (actions.includes("send")) capabilities.add("presentation");
75
+ if (isSlackInteractiveRepliesEnabled({
76
+ cfg,
77
+ accountId
78
+ })) capabilities.add("presentation");
79
+ if (actions.includes("download-file")) schema.push({
80
+ properties: createSlackFileActionSchema(),
81
+ actions: ["download-file"]
82
+ });
83
+ if (actions.includes("send")) schema.push({
84
+ properties: createSlackSendActionSchema(),
85
+ actions: ["send"]
86
+ });
87
+ if (actions.includes("upload-file")) schema.push({
88
+ properties: createSlackTopLevelActionSchema(),
89
+ actions: ["upload-file"]
90
+ });
91
+ const messageIdActions = [];
92
+ for (const action of SLACK_MESSAGE_ID_ACTIONS) if (actions.includes(action)) messageIdActions.push(action);
93
+ if (messageIdActions.length > 0) schema.push({
94
+ properties: createSlackMessageIdActionSchema(),
95
+ actions: messageIdActions
96
+ });
97
+ return {
98
+ actions,
99
+ capabilities: Array.from(capabilities),
100
+ schema: schema.length > 0 ? schema : null
101
+ };
102
+ }
103
+ //#endregion
104
+ export { extractSlackToolSend as n, listSlackMessageActions as r, describeSlackMessageTool as t };
@@ -0,0 +1,2 @@
1
+ import { t as describeSlackMessageTool } from "./message-tool-api-6lowf9zE.js";
2
+ export { describeSlackMessageTool as describeMessageTool };
@@ -0,0 +1,13 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-CiIaOW0V.js";
2
+ import { D as buildSlackSlashCommandMatcher, p as isSlackChannelAllowedByPolicy } from "./room-context-0vovmZPU.js";
3
+ import { t as monitorSlackProvider } from "./provider-D7uAN3Fq.js";
4
+ import { o as resolveSlackThreadTs } from "./replies-Xe_jMR6o.js";
5
+ //#region extensions/slack/src/monitor.ts
6
+ var monitor_exports = /* @__PURE__ */ __exportAll({
7
+ buildSlackSlashCommandMatcher: () => buildSlackSlashCommandMatcher,
8
+ isSlackChannelAllowedByPolicy: () => isSlackChannelAllowedByPolicy,
9
+ monitorSlackProvider: () => monitorSlackProvider,
10
+ resolveSlackThreadTs: () => resolveSlackThreadTs
11
+ });
12
+ //#endregion
13
+ export { monitor_exports as t };
@@ -0,0 +1,6 @@
1
+ //#region extensions/slack/src/monitor/mrkdwn.ts
2
+ function escapeSlackMrkdwn(value) {
3
+ return value.replaceAll("\\", "\\\\").replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replace(/([*_`~])/g, "\\$1");
4
+ }
5
+ //#endregion
6
+ export { escapeSlackMrkdwn as t };