@openclaw/slack 2026.9.1-beta.1 → 2026.9.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/{account-inspect-CN2hBsim.js → account-inspect-C3sePHWa.js} +3 -3
- package/dist/account-inspect-api.js +1 -1
- package/dist/{action-runtime-BoSeX09X.js → action-runtime-DftQWpI4.js} +84 -66
- package/dist/action-runtime.runtime-rHI3UAh4.js +2 -0
- package/dist/{actions-XdQyjG_p.js → actions-CFkvEv1d.js} +198 -54
- package/dist/api.js +14 -14
- package/dist/{approval-handler.runtime-DxXfGX_Z.js → approval-handler.runtime-BeILKdw4.js} +47 -25
- package/dist/{channel-C_vnTgxo.js → channel-CmOuxbOD.js} +76 -133
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel-type-j-2lalcC.js → channel-type-BWt2CRaI.js} +2 -2
- package/dist/{channel.setup-aal8tokM.js → channel.setup-D5z1ReaT.js} +29 -23
- package/dist/{config-schema-CN3XZRX5.js → config-schema-JDx_f9Zb.js} +26 -17
- package/dist/{cursor-pages-eX7p8Wwt.js → cursor-pages-ByFwo-Py.js} +7 -1
- package/dist/{directory-config-DqfQ42OV.js → directory-config-Bmw4ykSy.js} +4 -3
- package/dist/directory-contract-api.js +1 -1
- package/dist/{directory-live-lC6X_Ru-.js → directory-live-BuBPHmju.js} +3 -8
- package/dist/{doctor-contract-BAy7-EUQ.js → doctor-contract-BNjw9bxb.js} +5 -4
- package/dist/doctor-contract-api.js +1 -1
- package/dist/{message-tool-api-CWosHSi5.js → message-tool-api-uNEfQYGY.js} +19 -2
- package/dist/message-tool-api.js +1 -1
- package/dist/{monitor-DrNCGneD.js → monitor-DI07pfJl.js} +3 -3
- package/dist/{outbound-adapter-DkT2vUE9.js → outbound-adapter-B5QyTRpJ.js} +96 -91
- package/dist/{pipeline.runtime-CRRng_Nw.js → pipeline.runtime-BdIwZgm4.js} +596 -920
- package/dist/{policy-BoP6t88v.js → policy-Bh5TOqII.js} +7 -4
- package/dist/{probe-4_aHtVT3.js → probe-Biy--yhL.js} +251 -12
- package/dist/{provider-D4nDgxCh.js → provider-DoaUvAM4.js} +5621 -4944
- package/dist/{relay-source-CEmze11m.js → relay-source-D4uecp68.js} +6 -4
- package/dist/{replies-D9XMM_S0.js → replies-DENuTcPO.js} +21 -16
- package/dist/reply-blocks-PRkhyAN7.js +790 -0
- package/dist/{resolve-channels-DSamDgVs.js → resolve-channels-DQGV2VrE.js} +6 -10
- package/dist/{resolve-users-CIzwbaJR.js → resolve-users-DRGt_Q4I.js} +5 -4
- package/dist/runtime-api.js +10 -10
- package/dist/{scopes-BBrUzpBH.js → scopes-CZzeGdWV.js} +1 -1
- package/dist/{send-DtjEnlLh.js → send-DElUcgIs.js} +32 -24
- package/dist/send-results-eeOu_HYm.js +23 -0
- package/dist/{send.runtime-DQeNKa2J.js → send.runtime-C8D6cKGm.js} +1 -1
- package/dist/{group-policy-CU1tEGpS.js → session-status-BynwcZSO.js} +284 -1013
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{setup-surface-CsBCM4rP.js → setup-surface-B1_gpGBl.js} +5 -5
- package/dist/{shared-D9GeeR9K.js → shared-CNQsWJT7.js} +7 -5
- package/dist/{slash-dispatch.runtime-Chr3YHnT.js → slash-dispatch.runtime-DhsXavSY.js} +2 -2
- package/dist/{target-parsing-BnMD2ZqZ.js → target-parsing-PbxGDbr9.js} +11 -6
- package/dist/{targets-Cx5W_n3W.js → targets-7FexXJ62.js} +1 -1
- package/dist/{thread-ts-DUGhaYKq.js → thread-ts-CukseRVL.js} +5 -1
- package/node_modules/@slack/bolt/node_modules/negotiator/lib/accept.js +54 -0
- package/node_modules/@slack/bolt/node_modules/negotiator/lib/charset.js +10 -34
- package/node_modules/@slack/bolt/node_modules/negotiator/lib/encoding.js +9 -30
- package/node_modules/@slack/bolt/node_modules/negotiator/lib/language.js +15 -36
- package/node_modules/@slack/bolt/node_modules/negotiator/lib/mediaType.js +16 -134
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/LICENSE +22 -0
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/README.md +71 -0
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/dist/index.d.ts +46 -0
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/dist/index.js +176 -0
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/dist/index.js.map +1 -0
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/package.json +52 -0
- package/node_modules/@slack/bolt/node_modules/negotiator/package.json +16 -10
- package/node_modules/typebox/build/format/email.mjs +1 -1
- package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
- package/node_modules/typebox/build/format/json_pointer.d.mts +2 -2
- package/node_modules/typebox/build/format/json_pointer.mjs +2 -2
- package/node_modules/typebox/build/format/json_pointer_uri_fragment.d.mts +2 -2
- package/node_modules/typebox/build/format/json_pointer_uri_fragment.mjs +2 -2
- package/node_modules/typebox/build/format/relative_json_pointer.d.mts +2 -2
- package/node_modules/typebox/build/format/relative_json_pointer.mjs +2 -2
- package/node_modules/typebox/build/system/memory/assign.mjs +2 -1
- package/node_modules/typebox/build/system/memory/create.mjs +3 -4
- package/node_modules/typebox/build/system/memory/discard.mjs +2 -2
- package/node_modules/typebox/build/system/memory/freeze.d.mts +4 -0
- package/node_modules/typebox/build/system/memory/freeze.mjs +6 -0
- package/node_modules/typebox/build/system/memory/update.mjs +3 -2
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/undici/lib/dispatcher/balanced-pool.js +4 -2
- package/node_modules/undici/lib/dispatcher/client-h1.js +12 -4
- package/node_modules/undici/lib/dispatcher/client-h2.js +70 -14
- package/node_modules/undici/lib/dispatcher/client.js +6 -2
- package/node_modules/undici/lib/handler/cache-handler.js +21 -5
- package/node_modules/undici/lib/handler/retry-handler.js +42 -7
- package/node_modules/undici/lib/interceptor/cache.js +20 -1
- package/node_modules/undici/lib/interceptor/decompress.js +146 -14
- package/node_modules/undici/lib/interceptor/dump.js +10 -23
- package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +245 -150
- package/node_modules/undici/lib/web/websocket/connection.js +1 -1
- package/node_modules/undici/lib/web/websocket/permessage-deflate.js +5 -0
- package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +8 -10
- package/node_modules/undici/package.json +1 -1
- package/node_modules/undici/types/interceptors.d.ts +2 -0
- package/openclaw.plugin.json +17 -9
- package/package.json +6 -6
- package/skills/_vendor/slack-skills-plugin.LICENSE +21 -0
- package/skills/_vendor/slack-skills-plugin.json +31 -0
- package/skills/block-kit/SKILL.md +37 -0
- package/skills/block-kit/references/official-block-kit.md +257 -0
- package/skills/block-kit/references/official-common-patterns.md +385 -0
- package/skills/slack/SKILL.md +1 -1
- package/dist/action-runtime.runtime-Dpa16J87.js +0 -2
- package/dist/conversation-binding-route-DFVLUdYF.js +0 -115
- package/dist/slack-client-kind-hSfjXyUM.js +0 -94
- package/node_modules/@slack/bolt/node_modules/negotiator/HISTORY.md +0 -114
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
import { n as isSlackPluginAccountConfigured } from "./account-configured-sUohAxZr.js";
|
|
2
2
|
import { a as resolveSlackAccount, i as resolveDefaultSlackAccountId, n as listSlackAccountIds, o as resolveSlackAccountAllowFrom, r as mergeSlackAccountConfig } from "./accounts-Dm_H77gH.js";
|
|
3
|
-
import { a as parseSlackTarget, n as formatSlackTarget, t as canonicalizeSlackApiTargetId } from "./target-parsing-
|
|
4
|
-
import { t as slackContextTargetsMatch } from "./targets-
|
|
3
|
+
import { a as parseSlackTarget, n as formatSlackTarget, t as canonicalizeSlackApiTargetId } from "./target-parsing-PbxGDbr9.js";
|
|
4
|
+
import { t as slackContextTargetsMatch } from "./targets-7FexXJ62.js";
|
|
5
|
+
import { C as normalizeAllowListLower, D as resolveSlackUserAllowListForTeam, E as resolveSlackAllowListMatch, o as formatSlackError } from "./probe-Biy--yhL.js";
|
|
5
6
|
import { normalizeAccountId } from "openclaw/plugin-sdk/account-resolution";
|
|
6
|
-
import { asOptionalRecord, normalizeLowercaseStringOrEmpty,
|
|
7
|
+
import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringifiedOptionalString, readNonBlankString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
7
8
|
import { normalizeMessageChannel } from "openclaw/plugin-sdk/routing";
|
|
9
|
+
import { defaultRuntime, logVerbose, warn } from "openclaw/plugin-sdk/runtime-env";
|
|
8
10
|
import { FormatCapabilityProfile, chunkTextForOutbound, markdownToIR, renderMarkdownIRChunksWithinLimit, renderMarkdownWithMarkers } from "openclaw/plugin-sdk/text-chunking";
|
|
9
11
|
import { isSingleUseReplyToMode } from "openclaw/plugin-sdk/reply-reference";
|
|
10
12
|
import { buildChannelKeyCandidates } from "openclaw/plugin-sdk/channel-targets";
|
|
11
13
|
import { createNativeApprovalChannelRouteGates, doesApprovalRequestSelectChannelAccount, resolveApprovalKind, resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
|
|
12
14
|
import { createChannelApprovalAuth, resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
|
|
13
15
|
import { createChannelExecApprovalProfile, isChannelExecApprovalClientEnabledFromConfig, isChannelExecApprovalTargetRecipient, matchesApprovalRequestFilters } from "openclaw/plugin-sdk/approval-client-runtime";
|
|
16
|
+
import { normalizeHyphenSlug } from "openclaw/plugin-sdk/string-normalization-runtime";
|
|
14
17
|
import { channelRouteTargetsMatchExact, stringifyRouteThreadId } from "openclaw/plugin-sdk/channel-route";
|
|
15
18
|
import { resolveGlobalMap } from "openclaw/plugin-sdk/global-singleton";
|
|
16
|
-
import {
|
|
17
|
-
import { parseExecApprovalCommandText } from "openclaw/plugin-sdk/approval-reply-runtime";
|
|
18
|
-
import { buildApprovalResolutionRef } from "openclaw/plugin-sdk/approval-reference-runtime";
|
|
19
|
+
import { normalizeMessagePresentation, renderMessagePresentationChartFallbackText, renderMessagePresentationFallbackText, renderMessagePresentationTableFallbackText } from "openclaw/plugin-sdk/interactive-runtime";
|
|
19
20
|
import { eastAsianWidthType } from "get-east-asian-width";
|
|
20
|
-
import {
|
|
21
|
-
import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
21
|
+
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
22
22
|
import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
|
23
|
-
import {
|
|
24
|
-
import { normalizeHyphenSlug, normalizeStringEntries as normalizeStringEntries$1, normalizeStringEntriesLower } from "openclaw/plugin-sdk/string-normalization-runtime";
|
|
23
|
+
import { resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
|
|
25
24
|
import { resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
|
|
25
|
+
//#region extensions/slack/src/account-reply-mode.ts
|
|
26
|
+
function normalizeSlackChatType(raw) {
|
|
27
|
+
const value = raw?.trim().toLowerCase();
|
|
28
|
+
if (!value) return;
|
|
29
|
+
if (value === "direct" || value === "dm") return "direct";
|
|
30
|
+
if (value === "group" || value === "channel") return value;
|
|
31
|
+
}
|
|
32
|
+
function resolveSlackReplyToMode(account, chatType) {
|
|
33
|
+
const normalized = normalizeSlackChatType(chatType ?? void 0);
|
|
34
|
+
if (normalized && account.replyToModeByChatType?.[normalized] !== void 0) return account.replyToModeByChatType[normalized] ?? "off";
|
|
35
|
+
return account.replyToMode ?? "off";
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
26
38
|
//#region extensions/slack/src/action-threading.ts
|
|
27
39
|
const SLACK_PRIVATE_ACTION_DELIVERY_RESULT = Symbol("slack.action.delivery-result");
|
|
28
40
|
function resolveSlackAutoThreadId(params) {
|
|
@@ -43,14 +55,24 @@ function normalizeSlackUserLikeId(value) {
|
|
|
43
55
|
const upper = value.toUpperCase();
|
|
44
56
|
return /^[UW][A-Z0-9]+$/.test(upper) ? upper : void 0;
|
|
45
57
|
}
|
|
46
|
-
function
|
|
58
|
+
function normalizeSlackApproverTarget(value) {
|
|
47
59
|
const trimmed = normalizeStringifiedOptionalString(value);
|
|
48
60
|
if (!trimmed) return;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
try {
|
|
62
|
+
const target = parseSlackTarget(trimmed, { defaultKind: "user" });
|
|
63
|
+
const id = target?.kind === "user" ? normalizeSlackUserLikeId(target.id) : void 0;
|
|
64
|
+
return target?.teamId && id ? formatSlackTarget({
|
|
65
|
+
kind: "user",
|
|
66
|
+
id,
|
|
67
|
+
teamId: target.teamId.toUpperCase()
|
|
68
|
+
}) : id;
|
|
69
|
+
} catch {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function normalizeSlackApproverId(value) {
|
|
74
|
+
const target = normalizeSlackApproverTarget(value);
|
|
75
|
+
return target?.startsWith("team:") ? void 0 : target;
|
|
54
76
|
}
|
|
55
77
|
function resolveSlackOwnerApprovers(cfg) {
|
|
56
78
|
const ownerAllowFrom = cfg.commands?.ownerAllowFrom;
|
|
@@ -87,27 +109,40 @@ const resolveSlackExecApprovalTarget = slackExecApprovalProfile.resolveTarget;
|
|
|
87
109
|
const shouldSuppressLocalSlackExecApprovalPrompt = slackExecApprovalProfile.shouldSuppressLocalPrompt;
|
|
88
110
|
//#endregion
|
|
89
111
|
//#region extensions/slack/src/approval-auth.ts
|
|
112
|
+
function resolveSlackApprovalInputs(params) {
|
|
113
|
+
const account = resolveSlackAccount(params).config;
|
|
114
|
+
return {
|
|
115
|
+
allowFrom: resolveSlackAccountAllowFrom(params),
|
|
116
|
+
defaultTo: account.defaultTo
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function slackApprovalTargetMatches(senderId, approvers) {
|
|
120
|
+
const sender = parseSlackTarget(senderId, { defaultKind: "user" });
|
|
121
|
+
return sender?.kind === "user" && resolveSlackAllowListMatch({
|
|
122
|
+
allowList: normalizeAllowListLower([...approvers]),
|
|
123
|
+
teamId: sender.teamId,
|
|
124
|
+
id: sender.id
|
|
125
|
+
}).allowed;
|
|
126
|
+
}
|
|
90
127
|
const slackApproval = createChannelApprovalAuth({
|
|
91
128
|
channelLabel: "Slack",
|
|
92
|
-
resolveInputs:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
allowFrom: resolveSlackAccountAllowFrom({
|
|
99
|
-
cfg,
|
|
100
|
-
accountId
|
|
101
|
-
}),
|
|
102
|
-
defaultTo: account.defaultTo
|
|
103
|
-
};
|
|
104
|
-
},
|
|
105
|
-
normalizeApprover: normalizeSlackApproverId,
|
|
106
|
-
normalizeDefaultTo: normalizeSlackApproverId,
|
|
107
|
-
isWildcardAuthorized: ({ purpose, senderId, inputs, approvers }) => purpose === "sender" && Boolean(senderId) && approvers.length === 0 && inputs.allowFrom?.some((entry) => String(entry).trim() === "*") === true
|
|
129
|
+
resolveInputs: resolveSlackApprovalInputs,
|
|
130
|
+
normalizeApprover: normalizeSlackApproverTarget,
|
|
131
|
+
normalizeDefaultTo: normalizeSlackApproverTarget,
|
|
132
|
+
normalizeSenderId: normalizeSlackApproverTarget,
|
|
133
|
+
isWildcardAuthorized: ({ purpose, senderId, inputs, approvers }) => Boolean(senderId && (slackApprovalTargetMatches(senderId, approvers) || purpose === "sender" && approvers.length === 0 && inputs.allowFrom?.some((entry) => String(entry).trim() === "*")))
|
|
108
134
|
});
|
|
109
135
|
const getSlackApprovalApprovers = slackApproval.resolveApprovers;
|
|
110
136
|
const isSlackApprovalAuthorizedSender = slackApproval.isAuthorizedSender;
|
|
137
|
+
function getSlackApprovalApproversForTeam(params) {
|
|
138
|
+
return resolveApprovalApprovers({
|
|
139
|
+
allowFrom: resolveSlackUserAllowListForTeam({
|
|
140
|
+
allowList: getSlackApprovalApprovers(params),
|
|
141
|
+
teamId: params.teamId
|
|
142
|
+
}),
|
|
143
|
+
normalizeApprover: normalizeSlackApproverTarget
|
|
144
|
+
});
|
|
145
|
+
}
|
|
111
146
|
//#endregion
|
|
112
147
|
//#region extensions/slack/src/installation-identity-state.ts
|
|
113
148
|
const slackInstallationStates = resolveGlobalMap(Symbol.for("openclaw.slack.installation-identities"), "close-and-restart");
|
|
@@ -142,7 +177,8 @@ const DEFAULT_APPROVAL_FORWARDING_MODE = "session";
|
|
|
142
177
|
const SLACK_DM_CHANNEL_ID_RE = /^D[A-Z0-9]{8,}$/i;
|
|
143
178
|
const SLACK_USER_ID_RE = /^[UW][A-Z0-9]{8,}$/i;
|
|
144
179
|
function isSlackApprovalTransportEnabled(params) {
|
|
145
|
-
|
|
180
|
+
const account = resolveSlackAccount(params);
|
|
181
|
+
return isSlackPluginAccountConfigured(account);
|
|
146
182
|
}
|
|
147
183
|
function resolveSlackNativeApprovalConfig(params) {
|
|
148
184
|
return resolveSlackAccount(params).config.execApprovals;
|
|
@@ -158,7 +194,8 @@ function normalizeComparableTarget(value) {
|
|
|
158
194
|
}
|
|
159
195
|
function extractSlackSessionKind(sessionKey) {
|
|
160
196
|
if (!sessionKey) return null;
|
|
161
|
-
const
|
|
197
|
+
const match = sessionKey.match(/slack:(direct|channel|group):/i);
|
|
198
|
+
const kind = normalizeLowercaseStringOrEmpty(match?.[1]);
|
|
162
199
|
return kind ? kind : null;
|
|
163
200
|
}
|
|
164
201
|
function resolveSlackTurnSourceDefaultKind(params) {
|
|
@@ -169,9 +206,10 @@ function resolveTurnSourceSlackOriginTarget(request) {
|
|
|
169
206
|
const turnSourceChannel = normalizeLowercaseStringOrEmpty(request.request.turnSourceChannel);
|
|
170
207
|
const turnSourceTo = normalizeOptionalString(request.request.turnSourceTo) ?? "";
|
|
171
208
|
if (turnSourceChannel !== "slack" || !turnSourceTo) return null;
|
|
209
|
+
const sessionKind = extractSlackSessionKind(request.request.sessionKey ?? void 0);
|
|
172
210
|
const parsed = parseSlackTarget(turnSourceTo, { defaultKind: resolveSlackTurnSourceDefaultKind({
|
|
173
211
|
turnSourceTo,
|
|
174
|
-
sessionKind
|
|
212
|
+
sessionKind
|
|
175
213
|
}) });
|
|
176
214
|
if (!parsed) return null;
|
|
177
215
|
return {
|
|
@@ -263,11 +301,12 @@ const { canApprovalPotentiallyRouteToChannel: canApprovalPotentiallyRouteToSlack
|
|
|
263
301
|
targetsMatch: slackTargetsMatch
|
|
264
302
|
});
|
|
265
303
|
function hasSlackPluginApprovers(params) {
|
|
266
|
-
return
|
|
304
|
+
return getSlackApprovalApproversForTeam(params).length > 0;
|
|
267
305
|
}
|
|
268
306
|
function isSlackPluginNativeApprovalClientConfigEnabled(params) {
|
|
307
|
+
const slackNativeConfig = resolveSlackNativeApprovalConfig(params);
|
|
269
308
|
return isChannelExecApprovalClientEnabledFromConfig({
|
|
270
|
-
enabled:
|
|
309
|
+
enabled: slackNativeConfig?.enabled,
|
|
271
310
|
approverCount: getSlackApprovalApprovers(params).length
|
|
272
311
|
});
|
|
273
312
|
}
|
|
@@ -304,7 +343,10 @@ function matchesSlackNativeApprovalFilters(params) {
|
|
|
304
343
|
}
|
|
305
344
|
function isSlackNativeApprovalAccountEligible(params) {
|
|
306
345
|
const config = resolveSlackNativeApprovalConfig(params);
|
|
307
|
-
const approverCount = params.approvalKind === "exec" ? getSlackExecApprovalApprovers(params).length :
|
|
346
|
+
const approverCount = params.approvalKind === "exec" ? getSlackExecApprovalApprovers(params).length : getSlackApprovalApproversForTeam({
|
|
347
|
+
...params,
|
|
348
|
+
teamId: resolveEnterpriseApprovalTeamId(params.request)
|
|
349
|
+
}).length;
|
|
308
350
|
return isSlackApprovalTransportEnabled(params) && isChannelExecApprovalClientEnabledFromConfig({
|
|
309
351
|
enabled: config?.enabled,
|
|
310
352
|
approverCount
|
|
@@ -408,75 +450,10 @@ function parseSlackBlocksInput(raw) {
|
|
|
408
450
|
if (raw == null) return;
|
|
409
451
|
return validateSlackBlocksArray(typeof raw === "string" ? parseBlocksJson(raw) : raw);
|
|
410
452
|
}
|
|
411
|
-
const SLACK_BUTTON_VALUE_MAX = 2e3;
|
|
412
|
-
const SLACK_SECTION_TEXT_MAX = 3e3;
|
|
413
|
-
const SLACK_PRESENTATION_CAPABILITIES = {
|
|
414
|
-
supported: true,
|
|
415
|
-
buttons: true,
|
|
416
|
-
selects: true,
|
|
417
|
-
context: true,
|
|
418
|
-
divider: true,
|
|
419
|
-
charts: true,
|
|
420
|
-
tables: true,
|
|
421
|
-
limits: {
|
|
422
|
-
actions: {
|
|
423
|
-
maxActionsPerRow: 25,
|
|
424
|
-
maxValueBytes: SLACK_BUTTON_VALUE_MAX,
|
|
425
|
-
supportsStyles: true
|
|
426
|
-
},
|
|
427
|
-
selects: {
|
|
428
|
-
maxOptions: 100,
|
|
429
|
-
maxValueBytes: 150
|
|
430
|
-
},
|
|
431
|
-
text: {
|
|
432
|
-
encoding: "characters",
|
|
433
|
-
markdownDialect: "slack-mrkdwn",
|
|
434
|
-
supportsEdit: true
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
};
|
|
438
|
-
//#endregion
|
|
439
|
-
//#region extensions/slack/src/approval-actions.ts
|
|
440
|
-
const SLACK_APPROVAL_VALUE_PREFIX = "openclaw:approval:v1:";
|
|
441
|
-
const SLACK_APPROVAL_HEADER_BLOCK_ID = "openclaw_approval_header";
|
|
442
|
-
function isApprovalDecision(value) {
|
|
443
|
-
return value === "allow-once" || value === "allow-always" || value === "deny";
|
|
444
|
-
}
|
|
445
|
-
/** Encode portable approval facts without exposing a slash command to Slack callbacks. */
|
|
446
|
-
function encodeSlackApprovalAction(action) {
|
|
447
|
-
const encode = (approvalId) => `${SLACK_APPROVAL_VALUE_PREFIX}${JSON.stringify({
|
|
448
|
-
approvalId,
|
|
449
|
-
approvalKind: action.approvalKind,
|
|
450
|
-
decision: action.decision
|
|
451
|
-
})}`;
|
|
452
|
-
const exact = encode(action.approvalId);
|
|
453
|
-
return exact.length <= 2e3 ? exact : encode(buildApprovalResolutionRef({
|
|
454
|
-
approvalId: action.approvalId,
|
|
455
|
-
approvalKind: action.approvalKind
|
|
456
|
-
}));
|
|
457
|
-
}
|
|
458
|
-
/** Decode only the exact Slack-owned approval envelope. Malformed callbacks fail closed. */
|
|
459
|
-
function decodeSlackApprovalAction(value) {
|
|
460
|
-
if (typeof value !== "string" || !value.startsWith(SLACK_APPROVAL_VALUE_PREFIX)) return null;
|
|
461
|
-
try {
|
|
462
|
-
const decoded = JSON.parse(value.slice(21));
|
|
463
|
-
if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) return null;
|
|
464
|
-
const record = decoded;
|
|
465
|
-
if (Object.keys(record).length !== 3 || typeof record.approvalId !== "string" || record.approvalId.length === 0 || record.approvalKind !== "exec" && record.approvalKind !== "plugin" || !isApprovalDecision(record.decision)) return null;
|
|
466
|
-
return {
|
|
467
|
-
type: "approval",
|
|
468
|
-
approvalId: record.approvalId,
|
|
469
|
-
approvalKind: record.approvalKind,
|
|
470
|
-
decision: record.decision
|
|
471
|
-
};
|
|
472
|
-
} catch {
|
|
473
|
-
return null;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
453
|
//#endregion
|
|
477
454
|
//#region extensions/slack/src/monitor/mrkdwn.ts
|
|
478
455
|
function escapeSlackMrkdwn(value) {
|
|
479
|
-
return value.replaceAll("
|
|
456
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
480
457
|
}
|
|
481
458
|
//#endregion
|
|
482
459
|
//#region extensions/slack/src/presentation-fallback.ts
|
|
@@ -568,58 +545,56 @@ function renderSlackMessagePresentationFallbackText(params) {
|
|
|
568
545
|
});
|
|
569
546
|
}
|
|
570
547
|
//#endregion
|
|
548
|
+
//#region extensions/slack/src/rich-text.ts
|
|
549
|
+
const RICH_TEXT_CONTAINER_TYPES = /* @__PURE__ */ new Set([
|
|
550
|
+
"rich_text_section",
|
|
551
|
+
"rich_text_preformatted",
|
|
552
|
+
"rich_text_quote",
|
|
553
|
+
"rich_text_list"
|
|
554
|
+
]);
|
|
555
|
+
function renderSlackRichText(value, mode, separator = "") {
|
|
556
|
+
if (!Array.isArray(value)) return "";
|
|
557
|
+
const table = mode === "table";
|
|
558
|
+
const read = table ? readNonBlankString : normalizeOptionalString;
|
|
559
|
+
const literal = table ? (text) => text : escapeSlackMrkdwn;
|
|
560
|
+
const reference = mode === "escaped" ? escapeSlackMrkdwn : (text) => text;
|
|
561
|
+
const formatReference = (raw, prefix, suffix) => {
|
|
562
|
+
const text = read(raw);
|
|
563
|
+
return text ? reference(`${prefix}${text}${suffix}`) : "";
|
|
564
|
+
};
|
|
565
|
+
return value.map((rawElement) => {
|
|
566
|
+
const element = asOptionalRecord(rawElement);
|
|
567
|
+
if (!element) return "";
|
|
568
|
+
if (Array.isArray(element.elements) && (table || RICH_TEXT_CONTAINER_TYPES.has(element.type))) return renderSlackRichText(element.elements, mode, element.type === "rich_text_list" ? "\n" : "");
|
|
569
|
+
if (table) {
|
|
570
|
+
if (element.type === "text" && typeof element.text === "string") return element.text;
|
|
571
|
+
const text = readNonBlankString(element.text);
|
|
572
|
+
if (text) return text;
|
|
573
|
+
}
|
|
574
|
+
switch (element.type) {
|
|
575
|
+
case "text": return typeof element.text === "string" ? literal(element.text) : "";
|
|
576
|
+
case "link": return literal(read(element.text) ?? read(element.url) ?? "");
|
|
577
|
+
case "user": return formatReference(element.user_id, "<@", ">");
|
|
578
|
+
case "channel": return formatReference(element.channel_id, "<#", ">");
|
|
579
|
+
case "usergroup": return formatReference(element.usergroup_id, "<!subteam^", ">");
|
|
580
|
+
case "broadcast": return formatReference(element.range, "<!", ">");
|
|
581
|
+
case "emoji": {
|
|
582
|
+
const name = read(element.name);
|
|
583
|
+
return name ? `:${name}:` : "";
|
|
584
|
+
}
|
|
585
|
+
case "date": return literal(read(element.fallback) ?? "");
|
|
586
|
+
default: return "";
|
|
587
|
+
}
|
|
588
|
+
}).filter(Boolean).join(separator);
|
|
589
|
+
}
|
|
590
|
+
//#endregion
|
|
571
591
|
//#region extensions/slack/src/data-table.ts
|
|
572
592
|
const SLACK_DATA_TABLE_COLUMNS_MAX = 20;
|
|
573
593
|
const SLACK_DATA_TABLE_ROWS_MAX = 100;
|
|
594
|
+
const SLACK_DATA_TABLE_AGGREGATE_CELL_CHARACTERS_MAX = 1e4;
|
|
574
595
|
function countCharacters(value) {
|
|
575
596
|
return Array.from(value).length;
|
|
576
597
|
}
|
|
577
|
-
function readRichTextLeaf(record) {
|
|
578
|
-
if (record.type === "text" && typeof record.text === "string") return record.text;
|
|
579
|
-
const text = readNonBlankString(record.text);
|
|
580
|
-
if (text) return text;
|
|
581
|
-
switch (record.type) {
|
|
582
|
-
case "link": return readNonBlankString(record.url) ?? "";
|
|
583
|
-
case "user": {
|
|
584
|
-
const userId = readNonBlankString(record.user_id);
|
|
585
|
-
return userId ? `<@${userId}>` : "";
|
|
586
|
-
}
|
|
587
|
-
case "channel": {
|
|
588
|
-
const channelId = readNonBlankString(record.channel_id);
|
|
589
|
-
return channelId ? `<#${channelId}>` : "";
|
|
590
|
-
}
|
|
591
|
-
case "usergroup": {
|
|
592
|
-
const usergroupId = readNonBlankString(record.usergroup_id);
|
|
593
|
-
return usergroupId ? `<!subteam^${usergroupId}>` : "";
|
|
594
|
-
}
|
|
595
|
-
case "broadcast": {
|
|
596
|
-
const range = readNonBlankString(record.range);
|
|
597
|
-
return range ? `<!${range}>` : "";
|
|
598
|
-
}
|
|
599
|
-
case "emoji": {
|
|
600
|
-
const name = readNonBlankString(record.name);
|
|
601
|
-
return name ? `:${name}:` : "";
|
|
602
|
-
}
|
|
603
|
-
case "date": return readNonBlankString(record.fallback) ?? "";
|
|
604
|
-
default: return "";
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
function readRichTextElements(value, separator = "") {
|
|
608
|
-
if (!Array.isArray(value)) return "";
|
|
609
|
-
const parts = [];
|
|
610
|
-
for (const rawElement of value) {
|
|
611
|
-
const element = asOptionalRecord(rawElement);
|
|
612
|
-
if (!element) continue;
|
|
613
|
-
if (Array.isArray(element.elements)) {
|
|
614
|
-
const rendered = readRichTextElements(element.elements, element.type === "rich_text_list" ? "\n" : "");
|
|
615
|
-
if (rendered) parts.push(rendered);
|
|
616
|
-
continue;
|
|
617
|
-
}
|
|
618
|
-
const rendered = readRichTextLeaf(element);
|
|
619
|
-
if (rendered) parts.push(rendered);
|
|
620
|
-
}
|
|
621
|
-
return parts.join(separator);
|
|
622
|
-
}
|
|
623
598
|
function readSlackBasicTableCell(value) {
|
|
624
599
|
const cell = asOptionalRecord(value);
|
|
625
600
|
if (!cell) return "";
|
|
@@ -629,7 +604,7 @@ function readSlackBasicTableCell(value) {
|
|
|
629
604
|
if (typeof cell.value === "number" && Number.isFinite(cell.value)) return String(cell.value);
|
|
630
605
|
return typeof cell.value === "string" ? cell.value : "";
|
|
631
606
|
}
|
|
632
|
-
return cell.type === "rich_text" ?
|
|
607
|
+
return cell.type === "rich_text" ? renderSlackRichText(cell.elements, "table", "\n") : "";
|
|
633
608
|
}
|
|
634
609
|
function parseSlackBasicTableRows(value) {
|
|
635
610
|
const block = asOptionalRecord(value);
|
|
@@ -651,7 +626,7 @@ function readSlackDataTableCell(value, allowRichText) {
|
|
|
651
626
|
if (!cell) return;
|
|
652
627
|
if (cell.type === "raw_text") return readNonBlankString(cell.text);
|
|
653
628
|
if (cell.type === "raw_number") return typeof cell.value === "number" && Number.isFinite(cell.value) ? readNonBlankString(cell.text) : void 0;
|
|
654
|
-
if (allowRichText && cell.type === "rich_text") return readNonBlankString(
|
|
629
|
+
if (allowRichText && cell.type === "rich_text") return readNonBlankString(renderSlackRichText(cell.elements, "table"));
|
|
655
630
|
}
|
|
656
631
|
function parseSlackDataTable(value, options = {}) {
|
|
657
632
|
const block = asOptionalRecord(value);
|
|
@@ -758,7 +733,7 @@ function escapeCompactFallbackCell(value) {
|
|
|
758
733
|
return value.replaceAll("\\", "\\\\").replaceAll(" ", "\\t").replaceAll("\r", "\\r").replaceAll("\n", "\\n");
|
|
759
734
|
}
|
|
760
735
|
function escapeSlackBasicTableCell(value, mrkdwnSafe) {
|
|
761
|
-
return (mrkdwnSafe ? escapeSlackMrkdwn(value) : value
|
|
736
|
+
return escapeCompactFallbackCell(mrkdwnSafe ? escapeSlackMrkdwn(value) : value);
|
|
762
737
|
}
|
|
763
738
|
function renderSlackBasicTableRows(value, mrkdwnSafe) {
|
|
764
739
|
return parseSlackBasicTableRows(value)?.map((row) => row.map((cell) => escapeSlackBasicTableCell(cell, mrkdwnSafe)).join(" ")).join("\n");
|
|
@@ -925,16 +900,14 @@ function renderSlackDataVisualizationMrkdwnFallbackText(value) {
|
|
|
925
900
|
}
|
|
926
901
|
//#endregion
|
|
927
902
|
//#region extensions/slack/src/format.ts
|
|
928
|
-
function escapeSlackMrkdwnSegment(text) {
|
|
929
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
930
|
-
}
|
|
931
903
|
const SLACK_ANGLE_TOKEN_RE = /<[^>\n]+>/g;
|
|
932
904
|
function isAllowedSlackAngleToken(token) {
|
|
933
905
|
if (!token.startsWith("<") || !token.endsWith(">")) return false;
|
|
934
906
|
const inner = token.slice(1, -1);
|
|
935
907
|
return inner.startsWith("@") || inner.startsWith("#") || inner.startsWith("!") || inner.startsWith("mailto:") || inner.startsWith("tel:") || inner.startsWith("http://") || inner.startsWith("https://") || inner.startsWith("slack://");
|
|
936
908
|
}
|
|
937
|
-
function escapeSlackMrkdwnContent(text) {
|
|
909
|
+
function escapeSlackMrkdwnContent(text, mentions) {
|
|
910
|
+
if (mentions === "escape") return escapeSlackMrkdwn(text);
|
|
938
911
|
if (!text) return "";
|
|
939
912
|
if (!text.includes("&") && !text.includes("<") && !text.includes(">")) return text;
|
|
940
913
|
SLACK_ANGLE_TOKEN_RE.lastIndex = 0;
|
|
@@ -942,20 +915,20 @@ function escapeSlackMrkdwnContent(text) {
|
|
|
942
915
|
let lastIndex = 0;
|
|
943
916
|
for (let match = SLACK_ANGLE_TOKEN_RE.exec(text); match; match = SLACK_ANGLE_TOKEN_RE.exec(text)) {
|
|
944
917
|
const matchIndex = match.index ?? 0;
|
|
945
|
-
out.push(
|
|
918
|
+
out.push(escapeSlackMrkdwn(text.slice(lastIndex, matchIndex)));
|
|
946
919
|
const token = match[0] ?? "";
|
|
947
|
-
out.push(isAllowedSlackAngleToken(token) ? token :
|
|
920
|
+
out.push(isAllowedSlackAngleToken(token) ? token : escapeSlackMrkdwn(token));
|
|
948
921
|
lastIndex = matchIndex + token.length;
|
|
949
922
|
}
|
|
950
|
-
out.push(
|
|
923
|
+
out.push(escapeSlackMrkdwn(text.slice(lastIndex)));
|
|
951
924
|
return out.join("");
|
|
952
925
|
}
|
|
953
|
-
function escapeSlackMrkdwnText(text) {
|
|
926
|
+
function escapeSlackMrkdwnText(text, mentions) {
|
|
954
927
|
if (!text) return "";
|
|
955
928
|
if (!text.includes("&") && !text.includes("<") && !text.includes(">")) return text;
|
|
956
929
|
return text.split("\n").map((line) => {
|
|
957
|
-
if (line.startsWith("> ")) return `> ${escapeSlackMrkdwnContent(line.slice(2))}`;
|
|
958
|
-
return escapeSlackMrkdwnContent(line);
|
|
930
|
+
if (line.startsWith("> ")) return `> ${escapeSlackMrkdwnContent(line.slice(2), mentions)}`;
|
|
931
|
+
return escapeSlackMrkdwnContent(line, mentions);
|
|
959
932
|
}).join("\n");
|
|
960
933
|
}
|
|
961
934
|
function buildSlackLink(link, text) {
|
|
@@ -964,7 +937,7 @@ function buildSlackLink(link, text) {
|
|
|
964
937
|
const trimmedLabel = text.slice(link.start, link.end).trim();
|
|
965
938
|
const comparableHref = href.startsWith("mailto:") ? href.slice(7) : href;
|
|
966
939
|
if (!(trimmedLabel.length > 0 && trimmedLabel !== href && trimmedLabel !== comparableHref)) return null;
|
|
967
|
-
const safeHref =
|
|
940
|
+
const safeHref = escapeSlackMrkdwn(href);
|
|
968
941
|
return {
|
|
969
942
|
start: link.start,
|
|
970
943
|
end: link.end,
|
|
@@ -1163,12 +1136,10 @@ function projectSlackMrkdwnVisibleText(text, dateDisplay) {
|
|
|
1163
1136
|
}
|
|
1164
1137
|
function protectSlackAssistantTranscriptRoleHeaders(text) {
|
|
1165
1138
|
if (text.startsWith(SLACK_ASSISTANT_TRANSCRIPT_PREFIX)) return text;
|
|
1166
|
-
|
|
1167
|
-
const fallbackProjection = projectSlackMrkdwnVisibleText(text, "fallback");
|
|
1168
|
-
if (!slackProjectionHasRoleHeader(tokenProjection) && !slackProjectionHasRoleHeader(fallbackProjection)) return text;
|
|
1139
|
+
if (!slackProjectionHasRoleHeader(projectSlackMrkdwnVisibleText(text, "token")) && (!text.includes("<!date^") || !slackProjectionHasRoleHeader(projectSlackMrkdwnVisibleText(text, "fallback")))) return text;
|
|
1169
1140
|
return `${SLACK_ASSISTANT_TRANSCRIPT_PREFIX}${text}`;
|
|
1170
1141
|
}
|
|
1171
|
-
function buildSlackRenderOptions() {
|
|
1142
|
+
function buildSlackRenderOptions({ enclosingStyle, mentions } = {}) {
|
|
1172
1143
|
return {
|
|
1173
1144
|
annotationMarkers: { assistant_transcript_role: {
|
|
1174
1145
|
open: "`",
|
|
@@ -1176,14 +1147,14 @@ function buildSlackRenderOptions() {
|
|
|
1176
1147
|
suppressNestedFormatting: true
|
|
1177
1148
|
} },
|
|
1178
1149
|
styleMarkers: {
|
|
1179
|
-
bold: {
|
|
1150
|
+
...enclosingStyle !== "bold" ? { bold: {
|
|
1180
1151
|
open: "*",
|
|
1181
1152
|
close: "*"
|
|
1182
|
-
},
|
|
1183
|
-
italic: {
|
|
1153
|
+
} } : {},
|
|
1154
|
+
...enclosingStyle !== "italic" ? { italic: {
|
|
1184
1155
|
open: "_",
|
|
1185
1156
|
close: "_"
|
|
1186
|
-
},
|
|
1157
|
+
} } : {},
|
|
1187
1158
|
strikethrough: {
|
|
1188
1159
|
open: "~",
|
|
1189
1160
|
close: "~"
|
|
@@ -1197,22 +1168,20 @@ function buildSlackRenderOptions() {
|
|
|
1197
1168
|
close: "```"
|
|
1198
1169
|
}
|
|
1199
1170
|
},
|
|
1200
|
-
escapeText: escapeSlackMrkdwnText,
|
|
1171
|
+
escapeText: (text) => escapeSlackMrkdwnText(text, mentions),
|
|
1201
1172
|
buildLink: buildSlackLink
|
|
1202
1173
|
};
|
|
1203
1174
|
}
|
|
1204
|
-
function
|
|
1205
|
-
|
|
1175
|
+
function normalizeSlackOutboundText(markdown, options = {}) {
|
|
1176
|
+
const ir = makeSlackEmphasisStylesSafe(markdownToIR(markdown ?? "", {
|
|
1206
1177
|
assistantTranscriptRoleHeaders: true,
|
|
1207
1178
|
linkify: false,
|
|
1208
1179
|
autolink: false,
|
|
1209
1180
|
headingStyle: "rich",
|
|
1210
1181
|
blockquotePrefix: "> ",
|
|
1211
1182
|
tableMode: options.tableMode
|
|
1212
|
-
}))
|
|
1213
|
-
|
|
1214
|
-
function normalizeSlackOutboundText(markdown) {
|
|
1215
|
-
return protectSlackAssistantTranscriptRoleHeaders(markdownToSlackMrkdwn(markdown ?? ""));
|
|
1183
|
+
}));
|
|
1184
|
+
return protectSlackAssistantTranscriptRoleHeaders(renderMarkdownWithMarkers(ir, buildSlackRenderOptions(options), SLACK_FORMAT_PROFILE));
|
|
1216
1185
|
}
|
|
1217
1186
|
/** Chunk already-rendered Slack mrkdwn without splitting entities or code markers. */
|
|
1218
1187
|
function chunkSlackMrkdwnText(text, limit) {
|
|
@@ -1246,7 +1215,7 @@ function chunkSlackMrkdwnText(text, limit) {
|
|
|
1246
1215
|
const marker = wrapper(activeMarker);
|
|
1247
1216
|
if (activeMarker && isAllowedSlackAngleToken(token)) {
|
|
1248
1217
|
if (marker) chunks.push(...chunkTextForOutbound(token, Math.max(1, Math.floor(contentLimit)), { preserveWhitespace: true }).map((fragment) => `${marker}${fragment}${marker}`));
|
|
1249
|
-
else chunks.push(...chunkTextForOutbound(
|
|
1218
|
+
else chunks.push(...chunkTextForOutbound(escapeSlackMrkdwn(token), Math.max(1, Math.floor(limit)), { preserveWhitespace: true }));
|
|
1250
1219
|
continue;
|
|
1251
1220
|
}
|
|
1252
1221
|
chunks.push(...token.length <= limit ? [token] : chunkTextForOutbound(token, limit));
|
|
@@ -1275,39 +1244,6 @@ function markdownToSlackMrkdwnChunks(markdown, limit, options = {}) {
|
|
|
1275
1244
|
}).map(({ rendered }) => rendered);
|
|
1276
1245
|
}
|
|
1277
1246
|
//#endregion
|
|
1278
|
-
//#region extensions/slack/src/question-actions.ts
|
|
1279
|
-
const SLACK_QUESTION_VALUE_PREFIX = "slq1:";
|
|
1280
|
-
const QUESTION_RECORD_ID_PATTERN = /^ask_[a-f0-9]{32}$/u;
|
|
1281
|
-
function encodeSlackQuestionAction(action) {
|
|
1282
|
-
if (!QUESTION_RECORD_ID_PATTERN.test(action.questionId) || !Number.isInteger(action.optionIndex) || action.optionIndex < 0 || action.optionIndex > 3) return;
|
|
1283
|
-
const value = `${SLACK_QUESTION_VALUE_PREFIX}${action.questionId}:${action.optionIndex}`;
|
|
1284
|
-
return value.length <= 2e3 ? value : void 0;
|
|
1285
|
-
}
|
|
1286
|
-
function decodeSlackQuestionAction(value) {
|
|
1287
|
-
if (typeof value !== "string" || value.length > 2e3) return null;
|
|
1288
|
-
const match = /^slq1:(ask_[a-f0-9]{32}):([0-3])$/u.exec(value);
|
|
1289
|
-
return match?.[1] && match[2] ? {
|
|
1290
|
-
questionId: match[1],
|
|
1291
|
-
optionIndex: Number(match[2])
|
|
1292
|
-
} : null;
|
|
1293
|
-
}
|
|
1294
|
-
async function resolveSlackQuestionAction(params) {
|
|
1295
|
-
let result;
|
|
1296
|
-
try {
|
|
1297
|
-
result = await (params.resolveQuestion ?? questionGatewayRuntime.resolveOption)({
|
|
1298
|
-
cfg: params.cfg,
|
|
1299
|
-
questionId: params.action.questionId,
|
|
1300
|
-
optionIndex: params.action.optionIndex,
|
|
1301
|
-
senderId: params.userId,
|
|
1302
|
-
clientDisplayName: `Slack question (${params.accountId})`
|
|
1303
|
-
});
|
|
1304
|
-
} catch {
|
|
1305
|
-
await params.respond("Could not submit this answer.").catch(() => {});
|
|
1306
|
-
return;
|
|
1307
|
-
}
|
|
1308
|
-
await params.respond(result.status === "answered" ? "Answer submitted." : "This question was already answered.").catch(() => {});
|
|
1309
|
-
}
|
|
1310
|
-
//#endregion
|
|
1311
1247
|
//#region extensions/slack/src/reply-action-ids.ts
|
|
1312
1248
|
const SLACK_REPLY_BUTTON_ACTION_ID = "openclaw:reply_button";
|
|
1313
1249
|
const SLACK_REPLY_LINK_ACTION_ID = "openclaw:reply_link";
|
|
@@ -1365,368 +1301,12 @@ function truncateSlackTextByUtf8Bytes(value, maxBytes) {
|
|
|
1365
1301
|
return maxBytes >= suffixBytes ? `${prefix}${suffix}` : prefix;
|
|
1366
1302
|
}
|
|
1367
1303
|
//#endregion
|
|
1368
|
-
//#region extensions/slack/src/blocks-render.ts
|
|
1369
|
-
const SLACK_BUTTON_URL_MAX = 3e3;
|
|
1370
|
-
function buildSlackReplyButtonActionId(buttonIndex, choiceIndex) {
|
|
1371
|
-
return `${SLACK_REPLY_BUTTON_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
|
|
1372
|
-
}
|
|
1373
|
-
function buildSlackReplyLinkActionId(buttonIndex, choiceIndex) {
|
|
1374
|
-
return `${SLACK_REPLY_LINK_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
|
|
1375
|
-
}
|
|
1376
|
-
function buildSlackReplySelectActionId(selectIndex) {
|
|
1377
|
-
return `${SLACK_REPLY_SELECT_ACTION_ID}:${String(selectIndex)}`;
|
|
1378
|
-
}
|
|
1379
|
-
function buildSlackApprovalButtonActionId(buttonIndex, choiceIndex) {
|
|
1380
|
-
return `${SLACK_APPROVAL_BUTTON_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
|
|
1381
|
-
}
|
|
1382
|
-
function buildSlackApprovalSelectActionId(selectIndex) {
|
|
1383
|
-
return `${SLACK_APPROVAL_SELECT_ACTION_ID}:${String(selectIndex)}`;
|
|
1384
|
-
}
|
|
1385
|
-
function buildSlackCallbackButtonActionId(buttonIndex, choiceIndex) {
|
|
1386
|
-
return `${SLACK_CALLBACK_BUTTON_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
|
|
1387
|
-
}
|
|
1388
|
-
function buildSlackCallbackSelectActionId(selectIndex) {
|
|
1389
|
-
return `${SLACK_CALLBACK_SELECT_ACTION_ID}:${String(selectIndex)}`;
|
|
1390
|
-
}
|
|
1391
|
-
function buildSlackQuestionButtonActionId(buttonIndex, choiceIndex) {
|
|
1392
|
-
return `${SLACK_QUESTION_BUTTON_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
|
|
1393
|
-
}
|
|
1394
|
-
function resolveSlackButtonStyle(style) {
|
|
1395
|
-
if (style === "primary" || style === "danger") return style;
|
|
1396
|
-
if (style === "success") return "primary";
|
|
1397
|
-
}
|
|
1398
|
-
function resolveSlackActionTarget(action, optionIndex) {
|
|
1399
|
-
if (!action) return;
|
|
1400
|
-
if (action.type === "approval") return {
|
|
1401
|
-
kind: "approval",
|
|
1402
|
-
value: encodeSlackApprovalAction(action)
|
|
1403
|
-
};
|
|
1404
|
-
if (action.type === "question") {
|
|
1405
|
-
const value = optionIndex === void 0 ? void 0 : encodeSlackQuestionAction({
|
|
1406
|
-
questionId: action.questionId,
|
|
1407
|
-
optionIndex
|
|
1408
|
-
});
|
|
1409
|
-
return value ? {
|
|
1410
|
-
kind: "question",
|
|
1411
|
-
value
|
|
1412
|
-
} : void 0;
|
|
1413
|
-
}
|
|
1414
|
-
if (action.type === "url" || action.type === "web-app") {
|
|
1415
|
-
const url = normalizeOptionalString(action.url);
|
|
1416
|
-
return url ? {
|
|
1417
|
-
kind: "link",
|
|
1418
|
-
url
|
|
1419
|
-
} : void 0;
|
|
1420
|
-
}
|
|
1421
|
-
if (action.type === "callback") {
|
|
1422
|
-
const value = normalizeOptionalString(action.value);
|
|
1423
|
-
return value ? {
|
|
1424
|
-
kind: "callback",
|
|
1425
|
-
value
|
|
1426
|
-
} : void 0;
|
|
1427
|
-
}
|
|
1428
|
-
const command = normalizeOptionalString(action.command);
|
|
1429
|
-
return command && parseExecApprovalCommandText(command) ? {
|
|
1430
|
-
kind: "reply",
|
|
1431
|
-
value: command
|
|
1432
|
-
} : void 0;
|
|
1433
|
-
}
|
|
1434
|
-
function resolveSlackButtonTarget(button, optionIndex) {
|
|
1435
|
-
if (button.action !== void 0) {
|
|
1436
|
-
const action = resolveMessagePresentationButtonAction(button);
|
|
1437
|
-
return action ? resolveSlackActionTarget(action, optionIndex) : void 0;
|
|
1438
|
-
}
|
|
1439
|
-
const legacyUrl = normalizeOptionalString(button.url ?? button.webApp?.url ?? button.web_app?.url);
|
|
1440
|
-
if (legacyUrl && isWithinSlackLimit(legacyUrl, SLACK_BUTTON_URL_MAX)) return {
|
|
1441
|
-
kind: "link",
|
|
1442
|
-
url: legacyUrl
|
|
1443
|
-
};
|
|
1444
|
-
const legacyValue = normalizeOptionalString(button.value);
|
|
1445
|
-
if (legacyValue) return {
|
|
1446
|
-
kind: "reply",
|
|
1447
|
-
value: legacyValue
|
|
1448
|
-
};
|
|
1449
|
-
return legacyUrl ? {
|
|
1450
|
-
kind: "link",
|
|
1451
|
-
url: legacyUrl
|
|
1452
|
-
} : void 0;
|
|
1453
|
-
}
|
|
1454
|
-
function resolveSlackOptionTarget(option) {
|
|
1455
|
-
if (option.action !== void 0) {
|
|
1456
|
-
const action = resolveMessagePresentationOptionAction(option);
|
|
1457
|
-
const target = action ? resolveSlackActionTarget(action) : void 0;
|
|
1458
|
-
return target?.kind === "link" || target?.kind === "question" ? void 0 : target;
|
|
1459
|
-
}
|
|
1460
|
-
const value = normalizeOptionalString(option.value);
|
|
1461
|
-
return value ? {
|
|
1462
|
-
kind: "reply",
|
|
1463
|
-
value
|
|
1464
|
-
} : void 0;
|
|
1465
|
-
}
|
|
1466
|
-
function isWithinSlackLimit(value, maxLength) {
|
|
1467
|
-
return value.length <= maxLength;
|
|
1468
|
-
}
|
|
1469
|
-
function isRenderableSlackOption(option) {
|
|
1470
|
-
return isWithinSlackLimit(option.value, 150);
|
|
1471
|
-
}
|
|
1472
|
-
function readSlackBlockId(block) {
|
|
1473
|
-
const value = block.block_id;
|
|
1474
|
-
return typeof value === "string" ? value : void 0;
|
|
1475
|
-
}
|
|
1476
|
-
function readSlackOpenClawBlockIndex(blockId, prefix) {
|
|
1477
|
-
if (!blockId.startsWith(prefix)) return;
|
|
1478
|
-
const value = Number.parseInt(blockId.slice(prefix.length), 10);
|
|
1479
|
-
return Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
1480
|
-
}
|
|
1481
|
-
/** Resolve existing Block Kit indexes and native-data budgets before appending portable blocks. */
|
|
1482
|
-
function resolveSlackBlockOffsets(blocks) {
|
|
1483
|
-
let buttonIndexOffset = 0;
|
|
1484
|
-
const dataTableCellCharacterCountOffset = countSlackDataTableBlocksCellCharacters(blocks) ?? 10001;
|
|
1485
|
-
let dataVisualizationCountOffset = 0;
|
|
1486
|
-
let selectIndexOffset = 0;
|
|
1487
|
-
for (const block of blocks ?? []) {
|
|
1488
|
-
if (hasSlackDataVisualizationBlock([block])) dataVisualizationCountOffset += 1;
|
|
1489
|
-
const blockId = readSlackBlockId(block);
|
|
1490
|
-
if (!blockId) continue;
|
|
1491
|
-
buttonIndexOffset = Math.max(buttonIndexOffset, readSlackOpenClawBlockIndex(blockId, "openclaw_reply_buttons_") ?? 0);
|
|
1492
|
-
selectIndexOffset = Math.max(selectIndexOffset, readSlackOpenClawBlockIndex(blockId, "openclaw_reply_select_") ?? 0);
|
|
1493
|
-
}
|
|
1494
|
-
return {
|
|
1495
|
-
buttonIndexOffset,
|
|
1496
|
-
dataTableCellCharacterCountOffset,
|
|
1497
|
-
dataVisualizationCountOffset,
|
|
1498
|
-
selectIndexOffset
|
|
1499
|
-
};
|
|
1500
|
-
}
|
|
1501
|
-
/**
|
|
1502
|
-
* @deprecated Use buildSlackPresentationBlocks with MessagePresentation.
|
|
1503
|
-
*/
|
|
1504
|
-
function buildSlackInteractiveBlocks(interactive, options = {}) {
|
|
1505
|
-
return buildSlackPresentationBlocks(interactive ? legacyInteractiveReplyToPresentation(interactive) : void 0, options);
|
|
1506
|
-
}
|
|
1507
|
-
/** Render portable presentation blocks as Slack Block Kit blocks. */
|
|
1508
|
-
function buildSlackPresentationBlocks(presentation, options = {}) {
|
|
1509
|
-
if (!presentation) return [];
|
|
1510
|
-
const renderTablesNatively = canRenderSlackPresentationTables(presentation, options);
|
|
1511
|
-
const blocks = [];
|
|
1512
|
-
if (presentation.title) blocks.push({
|
|
1513
|
-
type: "header",
|
|
1514
|
-
text: {
|
|
1515
|
-
type: "plain_text",
|
|
1516
|
-
text: truncateSlackText(presentation.title, 150),
|
|
1517
|
-
emoji: true
|
|
1518
|
-
}
|
|
1519
|
-
});
|
|
1520
|
-
let buttonIndex = options.buttonIndexOffset ?? 0;
|
|
1521
|
-
let dataTableCellCharacterCount = options.dataTableCellCharacterCountOffset ?? 0;
|
|
1522
|
-
let dataVisualizationCount = options.dataVisualizationCountOffset ?? 0;
|
|
1523
|
-
let selectIndex = options.selectIndexOffset ?? 0;
|
|
1524
|
-
for (const block of presentation.blocks) {
|
|
1525
|
-
if (block.type === "text" || block.type === "context") {
|
|
1526
|
-
const text = block.text.trim();
|
|
1527
|
-
if (!text) continue;
|
|
1528
|
-
for (const chunk of chunkSlackMrkdwnText(text, SLACK_SECTION_TEXT_MAX)) blocks.push(block.type === "context" ? {
|
|
1529
|
-
type: "context",
|
|
1530
|
-
elements: [{
|
|
1531
|
-
type: "mrkdwn",
|
|
1532
|
-
text: chunk,
|
|
1533
|
-
verbatim: true
|
|
1534
|
-
}]
|
|
1535
|
-
} : {
|
|
1536
|
-
type: "section",
|
|
1537
|
-
text: {
|
|
1538
|
-
type: "mrkdwn",
|
|
1539
|
-
text: chunk
|
|
1540
|
-
}
|
|
1541
|
-
});
|
|
1542
|
-
continue;
|
|
1543
|
-
}
|
|
1544
|
-
if (block.type === "divider") {
|
|
1545
|
-
blocks.push({ type: "divider" });
|
|
1546
|
-
continue;
|
|
1547
|
-
}
|
|
1548
|
-
if (block.type === "buttons") {
|
|
1549
|
-
const rendered = buildSlackPresentationButtonBlock(block, buttonIndex + 1);
|
|
1550
|
-
if (rendered) {
|
|
1551
|
-
buttonIndex += 1;
|
|
1552
|
-
blocks.push(rendered);
|
|
1553
|
-
}
|
|
1554
|
-
continue;
|
|
1555
|
-
}
|
|
1556
|
-
if (block.type === "chart") {
|
|
1557
|
-
const rendered = dataVisualizationCount < 2 ? buildSlackPresentationChartBlock(block) : void 0;
|
|
1558
|
-
if (rendered) {
|
|
1559
|
-
dataVisualizationCount += 1;
|
|
1560
|
-
blocks.push(rendered);
|
|
1561
|
-
} else {
|
|
1562
|
-
const fallback = renderSlackMessagePresentationChartFallbackText(block);
|
|
1563
|
-
blocks.push(...chunkTextForOutbound(fallback, SLACK_SECTION_TEXT_MAX).map((text) => ({
|
|
1564
|
-
type: "context",
|
|
1565
|
-
elements: [{
|
|
1566
|
-
type: "mrkdwn",
|
|
1567
|
-
text,
|
|
1568
|
-
verbatim: true
|
|
1569
|
-
}]
|
|
1570
|
-
})));
|
|
1571
|
-
}
|
|
1572
|
-
continue;
|
|
1573
|
-
}
|
|
1574
|
-
if (block.type === "table") {
|
|
1575
|
-
if (!renderTablesNatively) continue;
|
|
1576
|
-
const rendered = buildSlackDataTableBlock(block, { cellCharacterCountOffset: dataTableCellCharacterCount });
|
|
1577
|
-
if (rendered) {
|
|
1578
|
-
dataTableCellCharacterCount += countSlackDataTableCellCharacters(rendered);
|
|
1579
|
-
blocks.push(rendered);
|
|
1580
|
-
}
|
|
1581
|
-
continue;
|
|
1582
|
-
}
|
|
1583
|
-
if (block.type === "select") {
|
|
1584
|
-
const rendered = buildSlackPresentationSelectBlock(block, selectIndex + 1);
|
|
1585
|
-
if (rendered) {
|
|
1586
|
-
selectIndex += 1;
|
|
1587
|
-
blocks.push(rendered);
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
return blocks;
|
|
1592
|
-
}
|
|
1593
|
-
function buildSlackPresentationChartBlock(block) {
|
|
1594
|
-
return buildSlackDataVisualizationBlock(block);
|
|
1595
|
-
}
|
|
1596
|
-
function buildSlackPresentationButtonBlock(block, buttonIndex) {
|
|
1597
|
-
const elements = block.buttons.flatMap((button, choiceIndex) => {
|
|
1598
|
-
const target = resolveSlackButtonTarget(button, choiceIndex);
|
|
1599
|
-
if (!target || (target.kind === "link" ? !isWithinSlackLimit(target.url, SLACK_BUTTON_URL_MAX) : !isWithinSlackLimit(target.value, 2e3))) return [];
|
|
1600
|
-
const style = resolveSlackButtonStyle(button.style);
|
|
1601
|
-
return [{
|
|
1602
|
-
type: "button",
|
|
1603
|
-
action_id: target.kind === "link" ? buildSlackReplyLinkActionId(buttonIndex, choiceIndex) : target.kind === "approval" ? buildSlackApprovalButtonActionId(buttonIndex, choiceIndex) : target.kind === "callback" ? buildSlackCallbackButtonActionId(buttonIndex, choiceIndex) : target.kind === "question" ? buildSlackQuestionButtonActionId(buttonIndex, choiceIndex) : buildSlackReplyButtonActionId(buttonIndex, choiceIndex),
|
|
1604
|
-
text: {
|
|
1605
|
-
type: "plain_text",
|
|
1606
|
-
text: truncateSlackText(button.label, 75),
|
|
1607
|
-
emoji: true
|
|
1608
|
-
},
|
|
1609
|
-
...target.kind === "link" ? { url: target.url } : { value: target.value },
|
|
1610
|
-
...style ? { style } : {}
|
|
1611
|
-
}];
|
|
1612
|
-
}).slice(0, 25);
|
|
1613
|
-
return elements.length > 0 ? {
|
|
1614
|
-
type: "actions",
|
|
1615
|
-
block_id: `openclaw_reply_buttons_${buttonIndex}`,
|
|
1616
|
-
elements
|
|
1617
|
-
} : void 0;
|
|
1618
|
-
}
|
|
1619
|
-
/** True when every portable table fits Slack's native per-message table budget. */
|
|
1620
|
-
function canRenderSlackPresentationTables(presentation, options = {}) {
|
|
1621
|
-
let cellCharacterCount = options.dataTableCellCharacterCountOffset ?? 0;
|
|
1622
|
-
for (const block of presentation.blocks) {
|
|
1623
|
-
if (block.type !== "table") continue;
|
|
1624
|
-
const rendered = buildSlackDataTableBlock(block, { cellCharacterCountOffset: cellCharacterCount });
|
|
1625
|
-
if (!rendered) return false;
|
|
1626
|
-
cellCharacterCount += countSlackDataTableCellCharacters(rendered);
|
|
1627
|
-
}
|
|
1628
|
-
return true;
|
|
1629
|
-
}
|
|
1630
|
-
/** True when native Slack rendering preserves every portable control. */
|
|
1631
|
-
function canRenderSlackPresentation(presentation, options = {}) {
|
|
1632
|
-
if (presentation.title && !isWithinSlackLimit(presentation.title.trim(), 150)) return false;
|
|
1633
|
-
if (!canRenderSlackPresentationTables(presentation, options)) return false;
|
|
1634
|
-
let dataVisualizationCount = options.dataVisualizationCountOffset ?? 0;
|
|
1635
|
-
for (const block of presentation.blocks) {
|
|
1636
|
-
if (block.type === "text" || block.type === "context") continue;
|
|
1637
|
-
if (block.type === "buttons") {
|
|
1638
|
-
if (!(block.buttons.length <= 25 && block.buttons.every((button, choiceIndex) => {
|
|
1639
|
-
if (!isWithinSlackLimit(button.label, 75)) return false;
|
|
1640
|
-
const target = resolveSlackButtonTarget(button, choiceIndex);
|
|
1641
|
-
return target ? target.kind === "link" ? isWithinSlackLimit(target.url, SLACK_BUTTON_URL_MAX) : isWithinSlackLimit(target.value, 2e3) : false;
|
|
1642
|
-
}))) return false;
|
|
1643
|
-
continue;
|
|
1644
|
-
}
|
|
1645
|
-
if (block.type === "select") {
|
|
1646
|
-
if (!(isWithinSlackLimit(normalizeOptionalString(block.placeholder) ?? "Choose an option", 75) && block.options.length <= 100 && (!block.placeholder || isWithinSlackLimit(block.placeholder, 75)) && block.options.every((option) => {
|
|
1647
|
-
if (!isWithinSlackLimit(option.label, 75)) return false;
|
|
1648
|
-
const target = resolveSlackOptionTarget(option);
|
|
1649
|
-
return target ? isRenderableSlackOption({
|
|
1650
|
-
label: option.label,
|
|
1651
|
-
...target
|
|
1652
|
-
}) : false;
|
|
1653
|
-
}) && new Set(block.options.map((option) => resolveSlackOptionTarget(option)?.kind)).size === 1)) return false;
|
|
1654
|
-
continue;
|
|
1655
|
-
}
|
|
1656
|
-
if (block.type === "chart") {
|
|
1657
|
-
if (dataVisualizationCount >= 2 || !canRenderSlackDataVisualization(block)) return false;
|
|
1658
|
-
dataVisualizationCount += 1;
|
|
1659
|
-
continue;
|
|
1660
|
-
}
|
|
1661
|
-
if (block.type === "table") continue;
|
|
1662
|
-
}
|
|
1663
|
-
return true;
|
|
1664
|
-
}
|
|
1665
|
-
function buildSlackPresentationSelectBlock(block, selectIndex) {
|
|
1666
|
-
const options = block.options.flatMap((option) => {
|
|
1667
|
-
const target = resolveSlackOptionTarget(option);
|
|
1668
|
-
return target ? [{
|
|
1669
|
-
label: option.label,
|
|
1670
|
-
...target
|
|
1671
|
-
}] : [];
|
|
1672
|
-
}).filter(isRenderableSlackOption).slice(0, 100);
|
|
1673
|
-
const optionKinds = new Set(options.map((option) => option.kind));
|
|
1674
|
-
return options.length > 0 && optionKinds.size === 1 ? {
|
|
1675
|
-
type: "actions",
|
|
1676
|
-
block_id: `openclaw_reply_select_${selectIndex}`,
|
|
1677
|
-
elements: [{
|
|
1678
|
-
type: "static_select",
|
|
1679
|
-
action_id: options[0]?.kind === "approval" ? buildSlackApprovalSelectActionId(selectIndex) : options[0]?.kind === "callback" ? buildSlackCallbackSelectActionId(selectIndex) : buildSlackReplySelectActionId(selectIndex),
|
|
1680
|
-
placeholder: {
|
|
1681
|
-
type: "plain_text",
|
|
1682
|
-
text: truncateSlackText(normalizeOptionalString(block.placeholder) ?? "Choose an option", 75),
|
|
1683
|
-
emoji: true
|
|
1684
|
-
},
|
|
1685
|
-
options: options.map((option) => ({
|
|
1686
|
-
text: {
|
|
1687
|
-
type: "plain_text",
|
|
1688
|
-
text: truncateSlackText(option.label, 75),
|
|
1689
|
-
emoji: true
|
|
1690
|
-
},
|
|
1691
|
-
value: option.value
|
|
1692
|
-
}))
|
|
1693
|
-
}]
|
|
1694
|
-
} : void 0;
|
|
1695
|
-
}
|
|
1696
|
-
//#endregion
|
|
1697
1304
|
//#region extensions/slack/src/limits.ts
|
|
1698
1305
|
const SLACK_TEXT_LIMIT = 8e3;
|
|
1699
1306
|
const SLACK_MESSAGE_TEXT_RECOMMENDED_LIMIT = 4e3;
|
|
1700
1307
|
const SLACK_EDIT_TEXT_MAX_BYTES = 4e3;
|
|
1701
1308
|
const SLACK_MESSAGE_TEXT_HARD_LIMIT = 4e4;
|
|
1702
1309
|
//#endregion
|
|
1703
|
-
//#region extensions/slack/src/authored-text.ts
|
|
1704
|
-
function normalizeComparableSlackText(text) {
|
|
1705
|
-
return text.trim().replace(/\s+/g, " ");
|
|
1706
|
-
}
|
|
1707
|
-
function isSlackAuthoredTextRepresentedInInteractive(text, interactive) {
|
|
1708
|
-
return isSlackAuthoredTextRepresentedInFragments(text, interactive?.blocks.flatMap((block) => block.type === "text" ? [block.text] : []) ?? []);
|
|
1709
|
-
}
|
|
1710
|
-
function isSlackAuthoredTextRepresentedInFragments(text, rawFragments) {
|
|
1711
|
-
const target = normalizeComparableSlackText(text);
|
|
1712
|
-
const fragments = rawFragments.map(normalizeComparableSlackText).filter(Boolean);
|
|
1713
|
-
for (let start = 0; start < fragments.length; start += 1) {
|
|
1714
|
-
let combined = "";
|
|
1715
|
-
for (let end = start; end < fragments.length; end += 1) {
|
|
1716
|
-
combined = normalizeComparableSlackText(`${combined} ${fragments[end]}`);
|
|
1717
|
-
if (combined === target) return true;
|
|
1718
|
-
if (combined.length > target.length) break;
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
return false;
|
|
1722
|
-
}
|
|
1723
|
-
/** Resolve placement from producer facts, before accessibility text changes the payload text. */
|
|
1724
|
-
function resolveSlackAuthoredTextPlacement(params) {
|
|
1725
|
-
const text = normalizeOptionalString(params.text);
|
|
1726
|
-
if (!text) return "none";
|
|
1727
|
-
return params.renderedInBlocks || isSlackAuthoredTextRepresentedInFragments(text, params.renderedTextFragments ?? []) || isSlackAuthoredTextRepresentedInInteractive(text, params.interactive) ? "blocks" : "outside-blocks";
|
|
1728
|
-
}
|
|
1729
|
-
//#endregion
|
|
1730
1310
|
//#region extensions/slack/src/blocks-fallback.ts
|
|
1731
1311
|
const SLACK_SELECT_ELEMENT_TYPES = /* @__PURE__ */ new Set([
|
|
1732
1312
|
"static_select",
|
|
@@ -1750,49 +1330,6 @@ function readTextObject(value, options = {}) {
|
|
|
1750
1330
|
function readTextValue(value, options = {}) {
|
|
1751
1331
|
return normalizeOptionalString(value) ?? readTextObject(value, options);
|
|
1752
1332
|
}
|
|
1753
|
-
function renderSlackRichTextLeaf(element) {
|
|
1754
|
-
switch (element.type) {
|
|
1755
|
-
case "text": return typeof element.text === "string" ? escapeSlackMrkdwn(element.text) : "";
|
|
1756
|
-
case "link": return escapeSlackMrkdwn(normalizeOptionalString(element.text) ?? normalizeOptionalString(element.url) ?? "");
|
|
1757
|
-
case "user": {
|
|
1758
|
-
const userId = normalizeOptionalString(element.user_id);
|
|
1759
|
-
return userId ? escapeSlackMrkdwn(`<@${userId}>`) : "";
|
|
1760
|
-
}
|
|
1761
|
-
case "channel": {
|
|
1762
|
-
const channelId = normalizeOptionalString(element.channel_id);
|
|
1763
|
-
return channelId ? escapeSlackMrkdwn(`<#${channelId}>`) : "";
|
|
1764
|
-
}
|
|
1765
|
-
case "usergroup": {
|
|
1766
|
-
const usergroupId = normalizeOptionalString(element.usergroup_id);
|
|
1767
|
-
return usergroupId ? escapeSlackMrkdwn(`<!subteam^${usergroupId}>`) : "";
|
|
1768
|
-
}
|
|
1769
|
-
case "broadcast": {
|
|
1770
|
-
const range = normalizeOptionalString(element.range);
|
|
1771
|
-
return range ? escapeSlackMrkdwn(`<!${range}>`) : "";
|
|
1772
|
-
}
|
|
1773
|
-
case "emoji": {
|
|
1774
|
-
const name = normalizeOptionalString(element.name);
|
|
1775
|
-
return name ? `:${name}:` : "";
|
|
1776
|
-
}
|
|
1777
|
-
case "date": return escapeSlackMrkdwn(normalizeOptionalString(element.fallback) ?? "");
|
|
1778
|
-
default: return "";
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
function renderSlackRichTextElement(value) {
|
|
1782
|
-
const element = asOptionalRecord(value);
|
|
1783
|
-
if (!element) return "";
|
|
1784
|
-
switch (element.type) {
|
|
1785
|
-
case "rich_text_section":
|
|
1786
|
-
case "rich_text_preformatted":
|
|
1787
|
-
case "rich_text_quote": return renderSlackRichTextElements(element.elements, "");
|
|
1788
|
-
case "rich_text_list": return renderSlackRichTextElements(element.elements, "\n");
|
|
1789
|
-
default: return renderSlackRichTextLeaf(element);
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
function renderSlackRichTextElements(value, separator) {
|
|
1793
|
-
if (!Array.isArray(value)) return "";
|
|
1794
|
-
return value.map(renderSlackRichTextElement).filter(Boolean).join(separator);
|
|
1795
|
-
}
|
|
1796
1333
|
function readImageText(block) {
|
|
1797
1334
|
const altText = normalizeOptionalString(block.alt_text);
|
|
1798
1335
|
return (altText ? escapeSlackMrkdwn(altText) : void 0) ?? readTextObject(block.title);
|
|
@@ -1842,7 +1379,7 @@ function renderSlackBlockFallbackText(raw, options = {}) {
|
|
|
1842
1379
|
const block = asOptionalRecord(raw);
|
|
1843
1380
|
if (!block) return;
|
|
1844
1381
|
switch (block.type) {
|
|
1845
|
-
case "rich_text": return normalizeOptionalString(
|
|
1382
|
+
case "rich_text": return normalizeOptionalString(renderSlackRichText(block.elements, options.nativeReferenceFormat === "plain" ? "native-reference" : "escaped", "\n"));
|
|
1846
1383
|
case "header": return readTextObject(block.text, options);
|
|
1847
1384
|
case "section": return readSectionText(block, options);
|
|
1848
1385
|
case "image": return readImageText(block) ?? "Shared an image";
|
|
@@ -1869,7 +1406,7 @@ function buildSlackCompleteBlocksFallbackText(blocks) {
|
|
|
1869
1406
|
//#endregion
|
|
1870
1407
|
//#region extensions/slack/src/native-data-blocks.ts
|
|
1871
1408
|
const SLACK_MALFORMED_NATIVE_DATA_FALLBACK = "Slack could not render this chart or table data.";
|
|
1872
|
-
const SLACK_RESPONSE_URL_BODY_LIMIT_BYTES =
|
|
1409
|
+
const SLACK_RESPONSE_URL_BODY_LIMIT_BYTES = 16384;
|
|
1873
1410
|
const SLACK_RESPONSE_URL_BODY_TIMEOUT_MS = 3e4;
|
|
1874
1411
|
/** Detect a native Slack chart or table block. */
|
|
1875
1412
|
function hasSlackNativeDataBlock(blocks) {
|
|
@@ -1987,418 +1524,109 @@ function appendSlackNativeDataPlainTextFallback(text, blocks) {
|
|
|
1987
1524
|
return appendSlackNativeDataFallback(text, blocks, renderSlackNativeDataPlainTextBlock);
|
|
1988
1525
|
}
|
|
1989
1526
|
//#endregion
|
|
1990
|
-
//#region extensions/slack/src/
|
|
1991
|
-
function
|
|
1992
|
-
|
|
1993
|
-
if (!
|
|
1994
|
-
return value.map((raw) => {
|
|
1995
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("Slack rendered presentation segment must be an object");
|
|
1996
|
-
const segment = raw;
|
|
1997
|
-
if (segment.kind === "text" && typeof segment.text === "string" && segment.mrkdwn === false) return {
|
|
1998
|
-
kind: "text",
|
|
1999
|
-
text: segment.text,
|
|
2000
|
-
mrkdwn: false
|
|
2001
|
-
};
|
|
2002
|
-
if (segment.kind === "blocks") {
|
|
2003
|
-
const blocks = parseSlackBlocksInput(segment.blocks);
|
|
2004
|
-
if (blocks?.length) return {
|
|
2005
|
-
kind: "blocks",
|
|
2006
|
-
blocks
|
|
2007
|
-
};
|
|
2008
|
-
}
|
|
2009
|
-
throw new Error("Slack rendered presentation segment is invalid");
|
|
2010
|
-
});
|
|
2011
|
-
}
|
|
2012
|
-
/** Convert compiled segments into ordered sender calls without re-inferring text placement. */
|
|
2013
|
-
function resolveSlackReplyDeliveryMessages(params) {
|
|
2014
|
-
const messages = [];
|
|
2015
|
-
let outsideText = params.authoredTextPlacement === "outside-blocks" ? params.text?.trim() ?? "" : "";
|
|
2016
|
-
for (const segment of params.segments) {
|
|
2017
|
-
if (segment.kind === "text") {
|
|
2018
|
-
const text = [outsideText, segment.text].filter(Boolean).join("\n\n");
|
|
2019
|
-
outsideText = "";
|
|
2020
|
-
if (text) messages.push({
|
|
2021
|
-
text,
|
|
2022
|
-
textIsSlackPlainText: true
|
|
2023
|
-
});
|
|
2024
|
-
continue;
|
|
2025
|
-
}
|
|
2026
|
-
const baseText = outsideText;
|
|
2027
|
-
outsideText = "";
|
|
2028
|
-
const text = buildSlackNativeDataAccessibilityText(baseText, segment.blocks) || buildSlackBlocksFallbackText(segment.blocks);
|
|
2029
|
-
const authoredTextPlacement = baseText ? "outside-blocks" : params.authoredTextPlacement === "blocks" ? "blocks" : "none";
|
|
2030
|
-
messages.push({
|
|
2031
|
-
text,
|
|
2032
|
-
blocks: segment.blocks,
|
|
2033
|
-
authoredTextPlacement,
|
|
2034
|
-
...baseText ? { nativeDataFallbackBaseText: baseText } : {}
|
|
2035
|
-
});
|
|
2036
|
-
}
|
|
2037
|
-
if (outsideText) messages.push({ text: outsideText });
|
|
2038
|
-
return messages;
|
|
2039
|
-
}
|
|
2040
|
-
function resolveSlackReplyText(payload, text = payload.text) {
|
|
2041
|
-
const presentation = normalizeMessagePresentation(payload.presentation);
|
|
2042
|
-
return presentation ? renderSlackMessagePresentationFallbackText({
|
|
2043
|
-
text,
|
|
2044
|
-
presentation
|
|
2045
|
-
}) : text ?? "";
|
|
1527
|
+
//#region extensions/slack/src/detached-target-admission.ts
|
|
1528
|
+
function assertSlackDetachedTargetAllowed(accountId, teamId) {
|
|
1529
|
+
const installationKind = getSlackInstallationKind(accountId);
|
|
1530
|
+
if (installationKind && installationKind !== "workspace" && !teamId) throw new Error("unsupported_enterprise_slack_delivery: detached Slack operations require team:<team-id>:channel:<channel-id> or team:<team-id>:user:<user-id> until a workspace install is authenticated");
|
|
2046
1531
|
}
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
if (hasSlackNativeDataBlock(blocks)) {
|
|
2066
|
-
renderedText = appendSlackNativeDataFallbackText(sourceText, blocks) || renderedText;
|
|
2067
|
-
textIsSlackMrkdwn = true;
|
|
2068
|
-
} else if (message?.authoredTextPlacement === "blocks") {
|
|
2069
|
-
renderedText = sourceText;
|
|
2070
|
-
textIsSlackMrkdwn = false;
|
|
2071
|
-
}
|
|
1532
|
+
//#endregion
|
|
1533
|
+
//#region extensions/slack/src/conversation-binding-route.ts
|
|
1534
|
+
const slackRouteBindingConfigCache = /* @__PURE__ */ new WeakMap();
|
|
1535
|
+
function slackTargetDefaultKindForPeer(kind) {
|
|
1536
|
+
return kind === "direct" ? "user" : "channel";
|
|
1537
|
+
}
|
|
1538
|
+
function slackTargetKindMatchesPeer(peerKind, targetKind) {
|
|
1539
|
+
if (targetKind === "user") return peerKind === "direct";
|
|
1540
|
+
return peerKind === "channel" || peerKind === "group";
|
|
1541
|
+
}
|
|
1542
|
+
function normalizeSlackRouteBindingPeer(peer) {
|
|
1543
|
+
const rawId = peer.id.trim();
|
|
1544
|
+
if (!rawId || rawId === "*") return peer;
|
|
1545
|
+
const target = (() => {
|
|
1546
|
+
try {
|
|
1547
|
+
return parseSlackTarget(rawId, { defaultKind: slackTargetDefaultKindForPeer(peer.kind) });
|
|
1548
|
+
} catch {
|
|
1549
|
+
return;
|
|
2072
1550
|
}
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
}
|
|
2080
|
-
const blockPart = messages.find((message) => message.blocks?.length);
|
|
2081
|
-
return {
|
|
2082
|
-
mode: "split",
|
|
2083
|
-
fallbackText: messages.map((message) => message.text).filter(Boolean).join("\n\n"),
|
|
2084
|
-
...blockPart ? { blockPart: {
|
|
2085
|
-
text: blockPart.text,
|
|
2086
|
-
blocks: blockPart.blocks
|
|
2087
|
-
} } : {}
|
|
1551
|
+
})();
|
|
1552
|
+
if (!target || !slackTargetKindMatchesPeer(peer.kind, target.kind)) return peer;
|
|
1553
|
+
const normalizedId = target.teamId ? `team:${target.teamId}:${target.kind}:${target.id}` : target.id;
|
|
1554
|
+
return normalizedId === peer.id ? peer : {
|
|
1555
|
+
...peer,
|
|
1556
|
+
id: normalizedId
|
|
2088
1557
|
};
|
|
2089
1558
|
}
|
|
2090
|
-
function
|
|
2091
|
-
const
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
});
|
|
2104
|
-
}
|
|
2105
|
-
function buildSlackAuthoredTextBlocks(text) {
|
|
2106
|
-
return markdownToSlackMrkdwnChunks(text, SLACK_SECTION_TEXT_MAX).map((chunk) => ({
|
|
2107
|
-
type: "section",
|
|
2108
|
-
text: {
|
|
2109
|
-
type: "mrkdwn",
|
|
2110
|
-
text: chunk,
|
|
2111
|
-
verbatim: true
|
|
2112
|
-
}
|
|
2113
|
-
}));
|
|
2114
|
-
}
|
|
2115
|
-
function addPreviewVerbatimToAuthoredTextBlocks(blocks, sourceText) {
|
|
2116
|
-
if (!blocks?.length || !sourceText) return blocks;
|
|
2117
|
-
const authoredChunks = new Set(markdownToSlackMrkdwnChunks(sourceText, SLACK_SECTION_TEXT_MAX));
|
|
2118
|
-
return blocks.map((block) => {
|
|
2119
|
-
const text = block.text;
|
|
2120
|
-
if (block.type !== "section" || text?.type !== "mrkdwn" || typeof text.text !== "string" || !authoredChunks.has(text.text)) return block;
|
|
1559
|
+
function normalizeSlackRouteBindingConfig(cfg) {
|
|
1560
|
+
const bindings = cfg.bindings;
|
|
1561
|
+
const cached = slackRouteBindingConfigCache.get(cfg);
|
|
1562
|
+
if (cached && cached.bindingsRef === bindings) return cached.normalizedCfg;
|
|
1563
|
+
if (!Array.isArray(bindings)) return cfg;
|
|
1564
|
+
let changed = false;
|
|
1565
|
+
const normalizedBindings = bindings.map((binding) => {
|
|
1566
|
+
if (binding.type === "acp" || binding.match.channel.trim().toLowerCase() !== "slack") return binding;
|
|
1567
|
+
const peer = binding.match.peer;
|
|
1568
|
+
if (!peer) return binding;
|
|
1569
|
+
const normalizedPeer = normalizeSlackRouteBindingPeer(peer);
|
|
1570
|
+
if (normalizedPeer === peer) return binding;
|
|
1571
|
+
changed = true;
|
|
2121
1572
|
return {
|
|
2122
|
-
...
|
|
2123
|
-
|
|
2124
|
-
...
|
|
2125
|
-
|
|
1573
|
+
...binding,
|
|
1574
|
+
match: {
|
|
1575
|
+
...binding.match,
|
|
1576
|
+
peer: normalizedPeer
|
|
2126
1577
|
}
|
|
2127
1578
|
};
|
|
2128
1579
|
});
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
}
|
|
2137
|
-
function appendTextSegment(segments, text) {
|
|
2138
|
-
const trimmed = text.trim();
|
|
2139
|
-
if (!trimmed) return;
|
|
2140
|
-
const last = segments.at(-1);
|
|
2141
|
-
if (last?.kind === "text") {
|
|
2142
|
-
last.text = `${last.text}\n\n${trimmed}`;
|
|
2143
|
-
return;
|
|
2144
|
-
}
|
|
2145
|
-
segments.push({
|
|
2146
|
-
kind: "text",
|
|
2147
|
-
text: trimmed,
|
|
2148
|
-
mrkdwn: false
|
|
2149
|
-
});
|
|
2150
|
-
}
|
|
2151
|
-
function appendBlockSegment(segments, blocks, startNew = false) {
|
|
2152
|
-
let shouldStartNew = startNew;
|
|
2153
|
-
for (const block of blocks) {
|
|
2154
|
-
const last = segments.at(-1);
|
|
2155
|
-
if (!shouldStartNew && last?.kind === "blocks" && last.blocks.length < 50) last.blocks.push(block);
|
|
2156
|
-
else segments.push({
|
|
2157
|
-
kind: "blocks",
|
|
2158
|
-
blocks: [block]
|
|
2159
|
-
});
|
|
2160
|
-
shouldStartNew = false;
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
function resolvePresentationRenderOptions(segments, mode) {
|
|
2164
|
-
const allOffsets = resolveSlackBlockOffsets(readAllNativeBlocks(segments));
|
|
2165
|
-
return {
|
|
2166
|
-
...mode === "current" ? resolveSlackBlockOffsets(readLastBlockSegment(segments)) : {},
|
|
2167
|
-
buttonIndexOffset: allOffsets.buttonIndexOffset,
|
|
2168
|
-
selectIndexOffset: allOffsets.selectIndexOffset
|
|
2169
|
-
};
|
|
2170
|
-
}
|
|
2171
|
-
function renderNativePresentation(presentation, options) {
|
|
2172
|
-
if (!canRenderSlackPresentation(presentation, options)) return;
|
|
2173
|
-
const blocks = buildSlackPresentationBlocks(presentation, options);
|
|
2174
|
-
return blocks.length > 0 ? blocks : void 0;
|
|
2175
|
-
}
|
|
2176
|
-
function appendPresentationPart(segments, presentation) {
|
|
2177
|
-
const currentBlocks = readLastBlockSegment(segments);
|
|
2178
|
-
const currentRendered = renderNativePresentation(presentation, resolvePresentationRenderOptions(segments, "current"));
|
|
2179
|
-
if (currentRendered && currentBlocks.length + currentRendered.length <= 50) {
|
|
2180
|
-
appendBlockSegment(segments, currentRendered);
|
|
2181
|
-
return;
|
|
2182
|
-
}
|
|
2183
|
-
const freshRendered = renderNativePresentation(presentation, resolvePresentationRenderOptions(segments, "new-message"));
|
|
2184
|
-
if (freshRendered) {
|
|
2185
|
-
appendBlockSegment(segments, freshRendered, true);
|
|
2186
|
-
return;
|
|
2187
|
-
}
|
|
2188
|
-
appendTextSegment(segments, renderMessagePresentationFallbackText({ presentation }));
|
|
2189
|
-
}
|
|
2190
|
-
const SLACK_BUTTON_CONTROL_ACTION_IDS = [
|
|
2191
|
-
SLACK_APPROVAL_BUTTON_ACTION_ID,
|
|
2192
|
-
SLACK_CALLBACK_BUTTON_ACTION_ID,
|
|
2193
|
-
SLACK_QUESTION_BUTTON_ACTION_ID,
|
|
2194
|
-
SLACK_REPLY_BUTTON_ACTION_ID,
|
|
2195
|
-
SLACK_REPLY_LINK_ACTION_ID
|
|
2196
|
-
];
|
|
2197
|
-
const SLACK_SELECT_CONTROL_ACTION_IDS = [
|
|
2198
|
-
SLACK_APPROVAL_SELECT_ACTION_ID,
|
|
2199
|
-
SLACK_CALLBACK_SELECT_ACTION_ID,
|
|
2200
|
-
SLACK_REPLY_SELECT_ACTION_ID
|
|
2201
|
-
];
|
|
2202
|
-
function readGeneratedSlackControlRowKey(block) {
|
|
2203
|
-
const record = block;
|
|
2204
|
-
if (record.type !== "actions" || typeof record.block_id !== "string") return;
|
|
2205
|
-
const expectedElementType = /^openclaw_reply_buttons_[1-9]\d*$/.test(record.block_id) ? "button" : /^openclaw_reply_select_[1-9]\d*$/.test(record.block_id) ? "static_select" : void 0;
|
|
2206
|
-
if (!expectedElementType || !Array.isArray(record.elements) || record.elements.length === 0) return;
|
|
2207
|
-
const actionIds = expectedElementType === "button" ? SLACK_BUTTON_CONTROL_ACTION_IDS : SLACK_SELECT_CONTROL_ACTION_IDS;
|
|
2208
|
-
const elements = record.elements.map((element) => {
|
|
2209
|
-
if (!element || typeof element !== "object" || Array.isArray(element)) return;
|
|
2210
|
-
const { action_id: actionId, ...content } = element;
|
|
2211
|
-
const actionFamily = typeof actionId === "string" ? actionIds.find((candidate) => actionId.startsWith(`${candidate}:`)) : void 0;
|
|
2212
|
-
return actionFamily && content.type === expectedElementType ? [actionFamily, content] : void 0;
|
|
2213
|
-
});
|
|
2214
|
-
return elements.some((element) => element === void 0) ? void 0 : JSON.stringify(elements);
|
|
2215
|
-
}
|
|
2216
|
-
function subtractMirroredSlackControlRows(params) {
|
|
2217
|
-
const remainingMirrors = /* @__PURE__ */ new Map();
|
|
2218
|
-
for (const block of params.presentationBlocks) {
|
|
2219
|
-
const key = readGeneratedSlackControlRowKey(block);
|
|
2220
|
-
if (key) remainingMirrors.set(key, (remainingMirrors.get(key) ?? 0) + 1);
|
|
2221
|
-
}
|
|
2222
|
-
return params.interactiveBlocks.filter((block) => {
|
|
2223
|
-
const key = readGeneratedSlackControlRowKey(block);
|
|
2224
|
-
const remaining = key ? remainingMirrors.get(key) ?? 0 : 0;
|
|
2225
|
-
if (!key || remaining === 0) return true;
|
|
2226
|
-
remainingMirrors.set(key, remaining - 1);
|
|
2227
|
-
return false;
|
|
1580
|
+
const normalizedCfg = changed ? {
|
|
1581
|
+
...cfg,
|
|
1582
|
+
bindings: normalizedBindings
|
|
1583
|
+
} : cfg;
|
|
1584
|
+
slackRouteBindingConfigCache.set(cfg, {
|
|
1585
|
+
bindingsRef: bindings,
|
|
1586
|
+
normalizedCfg
|
|
2228
1587
|
});
|
|
1588
|
+
return normalizedCfg;
|
|
2229
1589
|
}
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
}) !== "blocks") compiledChannelBlocks = [...channelBlocks, ...textBlocks];
|
|
2254
|
-
authoredTextKnownInBlocks = true;
|
|
1590
|
+
function resolveSlackConversationBindingRoute(params) {
|
|
1591
|
+
const boundThreadRoute = params.bindingsEnabled && params.runtimeBindingThreadId ? resolveRuntimeConversationBindingRoute({
|
|
1592
|
+
route: params.route,
|
|
1593
|
+
touchBinding: params.touchBinding,
|
|
1594
|
+
conversation: {
|
|
1595
|
+
channel: "slack",
|
|
1596
|
+
accountId: params.accountId,
|
|
1597
|
+
conversationId: params.runtimeBindingThreadId,
|
|
1598
|
+
parentConversationId: params.baseConversationId
|
|
1599
|
+
}
|
|
1600
|
+
}) : null;
|
|
1601
|
+
const runtimeRoute = !params.bindingsEnabled ? {
|
|
1602
|
+
bindingOwnerAvailable: true,
|
|
1603
|
+
route: params.route,
|
|
1604
|
+
bindingRecord: null,
|
|
1605
|
+
boundSessionKey: void 0
|
|
1606
|
+
} : boundThreadRoute?.boundSessionKey || boundThreadRoute?.bindingRecord ? boundThreadRoute : resolveRuntimeConversationBindingRoute({
|
|
1607
|
+
route: params.route,
|
|
1608
|
+
touchBinding: params.touchBinding,
|
|
1609
|
+
conversation: {
|
|
1610
|
+
channel: "slack",
|
|
1611
|
+
accountId: params.accountId,
|
|
1612
|
+
conversationId: params.baseConversationId
|
|
2255
1613
|
}
|
|
2256
|
-
}
|
|
2257
|
-
if (compiledChannelBlocks.length > 0) appendBlockSegment(segments, compiledChannelBlocks);
|
|
2258
|
-
const presentation = normalizeMessagePresentation(payload.presentation);
|
|
2259
|
-
const presentationBlockOffset = readAllNativeBlocks(segments).length;
|
|
2260
|
-
if (presentation?.title) appendPresentationPart(segments, {
|
|
2261
|
-
title: presentation.title,
|
|
2262
|
-
blocks: []
|
|
2263
|
-
});
|
|
2264
|
-
for (const block of presentation?.blocks ?? []) appendPresentationPart(segments, { blocks: [block] });
|
|
2265
|
-
const renderedPresentationBlocks = readAllNativeBlocks(segments).slice(presentationBlockOffset);
|
|
2266
|
-
appendBlockSegment(segments, subtractMirroredSlackControlRows({
|
|
2267
|
-
interactiveBlocks: buildSlackInteractiveBlocks(payload.interactive, resolveSlackBlockOffsets(readAllNativeBlocks(segments))),
|
|
2268
|
-
presentationBlocks: renderedPresentationBlocks
|
|
2269
|
-
}));
|
|
2270
|
-
const renderedTextFragments = segments.flatMap((segment) => {
|
|
2271
|
-
if (segment.kind === "text") return [segment.text];
|
|
2272
|
-
return renderSlackAuthoredTextFragments(segment.blocks);
|
|
2273
|
-
});
|
|
2274
|
-
const authoredTextPlacement = resolveSlackAuthoredTextPlacement({
|
|
2275
|
-
text: payload.text,
|
|
2276
|
-
interactive: payload.interactive,
|
|
2277
|
-
renderedTextFragments
|
|
2278
1614
|
});
|
|
1615
|
+
const configuredRoute = params.bindingsEnabled && !runtimeRoute.boundSessionKey && !runtimeRoute.bindingRecord ? resolveConfiguredBindingRoute({
|
|
1616
|
+
cfg: params.cfg,
|
|
1617
|
+
route: params.route,
|
|
1618
|
+
conversation: {
|
|
1619
|
+
channel: "slack",
|
|
1620
|
+
accountId: params.accountId,
|
|
1621
|
+
conversationId: params.baseConversationId
|
|
1622
|
+
}
|
|
1623
|
+
}) : null;
|
|
2279
1624
|
return {
|
|
2280
|
-
|
|
2281
|
-
|
|
1625
|
+
runtimeRoute,
|
|
1626
|
+
configuredRoute,
|
|
1627
|
+
route: runtimeRoute.boundSessionKey ? runtimeRoute.route : configuredRoute?.route ?? params.route
|
|
2282
1628
|
};
|
|
2283
1629
|
}
|
|
2284
|
-
/** Return the single-message native shape when no ordered text fallback is required. */
|
|
2285
|
-
function resolveSlackReplyBlocks(payload) {
|
|
2286
|
-
const { segments } = resolveSlackReplyBlockResolution(payload);
|
|
2287
|
-
return segments.length === 1 && segments[0]?.kind === "blocks" ? segments[0].blocks : void 0;
|
|
2288
|
-
}
|
|
2289
|
-
//#endregion
|
|
2290
|
-
//#region extensions/slack/src/detached-target-admission.ts
|
|
2291
|
-
function assertSlackDetachedTargetAllowed(accountId, teamId) {
|
|
2292
|
-
const installationKind = getSlackInstallationKind(accountId);
|
|
2293
|
-
if (installationKind && installationKind !== "workspace" && !teamId) throw new Error("unsupported_enterprise_slack_delivery: detached Slack operations require team:<team-id>:channel:<channel-id> or team:<team-id>:user:<user-id> until a workspace install is authenticated");
|
|
2294
|
-
}
|
|
2295
|
-
//#endregion
|
|
2296
|
-
//#region extensions/slack/src/monitor/allow-list.ts
|
|
2297
|
-
const SLACK_SLUG_CACHE_MAX = 512;
|
|
2298
|
-
const SLACK_STABLE_USER_ID_RE = /^[ubw][a-z0-9]+$/;
|
|
2299
|
-
const slackSlugCache = /* @__PURE__ */ new Map();
|
|
2300
|
-
function normalizeSlackSlug(raw) {
|
|
2301
|
-
const key = raw ?? "";
|
|
2302
|
-
const cached = slackSlugCache.get(key);
|
|
2303
|
-
if (cached !== void 0) return cached;
|
|
2304
|
-
const normalized = normalizeHyphenSlug(raw);
|
|
2305
|
-
slackSlugCache.set(key, normalized);
|
|
2306
|
-
if (slackSlugCache.size > SLACK_SLUG_CACHE_MAX) {
|
|
2307
|
-
const oldest = slackSlugCache.keys().next();
|
|
2308
|
-
if (!oldest.done) slackSlugCache.delete(oldest.value);
|
|
2309
|
-
}
|
|
2310
|
-
return normalized;
|
|
2311
|
-
}
|
|
2312
|
-
function normalizeAllowList(list) {
|
|
2313
|
-
return normalizeStringEntries$1(list);
|
|
2314
|
-
}
|
|
2315
|
-
function normalizeAllowListLower(list) {
|
|
2316
|
-
return normalizeStringEntriesLower(list);
|
|
2317
|
-
}
|
|
2318
|
-
function normalizeSlackAllowOwnerEntry(entry) {
|
|
2319
|
-
const trimmed = normalizeOptionalLowercaseString(entry);
|
|
2320
|
-
if (!trimmed || trimmed === "*") return;
|
|
2321
|
-
try {
|
|
2322
|
-
const target = parseSlackTarget(trimmed);
|
|
2323
|
-
if (target?.kind === "user" && target.teamId) return target.id.toLowerCase();
|
|
2324
|
-
} catch {
|
|
2325
|
-
return;
|
|
2326
|
-
}
|
|
2327
|
-
const withoutPrefix = trimmed.replace(/^(slack:|user:)/, "");
|
|
2328
|
-
return SLACK_STABLE_USER_ID_RE.test(withoutPrefix) ? withoutPrefix : void 0;
|
|
2329
|
-
}
|
|
2330
|
-
function resolveSlackAllowListMatch(params) {
|
|
2331
|
-
const compiledAllowList = compileAllowlist(params.allowList);
|
|
2332
|
-
const teamId = normalizeOptionalLowercaseString(params.teamId);
|
|
2333
|
-
const id = normalizeOptionalLowercaseString(params.id);
|
|
2334
|
-
const name = normalizeOptionalLowercaseString(params.name);
|
|
2335
|
-
const slug = normalizeSlackSlug(name);
|
|
2336
|
-
const scopedCandidates = [{
|
|
2337
|
-
value: teamId && id ? `team:${teamId}:user:${id}` : void 0,
|
|
2338
|
-
source: "workspace-id"
|
|
2339
|
-
}];
|
|
2340
|
-
const unscopedCandidates = [
|
|
2341
|
-
{
|
|
2342
|
-
value: id,
|
|
2343
|
-
source: "id"
|
|
2344
|
-
},
|
|
2345
|
-
{
|
|
2346
|
-
value: id ? `slack:${id}` : void 0,
|
|
2347
|
-
source: "prefixed-id"
|
|
2348
|
-
},
|
|
2349
|
-
{
|
|
2350
|
-
value: id ? `user:${id}` : void 0,
|
|
2351
|
-
source: "prefixed-user"
|
|
2352
|
-
},
|
|
2353
|
-
...params.allowNameMatching === true ? [
|
|
2354
|
-
{
|
|
2355
|
-
value: name,
|
|
2356
|
-
source: "name"
|
|
2357
|
-
},
|
|
2358
|
-
{
|
|
2359
|
-
value: name ? `slack:${name}` : void 0,
|
|
2360
|
-
source: "prefixed-name"
|
|
2361
|
-
},
|
|
2362
|
-
{
|
|
2363
|
-
value: slug,
|
|
2364
|
-
source: "slug"
|
|
2365
|
-
}
|
|
2366
|
-
] : []
|
|
2367
|
-
];
|
|
2368
|
-
return resolveCompiledAllowlistMatch({
|
|
2369
|
-
compiledAllowlist: compiledAllowList,
|
|
2370
|
-
candidates: [...scopedCandidates, ...unscopedCandidates]
|
|
2371
|
-
});
|
|
2372
|
-
}
|
|
2373
|
-
function allowListMatches(params) {
|
|
2374
|
-
return resolveSlackAllowListMatch(params).allowed;
|
|
2375
|
-
}
|
|
2376
|
-
function resolveSlackUserAllowed(params) {
|
|
2377
|
-
const allowList = normalizeAllowListLower(params.allowList);
|
|
2378
|
-
if (allowList.length === 0) return true;
|
|
2379
|
-
return allowListMatches({
|
|
2380
|
-
allowList,
|
|
2381
|
-
teamId: params.teamId,
|
|
2382
|
-
id: params.userId,
|
|
2383
|
-
name: params.userName,
|
|
2384
|
-
allowNameMatching: params.allowNameMatching
|
|
2385
|
-
});
|
|
2386
|
-
}
|
|
2387
|
-
function resolveSlackUserAllowListForTeam(params) {
|
|
2388
|
-
const allowList = normalizeAllowListLower(params.allowList);
|
|
2389
|
-
const teamId = normalizeOptionalLowercaseString(params.teamId);
|
|
2390
|
-
return allowList.flatMap((entry) => {
|
|
2391
|
-
if (entry === "*") return [entry];
|
|
2392
|
-
if (!entry.startsWith("team:")) return [entry];
|
|
2393
|
-
try {
|
|
2394
|
-
const target = parseSlackTarget(entry);
|
|
2395
|
-
if (target?.kind === "user" && target.teamId?.toLowerCase() === teamId) return [entry];
|
|
2396
|
-
return params.preserveUnmatchedScopedEntries ? [entry] : [];
|
|
2397
|
-
} catch {
|
|
2398
|
-
return params.preserveUnmatchedScopedEntries ? [entry] : [];
|
|
2399
|
-
}
|
|
2400
|
-
});
|
|
2401
|
-
}
|
|
2402
1630
|
//#endregion
|
|
2403
1631
|
//#region extensions/slack/src/monitor/workspace-routing.ts
|
|
2404
1632
|
function resolveSlackEnterpriseMainDmSessionKey(params) {
|
|
@@ -2455,8 +1683,9 @@ function resolveSlackGroupRequireMention(params) {
|
|
|
2455
1683
|
return resolveScopeRequireMention(resolveSlackGroupPolicyScope(params));
|
|
2456
1684
|
}
|
|
2457
1685
|
function resolveSlackGroupToolPolicy(params) {
|
|
1686
|
+
const scope = resolveSlackGroupPolicyScope(params);
|
|
2458
1687
|
return resolveScopeToolsPolicy({
|
|
2459
|
-
...
|
|
1688
|
+
...scope,
|
|
2460
1689
|
senderPolicyMode: params.senderPolicyMode,
|
|
2461
1690
|
senderId: params.senderId,
|
|
2462
1691
|
senderName: params.senderName,
|
|
@@ -2465,4 +1694,46 @@ function resolveSlackGroupToolPolicy(params) {
|
|
|
2465
1694
|
});
|
|
2466
1695
|
}
|
|
2467
1696
|
//#endregion
|
|
2468
|
-
|
|
1697
|
+
//#region extensions/slack/src/session-status.ts
|
|
1698
|
+
const MISSING_STOP_SUBSCRIPTION = "missing_agent_session_stopped_event_subscription";
|
|
1699
|
+
let warnedMissingStopSubscription = false;
|
|
1700
|
+
async function setSlackSessionStatus(params) {
|
|
1701
|
+
if (!params.threadTs) return { ok: false };
|
|
1702
|
+
const request = {
|
|
1703
|
+
channel_id: params.channelId,
|
|
1704
|
+
thread_ts: params.threadTs,
|
|
1705
|
+
status: params.status,
|
|
1706
|
+
...params.token ? { token: params.token } : {},
|
|
1707
|
+
...params.title !== void 0 ? { title: truncateUtf16Safe(params.title, 200) } : {}
|
|
1708
|
+
};
|
|
1709
|
+
try {
|
|
1710
|
+
const response = await params.client.apiCall("agents.sessions.setStatus", request);
|
|
1711
|
+
if (!warnedMissingStopSubscription && (response.warning === MISSING_STOP_SUBSCRIPTION || response.response_metadata?.warnings?.includes(MISSING_STOP_SUBSCRIPTION))) {
|
|
1712
|
+
warnedMissingStopSubscription = true;
|
|
1713
|
+
(params.runtime ?? defaultRuntime).log?.(warn("Slack's Stop button is unavailable until the app subscribes to agent_session_stopped. See https://docs.openclaw.ai/channels/slack#additional-manifest-settings"));
|
|
1714
|
+
}
|
|
1715
|
+
return response.ok ? {
|
|
1716
|
+
ok: true,
|
|
1717
|
+
title: response.title
|
|
1718
|
+
} : { ok: false };
|
|
1719
|
+
} catch (error) {
|
|
1720
|
+
logVerbose(`slack status update failed for channel ${params.channelId}: ${formatSlackError(error)}`);
|
|
1721
|
+
return { ok: false };
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
async function renameSlackSession(params) {
|
|
1725
|
+
const request = {
|
|
1726
|
+
channel_id: params.channelId,
|
|
1727
|
+
thread_ts: params.threadTs,
|
|
1728
|
+
title: truncateUtf16Safe(params.title, 200),
|
|
1729
|
+
...params.token ? { token: params.token } : {}
|
|
1730
|
+
};
|
|
1731
|
+
try {
|
|
1732
|
+
return (await params.client.apiCall("agents.sessions.rename", request)).ok;
|
|
1733
|
+
} catch (error) {
|
|
1734
|
+
logVerbose(`slack session rename failed for channel ${params.channelId}: ${formatSlackError(error)}`);
|
|
1735
|
+
return false;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
//#endregion
|
|
1739
|
+
export { SLACK_DATA_TABLE_AGGREGATE_CELL_CHARACTERS_MAX as $, countSlackTextUtf8Bytes as A, shouldSuppressLocalSlackExecApprovalPrompt as At, SLACK_REPLY_LINK_ACTION_ID as B, buildSlackBlocksFallbackText as C, getSlackApprovalApproversForTeam as Ct, SLACK_MESSAGE_TEXT_HARD_LIMIT as D, isSlackExecApprovalClientEnabled as Dt, SLACK_EDIT_TEXT_MAX_BYTES as E, isSlackExecApprovalAuthorizedSender as Et, SLACK_CALLBACK_BUTTON_ACTION_ID as F, isSlackQuestionActionId as G, SLACK_SESSION_LINK_ACTION_ID as H, SLACK_CALLBACK_SELECT_ACTION_ID as I, markdownToSlackMrkdwnChunks as J, resolveSlackQuestionActionIds as K, SLACK_QUESTION_BUTTON_ACTION_ID as L, truncateSlackTextByUtf8Bytes as M, resolveSlackAutoThreadId as Mt, SLACK_APPROVAL_BUTTON_ACTION_ID as N, resolveSlackReplyToMode as Nt, SLACK_MESSAGE_TEXT_RECOMMENDED_LIMIT as O, normalizeSlackApproverId as Ot, SLACK_APPROVAL_SELECT_ACTION_ID as P, hasSlackDataVisualizationBlock as Q, SLACK_QUESTION_FINALIZATION_BLOCKS as R, stripSlackNativeDataBlocks as S, registerSlackInstallationState as St, renderSlackBlockFallbackText as T, getSlackExecApprovalApprovers as Tt, isSlackApprovalActionId as U, SLACK_REPLY_SELECT_ACTION_ID as V, isSlackCallbackActionId as W, buildSlackDataVisualizationBlock as X, normalizeSlackOutboundText as Y, canRenderSlackDataVisualization as Z, createSlackNativeDataBaseTextConsumer as _, shouldHandleSlackNativeApprovalRequest as _t, resolveSlackGroupRequireMention as a, escapeSlackMrkdwn as at, isSlackInvalidBlocksResponse as b, getSlackInstallationKind as bt, qualifySlackRoutePeerId as c, validateSlackBlocksArray as ct, resolveSlackConversationBindingRoute as d, normalizeSlackForwardTarget as dt, buildSlackDataTableBlock as et, assertSlackDetachedTargetAllowed as f, normalizeSlackOriginTarget as ft, buildSlackNativeDataAccessibilityText as g, resolveTurnSourceSlackOriginTarget as gt, appendSlackNativeDataPlainTextFallback as h, resolveSlackFallbackOriginTarget as ht, buildSlackChannelPolicyScope as i, renderSlackMessagePresentationFallbackText as it, truncateSlackText as j, SLACK_PRIVATE_ACTION_DELIVERY_RESULT as jt, SLACK_TEXT_LIMIT as k, resolveSlackExecApprovalTarget as kt, resolveSlackEnterpriseMainDmSessionKey as l, hasSlackPluginApprovers as lt, appendSlackNativeDataFallbackText as m, resolveSessionSlackOriginTarget as mt, setSlackSessionStatus as n, countSlackDataTableCellCharacters as nt, resolveSlackGroupToolPolicy as o, SLACK_MAX_BLOCKS as ot, SLACK_MALFORMED_NATIVE_DATA_FALLBACK as p, resolveEnterpriseApprovalTeamId as pt, chunkSlackMrkdwnText as q, buildSlackChannelIdCandidates as r, renderSlackMessagePresentationChartFallbackText as rt, qualifySlackConversationId as s, parseSlackBlocksInput as st, renameSlackSession as t, countSlackDataTableBlocksCellCharacters as tt, normalizeSlackRouteBindingConfig as u, isSlackAnyNativeApprovalClientEnabled as ut, hasSlackNativeDataBlock as v, shouldHandleSlackPluginViaForwardingSession as vt, buildSlackCompleteBlocksFallbackText as w, isSlackApprovalAuthorizedSender as wt, isSlackNativeResponseUrlRejection as x, isSlackWorkspaceInstallation as xt, isSlackInvalidBlocksError as y, slackTargetsMatch as yt, SLACK_REPLY_BUTTON_ACTION_ID as z };
|