@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.
- package/dist/account-inspect-D7AZNs8C.js +77 -0
- package/dist/account-inspect-api.js +10 -0
- package/dist/accounts-ClAPP5ry.js +139 -0
- package/dist/accounts.runtime-DDVcLJUI.js +2 -0
- package/dist/action-runtime-e2UhRsNx.js +350 -0
- package/dist/action-runtime.runtime-BFcqMbOm.js +2 -0
- package/dist/actions-CYLFK-Zy.js +292 -0
- package/dist/actions.runtime-CO3OaTLb.js +2 -0
- package/dist/allow-list-BPnnlRPL.js +82 -0
- package/dist/api.js +21 -0
- package/dist/approval-handler.runtime-CmeRr9qA.js +256 -0
- package/dist/blocks-input-CwTFVImV.js +29 -0
- package/dist/blocks-render-BIDw-Pom.js +161 -0
- package/dist/channel-DRjHBTDB.js +1020 -0
- package/dist/channel-api-B_nZwosg.js +20 -0
- package/dist/channel-config-api.js +2 -0
- package/dist/channel-entry.js +22 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.setup-Cayn7afd.js +73 -0
- package/dist/client-CPe4GmDR.js +103 -0
- package/dist/config-api-B_jq4NJW.js +2 -0
- package/dist/config-schema-D9B5LB_L.js +167 -0
- package/dist/configured-state.js +11 -0
- package/dist/contract-api.js +5 -0
- package/dist/directory-config-B3JiHeB7.js +54 -0
- package/dist/directory-contract-api.js +2 -0
- package/dist/directory-live-Bf16GwDh.js +133 -0
- package/dist/doctor-contract-KUjHnkQm.js +147 -0
- package/dist/doctor-contract-api.js +2 -0
- package/dist/errors-BYFHR24f.js +109 -0
- package/dist/exec-approvals-7xUNgLi9.js +58 -0
- package/dist/group-policy-CyLUK6My.js +41 -0
- package/dist/http-routes-api.js +2 -0
- package/dist/inbound-contract-test-api.js +3 -0
- package/dist/index.js +33 -0
- package/dist/interactive-replies-api.js +2 -0
- package/dist/interactive-replies-qAIfuBor.js +173 -0
- package/dist/magic-string.es-BMaGRRZ1.js +1011 -0
- package/dist/media-D1XCd1uP.js +469 -0
- package/dist/message-tool-api-6lowf9zE.js +104 -0
- package/dist/message-tool-api.js +2 -0
- package/dist/monitor-a97o17G6.js +13 -0
- package/dist/mrkdwn-Cax-eSfK.js +6 -0
- package/dist/outbound-adapter-B_5sEhCg.js +174 -0
- package/dist/outbound-payload-test-api.js +2 -0
- package/dist/outbound-payload.test-harness-CVCamg1x.js +13558 -0
- package/dist/pipeline.runtime-DT0hLnq2.js +1379 -0
- package/dist/plugin-routes-DtTPmga1.js +20 -0
- package/dist/prepare-D3YqV8jB.js +1482 -0
- package/dist/prepare.test-helpers-DVcjRhfG.js +49 -0
- package/dist/probe-3eZf1FjI.js +42 -0
- package/dist/provider-D7uAN3Fq.js +3235 -0
- package/dist/registry-CeaoNfoP.js +39 -0
- package/dist/replies-Xe_jMR6o.js +139 -0
- package/dist/reply-blocks-Z5l6_R6H.js +14 -0
- package/dist/resolve-allowlist-common-Bk3clYPK.js +43 -0
- package/dist/resolve-channels-BRYqyNVJ.js +81 -0
- package/dist/resolve-users-Bd_SdP8j.js +113 -0
- package/dist/rolldown-runtime-CiIaOW0V.js +13 -0
- package/dist/room-context-0vovmZPU.js +787 -0
- package/dist/runtime-Bo-KHM-F.js +8 -0
- package/dist/runtime-api-Dd1xIV5v.js +9 -0
- package/dist/runtime-api.js +14 -0
- package/dist/runtime-setter-api.js +2 -0
- package/dist/scopes-CDevO8jg.js +74 -0
- package/dist/secret-contract-Bo6lbSkh.js +141 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/security-audit-BtHGnD3d.js +51 -0
- package/dist/security-contract-api.js +2 -0
- package/dist/send-D_A9kL-C.js +721 -0
- package/dist/send.runtime-BRE_ncCU.js +2 -0
- package/dist/send.runtime-_l76lUuL.js +2 -0
- package/dist/setup-core-B9NetDkM.js +320 -0
- package/dist/setup-entry.js +15 -0
- package/dist/setup-plugin-api.js +2 -0
- package/dist/setup-surface-D88QBVOW.js +128 -0
- package/dist/shared-D8U42xFL.js +208 -0
- package/dist/slash-commands.runtime-22kgyst2.js +19 -0
- package/dist/slash-dispatch.runtime-BJgT0jwV.js +32 -0
- package/dist/slash-plugin-commands.runtime-CF-n3MeP.js +2 -0
- package/dist/slash-skill-commands.runtime-BMs0VjTe.js +7 -0
- package/dist/streaming-compat-RkZgTmQ2.js +43 -0
- package/dist/target-parsing-CQmv-iSm.js +55 -0
- package/dist/targets-B1tYCAr6.js +2 -0
- package/dist/test-api.js +8 -0
- package/dist/thread-ts-C2x7c5PP.js +24 -0
- package/openclaw.plugin.json +2405 -0
- package/package.json +84 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { a as resolveSlackAccount } from "./accounts-ClAPP5ry.js";
|
|
2
|
+
import { t as inspectSlackAccount } from "./account-inspect-D7AZNs8C.js";
|
|
3
|
+
import { hasConfiguredSecretInput } from "openclaw/plugin-sdk/secret-input";
|
|
4
|
+
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
5
|
+
import { DEFAULT_ACCOUNT_ID, createAccountScopedAllowFromSection, createAccountScopedGroupAccessSection, createAllowlistSetupWizardProxy, createEnvPatchedAccountSetupAdapter, createLegacyCompatChannelDmPolicy, createStandardChannelSetupStatus, parseMentionOrPrefixedId, patchChannelConfigForAccount, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup-runtime";
|
|
6
|
+
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
|
|
7
|
+
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
8
|
+
//#region extensions/slack/src/setup-shared.ts
|
|
9
|
+
const SLACK_CHANNEL = "slack";
|
|
10
|
+
function buildSlackManifest(botName = "OpenClaw") {
|
|
11
|
+
const safeName = botName.trim() || "OpenClaw";
|
|
12
|
+
const manifest = {
|
|
13
|
+
display_information: {
|
|
14
|
+
name: safeName,
|
|
15
|
+
description: `${safeName} connector for OpenClaw`
|
|
16
|
+
},
|
|
17
|
+
features: {
|
|
18
|
+
bot_user: {
|
|
19
|
+
display_name: safeName,
|
|
20
|
+
always_online: true
|
|
21
|
+
},
|
|
22
|
+
app_home: {
|
|
23
|
+
home_tab_enabled: true,
|
|
24
|
+
messages_tab_enabled: true,
|
|
25
|
+
messages_tab_read_only_enabled: false
|
|
26
|
+
},
|
|
27
|
+
slash_commands: [{
|
|
28
|
+
command: "/openclaw",
|
|
29
|
+
description: "Send a message to OpenClaw",
|
|
30
|
+
should_escape: false
|
|
31
|
+
}]
|
|
32
|
+
},
|
|
33
|
+
oauth_config: { scopes: { bot: [
|
|
34
|
+
"app_mentions:read",
|
|
35
|
+
"assistant:write",
|
|
36
|
+
"channels:history",
|
|
37
|
+
"channels:read",
|
|
38
|
+
"chat:write",
|
|
39
|
+
"commands",
|
|
40
|
+
"emoji:read",
|
|
41
|
+
"files:read",
|
|
42
|
+
"files:write",
|
|
43
|
+
"groups:history",
|
|
44
|
+
"groups:read",
|
|
45
|
+
"im:history",
|
|
46
|
+
"im:read",
|
|
47
|
+
"im:write",
|
|
48
|
+
"mpim:history",
|
|
49
|
+
"mpim:read",
|
|
50
|
+
"mpim:write",
|
|
51
|
+
"pins:read",
|
|
52
|
+
"pins:write",
|
|
53
|
+
"reactions:read",
|
|
54
|
+
"reactions:write",
|
|
55
|
+
"usergroups:read",
|
|
56
|
+
"users:read"
|
|
57
|
+
] } },
|
|
58
|
+
settings: {
|
|
59
|
+
socket_mode_enabled: true,
|
|
60
|
+
event_subscriptions: { bot_events: [
|
|
61
|
+
"app_home_opened",
|
|
62
|
+
"app_mention",
|
|
63
|
+
"channel_rename",
|
|
64
|
+
"member_joined_channel",
|
|
65
|
+
"member_left_channel",
|
|
66
|
+
"message.channels",
|
|
67
|
+
"message.groups",
|
|
68
|
+
"message.im",
|
|
69
|
+
"message.mpim",
|
|
70
|
+
"pin_added",
|
|
71
|
+
"pin_removed",
|
|
72
|
+
"reaction_added",
|
|
73
|
+
"reaction_removed"
|
|
74
|
+
] }
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
return JSON.stringify(manifest, null, 2);
|
|
78
|
+
}
|
|
79
|
+
function buildSlackSetupLines() {
|
|
80
|
+
return [
|
|
81
|
+
"1) Slack API -> Create App -> From scratch or From manifest (with the JSON below)",
|
|
82
|
+
"2) Add Socket Mode + enable it to get the app-level token (xapp-...)",
|
|
83
|
+
"3) Install App to workspace to get the xoxb- bot token",
|
|
84
|
+
"4) Enable Event Subscriptions (socket) for message and App Home events",
|
|
85
|
+
"5) App Home -> enable the Home tab and Messages tab for DMs",
|
|
86
|
+
"Manifest JSON follows as plain text for copy/paste.",
|
|
87
|
+
"Tip: set SLACK_BOT_TOKEN + SLACK_APP_TOKEN in your env.",
|
|
88
|
+
`Docs: ${formatDocsLink("/slack", "slack")}`
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
function setSlackChannelAllowlist(cfg, accountId, channelKeys) {
|
|
92
|
+
return patchChannelConfigForAccount({
|
|
93
|
+
cfg,
|
|
94
|
+
channel: SLACK_CHANNEL,
|
|
95
|
+
accountId,
|
|
96
|
+
patch: { channels: Object.fromEntries(channelKeys.map((key) => [key, { enabled: true }])) }
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function isSlackSetupAccountConfigured(account) {
|
|
100
|
+
const hasConfiguredBotToken = Boolean(account.botToken?.trim()) || hasConfiguredSecretInput(account.config.botToken);
|
|
101
|
+
const hasConfiguredAppToken = Boolean(account.appToken?.trim()) || hasConfiguredSecretInput(account.config.appToken);
|
|
102
|
+
return hasConfiguredBotToken && hasConfiguredAppToken;
|
|
103
|
+
}
|
|
104
|
+
function describeSlackSetupAccount(account) {
|
|
105
|
+
return describeAccountSnapshot({
|
|
106
|
+
account,
|
|
107
|
+
configured: isSlackSetupAccountConfigured(account),
|
|
108
|
+
extra: {
|
|
109
|
+
botTokenSource: account.botTokenSource,
|
|
110
|
+
appTokenSource: account.appTokenSource
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region extensions/slack/src/setup-core.ts
|
|
116
|
+
function enableSlackAccount(cfg, accountId) {
|
|
117
|
+
return patchChannelConfigForAccount({
|
|
118
|
+
cfg,
|
|
119
|
+
channel: SLACK_CHANNEL,
|
|
120
|
+
accountId,
|
|
121
|
+
patch: { enabled: true }
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function hasSlackInteractiveRepliesConfig(cfg, accountId) {
|
|
125
|
+
const capabilities = resolveSlackAccount({
|
|
126
|
+
cfg,
|
|
127
|
+
accountId
|
|
128
|
+
}).config.capabilities;
|
|
129
|
+
if (Array.isArray(capabilities)) return capabilities.some((entry) => normalizeLowercaseStringOrEmpty(entry) === "interactivereplies");
|
|
130
|
+
if (!capabilities || typeof capabilities !== "object") return false;
|
|
131
|
+
return "interactiveReplies" in capabilities;
|
|
132
|
+
}
|
|
133
|
+
function setSlackInteractiveReplies(cfg, accountId, interactiveReplies) {
|
|
134
|
+
const capabilities = resolveSlackAccount({
|
|
135
|
+
cfg,
|
|
136
|
+
accountId
|
|
137
|
+
}).config.capabilities;
|
|
138
|
+
return patchChannelConfigForAccount({
|
|
139
|
+
cfg,
|
|
140
|
+
channel: SLACK_CHANNEL,
|
|
141
|
+
accountId,
|
|
142
|
+
patch: { capabilities: Array.isArray(capabilities) ? interactiveReplies ? [...new Set([...capabilities, "interactiveReplies"])] : capabilities.filter((entry) => normalizeLowercaseStringOrEmpty(entry) !== "interactivereplies") : {
|
|
143
|
+
...capabilities && typeof capabilities === "object" ? capabilities : {},
|
|
144
|
+
interactiveReplies
|
|
145
|
+
} }
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function createSlackTokenCredential(params) {
|
|
149
|
+
return {
|
|
150
|
+
inputKey: params.inputKey,
|
|
151
|
+
providerHint: params.providerHint,
|
|
152
|
+
credentialLabel: params.credentialLabel,
|
|
153
|
+
preferredEnvVar: params.preferredEnvVar,
|
|
154
|
+
envPrompt: `${params.preferredEnvVar} detected. Use env var?`,
|
|
155
|
+
keepPrompt: params.keepPrompt,
|
|
156
|
+
inputPrompt: params.inputPrompt,
|
|
157
|
+
allowEnv: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID,
|
|
158
|
+
inspect: ({ cfg, accountId }) => {
|
|
159
|
+
const resolved = resolveSlackAccount({
|
|
160
|
+
cfg,
|
|
161
|
+
accountId
|
|
162
|
+
});
|
|
163
|
+
const configuredValue = params.inputKey === "botToken" ? resolved.config.botToken : resolved.config.appToken;
|
|
164
|
+
const resolvedValue = params.inputKey === "botToken" ? resolved.botToken : resolved.appToken;
|
|
165
|
+
return {
|
|
166
|
+
accountConfigured: Boolean(resolvedValue) || hasConfiguredSecretInput(configuredValue),
|
|
167
|
+
hasConfiguredValue: hasConfiguredSecretInput(configuredValue),
|
|
168
|
+
resolvedValue: normalizeOptionalString(resolvedValue),
|
|
169
|
+
envValue: accountId === DEFAULT_ACCOUNT_ID ? normalizeOptionalString(process.env[params.preferredEnvVar]) : void 0
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
applyUseEnv: ({ cfg, accountId }) => enableSlackAccount(cfg, accountId),
|
|
173
|
+
applySet: ({ cfg, accountId, value }) => patchChannelConfigForAccount({
|
|
174
|
+
cfg,
|
|
175
|
+
channel: SLACK_CHANNEL,
|
|
176
|
+
accountId,
|
|
177
|
+
patch: {
|
|
178
|
+
enabled: true,
|
|
179
|
+
[params.inputKey]: value
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const slackSetupAdapter = createEnvPatchedAccountSetupAdapter({
|
|
185
|
+
channelKey: SLACK_CHANNEL,
|
|
186
|
+
defaultAccountOnlyEnvError: "Slack env tokens can only be used for the default account.",
|
|
187
|
+
missingCredentialError: "Slack requires --bot-token and --app-token (or --use-env).",
|
|
188
|
+
hasCredentials: (input) => Boolean(input.botToken && input.appToken),
|
|
189
|
+
buildPatch: (input) => ({
|
|
190
|
+
...input.botToken ? { botToken: input.botToken } : {},
|
|
191
|
+
...input.appToken ? { appToken: input.appToken } : {}
|
|
192
|
+
})
|
|
193
|
+
});
|
|
194
|
+
function createSlackSetupWizardBase(handlers) {
|
|
195
|
+
const slackDmPolicy = createLegacyCompatChannelDmPolicy({
|
|
196
|
+
label: "Slack",
|
|
197
|
+
channel: SLACK_CHANNEL,
|
|
198
|
+
promptAllowFrom: handlers.promptAllowFrom
|
|
199
|
+
});
|
|
200
|
+
return {
|
|
201
|
+
channel: SLACK_CHANNEL,
|
|
202
|
+
status: createStandardChannelSetupStatus({
|
|
203
|
+
channelLabel: "Slack",
|
|
204
|
+
configuredLabel: "configured",
|
|
205
|
+
unconfiguredLabel: "needs tokens",
|
|
206
|
+
configuredHint: "configured",
|
|
207
|
+
unconfiguredHint: "needs tokens",
|
|
208
|
+
configuredScore: 2,
|
|
209
|
+
unconfiguredScore: 1,
|
|
210
|
+
resolveConfigured: ({ cfg, accountId }) => inspectSlackAccount({
|
|
211
|
+
cfg,
|
|
212
|
+
accountId
|
|
213
|
+
}).configured
|
|
214
|
+
}),
|
|
215
|
+
introNote: {
|
|
216
|
+
title: "Slack socket mode tokens",
|
|
217
|
+
lines: buildSlackSetupLines(),
|
|
218
|
+
shouldShow: ({ cfg, accountId }) => !isSlackSetupAccountConfigured(resolveSlackAccount({
|
|
219
|
+
cfg,
|
|
220
|
+
accountId
|
|
221
|
+
}))
|
|
222
|
+
},
|
|
223
|
+
prepare: async ({ cfg, accountId, prompter }) => {
|
|
224
|
+
if (isSlackSetupAccountConfigured(resolveSlackAccount({
|
|
225
|
+
cfg,
|
|
226
|
+
accountId
|
|
227
|
+
}))) return;
|
|
228
|
+
const manifest = buildSlackManifest();
|
|
229
|
+
if (prompter.plain) await prompter.plain(manifest);
|
|
230
|
+
else await prompter.note(manifest, "Slack manifest JSON");
|
|
231
|
+
},
|
|
232
|
+
envShortcut: {
|
|
233
|
+
prompt: "SLACK_BOT_TOKEN + SLACK_APP_TOKEN detected. Use env vars?",
|
|
234
|
+
preferredEnvVar: "SLACK_BOT_TOKEN",
|
|
235
|
+
isAvailable: ({ cfg, accountId }) => accountId === DEFAULT_ACCOUNT_ID && Boolean(process.env.SLACK_BOT_TOKEN?.trim()) && Boolean(process.env.SLACK_APP_TOKEN?.trim()) && !isSlackSetupAccountConfigured(resolveSlackAccount({
|
|
236
|
+
cfg,
|
|
237
|
+
accountId
|
|
238
|
+
})),
|
|
239
|
+
apply: ({ cfg, accountId }) => enableSlackAccount(cfg, accountId)
|
|
240
|
+
},
|
|
241
|
+
credentials: [createSlackTokenCredential({
|
|
242
|
+
inputKey: "botToken",
|
|
243
|
+
providerHint: "slack-bot",
|
|
244
|
+
credentialLabel: "Slack bot token",
|
|
245
|
+
preferredEnvVar: "SLACK_BOT_TOKEN",
|
|
246
|
+
keepPrompt: "Slack bot token already configured. Keep it?",
|
|
247
|
+
inputPrompt: "Enter Slack bot token (xoxb-...)"
|
|
248
|
+
}), createSlackTokenCredential({
|
|
249
|
+
inputKey: "appToken",
|
|
250
|
+
providerHint: "slack-app",
|
|
251
|
+
credentialLabel: "Slack app token",
|
|
252
|
+
preferredEnvVar: "SLACK_APP_TOKEN",
|
|
253
|
+
keepPrompt: "Slack app token already configured. Keep it?",
|
|
254
|
+
inputPrompt: "Enter Slack app token (xapp-...)"
|
|
255
|
+
})],
|
|
256
|
+
dmPolicy: slackDmPolicy,
|
|
257
|
+
allowFrom: createAccountScopedAllowFromSection({
|
|
258
|
+
channel: SLACK_CHANNEL,
|
|
259
|
+
credentialInputKey: "botToken",
|
|
260
|
+
helpTitle: "Slack allowlist",
|
|
261
|
+
helpLines: [
|
|
262
|
+
"Allowlist Slack DMs by username (we resolve to user ids).",
|
|
263
|
+
"Examples:",
|
|
264
|
+
"- U12345678",
|
|
265
|
+
"- @alice",
|
|
266
|
+
"Multiple entries: comma-separated.",
|
|
267
|
+
`Docs: ${formatDocsLink("/slack", "slack")}`
|
|
268
|
+
],
|
|
269
|
+
message: "Slack allowFrom (usernames or ids)",
|
|
270
|
+
placeholder: "@alice, U12345678",
|
|
271
|
+
invalidWithoutCredentialNote: "Slack token missing; use user ids (or mention form) only.",
|
|
272
|
+
parseId: (value) => parseMentionOrPrefixedId({
|
|
273
|
+
value,
|
|
274
|
+
mentionPattern: /^<@([A-Z0-9]+)>$/i,
|
|
275
|
+
prefixPattern: /^(slack:|user:)/i,
|
|
276
|
+
idPattern: /^[A-Z][A-Z0-9]+$/i,
|
|
277
|
+
normalizeId: (id) => id.toUpperCase()
|
|
278
|
+
}),
|
|
279
|
+
resolveEntries: handlers.resolveAllowFromEntries
|
|
280
|
+
}),
|
|
281
|
+
groupAccess: createAccountScopedGroupAccessSection({
|
|
282
|
+
channel: SLACK_CHANNEL,
|
|
283
|
+
label: "Slack channels",
|
|
284
|
+
placeholder: "#general, #private, C123",
|
|
285
|
+
currentPolicy: ({ cfg, accountId }) => resolveSlackAccount({
|
|
286
|
+
cfg,
|
|
287
|
+
accountId
|
|
288
|
+
}).config.groupPolicy ?? "allowlist",
|
|
289
|
+
currentEntries: ({ cfg, accountId }) => Object.entries(resolveSlackAccount({
|
|
290
|
+
cfg,
|
|
291
|
+
accountId
|
|
292
|
+
}).config.channels ?? {}).filter(([, value]) => value?.enabled !== false).map(([key]) => key),
|
|
293
|
+
updatePrompt: ({ cfg, accountId }) => Boolean(resolveSlackAccount({
|
|
294
|
+
cfg,
|
|
295
|
+
accountId
|
|
296
|
+
}).config.channels),
|
|
297
|
+
resolveAllowlist: handlers.resolveGroupAllowlist,
|
|
298
|
+
fallbackResolved: (entries) => entries,
|
|
299
|
+
applyAllowlist: ({ cfg, accountId, resolved }) => setSlackChannelAllowlist(cfg, accountId, resolved)
|
|
300
|
+
}),
|
|
301
|
+
finalize: async ({ cfg, accountId, options, prompter }) => {
|
|
302
|
+
if (hasSlackInteractiveRepliesConfig(cfg, accountId)) return;
|
|
303
|
+
if (options?.quickstartDefaults) return { cfg: setSlackInteractiveReplies(cfg, accountId, true) };
|
|
304
|
+
return { cfg: setSlackInteractiveReplies(cfg, accountId, await prompter.confirm({
|
|
305
|
+
message: "Enable Slack interactive replies (buttons/selects) for agent responses?",
|
|
306
|
+
initialValue: true
|
|
307
|
+
})) };
|
|
308
|
+
},
|
|
309
|
+
disable: (cfg) => setSetupChannelEnabled(cfg, SLACK_CHANNEL, false)
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function createSlackSetupWizardProxy(loadWizard) {
|
|
313
|
+
return createAllowlistSetupWizardProxy({
|
|
314
|
+
loadWizard: async () => (await loadWizard()).slackSetupWizard,
|
|
315
|
+
createBase: createSlackSetupWizardBase,
|
|
316
|
+
fallbackResolvedGroupAllowlist: (entries) => entries
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
//#endregion
|
|
320
|
+
export { describeSlackSetupAccount as a, SLACK_CHANNEL as i, createSlackSetupWizardProxy as n, isSlackSetupAccountConfigured as o, slackSetupAdapter as r, createSlackSetupWizardBase as t };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract";
|
|
2
|
+
//#region extensions/slack/setup-entry.ts
|
|
3
|
+
var setup_entry_default = defineBundledChannelSetupEntry({
|
|
4
|
+
importMetaUrl: import.meta.url,
|
|
5
|
+
plugin: {
|
|
6
|
+
specifier: "./setup-plugin-api.js",
|
|
7
|
+
exportName: "slackSetupPlugin"
|
|
8
|
+
},
|
|
9
|
+
secrets: {
|
|
10
|
+
specifier: "./secret-contract-api.js",
|
|
11
|
+
exportName: "channelSecrets"
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
//#endregion
|
|
15
|
+
export { setup_entry_default as default };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { a as resolveSlackAccount, i as resolveDefaultSlackAccountId, o as resolveSlackAccountAllowFrom } from "./accounts-ClAPP5ry.js";
|
|
2
|
+
import "./shared-D8U42xFL.js";
|
|
3
|
+
import { i as SLACK_CHANNEL, t as createSlackSetupWizardBase } from "./setup-core-B9NetDkM.js";
|
|
4
|
+
import { t as resolveSlackChannelAllowlist } from "./resolve-channels-BRYqyNVJ.js";
|
|
5
|
+
import { t as resolveSlackUserAllowlist } from "./resolve-users-Bd_SdP8j.js";
|
|
6
|
+
import { adaptScopedAccountAccessor } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
7
|
+
import { noteChannelLookupFailure, noteChannelLookupSummary, parseMentionOrPrefixedId, promptLegacyChannelAllowFromForAccount, resolveEntriesWithOptionalToken } from "openclaw/plugin-sdk/setup-runtime";
|
|
8
|
+
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
|
|
9
|
+
//#region extensions/slack/src/setup-surface.ts
|
|
10
|
+
async function resolveSlackAllowFromEntries(params) {
|
|
11
|
+
return await resolveEntriesWithOptionalToken({
|
|
12
|
+
token: params.token,
|
|
13
|
+
entries: params.entries,
|
|
14
|
+
buildWithoutToken: (input) => ({
|
|
15
|
+
input,
|
|
16
|
+
resolved: false,
|
|
17
|
+
id: null
|
|
18
|
+
}),
|
|
19
|
+
resolveEntries: async ({ token, entries }) => (await resolveSlackUserAllowlist({
|
|
20
|
+
token,
|
|
21
|
+
entries
|
|
22
|
+
})).map((entry) => ({
|
|
23
|
+
input: entry.input,
|
|
24
|
+
resolved: entry.resolved,
|
|
25
|
+
id: entry.id ?? null
|
|
26
|
+
}))
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async function promptSlackAllowFrom(params) {
|
|
30
|
+
const parseId = (value) => parseMentionOrPrefixedId({
|
|
31
|
+
value,
|
|
32
|
+
mentionPattern: /^<@([A-Z0-9]+)>$/i,
|
|
33
|
+
prefixPattern: /^(slack:|user:)/i,
|
|
34
|
+
idPattern: /^[A-Z][A-Z0-9]+$/i,
|
|
35
|
+
normalizeId: (id) => id.toUpperCase()
|
|
36
|
+
});
|
|
37
|
+
return await promptLegacyChannelAllowFromForAccount({
|
|
38
|
+
cfg: params.cfg,
|
|
39
|
+
channel: SLACK_CHANNEL,
|
|
40
|
+
prompter: params.prompter,
|
|
41
|
+
accountId: params.accountId,
|
|
42
|
+
defaultAccountId: resolveDefaultSlackAccountId(params.cfg),
|
|
43
|
+
resolveAccount: adaptScopedAccountAccessor(resolveSlackAccount),
|
|
44
|
+
resolveExisting: (account, cfg) => resolveSlackAccountAllowFrom({
|
|
45
|
+
cfg,
|
|
46
|
+
accountId: account.accountId
|
|
47
|
+
}) ?? [],
|
|
48
|
+
resolveToken: (account) => account.userToken ?? account.botToken ?? "",
|
|
49
|
+
noteTitle: "Slack allowlist",
|
|
50
|
+
noteLines: [
|
|
51
|
+
"Allowlist Slack DMs by username (we resolve to user ids).",
|
|
52
|
+
"Examples:",
|
|
53
|
+
"- U12345678",
|
|
54
|
+
"- @alice",
|
|
55
|
+
"Multiple entries: comma-separated.",
|
|
56
|
+
`Docs: ${formatDocsLink("/slack", "slack")}`
|
|
57
|
+
],
|
|
58
|
+
message: "Slack allowFrom (usernames or ids)",
|
|
59
|
+
placeholder: "@alice, U12345678",
|
|
60
|
+
parseId,
|
|
61
|
+
invalidWithoutTokenNote: "Slack token missing; use user ids (or mention form) only.",
|
|
62
|
+
resolveEntries: async ({ token, entries }) => (await resolveSlackUserAllowlist({
|
|
63
|
+
token,
|
|
64
|
+
entries
|
|
65
|
+
})).map((entry) => ({
|
|
66
|
+
input: entry.input,
|
|
67
|
+
resolved: entry.resolved,
|
|
68
|
+
id: entry.id ?? null
|
|
69
|
+
}))
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async function resolveSlackGroupAllowlist(params) {
|
|
73
|
+
let keys = params.entries;
|
|
74
|
+
const activeBotToken = resolveSlackAccount({
|
|
75
|
+
cfg: params.cfg,
|
|
76
|
+
accountId: params.accountId
|
|
77
|
+
}).botToken || params.credentialValues.botToken || "";
|
|
78
|
+
if (params.entries.length > 0) try {
|
|
79
|
+
const resolved = await resolveEntriesWithOptionalToken({
|
|
80
|
+
token: activeBotToken,
|
|
81
|
+
entries: params.entries,
|
|
82
|
+
buildWithoutToken: (input) => ({
|
|
83
|
+
input,
|
|
84
|
+
resolved: false,
|
|
85
|
+
id: void 0
|
|
86
|
+
}),
|
|
87
|
+
resolveEntries: async ({ token, entries }) => await resolveSlackChannelAllowlist({
|
|
88
|
+
token,
|
|
89
|
+
entries
|
|
90
|
+
})
|
|
91
|
+
});
|
|
92
|
+
const resolvedKeys = resolved.filter((entry) => entry.resolved && entry.id).map((entry) => entry.id);
|
|
93
|
+
const unresolved = resolved.filter((entry) => !entry.resolved).map((entry) => entry.input);
|
|
94
|
+
keys = [...resolvedKeys, ...unresolved.map((entry) => entry.trim()).filter(Boolean)];
|
|
95
|
+
await noteChannelLookupSummary({
|
|
96
|
+
prompter: params.prompter,
|
|
97
|
+
label: "Slack channels",
|
|
98
|
+
resolvedSections: [{
|
|
99
|
+
title: "Resolved",
|
|
100
|
+
values: resolvedKeys
|
|
101
|
+
}],
|
|
102
|
+
unresolved
|
|
103
|
+
});
|
|
104
|
+
} catch (error) {
|
|
105
|
+
await noteChannelLookupFailure({
|
|
106
|
+
prompter: params.prompter,
|
|
107
|
+
label: "Slack channels",
|
|
108
|
+
error
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return keys;
|
|
112
|
+
}
|
|
113
|
+
const slackSetupWizard = createSlackSetupWizardBase({
|
|
114
|
+
promptAllowFrom: promptSlackAllowFrom,
|
|
115
|
+
resolveAllowFromEntries: async ({ credentialValues, entries }) => await resolveSlackAllowFromEntries({
|
|
116
|
+
token: credentialValues.botToken,
|
|
117
|
+
entries
|
|
118
|
+
}),
|
|
119
|
+
resolveGroupAllowlist: async ({ cfg, accountId, credentialValues, entries, prompter }) => await resolveSlackGroupAllowlist({
|
|
120
|
+
cfg,
|
|
121
|
+
accountId,
|
|
122
|
+
credentialValues,
|
|
123
|
+
entries,
|
|
124
|
+
prompter
|
|
125
|
+
})
|
|
126
|
+
});
|
|
127
|
+
//#endregion
|
|
128
|
+
export { slackSetupWizard };
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { a as resolveSlackAccount, c as resolveSlackConfigAccessorAccount, i as resolveDefaultSlackAccountId, n as listSlackAccountIds, o as resolveSlackAccountAllowFrom, s as resolveSlackAccountDmPolicy } from "./accounts-ClAPP5ry.js";
|
|
2
|
+
import { t as inspectSlackAccount } from "./account-inspect-D7AZNs8C.js";
|
|
3
|
+
import { n as isSlackInteractiveRepliesEnabled } from "./interactive-replies-qAIfuBor.js";
|
|
4
|
+
import { r as getChatChannelMeta } from "./channel-api-B_nZwosg.js";
|
|
5
|
+
import { i as SLACK_CHANNEL } from "./setup-core-B9NetDkM.js";
|
|
6
|
+
import { t as SlackChannelConfigSchema } from "./config-schema-D9B5LB_L.js";
|
|
7
|
+
import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-KUjHnkQm.js";
|
|
8
|
+
import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-Bo6lbSkh.js";
|
|
9
|
+
import { adaptScopedAccountAccessor, createScopedChannelConfigAdapter, createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
10
|
+
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
11
|
+
import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
|
|
12
|
+
import { createDangerousNameMatchingMutableAllowlistWarningCollector, createOpenProviderConfiguredRouteWarningCollector } from "openclaw/plugin-sdk/channel-policy";
|
|
13
|
+
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
14
|
+
//#region extensions/slack/src/security.ts
|
|
15
|
+
const resolveSlackDmPolicy = createScopedDmSecurityResolver({
|
|
16
|
+
channelKey: "slack",
|
|
17
|
+
resolvePolicy: (account) => account.config.dmPolicy,
|
|
18
|
+
resolveAllowFrom: (account) => account.config.allowFrom,
|
|
19
|
+
resolveAccess: ({ cfg, account }) => ({
|
|
20
|
+
dmPolicy: resolveSlackAccountDmPolicy({
|
|
21
|
+
cfg,
|
|
22
|
+
accountId: account.accountId
|
|
23
|
+
}),
|
|
24
|
+
allowFrom: resolveSlackAccountAllowFrom({
|
|
25
|
+
cfg,
|
|
26
|
+
accountId: account.accountId
|
|
27
|
+
})
|
|
28
|
+
}),
|
|
29
|
+
policyPathSuffix: "dmPolicy",
|
|
30
|
+
normalizeEntry: (raw) => raw.trim().replace(/^(slack|user):/i, "").trim()
|
|
31
|
+
});
|
|
32
|
+
const collectSlackSecurityWarnings = createOpenProviderConfiguredRouteWarningCollector({
|
|
33
|
+
providerConfigPresent: (cfg) => cfg.channels?.slack !== void 0,
|
|
34
|
+
resolveGroupPolicy: (account) => account.config.groupPolicy,
|
|
35
|
+
resolveRouteAllowlistConfigured: (account) => Boolean(account.config.channels) && Object.keys(account.config.channels ?? {}).length > 0,
|
|
36
|
+
configureRouteAllowlist: {
|
|
37
|
+
surface: "Slack channels",
|
|
38
|
+
openScope: "any channel not explicitly denied",
|
|
39
|
+
groupPolicyPath: "channels.slack.groupPolicy",
|
|
40
|
+
routeAllowlistPath: "channels.slack.channels"
|
|
41
|
+
},
|
|
42
|
+
missingRouteAllowlist: {
|
|
43
|
+
surface: "Slack channels",
|
|
44
|
+
openBehavior: "with no channel allowlist; any channel can trigger (mention-gated)",
|
|
45
|
+
remediation: "Set channels.slack.groupPolicy=\"allowlist\" and configure channels.slack.channels"
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
const loadSlackSecurityAuditModule = createLazyRuntimeModule(() => import("./security-audit-BtHGnD3d.js").then((n) => n.n));
|
|
49
|
+
const slackSecurityAdapter = {
|
|
50
|
+
resolveDmPolicy: resolveSlackDmPolicy,
|
|
51
|
+
collectWarnings: collectSlackSecurityWarnings,
|
|
52
|
+
collectAuditFindings: async (params) => {
|
|
53
|
+
const { collectSlackSecurityAuditFindings } = await loadSlackSecurityAuditModule();
|
|
54
|
+
return await collectSlackSecurityAuditFindings(params);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region extensions/slack/src/security-doctor.ts
|
|
59
|
+
function isSlackMutableAllowEntry(raw) {
|
|
60
|
+
const text = raw.trim();
|
|
61
|
+
if (!text || text === "*") return false;
|
|
62
|
+
const mentionMatch = text.match(/^<@([A-Z0-9]+)>$/i);
|
|
63
|
+
if (mentionMatch && /^[A-Z0-9]{8,}$/i.test(mentionMatch[1] ?? "")) return false;
|
|
64
|
+
const withoutPrefix = text.replace(/^(slack|user):/i, "").trim();
|
|
65
|
+
if (/^[UWBCGDT][A-Z0-9]{2,}$/.test(withoutPrefix)) return false;
|
|
66
|
+
if (/^[A-Z0-9]{8,}$/i.test(withoutPrefix)) return false;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region extensions/slack/src/doctor.ts
|
|
71
|
+
function asObjectRecord(value) {
|
|
72
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
73
|
+
}
|
|
74
|
+
const slackDoctor = {
|
|
75
|
+
dmAllowFromMode: "topOnly",
|
|
76
|
+
groupModel: "route",
|
|
77
|
+
groupAllowFromFallbackToAllowFrom: false,
|
|
78
|
+
warnOnEmptyGroupSenderAllowlist: false,
|
|
79
|
+
legacyConfigRules,
|
|
80
|
+
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
|
+
})
|
|
106
|
+
};
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region extensions/slack/src/shared.ts
|
|
109
|
+
function isSlackPluginAccountConfigured(account) {
|
|
110
|
+
const mode = account.config.mode ?? "socket";
|
|
111
|
+
if (!Boolean(account.botToken?.trim())) return false;
|
|
112
|
+
if (mode === "http") return Boolean(account.config.signingSecret?.trim());
|
|
113
|
+
return Boolean(account.appToken?.trim());
|
|
114
|
+
}
|
|
115
|
+
const slackConfigAdapter = createScopedChannelConfigAdapter({
|
|
116
|
+
sectionKey: SLACK_CHANNEL,
|
|
117
|
+
listAccountIds: listSlackAccountIds,
|
|
118
|
+
resolveAccount: adaptScopedAccountAccessor(resolveSlackAccount),
|
|
119
|
+
resolveAccessorAccount: resolveSlackConfigAccessorAccount,
|
|
120
|
+
inspectAccount: adaptScopedAccountAccessor(inspectSlackAccount),
|
|
121
|
+
defaultAccountId: resolveDefaultSlackAccountId,
|
|
122
|
+
clearBaseFields: [
|
|
123
|
+
"botToken",
|
|
124
|
+
"appToken",
|
|
125
|
+
"name"
|
|
126
|
+
],
|
|
127
|
+
resolveAllowFrom: (account) => account.allowFrom,
|
|
128
|
+
formatAllowFrom: (allowFrom) => formatAllowFromLowercase({ allowFrom }),
|
|
129
|
+
resolveDefaultTo: (account) => account.defaultTo
|
|
130
|
+
});
|
|
131
|
+
function createSlackPluginBase(params) {
|
|
132
|
+
return {
|
|
133
|
+
id: SLACK_CHANNEL,
|
|
134
|
+
meta: {
|
|
135
|
+
...getChatChannelMeta(SLACK_CHANNEL),
|
|
136
|
+
preferSessionLookupForAnnounceTarget: true
|
|
137
|
+
},
|
|
138
|
+
setupWizard: params.setupWizard,
|
|
139
|
+
capabilities: {
|
|
140
|
+
chatTypes: [
|
|
141
|
+
"direct",
|
|
142
|
+
"channel",
|
|
143
|
+
"thread"
|
|
144
|
+
],
|
|
145
|
+
reactions: true,
|
|
146
|
+
threads: true,
|
|
147
|
+
media: true,
|
|
148
|
+
nativeCommands: true
|
|
149
|
+
},
|
|
150
|
+
commands: {
|
|
151
|
+
nativeCommandsAutoEnabled: false,
|
|
152
|
+
nativeSkillsAutoEnabled: false,
|
|
153
|
+
resolveNativeCommandName: ({ commandKey, defaultName }) => commandKey === "status" ? "agentstatus" : defaultName
|
|
154
|
+
},
|
|
155
|
+
doctor: slackDoctor,
|
|
156
|
+
agentPrompt: {
|
|
157
|
+
inboundFormattingHints: () => ({
|
|
158
|
+
text_markup: "slack_mrkdwn",
|
|
159
|
+
rules: [
|
|
160
|
+
"Use Slack mrkdwn, not standard Markdown.",
|
|
161
|
+
"Bold uses *single asterisks*.",
|
|
162
|
+
"Links use <url|label>.",
|
|
163
|
+
"Code blocks use triple backticks without a language identifier.",
|
|
164
|
+
"Do not use markdown headings or pipe tables."
|
|
165
|
+
]
|
|
166
|
+
}),
|
|
167
|
+
messageToolHints: ({ cfg, accountId }) => (isSlackInteractiveRepliesEnabled({
|
|
168
|
+
cfg,
|
|
169
|
+
accountId
|
|
170
|
+
}) ? [
|
|
171
|
+
"- Prefer Slack buttons/selects for 2-5 discrete choices or parameter picks instead of asking the user to type one.",
|
|
172
|
+
"- Slack interactive replies: use `[[slack_buttons: Label:value, Other:other]]` to add action buttons that route clicks back as Slack interaction system events.",
|
|
173
|
+
"- Slack selects: use `[[slack_select: Placeholder | Label:value, Other:other]]` to add a static select menu that routes the chosen value back as a Slack interaction system event."
|
|
174
|
+
] : ["- Slack interactive replies are disabled. If needed, ask to set `channels.slack.capabilities.interactiveReplies=true` (or the same under `channels.slack.accounts.<account>.capabilities`)."]).concat(["- Slack plain text sends: write standard Markdown; OpenClaw converts it to Slack mrkdwn, including `**bold**`, headings, lists, and `[label](url)` links.", "- Slack Block Kit or presentation text fields are sent as Slack mrkdwn directly; use `*bold*`, `_italic_`, `~strike~`, `<url|label>` links, and avoid Markdown headings or pipe tables there."])
|
|
175
|
+
},
|
|
176
|
+
streaming: { blockStreamingCoalesceDefaults: {
|
|
177
|
+
minChars: 1500,
|
|
178
|
+
idleMs: 1e3
|
|
179
|
+
} },
|
|
180
|
+
reload: { configPrefixes: ["channels.slack"] },
|
|
181
|
+
security: slackSecurityAdapter,
|
|
182
|
+
configSchema: SlackChannelConfigSchema,
|
|
183
|
+
config: {
|
|
184
|
+
...slackConfigAdapter,
|
|
185
|
+
hasConfiguredState: ({ env }) => [
|
|
186
|
+
"SLACK_APP_TOKEN",
|
|
187
|
+
"SLACK_BOT_TOKEN",
|
|
188
|
+
"SLACK_USER_TOKEN"
|
|
189
|
+
].some((key) => typeof env?.[key] === "string" && env[key]?.trim().length > 0),
|
|
190
|
+
isConfigured: (account) => isSlackPluginAccountConfigured(account),
|
|
191
|
+
describeAccount: (account) => describeAccountSnapshot({
|
|
192
|
+
account,
|
|
193
|
+
configured: isSlackPluginAccountConfigured(account),
|
|
194
|
+
extra: {
|
|
195
|
+
botTokenSource: account.botTokenSource,
|
|
196
|
+
appTokenSource: account.appTokenSource
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
},
|
|
200
|
+
secrets: {
|
|
201
|
+
secretTargetRegistryEntries,
|
|
202
|
+
collectRuntimeConfigAssignments
|
|
203
|
+
},
|
|
204
|
+
setup: params.setup
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
//#endregion
|
|
208
|
+
export { slackSecurityAdapter as i, isSlackPluginAccountConfigured as n, slackConfigAdapter as r, createSlackPluginBase as t };
|