@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.
- package/dist/api.js +3 -3
- package/dist/{channel-i43aT1rv.js → channel-BxD37X5Y.js} +13 -3
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-DrCTObMp.js → channel.setup-W5YnieWd.js} +1 -1
- package/dist/{monitor-Szm2LjsM.js → monitor-DwDmBYfl.js} +2 -2
- package/dist/{pipeline.runtime-Dvqs0AT-.js → pipeline.runtime-1FYOQOt_.js} +182 -22
- package/dist/{provider-LH_j-bLA.js → provider-ChhYGpXx.js} +10 -3
- package/dist/replies-Bt4nWmSd.js +285 -0
- package/dist/runtime-api.js +2 -2
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{setup-surface-BB_ivsoR.js → setup-surface-NNMjGpXc.js} +1 -1
- package/dist/{shared-Bviszgb6.js → shared-Bkkmro6q.js} +88 -26
- package/dist/{slash-dispatch.runtime-CoFAysiw.js → slash-dispatch.runtime-Dvvs747S.js} +1 -1
- package/node_modules/form-data/CHANGELOG.md +26 -1
- package/node_modules/form-data/README.md +4 -4
- package/node_modules/form-data/lib/form_data.js +7 -2
- package/node_modules/form-data/package.json +11 -10
- package/npm-shrinkwrap.json +7 -15
- package/package.json +5 -5
- package/dist/replies-FEOdgSlE.js +0 -142
- package/node_modules/form-data/README.md.bak +0 -350
- package/node_modules/has-own/.travis.yml +0 -4
- package/node_modules/has-own/History.md +0 -5
- package/node_modules/has-own/LICENSE +0 -22
- package/node_modules/has-own/Makefile +0 -5
- package/node_modules/has-own/README.md +0 -19
- package/node_modules/has-own/index.js +0 -8
- package/node_modules/has-own/package.json +0 -19
- 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 };
|
package/dist/runtime-api.js
CHANGED
|
@@ -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-
|
|
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-
|
|
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 };
|
package/dist/setup-plugin-api.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as slackSetupPlugin } from "./channel.setup-
|
|
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-
|
|
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:
|
|
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-
|
|
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.
|
|
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
|
-
[](https://travis-ci.org/form-data/form-data)
|
|
10
|
+
[](https://travis-ci.org/form-data/form-data)
|
|
11
|
+
[](https://travis-ci.org/form-data/form-data)
|
|
12
12
|
|
|
13
|
-
[](https://coveralls.io/github/form-data/form-data?branch=master)
|
|
14
14
|
[](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.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
33
|
-
"
|
|
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
|
-
"
|
|
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": "^
|
|
50
|
-
"auto-changelog": "^2.
|
|
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": "
|
|
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": "^
|
|
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.
|
|
72
|
+
"tape": "^5.10.1"
|
|
72
73
|
},
|
|
73
74
|
"license": "MIT",
|
|
74
75
|
"auto-changelog": {
|