@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
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
import { B as SLACK_REPLY_LINK_ACTION_ID, C as buildSlackBlocksFallbackText, F as SLACK_CALLBACK_BUTTON_ACTION_ID, I as SLACK_CALLBACK_SELECT_ACTION_ID, J as markdownToSlackMrkdwnChunks, L as SLACK_QUESTION_BUTTON_ACTION_ID, N as SLACK_APPROVAL_BUTTON_ACTION_ID, P as SLACK_APPROVAL_SELECT_ACTION_ID, Q as hasSlackDataVisualizationBlock, T as renderSlackBlockFallbackText, V as SLACK_REPLY_SELECT_ACTION_ID, X as buildSlackDataVisualizationBlock, Z as canRenderSlackDataVisualization, et as buildSlackDataTableBlock, g as buildSlackNativeDataAccessibilityText, it as renderSlackMessagePresentationFallbackText, j as truncateSlackText, m as appendSlackNativeDataFallbackText, nt as countSlackDataTableCellCharacters, q as chunkSlackMrkdwnText, rt as renderSlackMessagePresentationChartFallbackText, st as parseSlackBlocksInput, tt as countSlackDataTableBlocksCellCharacters, v as hasSlackNativeDataBlock, z as SLACK_REPLY_BUTTON_ACTION_ID } from "./session-status-BynwcZSO.js";
|
|
2
|
+
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
3
|
+
import { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
|
|
4
|
+
import { legacyInteractiveReplyToPresentation, normalizeMessagePresentation, renderMessagePresentationFallbackText, resolveMessagePresentationButtonAction, resolveMessagePresentationOptionAction } from "openclaw/plugin-sdk/interactive-runtime";
|
|
5
|
+
import { parseExecApprovalCommandText } from "openclaw/plugin-sdk/approval-reply-runtime";
|
|
6
|
+
import { resolveAskUserQuestionOptionIndex, resolveAskUserQuestionOptionIndices } from "openclaw/plugin-sdk/reply-payload";
|
|
7
|
+
import { buildApprovalResolutionRef } from "openclaw/plugin-sdk/approval-reference-runtime";
|
|
8
|
+
import { questionGatewayRuntime } from "openclaw/plugin-sdk/question-gateway-runtime";
|
|
9
|
+
const SLACK_BUTTON_VALUE_MAX = 2e3;
|
|
10
|
+
const SLACK_SECTION_TEXT_MAX = 3e3;
|
|
11
|
+
const SLACK_PRESENTATION_CAPABILITIES = {
|
|
12
|
+
supported: true,
|
|
13
|
+
buttons: true,
|
|
14
|
+
selects: true,
|
|
15
|
+
context: true,
|
|
16
|
+
divider: true,
|
|
17
|
+
charts: true,
|
|
18
|
+
tables: true,
|
|
19
|
+
limits: {
|
|
20
|
+
actions: {
|
|
21
|
+
maxActionsPerRow: 25,
|
|
22
|
+
maxValueBytes: SLACK_BUTTON_VALUE_MAX,
|
|
23
|
+
supportsStyles: true
|
|
24
|
+
},
|
|
25
|
+
selects: {
|
|
26
|
+
maxOptions: 100,
|
|
27
|
+
maxValueBytes: 150
|
|
28
|
+
},
|
|
29
|
+
text: {
|
|
30
|
+
encoding: "characters",
|
|
31
|
+
markdownDialect: "slack-mrkdwn",
|
|
32
|
+
supportsEdit: true
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region extensions/slack/src/approval-actions.ts
|
|
38
|
+
const SLACK_APPROVAL_VALUE_PREFIX = "openclaw:approval:v1:";
|
|
39
|
+
const SLACK_APPROVAL_HEADER_BLOCK_ID = "openclaw_approval_header";
|
|
40
|
+
function isApprovalDecision(value) {
|
|
41
|
+
return value === "allow-once" || value === "allow-always" || value === "deny";
|
|
42
|
+
}
|
|
43
|
+
/** Encode portable approval facts without exposing a slash command to Slack callbacks. */
|
|
44
|
+
function encodeSlackApprovalAction(action) {
|
|
45
|
+
const encode = (approvalId) => `${SLACK_APPROVAL_VALUE_PREFIX}${JSON.stringify({
|
|
46
|
+
approvalId,
|
|
47
|
+
approvalKind: action.approvalKind,
|
|
48
|
+
decision: action.decision
|
|
49
|
+
})}`;
|
|
50
|
+
const exact = encode(action.approvalId);
|
|
51
|
+
return exact.length <= 2e3 ? exact : encode(buildApprovalResolutionRef({
|
|
52
|
+
approvalId: action.approvalId,
|
|
53
|
+
approvalKind: action.approvalKind
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
/** Decode only the exact Slack-owned approval envelope. Malformed callbacks fail closed. */
|
|
57
|
+
function decodeSlackApprovalAction(value) {
|
|
58
|
+
if (typeof value !== "string" || !value.startsWith(SLACK_APPROVAL_VALUE_PREFIX)) return null;
|
|
59
|
+
try {
|
|
60
|
+
const decoded = JSON.parse(value.slice(21));
|
|
61
|
+
if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) return null;
|
|
62
|
+
const record = decoded;
|
|
63
|
+
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;
|
|
64
|
+
return {
|
|
65
|
+
type: "approval",
|
|
66
|
+
approvalId: record.approvalId,
|
|
67
|
+
approvalKind: record.approvalKind,
|
|
68
|
+
decision: record.decision
|
|
69
|
+
};
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region extensions/slack/src/question-actions.ts
|
|
76
|
+
const SLACK_QUESTION_VALUE_PREFIX = "slq1:";
|
|
77
|
+
const QUESTION_RECORD_ID_PATTERN = /^ask_[a-f0-9]{32}$/u;
|
|
78
|
+
function encodeSlackQuestionAction(action) {
|
|
79
|
+
if (!QUESTION_RECORD_ID_PATTERN.test(action.questionId) || !Number.isInteger(action.optionIndex) || action.optionIndex < 0 || action.optionIndex > 3) return;
|
|
80
|
+
const value = `${SLACK_QUESTION_VALUE_PREFIX}${action.questionId}:${action.optionIndex}`;
|
|
81
|
+
return value.length <= 2e3 ? value : void 0;
|
|
82
|
+
}
|
|
83
|
+
function decodeSlackQuestionAction(value) {
|
|
84
|
+
if (typeof value !== "string" || value.length > 2e3) return null;
|
|
85
|
+
const match = /^slq1:(ask_[a-f0-9]{32}):([0-3])$/u.exec(value);
|
|
86
|
+
return match?.[1] && match[2] ? {
|
|
87
|
+
questionId: match[1],
|
|
88
|
+
optionIndex: Number(match[2])
|
|
89
|
+
} : null;
|
|
90
|
+
}
|
|
91
|
+
async function resolveSlackQuestionAction(params) {
|
|
92
|
+
let result;
|
|
93
|
+
try {
|
|
94
|
+
result = await (params.resolveQuestion ?? questionGatewayRuntime.resolveOption)({
|
|
95
|
+
cfg: params.cfg,
|
|
96
|
+
questionId: params.action.questionId,
|
|
97
|
+
optionIndex: params.action.optionIndex,
|
|
98
|
+
senderId: params.userId,
|
|
99
|
+
clientDisplayName: `Slack question (${params.accountId})`
|
|
100
|
+
});
|
|
101
|
+
} catch {
|
|
102
|
+
await params.respond("Could not submit this answer.").catch(() => {});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
await params.respond(result.status === "answered" ? "Answer submitted." : "This question was already answered.").catch(() => {});
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region extensions/slack/src/blocks-render.ts
|
|
109
|
+
const SLACK_BUTTON_URL_MAX = 3e3;
|
|
110
|
+
function buildSlackReplyButtonActionId(buttonIndex, choiceIndex) {
|
|
111
|
+
return `${SLACK_REPLY_BUTTON_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
|
|
112
|
+
}
|
|
113
|
+
function buildSlackReplyLinkActionId(buttonIndex, choiceIndex) {
|
|
114
|
+
return `${SLACK_REPLY_LINK_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
|
|
115
|
+
}
|
|
116
|
+
function buildSlackReplySelectActionId(selectIndex) {
|
|
117
|
+
return `${SLACK_REPLY_SELECT_ACTION_ID}:${String(selectIndex)}`;
|
|
118
|
+
}
|
|
119
|
+
function buildSlackApprovalButtonActionId(buttonIndex, choiceIndex) {
|
|
120
|
+
return `${SLACK_APPROVAL_BUTTON_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
|
|
121
|
+
}
|
|
122
|
+
function buildSlackApprovalSelectActionId(selectIndex) {
|
|
123
|
+
return `${SLACK_APPROVAL_SELECT_ACTION_ID}:${String(selectIndex)}`;
|
|
124
|
+
}
|
|
125
|
+
function buildSlackCallbackButtonActionId(buttonIndex, choiceIndex) {
|
|
126
|
+
return `${SLACK_CALLBACK_BUTTON_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
|
|
127
|
+
}
|
|
128
|
+
function buildSlackCallbackSelectActionId(selectIndex) {
|
|
129
|
+
return `${SLACK_CALLBACK_SELECT_ACTION_ID}:${String(selectIndex)}`;
|
|
130
|
+
}
|
|
131
|
+
function buildSlackQuestionButtonActionId(buttonIndex, choiceIndex) {
|
|
132
|
+
return `${SLACK_QUESTION_BUTTON_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
|
|
133
|
+
}
|
|
134
|
+
function resolveSlackButtonStyle(style) {
|
|
135
|
+
if (style === "primary" || style === "danger") return style;
|
|
136
|
+
if (style === "success") return "primary";
|
|
137
|
+
}
|
|
138
|
+
function resolveSlackActionTarget(action, questionOptionIndices) {
|
|
139
|
+
if (!action) return;
|
|
140
|
+
if (action.type === "approval") return {
|
|
141
|
+
kind: "approval",
|
|
142
|
+
value: encodeSlackApprovalAction(action)
|
|
143
|
+
};
|
|
144
|
+
if (action.type === "question") {
|
|
145
|
+
if ("intent" in action) return;
|
|
146
|
+
const optionIndex = resolveAskUserQuestionOptionIndex({
|
|
147
|
+
questionOptionIndices,
|
|
148
|
+
questionId: action.questionId,
|
|
149
|
+
optionValue: action.optionValue
|
|
150
|
+
});
|
|
151
|
+
const value = optionIndex === void 0 ? void 0 : encodeSlackQuestionAction({
|
|
152
|
+
questionId: action.questionId,
|
|
153
|
+
optionIndex
|
|
154
|
+
});
|
|
155
|
+
return value ? {
|
|
156
|
+
kind: "question",
|
|
157
|
+
value
|
|
158
|
+
} : void 0;
|
|
159
|
+
}
|
|
160
|
+
if (action.type === "url" || action.type === "web-app") {
|
|
161
|
+
const url = normalizeOptionalString(action.url);
|
|
162
|
+
return url ? {
|
|
163
|
+
kind: "link",
|
|
164
|
+
url
|
|
165
|
+
} : void 0;
|
|
166
|
+
}
|
|
167
|
+
if (action.type === "callback") {
|
|
168
|
+
const value = normalizeOptionalString(action.value);
|
|
169
|
+
return value ? {
|
|
170
|
+
kind: "callback",
|
|
171
|
+
value
|
|
172
|
+
} : void 0;
|
|
173
|
+
}
|
|
174
|
+
const command = normalizeOptionalString(action.command);
|
|
175
|
+
return command && parseExecApprovalCommandText(command) ? {
|
|
176
|
+
kind: "reply",
|
|
177
|
+
value: command
|
|
178
|
+
} : void 0;
|
|
179
|
+
}
|
|
180
|
+
function resolveSlackButtonTarget(button, questionOptionIndices) {
|
|
181
|
+
if (button.action !== void 0) {
|
|
182
|
+
const action = resolveMessagePresentationButtonAction(button);
|
|
183
|
+
return action ? resolveSlackActionTarget(action, questionOptionIndices) : void 0;
|
|
184
|
+
}
|
|
185
|
+
const legacyUrl = normalizeOptionalString(button.url ?? button.webApp?.url ?? button.web_app?.url);
|
|
186
|
+
if (legacyUrl && isWithinSlackLimit(legacyUrl, SLACK_BUTTON_URL_MAX)) return {
|
|
187
|
+
kind: "link",
|
|
188
|
+
url: legacyUrl
|
|
189
|
+
};
|
|
190
|
+
const legacyValue = normalizeOptionalString(button.value);
|
|
191
|
+
if (legacyValue) return {
|
|
192
|
+
kind: "reply",
|
|
193
|
+
value: legacyValue
|
|
194
|
+
};
|
|
195
|
+
return legacyUrl ? {
|
|
196
|
+
kind: "link",
|
|
197
|
+
url: legacyUrl
|
|
198
|
+
} : void 0;
|
|
199
|
+
}
|
|
200
|
+
function isSlackTextFallbackButton(button) {
|
|
201
|
+
const action = resolveMessagePresentationButtonAction(button);
|
|
202
|
+
return action?.type === "question" && "intent" in action && action.intent === "custom-input";
|
|
203
|
+
}
|
|
204
|
+
function resolveSlackOptionTarget(option) {
|
|
205
|
+
if (option.action !== void 0) {
|
|
206
|
+
const action = resolveMessagePresentationOptionAction(option);
|
|
207
|
+
const target = action ? resolveSlackActionTarget(action) : void 0;
|
|
208
|
+
return target?.kind === "link" || target?.kind === "question" ? void 0 : target;
|
|
209
|
+
}
|
|
210
|
+
const value = normalizeOptionalString(option.value);
|
|
211
|
+
return value ? {
|
|
212
|
+
kind: "reply",
|
|
213
|
+
value
|
|
214
|
+
} : void 0;
|
|
215
|
+
}
|
|
216
|
+
function isWithinSlackLimit(value, maxLength) {
|
|
217
|
+
return value.length <= maxLength;
|
|
218
|
+
}
|
|
219
|
+
function isRenderableSlackOption(option) {
|
|
220
|
+
return isWithinSlackLimit(option.value, 150);
|
|
221
|
+
}
|
|
222
|
+
function readSlackBlockId(block) {
|
|
223
|
+
const value = block.block_id;
|
|
224
|
+
return typeof value === "string" ? value : void 0;
|
|
225
|
+
}
|
|
226
|
+
function readSlackOpenClawBlockIndex(blockId, prefix) {
|
|
227
|
+
if (!blockId.startsWith(prefix)) return;
|
|
228
|
+
const value = Number.parseInt(blockId.slice(prefix.length), 10);
|
|
229
|
+
return Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
230
|
+
}
|
|
231
|
+
/** Resolve existing Block Kit indexes and native-data budgets before appending portable blocks. */
|
|
232
|
+
function resolveSlackBlockOffsets(blocks) {
|
|
233
|
+
let buttonIndexOffset = 0;
|
|
234
|
+
const dataTableCellCharacterCountOffset = countSlackDataTableBlocksCellCharacters(blocks) ?? 10001;
|
|
235
|
+
let dataVisualizationCountOffset = 0;
|
|
236
|
+
let selectIndexOffset = 0;
|
|
237
|
+
for (const block of blocks ?? []) {
|
|
238
|
+
if (hasSlackDataVisualizationBlock([block])) dataVisualizationCountOffset += 1;
|
|
239
|
+
const blockId = readSlackBlockId(block);
|
|
240
|
+
if (!blockId) continue;
|
|
241
|
+
buttonIndexOffset = Math.max(buttonIndexOffset, readSlackOpenClawBlockIndex(blockId, "openclaw_reply_buttons_") ?? 0);
|
|
242
|
+
selectIndexOffset = Math.max(selectIndexOffset, readSlackOpenClawBlockIndex(blockId, "openclaw_reply_select_") ?? 0);
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
buttonIndexOffset,
|
|
246
|
+
dataTableCellCharacterCountOffset,
|
|
247
|
+
dataVisualizationCountOffset,
|
|
248
|
+
selectIndexOffset
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* @deprecated Use buildSlackPresentationBlocks with MessagePresentation.
|
|
253
|
+
*/
|
|
254
|
+
function buildSlackInteractiveBlocks(interactive, options = {}) {
|
|
255
|
+
return buildSlackPresentationBlocks(interactive ? legacyInteractiveReplyToPresentation(interactive) : void 0, options);
|
|
256
|
+
}
|
|
257
|
+
/** Render portable presentation blocks as Slack Block Kit blocks. */
|
|
258
|
+
function buildSlackPresentationBlocks(presentation, options = {}) {
|
|
259
|
+
if (!presentation) return [];
|
|
260
|
+
const renderTablesNatively = canRenderSlackPresentationTables(presentation, options);
|
|
261
|
+
const blocks = [];
|
|
262
|
+
if (presentation.title) blocks.push({
|
|
263
|
+
type: "header",
|
|
264
|
+
text: {
|
|
265
|
+
type: "plain_text",
|
|
266
|
+
text: truncateSlackText(presentation.title, 150),
|
|
267
|
+
emoji: true
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
let buttonIndex = options.buttonIndexOffset ?? 0;
|
|
271
|
+
let dataTableCellCharacterCount = options.dataTableCellCharacterCountOffset ?? 0;
|
|
272
|
+
let dataVisualizationCount = options.dataVisualizationCountOffset ?? 0;
|
|
273
|
+
let selectIndex = options.selectIndexOffset ?? 0;
|
|
274
|
+
for (const block of presentation.blocks) {
|
|
275
|
+
if (block.type === "text" || block.type === "context") {
|
|
276
|
+
const text = block.text.trim();
|
|
277
|
+
if (!text) continue;
|
|
278
|
+
for (const chunk of chunkSlackMrkdwnText(text, SLACK_SECTION_TEXT_MAX)) blocks.push(block.type === "context" ? {
|
|
279
|
+
type: "context",
|
|
280
|
+
elements: [{
|
|
281
|
+
type: "mrkdwn",
|
|
282
|
+
text: chunk,
|
|
283
|
+
verbatim: true
|
|
284
|
+
}]
|
|
285
|
+
} : {
|
|
286
|
+
type: "section",
|
|
287
|
+
text: {
|
|
288
|
+
type: "mrkdwn",
|
|
289
|
+
text: chunk
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (block.type === "divider") {
|
|
295
|
+
blocks.push({ type: "divider" });
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (block.type === "buttons") {
|
|
299
|
+
const rendered = buildSlackPresentationButtonBlock(block, buttonIndex + 1, options.questionOptionIndices);
|
|
300
|
+
if (rendered) {
|
|
301
|
+
buttonIndex += 1;
|
|
302
|
+
blocks.push(rendered);
|
|
303
|
+
}
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (block.type === "chart") {
|
|
307
|
+
const rendered = dataVisualizationCount < 2 ? buildSlackPresentationChartBlock(block) : void 0;
|
|
308
|
+
if (rendered) {
|
|
309
|
+
dataVisualizationCount += 1;
|
|
310
|
+
blocks.push(rendered);
|
|
311
|
+
} else {
|
|
312
|
+
const fallback = renderSlackMessagePresentationChartFallbackText(block);
|
|
313
|
+
blocks.push(...chunkTextForOutbound(fallback, SLACK_SECTION_TEXT_MAX).map((text) => ({
|
|
314
|
+
type: "context",
|
|
315
|
+
elements: [{
|
|
316
|
+
type: "mrkdwn",
|
|
317
|
+
text,
|
|
318
|
+
verbatim: true
|
|
319
|
+
}]
|
|
320
|
+
})));
|
|
321
|
+
}
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (block.type === "table") {
|
|
325
|
+
if (!renderTablesNatively) continue;
|
|
326
|
+
const rendered = buildSlackDataTableBlock(block, { cellCharacterCountOffset: dataTableCellCharacterCount });
|
|
327
|
+
if (rendered) {
|
|
328
|
+
dataTableCellCharacterCount += countSlackDataTableCellCharacters(rendered);
|
|
329
|
+
blocks.push(rendered);
|
|
330
|
+
}
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (block.type === "select") {
|
|
334
|
+
const rendered = buildSlackPresentationSelectBlock(block, selectIndex + 1);
|
|
335
|
+
if (rendered) {
|
|
336
|
+
selectIndex += 1;
|
|
337
|
+
blocks.push(rendered);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return blocks;
|
|
342
|
+
}
|
|
343
|
+
function buildSlackPresentationChartBlock(block) {
|
|
344
|
+
return buildSlackDataVisualizationBlock(block);
|
|
345
|
+
}
|
|
346
|
+
function buildSlackPresentationButtonBlock(block, buttonIndex, questionOptionIndices) {
|
|
347
|
+
const elements = block.buttons.flatMap((button, choiceIndex) => {
|
|
348
|
+
const target = resolveSlackButtonTarget(button, questionOptionIndices);
|
|
349
|
+
if (!target || (target.kind === "link" ? !isWithinSlackLimit(target.url, SLACK_BUTTON_URL_MAX) : !isWithinSlackLimit(target.value, 2e3))) return [];
|
|
350
|
+
const style = resolveSlackButtonStyle(button.style);
|
|
351
|
+
return [{
|
|
352
|
+
type: "button",
|
|
353
|
+
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),
|
|
354
|
+
text: {
|
|
355
|
+
type: "plain_text",
|
|
356
|
+
text: truncateSlackText(button.label, 75),
|
|
357
|
+
emoji: true
|
|
358
|
+
},
|
|
359
|
+
...target.kind === "link" ? { url: target.url } : { value: target.value },
|
|
360
|
+
...style ? { style } : {}
|
|
361
|
+
}];
|
|
362
|
+
}).slice(0, 25);
|
|
363
|
+
return elements.length > 0 ? {
|
|
364
|
+
type: "actions",
|
|
365
|
+
block_id: `openclaw_reply_buttons_${buttonIndex}`,
|
|
366
|
+
elements
|
|
367
|
+
} : void 0;
|
|
368
|
+
}
|
|
369
|
+
/** True when every portable table fits Slack's native per-message table budget. */
|
|
370
|
+
function canRenderSlackPresentationTables(presentation, options = {}) {
|
|
371
|
+
let cellCharacterCount = options.dataTableCellCharacterCountOffset ?? 0;
|
|
372
|
+
for (const block of presentation.blocks) {
|
|
373
|
+
if (block.type !== "table") continue;
|
|
374
|
+
const rendered = buildSlackDataTableBlock(block, { cellCharacterCountOffset: cellCharacterCount });
|
|
375
|
+
if (!rendered) return false;
|
|
376
|
+
cellCharacterCount += countSlackDataTableCellCharacters(rendered);
|
|
377
|
+
}
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
/** True when native Slack rendering preserves every portable control. */
|
|
381
|
+
function canRenderSlackPresentation(presentation, options = {}) {
|
|
382
|
+
if (presentation.title && !isWithinSlackLimit(presentation.title.trim(), 150)) return false;
|
|
383
|
+
if (!canRenderSlackPresentationTables(presentation, options)) return false;
|
|
384
|
+
let dataVisualizationCount = options.dataVisualizationCountOffset ?? 0;
|
|
385
|
+
for (const block of presentation.blocks) {
|
|
386
|
+
if (block.type === "text" || block.type === "context") continue;
|
|
387
|
+
if (block.type === "buttons") {
|
|
388
|
+
let nativeButtonCount = 0;
|
|
389
|
+
if (!(block.buttons.every((button) => {
|
|
390
|
+
if (isSlackTextFallbackButton(button)) return true;
|
|
391
|
+
nativeButtonCount += 1;
|
|
392
|
+
if (!isWithinSlackLimit(button.label, 75)) return false;
|
|
393
|
+
const target = resolveSlackButtonTarget(button, options.questionOptionIndices);
|
|
394
|
+
return target ? target.kind === "link" ? isWithinSlackLimit(target.url, SLACK_BUTTON_URL_MAX) : isWithinSlackLimit(target.value, 2e3) : false;
|
|
395
|
+
}) && nativeButtonCount <= 25)) return false;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (block.type === "select") {
|
|
399
|
+
if (!(isWithinSlackLimit(normalizeOptionalString(block.placeholder) ?? "Choose an option", 75) && block.options.length <= 100 && (!block.placeholder || isWithinSlackLimit(block.placeholder, 75)) && block.options.every((option) => {
|
|
400
|
+
if (!isWithinSlackLimit(option.label, 75)) return false;
|
|
401
|
+
const target = resolveSlackOptionTarget(option);
|
|
402
|
+
return target ? isRenderableSlackOption({
|
|
403
|
+
label: option.label,
|
|
404
|
+
...target
|
|
405
|
+
}) : false;
|
|
406
|
+
}) && new Set(block.options.map((option) => resolveSlackOptionTarget(option)?.kind)).size === 1)) return false;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (block.type === "chart") {
|
|
410
|
+
if (dataVisualizationCount >= 2 || !canRenderSlackDataVisualization(block)) return false;
|
|
411
|
+
dataVisualizationCount += 1;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (block.type === "table") continue;
|
|
415
|
+
}
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
function buildSlackPresentationSelectBlock(block, selectIndex) {
|
|
419
|
+
const options = block.options.flatMap((option) => {
|
|
420
|
+
const target = resolveSlackOptionTarget(option);
|
|
421
|
+
return target ? [{
|
|
422
|
+
label: option.label,
|
|
423
|
+
...target
|
|
424
|
+
}] : [];
|
|
425
|
+
}).filter(isRenderableSlackOption).slice(0, 100);
|
|
426
|
+
const optionKinds = new Set(options.map((option) => option.kind));
|
|
427
|
+
return options.length > 0 && optionKinds.size === 1 ? {
|
|
428
|
+
type: "actions",
|
|
429
|
+
block_id: `openclaw_reply_select_${selectIndex}`,
|
|
430
|
+
elements: [{
|
|
431
|
+
type: "static_select",
|
|
432
|
+
action_id: options[0]?.kind === "approval" ? buildSlackApprovalSelectActionId(selectIndex) : options[0]?.kind === "callback" ? buildSlackCallbackSelectActionId(selectIndex) : buildSlackReplySelectActionId(selectIndex),
|
|
433
|
+
placeholder: {
|
|
434
|
+
type: "plain_text",
|
|
435
|
+
text: truncateSlackText(normalizeOptionalString(block.placeholder) ?? "Choose an option", 75),
|
|
436
|
+
emoji: true
|
|
437
|
+
},
|
|
438
|
+
options: options.map((option) => ({
|
|
439
|
+
text: {
|
|
440
|
+
type: "plain_text",
|
|
441
|
+
text: truncateSlackText(option.label, 75),
|
|
442
|
+
emoji: true
|
|
443
|
+
},
|
|
444
|
+
value: option.value
|
|
445
|
+
}))
|
|
446
|
+
}]
|
|
447
|
+
} : void 0;
|
|
448
|
+
}
|
|
449
|
+
//#endregion
|
|
450
|
+
//#region extensions/slack/src/authored-text.ts
|
|
451
|
+
function normalizeComparableSlackText(text) {
|
|
452
|
+
return text.trim().replace(/\s+/g, " ");
|
|
453
|
+
}
|
|
454
|
+
function isSlackAuthoredTextRepresentedInInteractive(text, interactive) {
|
|
455
|
+
return isSlackAuthoredTextRepresentedInFragments(text, interactive?.blocks.flatMap((block) => block.type === "text" ? [block.text] : []) ?? []);
|
|
456
|
+
}
|
|
457
|
+
function isSlackAuthoredTextRepresentedInFragments(text, rawFragments) {
|
|
458
|
+
const target = normalizeComparableSlackText(text);
|
|
459
|
+
const fragments = rawFragments.map(normalizeComparableSlackText).filter(Boolean);
|
|
460
|
+
for (let start = 0; start < fragments.length; start += 1) {
|
|
461
|
+
let combined = "";
|
|
462
|
+
for (let end = start; end < fragments.length; end += 1) {
|
|
463
|
+
combined = normalizeComparableSlackText(`${combined} ${fragments[end]}`);
|
|
464
|
+
if (combined === target) return true;
|
|
465
|
+
if (combined.length > target.length) break;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
/** Resolve placement from producer facts, before accessibility text changes the payload text. */
|
|
471
|
+
function resolveSlackAuthoredTextPlacement(params) {
|
|
472
|
+
const text = normalizeOptionalString(params.text);
|
|
473
|
+
if (!text) return "none";
|
|
474
|
+
return params.renderedInBlocks || isSlackAuthoredTextRepresentedInFragments(text, params.renderedTextFragments ?? []) || isSlackAuthoredTextRepresentedInInteractive(text, params.interactive) ? "blocks" : "outside-blocks";
|
|
475
|
+
}
|
|
476
|
+
//#endregion
|
|
477
|
+
//#region extensions/slack/src/reply-blocks.ts
|
|
478
|
+
function parseSlackReplyBlockSegments(value) {
|
|
479
|
+
if (value === void 0) return;
|
|
480
|
+
if (!Array.isArray(value)) throw new Error("Slack rendered presentation segments must be an array");
|
|
481
|
+
return value.map((raw) => {
|
|
482
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("Slack rendered presentation segment must be an object");
|
|
483
|
+
const segment = raw;
|
|
484
|
+
if (segment.kind === "text" && typeof segment.text === "string" && segment.mrkdwn === false) return {
|
|
485
|
+
kind: "text",
|
|
486
|
+
text: segment.text,
|
|
487
|
+
mrkdwn: false
|
|
488
|
+
};
|
|
489
|
+
if (segment.kind === "blocks") {
|
|
490
|
+
const blocks = parseSlackBlocksInput(segment.blocks);
|
|
491
|
+
if (blocks?.length) return {
|
|
492
|
+
kind: "blocks",
|
|
493
|
+
blocks
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
throw new Error("Slack rendered presentation segment is invalid");
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
/** Convert compiled segments into ordered sender calls without re-inferring text placement. */
|
|
500
|
+
function resolveSlackReplyDeliveryMessages(params) {
|
|
501
|
+
const messages = [];
|
|
502
|
+
let outsideText = params.authoredTextPlacement === "outside-blocks" ? params.text?.trim() ?? "" : "";
|
|
503
|
+
for (const segment of params.segments) {
|
|
504
|
+
if (segment.kind === "text") {
|
|
505
|
+
const text = [outsideText, segment.text].filter(Boolean).join("\n\n");
|
|
506
|
+
outsideText = "";
|
|
507
|
+
if (text) messages.push({
|
|
508
|
+
text,
|
|
509
|
+
textIsSlackPlainText: true
|
|
510
|
+
});
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
const baseText = outsideText;
|
|
514
|
+
outsideText = "";
|
|
515
|
+
const text = buildSlackNativeDataAccessibilityText(baseText, segment.blocks) || buildSlackBlocksFallbackText(segment.blocks);
|
|
516
|
+
const authoredTextPlacement = baseText ? "outside-blocks" : params.authoredTextPlacement === "blocks" ? "blocks" : "none";
|
|
517
|
+
messages.push({
|
|
518
|
+
text,
|
|
519
|
+
blocks: segment.blocks,
|
|
520
|
+
authoredTextPlacement,
|
|
521
|
+
...baseText ? { nativeDataFallbackBaseText: baseText } : {}
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
if (outsideText) messages.push({
|
|
525
|
+
text: outsideText,
|
|
526
|
+
authoredTextPlacement: "outside-blocks"
|
|
527
|
+
});
|
|
528
|
+
return messages;
|
|
529
|
+
}
|
|
530
|
+
function resolveSlackReplyText(payload, text = payload.text) {
|
|
531
|
+
const presentation = normalizeMessagePresentation(payload.presentation);
|
|
532
|
+
return presentation ? renderSlackMessagePresentationFallbackText({
|
|
533
|
+
text,
|
|
534
|
+
presentation
|
|
535
|
+
}) : text ?? "";
|
|
536
|
+
}
|
|
537
|
+
function resolveSlackReplyRenderPlan(payload, text = payload.text) {
|
|
538
|
+
const hasStructuredContent = hasSlackReplyStructuredContent(payload);
|
|
539
|
+
const resolution = resolveSlackReplyBlockResolution({
|
|
540
|
+
...payload,
|
|
541
|
+
text
|
|
542
|
+
}, { materializeAuthoredText: hasStructuredContent });
|
|
543
|
+
const messages = resolveSlackReplyDeliveryMessages({
|
|
544
|
+
authoredTextPlacement: resolution.authoredTextPlacement,
|
|
545
|
+
segments: resolution.segments,
|
|
546
|
+
text
|
|
547
|
+
});
|
|
548
|
+
if (messages.length <= 1) {
|
|
549
|
+
const [message] = messages;
|
|
550
|
+
const sourceText = text?.trim() ?? "";
|
|
551
|
+
const blocks = message?.authoredTextPlacement === "blocks" ? addPreviewVerbatimToAuthoredTextBlocks(message.blocks, sourceText) : message?.blocks;
|
|
552
|
+
let renderedText = message?.text ?? resolveSlackReplyText(payload, text);
|
|
553
|
+
let textIsSlackMrkdwn = Boolean(message && !message.textIsSlackPlainText && (message.authoredTextPlacement !== "outside-blocks" || message.nativeDataFallbackBaseText));
|
|
554
|
+
if (blocks?.length && sourceText) {
|
|
555
|
+
if (hasSlackNativeDataBlock(blocks)) {
|
|
556
|
+
renderedText = appendSlackNativeDataFallbackText(sourceText, blocks) || renderedText;
|
|
557
|
+
textIsSlackMrkdwn = true;
|
|
558
|
+
} else if (message?.authoredTextPlacement === "blocks") {
|
|
559
|
+
renderedText = sourceText;
|
|
560
|
+
textIsSlackMrkdwn = false;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
mode: "single",
|
|
565
|
+
text: renderedText,
|
|
566
|
+
...blocks ? { blocks } : {},
|
|
567
|
+
...textIsSlackMrkdwn ? { textIsSlackMrkdwn: true } : {}
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
const blockPart = messages.find((message) => message.blocks?.length);
|
|
571
|
+
return {
|
|
572
|
+
mode: "split",
|
|
573
|
+
fallbackText: messages.map((message) => message.text).filter(Boolean).join("\n\n"),
|
|
574
|
+
...blockPart ? { blockPart: {
|
|
575
|
+
text: blockPart.text,
|
|
576
|
+
blocks: blockPart.blocks
|
|
577
|
+
} } : {}
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function readSlackChannelBlocks(payload) {
|
|
581
|
+
const slackData = payload.channelData?.slack;
|
|
582
|
+
if (!slackData || typeof slackData !== "object" || Array.isArray(slackData)) return [];
|
|
583
|
+
return parseSlackBlocksInput(slackData.blocks) ?? [];
|
|
584
|
+
}
|
|
585
|
+
function hasSlackReplyStructuredContent(payload) {
|
|
586
|
+
return Boolean(readSlackChannelBlocks(payload).length || normalizeMessagePresentation(payload.presentation) || payload.interactive?.blocks.length);
|
|
587
|
+
}
|
|
588
|
+
function renderSlackAuthoredTextFragments(blocks) {
|
|
589
|
+
return blocks.flatMap((block) => {
|
|
590
|
+
if (block.type === "actions") return [];
|
|
591
|
+
const text = renderSlackBlockFallbackText(block, { nativeDataFormat: "plain" });
|
|
592
|
+
return text ? [text] : [];
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
function buildSlackAuthoredTextBlocks(text) {
|
|
596
|
+
return markdownToSlackMrkdwnChunks(text, SLACK_SECTION_TEXT_MAX).map((chunk) => ({
|
|
597
|
+
type: "section",
|
|
598
|
+
text: {
|
|
599
|
+
type: "mrkdwn",
|
|
600
|
+
text: chunk,
|
|
601
|
+
verbatim: true
|
|
602
|
+
}
|
|
603
|
+
}));
|
|
604
|
+
}
|
|
605
|
+
function addPreviewVerbatimToAuthoredTextBlocks(blocks, sourceText) {
|
|
606
|
+
if (!blocks?.length || !sourceText) return blocks;
|
|
607
|
+
const authoredChunks = new Set(markdownToSlackMrkdwnChunks(sourceText, SLACK_SECTION_TEXT_MAX));
|
|
608
|
+
return blocks.map((block) => {
|
|
609
|
+
const text = block.text;
|
|
610
|
+
if (block.type !== "section" || text?.type !== "mrkdwn" || typeof text.text !== "string" || !authoredChunks.has(text.text)) return block;
|
|
611
|
+
return {
|
|
612
|
+
...block,
|
|
613
|
+
text: {
|
|
614
|
+
...text,
|
|
615
|
+
verbatim: true
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
function readLastBlockSegment(segments) {
|
|
621
|
+
const last = segments.at(-1);
|
|
622
|
+
return last?.kind === "blocks" ? last.blocks : [];
|
|
623
|
+
}
|
|
624
|
+
function readAllNativeBlocks(segments) {
|
|
625
|
+
return segments.flatMap((segment) => segment.kind === "blocks" ? segment.blocks : []);
|
|
626
|
+
}
|
|
627
|
+
function appendTextSegment(segments, text) {
|
|
628
|
+
const trimmed = text.trim();
|
|
629
|
+
if (!trimmed) return;
|
|
630
|
+
const last = segments.at(-1);
|
|
631
|
+
if (last?.kind === "text") {
|
|
632
|
+
last.text = `${last.text}\n\n${trimmed}`;
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
segments.push({
|
|
636
|
+
kind: "text",
|
|
637
|
+
text: trimmed,
|
|
638
|
+
mrkdwn: false
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
function appendBlockSegment(segments, blocks, startNew = false) {
|
|
642
|
+
let shouldStartNew = startNew;
|
|
643
|
+
for (const block of blocks) {
|
|
644
|
+
const last = segments.at(-1);
|
|
645
|
+
if (!shouldStartNew && last?.kind === "blocks" && last.blocks.length < 50) last.blocks.push(block);
|
|
646
|
+
else segments.push({
|
|
647
|
+
kind: "blocks",
|
|
648
|
+
blocks: [block]
|
|
649
|
+
});
|
|
650
|
+
shouldStartNew = false;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
function resolvePresentationRenderOptions(segments, mode) {
|
|
654
|
+
const allOffsets = resolveSlackBlockOffsets(readAllNativeBlocks(segments));
|
|
655
|
+
return {
|
|
656
|
+
...mode === "current" ? resolveSlackBlockOffsets(readLastBlockSegment(segments)) : {},
|
|
657
|
+
buttonIndexOffset: allOffsets.buttonIndexOffset,
|
|
658
|
+
selectIndexOffset: allOffsets.selectIndexOffset
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
function renderNativePresentation(presentation, options) {
|
|
662
|
+
if (!canRenderSlackPresentation(presentation, options)) return;
|
|
663
|
+
const blocks = buildSlackPresentationBlocks(presentation, options);
|
|
664
|
+
return blocks.length > 0 ? blocks : void 0;
|
|
665
|
+
}
|
|
666
|
+
function appendPresentationPart(segments, presentation, questionOptionIndices) {
|
|
667
|
+
const currentBlocks = readLastBlockSegment(segments);
|
|
668
|
+
const currentRendered = renderNativePresentation(presentation, {
|
|
669
|
+
...resolvePresentationRenderOptions(segments, "current"),
|
|
670
|
+
questionOptionIndices
|
|
671
|
+
});
|
|
672
|
+
if (currentRendered && currentBlocks.length + currentRendered.length <= 50) {
|
|
673
|
+
appendBlockSegment(segments, currentRendered);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
const freshRendered = renderNativePresentation(presentation, {
|
|
677
|
+
...resolvePresentationRenderOptions(segments, "new-message"),
|
|
678
|
+
questionOptionIndices
|
|
679
|
+
});
|
|
680
|
+
if (freshRendered) {
|
|
681
|
+
appendBlockSegment(segments, freshRendered, true);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
appendTextSegment(segments, renderMessagePresentationFallbackText({ presentation }));
|
|
685
|
+
}
|
|
686
|
+
const SLACK_BUTTON_CONTROL_ACTION_IDS = [
|
|
687
|
+
SLACK_APPROVAL_BUTTON_ACTION_ID,
|
|
688
|
+
SLACK_CALLBACK_BUTTON_ACTION_ID,
|
|
689
|
+
SLACK_QUESTION_BUTTON_ACTION_ID,
|
|
690
|
+
SLACK_REPLY_BUTTON_ACTION_ID,
|
|
691
|
+
SLACK_REPLY_LINK_ACTION_ID
|
|
692
|
+
];
|
|
693
|
+
const SLACK_SELECT_CONTROL_ACTION_IDS = [
|
|
694
|
+
SLACK_APPROVAL_SELECT_ACTION_ID,
|
|
695
|
+
SLACK_CALLBACK_SELECT_ACTION_ID,
|
|
696
|
+
SLACK_REPLY_SELECT_ACTION_ID
|
|
697
|
+
];
|
|
698
|
+
function readGeneratedSlackControlRowKey(block) {
|
|
699
|
+
const record = block;
|
|
700
|
+
if (record.type !== "actions" || typeof record.block_id !== "string") return;
|
|
701
|
+
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;
|
|
702
|
+
if (!expectedElementType || !Array.isArray(record.elements) || record.elements.length === 0) return;
|
|
703
|
+
const actionIds = expectedElementType === "button" ? SLACK_BUTTON_CONTROL_ACTION_IDS : SLACK_SELECT_CONTROL_ACTION_IDS;
|
|
704
|
+
const elements = record.elements.map((element) => {
|
|
705
|
+
if (!element || typeof element !== "object" || Array.isArray(element)) return;
|
|
706
|
+
const { action_id: actionId, ...content } = element;
|
|
707
|
+
const actionFamily = typeof actionId === "string" ? actionIds.find((candidate) => actionId.startsWith(`${candidate}:`)) : void 0;
|
|
708
|
+
return actionFamily && content.type === expectedElementType ? [actionFamily, content] : void 0;
|
|
709
|
+
});
|
|
710
|
+
return elements.some((element) => element === void 0) ? void 0 : JSON.stringify(elements);
|
|
711
|
+
}
|
|
712
|
+
function subtractMirroredSlackControlRows(params) {
|
|
713
|
+
const remainingMirrors = /* @__PURE__ */ new Map();
|
|
714
|
+
for (const block of params.presentationBlocks) {
|
|
715
|
+
const key = readGeneratedSlackControlRowKey(block);
|
|
716
|
+
if (key) remainingMirrors.set(key, (remainingMirrors.get(key) ?? 0) + 1);
|
|
717
|
+
}
|
|
718
|
+
return params.interactiveBlocks.filter((block) => {
|
|
719
|
+
const key = readGeneratedSlackControlRowKey(block);
|
|
720
|
+
const remaining = key ? remainingMirrors.get(key) ?? 0 : 0;
|
|
721
|
+
if (!key || remaining === 0) return true;
|
|
722
|
+
remainingMirrors.set(key, remaining - 1);
|
|
723
|
+
return false;
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Resolve reply content into transport-order segments. Each blocks segment is
|
|
728
|
+
* one Slack message; text segments carry complete fallback content between it.
|
|
729
|
+
*/
|
|
730
|
+
function resolveSlackReplyBlockResolution(payload, options = {}) {
|
|
731
|
+
const segments = [];
|
|
732
|
+
const channelBlocks = readSlackChannelBlocks(payload);
|
|
733
|
+
let compiledChannelBlocks = channelBlocks;
|
|
734
|
+
let authoredTextKnownInBlocks = false;
|
|
735
|
+
if (options.materializeAuthoredText) {
|
|
736
|
+
const rawTextFragments = renderSlackAuthoredTextFragments(channelBlocks);
|
|
737
|
+
const initialPlacement = resolveSlackAuthoredTextPlacement({
|
|
738
|
+
text: payload.text,
|
|
739
|
+
interactive: payload.interactive,
|
|
740
|
+
renderedTextFragments: rawTextFragments
|
|
741
|
+
});
|
|
742
|
+
authoredTextKnownInBlocks = initialPlacement === "blocks";
|
|
743
|
+
const text = normalizeOptionalString(payload.text);
|
|
744
|
+
if (text && initialPlacement === "outside-blocks") {
|
|
745
|
+
const textBlocks = buildSlackAuthoredTextBlocks(text);
|
|
746
|
+
if (resolveSlackAuthoredTextPlacement({
|
|
747
|
+
text: renderSlackAuthoredTextFragments(textBlocks).join(" "),
|
|
748
|
+
renderedTextFragments: rawTextFragments
|
|
749
|
+
}) !== "blocks") compiledChannelBlocks = [...channelBlocks, ...textBlocks];
|
|
750
|
+
authoredTextKnownInBlocks = true;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (compiledChannelBlocks.length > 0) appendBlockSegment(segments, compiledChannelBlocks);
|
|
754
|
+
const presentation = normalizeMessagePresentation(payload.presentation);
|
|
755
|
+
const questionOptionIndices = resolveAskUserQuestionOptionIndices(payload);
|
|
756
|
+
const presentationBlockOffset = readAllNativeBlocks(segments).length;
|
|
757
|
+
if (presentation?.title) appendPresentationPart(segments, {
|
|
758
|
+
title: presentation.title,
|
|
759
|
+
blocks: []
|
|
760
|
+
}, questionOptionIndices);
|
|
761
|
+
for (const block of presentation?.blocks ?? []) appendPresentationPart(segments, { blocks: [block] }, questionOptionIndices);
|
|
762
|
+
const renderedPresentationBlocks = readAllNativeBlocks(segments).slice(presentationBlockOffset);
|
|
763
|
+
appendBlockSegment(segments, subtractMirroredSlackControlRows({
|
|
764
|
+
interactiveBlocks: buildSlackInteractiveBlocks(payload.interactive, {
|
|
765
|
+
...resolveSlackBlockOffsets(readAllNativeBlocks(segments)),
|
|
766
|
+
questionOptionIndices
|
|
767
|
+
}),
|
|
768
|
+
presentationBlocks: renderedPresentationBlocks
|
|
769
|
+
}));
|
|
770
|
+
const renderedTextFragments = segments.flatMap((segment) => {
|
|
771
|
+
if (segment.kind === "text") return [segment.text];
|
|
772
|
+
return renderSlackAuthoredTextFragments(segment.blocks);
|
|
773
|
+
});
|
|
774
|
+
const authoredTextPlacement = resolveSlackAuthoredTextPlacement({
|
|
775
|
+
text: payload.text,
|
|
776
|
+
interactive: payload.interactive,
|
|
777
|
+
renderedTextFragments
|
|
778
|
+
});
|
|
779
|
+
return {
|
|
780
|
+
authoredTextPlacement: authoredTextKnownInBlocks ? "blocks" : authoredTextPlacement,
|
|
781
|
+
segments
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
/** Return the single-message native shape when no ordered text fallback is required. */
|
|
785
|
+
function resolveSlackReplyBlocks(payload) {
|
|
786
|
+
const { segments } = resolveSlackReplyBlockResolution(payload);
|
|
787
|
+
return segments.length === 1 && segments[0]?.kind === "blocks" ? segments[0].blocks : void 0;
|
|
788
|
+
}
|
|
789
|
+
//#endregion
|
|
790
|
+
export { resolveSlackReplyDeliveryMessages as a, buildSlackInteractiveBlocks as c, decodeSlackQuestionAction as d, resolveSlackQuestionAction as f, SLACK_SECTION_TEXT_MAX as g, SLACK_PRESENTATION_CAPABILITIES as h, resolveSlackReplyBlocks as i, buildSlackPresentationBlocks as l, decodeSlackApprovalAction as m, parseSlackReplyBlockSegments as n, resolveSlackReplyRenderPlan as o, SLACK_APPROVAL_HEADER_BLOCK_ID as p, resolveSlackReplyBlockResolution as r, resolveSlackAuthoredTextPlacement as s, hasSlackReplyStructuredContent as t, canRenderSlackPresentation as u };
|