@openclaw/slack 2026.6.6 → 2026.6.8-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/api.js +3 -3
  2. package/dist/{channel-i43aT1rv.js → channel-BxD37X5Y.js} +13 -3
  3. package/dist/channel-plugin-api.js +1 -1
  4. package/dist/{channel.setup-DrCTObMp.js → channel.setup-W5YnieWd.js} +1 -1
  5. package/dist/{monitor-Szm2LjsM.js → monitor-DwDmBYfl.js} +2 -2
  6. package/dist/{pipeline.runtime-Dvqs0AT-.js → pipeline.runtime-1FYOQOt_.js} +182 -22
  7. package/dist/{provider-LH_j-bLA.js → provider-ChhYGpXx.js} +10 -3
  8. package/dist/replies-Bt4nWmSd.js +285 -0
  9. package/dist/runtime-api.js +2 -2
  10. package/dist/setup-plugin-api.js +1 -1
  11. package/dist/{setup-surface-BB_ivsoR.js → setup-surface-NNMjGpXc.js} +1 -1
  12. package/dist/{shared-Bviszgb6.js → shared-Bkkmro6q.js} +88 -26
  13. package/dist/{slash-dispatch.runtime-CoFAysiw.js → slash-dispatch.runtime-Dvvs747S.js} +1 -1
  14. package/node_modules/form-data/CHANGELOG.md +26 -1
  15. package/node_modules/form-data/README.md +4 -4
  16. package/node_modules/form-data/lib/form_data.js +7 -2
  17. package/node_modules/form-data/package.json +11 -10
  18. package/npm-shrinkwrap.json +7 -15
  19. package/package.json +5 -5
  20. package/dist/replies-FEOdgSlE.js +0 -142
  21. package/node_modules/form-data/README.md.bak +0 -350
  22. package/node_modules/has-own/.travis.yml +0 -4
  23. package/node_modules/has-own/History.md +0 -5
  24. package/node_modules/has-own/LICENSE +0 -22
  25. package/node_modules/has-own/Makefile +0 -5
  26. package/node_modules/has-own/README.md +0 -19
  27. package/node_modules/has-own/index.js +0 -8
  28. package/node_modules/has-own/package.json +0 -19
  29. package/node_modules/has-own/test/index.js +0 -36
@@ -0,0 +1,285 @@
1
+ import { r as resolveSlackReplyBlocks, s as SLACK_TEXT_LIMIT } from "./thread-ts-Cffag8e2.js";
2
+ import { o as markdownToSlackMrkdwnChunks, t as sendMessageSlack } from "./send-rekB-Xjp.js";
3
+ import { createReplyReferencePlanner } from "openclaw/plugin-sdk/reply-reference";
4
+ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
5
+ import { SILENT_REPLY_TOKEN, chunkMarkdownTextWithMode, isSilentReplyText } from "openclaw/plugin-sdk/reply-chunking";
6
+ import { deliverTextOrMediaReply, getReplyPayloadTtsSupplement, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
7
+ import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
8
+ import { buildCanonicalSentMessageHookContext, createInternalHookEvent, fireAndForgetHook, toInternalMessageSentContext, toPluginMessageContext, toPluginMessageSentEvent, triggerInternalHook } from "openclaw/plugin-sdk/hook-runtime";
9
+ //#region extensions/slack/src/message-sent-hook.ts
10
+ /**
11
+ * Slack-side emission of the `message_sent` plugin hook.
12
+ *
13
+ * Mirrors the Telegram pattern in `extensions/telegram/src/bot/delivery.replies.ts`
14
+ * (`buildTelegramSentHookContext`, `emitMessageSentHooks`, `emitTelegramMessageSentHooks`).
15
+ *
16
+ * Without this, plugins observing `message_sent` see Telegram outbound but not
17
+ * Slack outbound — even though `docs/plugins/hooks.md` documents the hook as
18
+ * firing for all successful outbound deliveries.
19
+ */
20
+ function buildSlackSentHookContext(params) {
21
+ return buildCanonicalSentMessageHookContext({
22
+ to: params.to,
23
+ content: params.content,
24
+ success: params.success,
25
+ error: params.error,
26
+ channelId: "slack",
27
+ accountId: params.accountId ?? void 0,
28
+ conversationId: params.to,
29
+ sessionKey: params.sessionKeyForInternalHooks,
30
+ messageId: params.messageId,
31
+ isGroup: params.isGroup,
32
+ groupId: params.groupId
33
+ });
34
+ }
35
+ function emitInternalSlackMessageSentHook(params) {
36
+ if (!params.sessionKeyForInternalHooks) return;
37
+ const canonical = buildSlackSentHookContext(params);
38
+ fireAndForgetHook(triggerInternalHook(createInternalHookEvent("message", "sent", params.sessionKeyForInternalHooks, toInternalMessageSentContext(canonical))), "slack: message:sent internal hook failed");
39
+ }
40
+ function emitMessageSentHooks(params) {
41
+ if (!params.enabled && !params.sessionKeyForInternalHooks) return;
42
+ const canonical = buildSlackSentHookContext(params);
43
+ if (params.enabled) fireAndForgetHook(Promise.resolve(params.hookRunner.runMessageSent(toPluginMessageSentEvent(canonical), toPluginMessageContext(canonical))), "slack: message_sent plugin hook failed");
44
+ emitInternalSlackMessageSentHook(params);
45
+ }
46
+ /**
47
+ * Fire both the plugin `message_sent` hook and (if a session key is supplied)
48
+ * the internal `message:sent` hook for a successful or failed Slack outbound
49
+ * delivery.
50
+ *
51
+ * Safe to call after every `chat.postMessage` — the function self-gates on
52
+ * `hookRunner.hasHooks("message_sent")` so plugins not observing the hook
53
+ * incur no cost.
54
+ */
55
+ function emitSlackMessageSentHooks(params) {
56
+ const hookRunner = getGlobalHookRunner();
57
+ emitMessageSentHooks({
58
+ ...params,
59
+ hookRunner,
60
+ enabled: hookRunner?.hasHooks("message_sent") ?? false
61
+ });
62
+ }
63
+ //#endregion
64
+ //#region extensions/slack/src/monitor/replies.ts
65
+ function readSlackReplyBlocks(payload) {
66
+ return resolveSlackReplyBlocks(payload);
67
+ }
68
+ function resolveSlackMediaHookSpokenText(payload) {
69
+ return (getReplyPayloadTtsSupplement(payload)?.spokenText ?? payload.spokenText)?.trim() || void 0;
70
+ }
71
+ function resolveDeliveredSlackReplyThreadTs(params) {
72
+ return (params.replyToMode === "off" ? void 0 : params.payloadReplyToId) ?? params.replyThreadTs;
73
+ }
74
+ async function deliverReplies(params) {
75
+ let latestResult;
76
+ for (const payload of params.replies) {
77
+ if (payload.isReasoning === true) continue;
78
+ const threadTs = resolveDeliveredSlackReplyThreadTs({
79
+ replyToMode: params.replyToMode,
80
+ payloadReplyToId: payload.replyToId,
81
+ replyThreadTs: params.replyThreadTs
82
+ });
83
+ const reply = resolveSendableOutboundReplyParts(payload);
84
+ const slackBlocks = readSlackReplyBlocks(payload);
85
+ if (!reply.hasContent && !slackBlocks?.length) continue;
86
+ const emitSent = (content, result) => {
87
+ if (params.deferMessageSentHooks) return;
88
+ emitSlackMessageSentHooks({
89
+ sessionKeyForInternalHooks: params.sessionKeyForInternalHooks,
90
+ to: params.messageSentHookTarget ?? params.target,
91
+ accountId: params.accountId,
92
+ content,
93
+ success: true,
94
+ messageId: result?.messageId,
95
+ isGroup: params.isGroup,
96
+ groupId: params.groupId
97
+ });
98
+ };
99
+ const emitFailed = (content, error) => {
100
+ if (params.deferMessageSentHooks) return;
101
+ emitSlackMessageSentHooks({
102
+ sessionKeyForInternalHooks: params.sessionKeyForInternalHooks,
103
+ to: params.messageSentHookTarget ?? params.target,
104
+ accountId: params.accountId,
105
+ content,
106
+ success: false,
107
+ error: formatErrorMessage(error),
108
+ isGroup: params.isGroup,
109
+ groupId: params.groupId
110
+ });
111
+ };
112
+ if (!reply.hasMedia && slackBlocks?.length) {
113
+ const trimmed = reply.trimmedText;
114
+ if (!trimmed && !slackBlocks?.length) continue;
115
+ if (trimmed && isSilentReplyText(trimmed, SILENT_REPLY_TOKEN)) continue;
116
+ let result;
117
+ try {
118
+ result = await sendMessageSlack(params.target, trimmed, {
119
+ cfg: params.cfg,
120
+ token: params.token,
121
+ threadTs,
122
+ accountId: params.accountId,
123
+ ...slackBlocks?.length ? { blocks: slackBlocks } : {},
124
+ ...params.identity ? { identity: params.identity } : {},
125
+ ...params.metadata ? { metadata: params.metadata } : {}
126
+ });
127
+ } catch (error) {
128
+ emitFailed(trimmed, error);
129
+ throw error;
130
+ }
131
+ emitSent(trimmed, result);
132
+ latestResult = result;
133
+ params.runtime.log?.(`delivered reply to ${params.target}`);
134
+ continue;
135
+ }
136
+ const spokenText = resolveSlackMediaHookSpokenText(payload);
137
+ const mediaHookContent = reply.hasText ? reply.text : spokenText || reply.text;
138
+ const hookContent = reply.hasMedia ? mediaHookContent : reply.trimmedText;
139
+ let lastResult;
140
+ let delivered;
141
+ try {
142
+ delivered = await deliverTextOrMediaReply({
143
+ payload,
144
+ text: reply.text,
145
+ chunkText: !reply.hasMedia ? (value) => {
146
+ const trimmed = value.trim();
147
+ if (!trimmed || isSilentReplyText(trimmed, SILENT_REPLY_TOKEN)) return [];
148
+ return [trimmed];
149
+ } : void 0,
150
+ sendText: async (trimmed) => {
151
+ lastResult = await sendMessageSlack(params.target, trimmed, {
152
+ cfg: params.cfg,
153
+ token: params.token,
154
+ threadTs,
155
+ accountId: params.accountId,
156
+ ...params.identity ? { identity: params.identity } : {},
157
+ ...params.metadata ? { metadata: params.metadata } : {}
158
+ });
159
+ },
160
+ sendMedia: async ({ mediaUrl, caption }) => {
161
+ lastResult = await sendMessageSlack(params.target, caption ?? "", {
162
+ cfg: params.cfg,
163
+ token: params.token,
164
+ mediaUrl,
165
+ threadTs,
166
+ accountId: params.accountId,
167
+ ...params.identity ? { identity: params.identity } : {},
168
+ ...params.metadata ? { metadata: params.metadata } : {}
169
+ });
170
+ }
171
+ });
172
+ } catch (error) {
173
+ emitFailed(hookContent, error);
174
+ throw error;
175
+ }
176
+ if (delivered !== "empty") {
177
+ emitSent(hookContent, reply.hasMedia ? void 0 : lastResult);
178
+ latestResult = lastResult;
179
+ params.runtime.log?.(`delivered reply to ${params.target}`);
180
+ }
181
+ }
182
+ return latestResult;
183
+ }
184
+ /**
185
+ * Compute effective threadTs for a Slack reply based on replyToMode.
186
+ * - "off": stay in thread if already in one, otherwise main channel
187
+ * - "first": first reply goes to thread, subsequent replies to main channel
188
+ * - "all": all replies go to thread
189
+ */
190
+ function resolveSlackThreadTs(params) {
191
+ return createSlackReplyReferencePlanner({
192
+ replyToMode: params.replyToMode,
193
+ incomingThreadTs: params.incomingThreadTs,
194
+ messageTs: params.messageTs,
195
+ hasReplied: params.hasReplied,
196
+ isThreadReply: params.isThreadReply
197
+ }).use();
198
+ }
199
+ function createSlackReplyReferencePlanner(params) {
200
+ return createReplyReferencePlanner({
201
+ replyToMode: params.isThreadReply ?? Boolean(params.incomingThreadTs && params.incomingThreadTs !== params.messageTs) ? "all" : params.replyToMode,
202
+ existingId: params.incomingThreadTs,
203
+ startId: params.messageTs,
204
+ hasReplied: params.hasReplied
205
+ });
206
+ }
207
+ function createSlackReplyDeliveryPlan(params) {
208
+ const replyReference = createSlackReplyReferencePlanner({
209
+ replyToMode: params.replyToMode,
210
+ incomingThreadTs: params.incomingThreadTs,
211
+ messageTs: params.messageTs,
212
+ hasReplied: params.hasRepliedRef.value,
213
+ isThreadReply: params.isThreadReply
214
+ });
215
+ return {
216
+ peekThreadTs: () => replyReference.peek(),
217
+ nextThreadTs: () => replyReference.use(),
218
+ markSent: () => {
219
+ replyReference.markSent();
220
+ params.hasRepliedRef.value = replyReference.hasReplied();
221
+ }
222
+ };
223
+ }
224
+ async function deliverSlackSlashReplies(params) {
225
+ const deliveries = [];
226
+ const chunkLimit = Math.min(params.textLimit, SLACK_TEXT_LIMIT);
227
+ for (const payload of params.replies) {
228
+ if (payload.isReasoning === true) continue;
229
+ const reply = resolveSendableOutboundReplyParts(payload);
230
+ const slackBlocks = readSlackReplyBlocks(payload);
231
+ const text = reply.hasText && !isSilentReplyText(reply.trimmedText, SILENT_REPLY_TOKEN) ? reply.trimmedText : void 0;
232
+ if (slackBlocks?.length && !reply.hasMedia) {
233
+ deliveries.push({
234
+ hookContent: text ?? "",
235
+ messages: [{
236
+ text: text ?? "",
237
+ blocks: slackBlocks
238
+ }]
239
+ });
240
+ continue;
241
+ }
242
+ const combined = [text ?? "", ...reply.mediaUrls].filter(Boolean).join("\n");
243
+ if (!combined) continue;
244
+ const chunkMode = params.chunkMode ?? "length";
245
+ const chunks = (chunkMode === "newline" ? chunkMarkdownTextWithMode(combined, chunkLimit, chunkMode) : [combined]).flatMap((markdown) => markdownToSlackMrkdwnChunks(markdown, chunkLimit, { tableMode: params.tableMode }));
246
+ if (!chunks.length && combined) chunks.push(combined);
247
+ deliveries.push({
248
+ hookContent: text ?? resolveSlackMediaHookSpokenText(payload) ?? combined,
249
+ messages: chunks.map((chunk) => ({ text: chunk }))
250
+ });
251
+ }
252
+ if (deliveries.length === 0) return;
253
+ const responseType = params.ephemeral ? "ephemeral" : "in_channel";
254
+ for (const delivery of deliveries) {
255
+ try {
256
+ for (const message of delivery.messages) await params.respond({
257
+ ...message,
258
+ response_type: responseType
259
+ });
260
+ } catch (error) {
261
+ if (params.messageSentHookTarget) emitSlackMessageSentHooks({
262
+ sessionKeyForInternalHooks: params.sessionKeyForInternalHooks,
263
+ to: params.messageSentHookTarget,
264
+ accountId: params.accountId,
265
+ content: delivery.hookContent,
266
+ success: false,
267
+ error: formatErrorMessage(error),
268
+ isGroup: params.isGroup,
269
+ groupId: params.groupId
270
+ });
271
+ throw error;
272
+ }
273
+ if (params.messageSentHookTarget) emitSlackMessageSentHooks({
274
+ sessionKeyForInternalHooks: params.sessionKeyForInternalHooks,
275
+ to: params.messageSentHookTarget,
276
+ accountId: params.accountId,
277
+ content: delivery.hookContent,
278
+ success: true,
279
+ isGroup: params.isGroup,
280
+ groupId: params.groupId
281
+ });
282
+ }
283
+ }
284
+ //#endregion
285
+ export { resolveDeliveredSlackReplyThreadTs as a, readSlackReplyBlocks as i, deliverReplies as n, resolveSlackThreadTs as o, deliverSlackSlashReplies as r, emitSlackMessageSentHooks as s, createSlackReplyDeliveryPlan as t };
@@ -7,8 +7,8 @@ import { a as listSlackEmojis, c as pinSlackMessage, d as removeOwnSlackReaction
7
7
  import { t as probeSlack } from "./probe-BTKzLT2u.js";
8
8
  import { t as resolveSlackChannelAllowlist } from "./resolve-channels-DX2GSx9c.js";
9
9
  import { t as resolveSlackUserAllowlist } from "./resolve-users-DOULgUwy.js";
10
- import { t as monitorSlackProvider } from "./provider-LH_j-bLA.js";
10
+ import { t as monitorSlackProvider } from "./provider-ChhYGpXx.js";
11
11
  import { n as slackActionRuntime, t as handleSlackAction } from "./action-runtime-CgrHdqkC.js";
12
12
  import { n as listSlackDirectoryGroupsLive, r as listSlackDirectoryPeersLive } from "./directory-live-C1acgXKJ.js";
13
- import "./monitor-Szm2LjsM.js";
13
+ import "./monitor-DwDmBYfl.js";
14
14
  export { deleteSlackMessage, editSlackMessage, getSlackMemberInfo, handleSlackAction, listEnabledSlackAccounts, listSlackAccountIds, listSlackDirectoryGroupsLive, listSlackDirectoryPeersLive, listSlackEmojis, listSlackPins, listSlackReactions, monitorSlackProvider, pinSlackMessage, probeSlack, reactSlackMessage, readSlackMessages, registerSlackPluginHttpRoutes, removeOwnSlackReactions, removeSlackReaction, resolveDefaultSlackAccountId, resolveSlackAccount, resolveSlackAppToken, resolveSlackBotToken, resolveSlackChannelAllowlist, resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy, resolveSlackUserAllowlist, sendMessageSlack, sendSlackMessage, setSlackRuntime, slackActionRuntime, unpinSlackMessage };
@@ -1,2 +1,2 @@
1
- import { t as slackSetupPlugin } from "./channel.setup-DrCTObMp.js";
1
+ import { t as slackSetupPlugin } from "./channel.setup-W5YnieWd.js";
2
2
  export { slackSetupPlugin };
@@ -1,5 +1,5 @@
1
1
  import { a as resolveSlackAccount, i as resolveDefaultSlackAccountId, o as resolveSlackAccountAllowFrom } from "./accounts-f6Xcv9Vi.js";
2
- import "./shared-Bviszgb6.js";
2
+ import "./shared-Bkkmro6q.js";
3
3
  import { i as SLACK_CHANNEL, t as createSlackSetupWizardBase } from "./setup-core-POfI_bgP.js";
4
4
  import { t as resolveSlackChannelAllowlist } from "./resolve-channels-DX2GSx9c.js";
5
5
  import { t as resolveSlackUserAllowlist } from "./resolve-users-DOULgUwy.js";
@@ -1,4 +1,4 @@
1
- import { a as resolveSlackAccount, c as resolveSlackConfigAccessorAccount, i as resolveDefaultSlackAccountId, n as listSlackAccountIds, o as resolveSlackAccountAllowFrom, s as resolveSlackAccountDmPolicy } from "./accounts-f6Xcv9Vi.js";
1
+ import { a as resolveSlackAccount, c as resolveSlackConfigAccessorAccount, i as resolveDefaultSlackAccountId, n as listSlackAccountIds, o as resolveSlackAccountAllowFrom, r as mergeSlackAccountConfig, s as resolveSlackAccountDmPolicy } from "./accounts-f6Xcv9Vi.js";
2
2
  import { t as inspectSlackAccount } from "./account-inspect-CdGk6R7l.js";
3
3
  import { f as getChatChannelMeta, h as isSlackPluginAccountConfigured } from "./client-Cn2WwpcA.js";
4
4
  import { n as isSlackInteractiveRepliesEnabled } from "./interactive-replies-DrBq4Mld.js";
@@ -71,6 +71,92 @@ function isSlackMutableAllowEntry(raw) {
71
71
  function asObjectRecord(value) {
72
72
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
73
73
  }
74
+ const collectSlackMutableAllowlistWarnings = createDangerousNameMatchingMutableAllowlistWarningCollector({
75
+ channel: "slack",
76
+ detector: isSlackMutableAllowEntry,
77
+ collectLists: (scope) => {
78
+ const lists = [{
79
+ pathLabel: `${scope.prefix}.allowFrom`,
80
+ list: scope.account.allowFrom
81
+ }];
82
+ const dm = asObjectRecord(scope.account.dm);
83
+ if (dm) lists.push({
84
+ pathLabel: `${scope.prefix}.dm.allowFrom`,
85
+ list: dm.allowFrom
86
+ });
87
+ const channels = asObjectRecord(scope.account.channels);
88
+ if (channels) for (const [channelKey, channelRaw] of Object.entries(channels)) {
89
+ const channel = asObjectRecord(channelRaw);
90
+ if (!channel) continue;
91
+ lists.push({
92
+ pathLabel: `${scope.prefix}.channels.${channelKey}.users`,
93
+ list: channel.users
94
+ });
95
+ }
96
+ return lists;
97
+ }
98
+ });
99
+ const SLACK_CANONICAL_CHANNEL_ID_RE = /^[CG][A-Z0-9]{8,}$/;
100
+ const SLACK_LOWERCASE_CHANNEL_ID_RE = /^[cg][0-9][a-z0-9]{7,}$/;
101
+ const SLACK_PREFIXED_CANONICAL_CHANNEL_ID_RE = /^channel:[CG][A-Z0-9]{8,}$/;
102
+ const SLACK_PREFIXED_LOWERCASE_CHANNEL_ID_RE = /^channel:[cg][0-9][a-z0-9]{7,}$/;
103
+ const SLACK_CANONICAL_DM_ID_RE = /^(?:channel:)?D[A-Z0-9]{8,}$/;
104
+ const SLACK_PREFIXED_LOWERCASE_DM_ID_RE = /^channel:d[a-z0-9]{8,}$/;
105
+ const SLACK_AMBIGUOUS_LOWERCASE_DM_ID_RE = /^d[a-z0-9]{8,}$/;
106
+ const SLACK_AMBIGUOUS_LOWERCASE_CHANNEL_ID_RE = /^(?:channel:)?[cgd][a-z][a-z0-9]{7,}$/;
107
+ const SLACK_CHANNEL_NAME_RE = /^[\p{L}\p{M}\p{N}_-]{1,80}$/u;
108
+ const SLACK_CHANNEL_NAME_ALPHANUMERIC_RE = /[\p{L}\p{N}]/u;
109
+ function looksLikeSlackChannelId(channelKey) {
110
+ return SLACK_CANONICAL_CHANNEL_ID_RE.test(channelKey) || SLACK_LOWERCASE_CHANNEL_ID_RE.test(channelKey) || SLACK_PREFIXED_CANONICAL_CHANNEL_ID_RE.test(channelKey) || SLACK_PREFIXED_LOWERCASE_CHANNEL_ID_RE.test(channelKey);
111
+ }
112
+ function looksLikeSlackDmId(channelKey) {
113
+ return SLACK_CANONICAL_DM_ID_RE.test(channelKey) || SLACK_PREFIXED_LOWERCASE_DM_ID_RE.test(channelKey);
114
+ }
115
+ function looksLikeSlackChannelNameKey(channelKey) {
116
+ const name = channelKey.startsWith("#") ? channelKey.slice(1) : channelKey;
117
+ return name === name.toLowerCase() && SLACK_CHANNEL_NAME_RE.test(name) && SLACK_CHANNEL_NAME_ALPHANUMERIC_RE.test(name);
118
+ }
119
+ function collectSlackNameKeyedChannelWarnings({ cfg }) {
120
+ const warnings = /* @__PURE__ */ new Set();
121
+ const slackCfg = asObjectRecord(asObjectRecord(cfg.channels)?.slack);
122
+ const providerChannels = asObjectRecord(slackCfg?.channels);
123
+ const accounts = asObjectRecord(slackCfg?.accounts);
124
+ for (const accountId of listSlackAccountIds(cfg)) {
125
+ const account = asObjectRecord(mergeSlackAccountConfig(cfg, accountId));
126
+ if (!account || slackCfg?.enabled === false || account.enabled === false) continue;
127
+ const effectiveGroupPolicy = (typeof account.groupPolicy === "string" ? account.groupPolicy : void 0) ?? "allowlist";
128
+ const rawAccount = asObjectRecord(accounts?.[accountId]);
129
+ const accountPrefix = rawAccount ? `channels.slack.accounts.${accountId}` : "channels.slack";
130
+ const accountChannels = asObjectRecord(rawAccount?.channels);
131
+ const channels = accountChannels ?? providerChannels;
132
+ if (!channels) continue;
133
+ const channelsPrefix = accountChannels ? `channels.slack.accounts.${accountId}` : "channels.slack";
134
+ const fallbackDescription = Object.hasOwn(channels, "*") ? `${channelsPrefix}.channels."*" applies instead and this entry's overrides are ignored` : effectiveGroupPolicy === "open" ? "this entry's overrides are ignored and the channel remains allowed by groupPolicy: \"open\"" : "messages from the channel are dropped";
135
+ for (const channelKey of Object.keys(channels)) {
136
+ if (channelKey === "*") continue;
137
+ if (looksLikeSlackDmId(channelKey)) {
138
+ warnings.add(`${channelsPrefix}.channels."${channelKey}" is a Slack DM conversation ID, but ${channelsPrefix}.channels only configures channel and group rooms. Configure DM access with ${accountPrefix}.dmPolicy and ${accountPrefix}.allowFrom instead.`);
139
+ continue;
140
+ }
141
+ if (SLACK_AMBIGUOUS_LOWERCASE_DM_ID_RE.test(channelKey)) {
142
+ if (account.dangerouslyAllowNameMatching === true && looksLikeSlackChannelNameKey(channelKey)) continue;
143
+ warnings.add(`${channelsPrefix}.channels."${channelKey}" is ambiguous: it may be a lowercase Slack DM conversation ID or a channel name. Configure DMs with ${accountPrefix}.dmPolicy and ${accountPrefix}.allowFrom; otherwise re-key the room with its stable C/G ID.`);
144
+ continue;
145
+ }
146
+ if (effectiveGroupPolicy === "disabled") continue;
147
+ const channelConfig = asObjectRecord(channels[channelKey]);
148
+ if (effectiveGroupPolicy === "open" && Object.keys(channelConfig ?? {}).length === 0) continue;
149
+ if (looksLikeSlackChannelId(channelKey)) continue;
150
+ if (account.dangerouslyAllowNameMatching === true && looksLikeSlackChannelNameKey(channelKey)) continue;
151
+ if (SLACK_AMBIGUOUS_LOWERCASE_CHANNEL_ID_RE.test(channelKey)) {
152
+ warnings.add(`${channelsPrefix}.channels."${channelKey}" is ambiguous: it may be a lowercase Slack channel ID or a channel name. If it is a channel name, inbound routing will not match it and ${fallbackDescription}. Re-key it with the channel's stable ID (e.g. C0123ABCD, from the channel's About details or conversations.info).`);
153
+ continue;
154
+ }
155
+ warnings.add(`${channelsPrefix}.channels."${channelKey}" is keyed by a channel name or non-canonical ID form, not a routable Slack channel ID; under groupPolicy: "${effectiveGroupPolicy}" inbound routing does not match this entry, so ${fallbackDescription}. Re-key it with the channel's ID (e.g. C0123ABCD, from the channel's About details or conversations.info).`);
156
+ }
157
+ }
158
+ return [...warnings];
159
+ }
74
160
  const slackDoctor = {
75
161
  dmAllowFromMode: "topOnly",
76
162
  groupModel: "route",
@@ -78,31 +164,7 @@ const slackDoctor = {
78
164
  warnOnEmptyGroupSenderAllowlist: false,
79
165
  legacyConfigRules,
80
166
  normalizeCompatibilityConfig,
81
- collectMutableAllowlistWarnings: createDangerousNameMatchingMutableAllowlistWarningCollector({
82
- channel: "slack",
83
- detector: isSlackMutableAllowEntry,
84
- collectLists: (scope) => {
85
- const lists = [{
86
- pathLabel: `${scope.prefix}.allowFrom`,
87
- list: scope.account.allowFrom
88
- }];
89
- const dm = asObjectRecord(scope.account.dm);
90
- if (dm) lists.push({
91
- pathLabel: `${scope.prefix}.dm.allowFrom`,
92
- list: dm.allowFrom
93
- });
94
- const channels = asObjectRecord(scope.account.channels);
95
- if (channels) for (const [channelKey, channelRaw] of Object.entries(channels)) {
96
- const channel = asObjectRecord(channelRaw);
97
- if (!channel) continue;
98
- lists.push({
99
- pathLabel: `${scope.prefix}.channels.${channelKey}.users`,
100
- list: channel.users
101
- });
102
- }
103
- return lists;
104
- }
105
- })
167
+ collectMutableAllowlistWarnings: ({ cfg }) => [...collectSlackMutableAllowlistWarnings({ cfg }), ...collectSlackNameKeyedChannelWarnings({ cfg })]
106
168
  };
107
169
  //#endregion
108
170
  //#region extensions/slack/src/shared.ts
@@ -1,4 +1,4 @@
1
- import { r as deliverSlackSlashReplies$1 } from "./replies-FEOdgSlE.js";
1
+ import { r as deliverSlackSlashReplies$1 } from "./replies-Bt4nWmSd.js";
2
2
  import { resolveAgentRoute as resolveAgentRoute$1 } from "openclaw/plugin-sdk/routing";
3
3
  import { resolveMarkdownTableMode as resolveMarkdownTableMode$1 } from "openclaw/plugin-sdk/markdown-table-runtime";
4
4
  import { recordInboundSessionMetaSafe as recordInboundSessionMetaSafe$1, resolveConversationLabel as resolveConversationLabel$1 } from "openclaw/plugin-sdk/conversation-runtime";
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [v4.0.5](https://github.com/form-data/form-data/compare/v4.0.4...v4.0.5) - 2025-11-17
9
+
10
+ ### Commits
11
+
12
+ - [Tests] Switch to newer v8 prediction library; enable node 24 testing [`16e0076`](https://github.com/form-data/form-data/commit/16e00765342106876f98a1c9703314006c9e937a)
13
+ - [Dev Deps] update `@ljharb/eslint-config`, `eslint` [`5822467`](https://github.com/form-data/form-data/commit/5822467f0ec21f6ad613c1c90856375e498793c7)
14
+ - [Fix] set Symbol.toStringTag in the proper place [`76d0dee`](https://github.com/form-data/form-data/commit/76d0dee43933b5e167f7f09e5d9cbbd1cf911aa7)
15
+
8
16
  ## [v4.0.4](https://github.com/form-data/form-data/compare/v4.0.3...v4.0.4) - 2025-07-16
9
17
 
10
18
  ### Commits
@@ -158,7 +166,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
158
166
 
159
167
  - feat: add setBoundary method [`55d90ce`](https://github.com/form-data/form-data/commit/55d90ce4a4c22b0ea0647991d85cb946dfb7395b)
160
168
 
161
- ## [v3.0.0](https://github.com/form-data/form-data/compare/v2.5.4...v3.0.0) - 2019-11-05
169
+ ## [v3.0.0](https://github.com/form-data/form-data/compare/v2.5.6...v3.0.0) - 2019-11-05
162
170
 
163
171
  ### Merged
164
172
 
@@ -182,6 +190,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
182
190
  - Pass options to constructor if not used with new [`4bde68e`](https://github.com/form-data/form-data/commit/4bde68e12de1ba90fefad2e7e643f6375b902763)
183
191
  - Make userHeaders optional [`2b4e478`](https://github.com/form-data/form-data/commit/2b4e4787031490942f2d1ee55c56b85a250875a7)
184
192
 
193
+ ## [v2.5.6](https://github.com/form-data/form-data/compare/v2.5.5...v2.5.6) - 2026-06-12
194
+
195
+ ### Commits
196
+
197
+ - [Fix] escape CR, LF, and `"` in field names and filenames [`b620316`](https://github.com/form-data/form-data/commit/b62031603c2d7c329b2a369b49466790f0ba6314)
198
+ - [Dev Deps] update `@ljharb/eslint-config`, `auto-changelog`, `eslint`, `tape` [`12be578`](https://github.com/form-data/form-data/commit/12be578e936fd77eee75e2e656955f5343c4b80f)
199
+ - [Dev Deps] update `js-randomness-predictor` [`46cfd23`](https://github.com/form-data/form-data/commit/46cfd23bd40be14cfa0391e1c5357c4d74098f23)
200
+ - [Tests] use `safe-buffer` so the header-injection test runs on node < 4 [`633044a`](https://github.com/form-data/form-data/commit/633044a57a7b19f41cec2271ffd24afa2f6280af)
201
+ - [Deps] update `hasown` [`e3b96ee`](https://github.com/form-data/form-data/commit/e3b96eef1661bca8ea4297de057b78bf2734e900)
202
+
203
+ ## [v2.5.5](https://github.com/form-data/form-data/compare/v2.5.4...v2.5.5) - 2025-07-18
204
+
205
+ ### Commits
206
+
207
+ - [meta] actually ensure the readme backup isn’t published [`10626c0`](https://github.com/form-data/form-data/commit/10626c0a9b78c7d3fcaa51772265015ee0afc25c)
208
+ - [Fix] use proper dependency [`026abe5`](https://github.com/form-data/form-data/commit/026abe5c5c0489d8a2ccb59d5cfd14fb63078377)
209
+
185
210
  ## [v2.5.4](https://github.com/form-data/form-data/compare/v2.5.3...v2.5.4) - 2025-07-17
186
211
 
187
212
  ### Fixed
@@ -6,11 +6,11 @@ The API of this library is inspired by the [XMLHttpRequest-2 FormData Interface]
6
6
 
7
7
  [xhr2-fd]: http://dev.w3.org/2006/webapi/XMLHttpRequest-2/Overview.html#the-formdata-interface
8
8
 
9
- [![Linux Build](https://img.shields.io/travis/form-data/form-data/v2.5.4.svg?label=linux:4.x-12.x)](https://travis-ci.org/form-data/form-data)
10
- [![MacOS Build](https://img.shields.io/travis/form-data/form-data/v2.5.4.svg?label=macos:4.x-12.x)](https://travis-ci.org/form-data/form-data)
11
- [![Windows Build](https://img.shields.io/travis/form-data/form-data/v2.5.4.svg?label=windows:4.x-12.x)](https://travis-ci.org/form-data/form-data)
9
+ [![Linux Build](https://img.shields.io/travis/form-data/form-data/v2.5.6.svg?label=linux:4.x-12.x)](https://travis-ci.org/form-data/form-data)
10
+ [![MacOS Build](https://img.shields.io/travis/form-data/form-data/v2.5.6.svg?label=macos:4.x-12.x)](https://travis-ci.org/form-data/form-data)
11
+ [![Windows Build](https://img.shields.io/travis/form-data/form-data/v2.5.6.svg?label=windows:4.x-12.x)](https://travis-ci.org/form-data/form-data)
12
12
 
13
- [![Coverage Status](https://img.shields.io/coveralls/form-data/form-data/v2.5.4.svg?label=code+coverage)](https://coveralls.io/github/form-data/form-data?branch=master)
13
+ [![Coverage Status](https://img.shields.io/coveralls/form-data/form-data/v2.5.6.svg?label=code+coverage)](https://coveralls.io/github/form-data/form-data?branch=master)
14
14
  [![Dependency Status](https://img.shields.io/david/form-data/form-data.svg)](https://david-dm.org/form-data/form-data)
15
15
 
16
16
  ## Install
@@ -15,6 +15,11 @@ var setToStringTag = require('es-set-tostringtag');
15
15
  var populate = require('./populate.js');
16
16
  var Buffer = require('safe-buffer').Buffer;
17
17
 
18
+ // escape CR/LF/`"` so a name/filename can't inject headers or smuggle parts; matches the WHATWG HTML multipart/form-data encoding
19
+ function escapeHeaderParam(str) {
20
+ return String(str).replace(/\r/g, '%0D').replace(/\n/g, '%0A').replace(/"/g, '%22');
21
+ }
22
+
18
23
  /**
19
24
  * Create readable "multipart/form-data" streams.
20
25
  * Can be used to submit forms
@@ -190,7 +195,7 @@ FormData.prototype._multiPartHeader = function (field, value, options) {
190
195
  var contents = '';
191
196
  var headers = {
192
197
  // add custom disposition as third element or keep it two elements if not
193
- 'Content-Disposition': ['form-data', 'name="' + field + '"'].concat(contentDisposition || []),
198
+ 'Content-Disposition': ['form-data', 'name="' + escapeHeaderParam(field) + '"'].concat(contentDisposition || []),
194
199
  // if no content type. allow it to be empty array
195
200
  'Content-Type': [].concat(contentType || []),
196
201
  };
@@ -245,7 +250,7 @@ FormData.prototype._getContentDisposition = function (value, options) {
245
250
  }
246
251
 
247
252
  if (filename) {
248
- contentDisposition = 'filename="' + filename + '"';
253
+ contentDisposition = 'filename="' + escapeHeaderParam(filename) + '"';
249
254
  }
250
255
 
251
256
  return contentDisposition;
@@ -2,7 +2,7 @@
2
2
  "author": "Felix Geisendörfer <felix@debuggable.com> (http://debuggable.com/)",
3
3
  "name": "form-data",
4
4
  "description": "A library to create readable \"multipart/form-data\" streams. Can be used to submit forms and file uploads to other web applications.",
5
- "version": "2.5.4",
5
+ "version": "2.5.6",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git://github.com/form-data/form-data.git"
@@ -27,10 +27,11 @@
27
27
  "files": "pkgfiles --sort=name",
28
28
  "get-version": "node -e \"console.log(require('./package.json').version)\"",
29
29
  "update-readme": "sed -i.bak 's/\\/master\\.svg/\\/v'$(npm --silent run get-version)'.svg/g' README.md",
30
- "restore-readme": "mv README.md.bak README.md",
30
+ "postupdate-readme": "mv README.md.bak READ.ME.md.bak",
31
+ "restore-readme": "mv READ.ME.md.bak README.md",
31
32
  "prepublish": "not-in-publish || npm run prepublishOnly",
32
- "prepublishOnly": "npm run update-readme",
33
- "postpublish": "npm run restore-readme",
33
+ "prepack": "npm run update-readme",
34
+ "postpack": "npm run restore-readme",
34
35
  "version": "auto-changelog && git add CHANGELOG.md",
35
36
  "postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\""
36
37
  },
@@ -41,25 +42,25 @@
41
42
  "asynckit": "^0.4.0",
42
43
  "combined-stream": "^1.0.8",
43
44
  "es-set-tostringtag": "^2.1.0",
44
- "has-own": "^1.0.1",
45
+ "hasown": "^2.0.4",
45
46
  "mime-types": "^2.1.35",
46
47
  "safe-buffer": "^5.2.1"
47
48
  },
48
49
  "devDependencies": {
49
- "@ljharb/eslint-config": "^21.2.0",
50
- "auto-changelog": "^2.5.0",
50
+ "@ljharb/eslint-config": "^22.2.3",
51
+ "auto-changelog": "^2.6.0",
51
52
  "browserify": "^13.3.0",
52
53
  "browserify-istanbul": "^2.0.0",
53
54
  "coveralls": "^3.1.1",
54
55
  "cross-spawn": "^4.0.2",
55
56
  "encoding": "^0.1.13",
56
- "eslint": "=8.8.0",
57
+ "eslint": "^8.57.1",
57
58
  "fake": "^0.2.2",
58
59
  "far": "^0.0.7",
59
60
  "formidable": "^1.2.6",
60
61
  "in-publish": "^2.0.1",
61
62
  "istanbul": "^0.4.5",
62
- "js-randomness-predictor": "^1.5.5",
63
+ "js-randomness-predictor": "^3.6.0",
63
64
  "obake": "^0.1.2",
64
65
  "phantomjs-prebuilt": "^2.1.16",
65
66
  "pkgfiles": "^2.3.2",
@@ -68,7 +69,7 @@
68
69
  "request": "~2.87.0",
69
70
  "rimraf": "^2.7.1",
70
71
  "semver": "^6.3.1",
71
- "tape": "^5.9.0"
72
+ "tape": "^5.10.1"
72
73
  },
73
74
  "license": "MIT",
74
75
  "auto-changelog": {