@openclaw/slack 2026.9.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/{action-runtime-DND4Pyp2.js → action-runtime-DftQWpI4.js} +4 -4
- package/dist/action-runtime.runtime-rHI3UAh4.js +2 -0
- package/dist/{actions-fh66A6lc.js → actions-CFkvEv1d.js} +3 -3
- package/dist/api.js +9 -9
- package/dist/{approval-handler.runtime-D7d8sKM0.js → approval-handler.runtime-BeILKdw4.js} +31 -15
- package/dist/{channel-4m8IAdMD.js → channel-CmOuxbOD.js} +24 -23
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel-type-3-SqEMVc.js → channel-type-BWt2CRaI.js} +2 -2
- package/dist/{channel.setup-t_ghLGb_.js → channel.setup-D5z1ReaT.js} +8 -3
- package/dist/{config-schema-bP3eV2ra.js → config-schema-JDx_f9Zb.js} +2 -2
- package/dist/{directory-live-q32AW7N1.js → directory-live-BuBPHmju.js} +1 -1
- package/dist/{monitor-Chwu_SiZ.js → monitor-DI07pfJl.js} +3 -3
- package/dist/{outbound-adapter-BB8wAjm5.js → outbound-adapter-B5QyTRpJ.js} +3 -2
- package/dist/{pipeline.runtime-BklS_NEj.js → pipeline.runtime-BdIwZgm4.js} +567 -733
- package/dist/{policy-D7PKkiet.js → policy-Bh5TOqII.js} +2 -2
- package/dist/{probe-gj5yQgN2.js → probe-Biy--yhL.js} +42 -10
- package/dist/{provider-Do50wMWn.js → provider-DoaUvAM4.js} +5583 -4921
- package/dist/{relay-source-DWuduiJ2.js → relay-source-D4uecp68.js} +1 -1
- package/dist/{replies-Dc6WVh_Q.js → replies-DENuTcPO.js} +4 -3
- package/dist/reply-blocks-PRkhyAN7.js +790 -0
- package/dist/{resolve-channels-CU7Bg5Ka.js → resolve-channels-DQGV2VrE.js} +1 -1
- package/dist/{resolve-users-DrhttvRQ.js → resolve-users-DRGt_Q4I.js} +1 -1
- package/dist/runtime-api.js +10 -10
- package/dist/{scopes-B9HRguDe.js → scopes-CZzeGdWV.js} +1 -1
- package/dist/{send-Djswy6lf.js → send-DElUcgIs.js} +10 -10
- package/dist/{send.runtime-__WVTWLQ.js → send.runtime-C8D6cKGm.js} +1 -1
- package/dist/{group-policy-BKjMqT-8.js → session-status-BynwcZSO.js} +169 -799
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{setup-surface-DO0e-MMS.js → setup-surface-B1_gpGBl.js} +4 -4
- package/dist/{shared-DEQBEKBL.js → shared-CNQsWJT7.js} +2 -2
- package/dist/{slash-dispatch.runtime-C3B4h5ZM.js → slash-dispatch.runtime-DhsXavSY.js} +1 -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 +3 -3
- 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-CEhbmS-Z.js +0 -2
- package/dist/conversation-binding-route-Vee8Dgbf.js +0 -115
|
@@ -2,10 +2,11 @@ import { n as isSlackPluginAccountConfigured } from "./account-configured-sUohAx
|
|
|
2
2
|
import { a as resolveSlackAccount, i as resolveDefaultSlackAccountId, n as listSlackAccountIds, o as resolveSlackAccountAllowFrom, r as mergeSlackAccountConfig } from "./accounts-Dm_H77gH.js";
|
|
3
3
|
import { a as parseSlackTarget, n as formatSlackTarget, t as canonicalizeSlackApiTargetId } from "./target-parsing-PbxGDbr9.js";
|
|
4
4
|
import { t as slackContextTargetsMatch } from "./targets-7FexXJ62.js";
|
|
5
|
-
import { C as normalizeAllowListLower, D as resolveSlackUserAllowListForTeam, E as resolveSlackAllowListMatch } from "./probe-
|
|
5
|
+
import { C as normalizeAllowListLower, D as resolveSlackUserAllowListForTeam, E as resolveSlackAllowListMatch, o as formatSlackError } from "./probe-Biy--yhL.js";
|
|
6
6
|
import { normalizeAccountId } from "openclaw/plugin-sdk/account-resolution";
|
|
7
7
|
import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringifiedOptionalString, readNonBlankString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
8
8
|
import { normalizeMessageChannel } from "openclaw/plugin-sdk/routing";
|
|
9
|
+
import { defaultRuntime, logVerbose, warn } from "openclaw/plugin-sdk/runtime-env";
|
|
9
10
|
import { FormatCapabilityProfile, chunkTextForOutbound, markdownToIR, renderMarkdownIRChunksWithinLimit, renderMarkdownWithMarkers } from "openclaw/plugin-sdk/text-chunking";
|
|
10
11
|
import { isSingleUseReplyToMode } from "openclaw/plugin-sdk/reply-reference";
|
|
11
12
|
import { buildChannelKeyCandidates } from "openclaw/plugin-sdk/channel-targets";
|
|
@@ -15,15 +16,25 @@ import { createChannelExecApprovalProfile, isChannelExecApprovalClientEnabledFro
|
|
|
15
16
|
import { normalizeHyphenSlug } from "openclaw/plugin-sdk/string-normalization-runtime";
|
|
16
17
|
import { channelRouteTargetsMatchExact, stringifyRouteThreadId } from "openclaw/plugin-sdk/channel-route";
|
|
17
18
|
import { resolveGlobalMap } from "openclaw/plugin-sdk/global-singleton";
|
|
18
|
-
import {
|
|
19
|
-
import { parseExecApprovalCommandText } from "openclaw/plugin-sdk/approval-reply-runtime";
|
|
20
|
-
import { resolveAskUserQuestionOptionIndex, resolveAskUserQuestionOptionIndices } from "openclaw/plugin-sdk/reply-payload";
|
|
21
|
-
import { buildApprovalResolutionRef } from "openclaw/plugin-sdk/approval-reference-runtime";
|
|
19
|
+
import { normalizeMessagePresentation, renderMessagePresentationChartFallbackText, renderMessagePresentationFallbackText, renderMessagePresentationTableFallbackText } from "openclaw/plugin-sdk/interactive-runtime";
|
|
22
20
|
import { eastAsianWidthType } from "get-east-asian-width";
|
|
23
|
-
import {
|
|
24
|
-
import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
21
|
+
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
25
22
|
import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
|
23
|
+
import { resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
|
|
26
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
|
|
27
38
|
//#region extensions/slack/src/action-threading.ts
|
|
28
39
|
const SLACK_PRIVATE_ACTION_DELIVERY_RESULT = Symbol("slack.action.delivery-result");
|
|
29
40
|
function resolveSlackAutoThreadId(params) {
|
|
@@ -439,75 +450,10 @@ function parseSlackBlocksInput(raw) {
|
|
|
439
450
|
if (raw == null) return;
|
|
440
451
|
return validateSlackBlocksArray(typeof raw === "string" ? parseBlocksJson(raw) : raw);
|
|
441
452
|
}
|
|
442
|
-
const SLACK_BUTTON_VALUE_MAX = 2e3;
|
|
443
|
-
const SLACK_SECTION_TEXT_MAX = 3e3;
|
|
444
|
-
const SLACK_PRESENTATION_CAPABILITIES = {
|
|
445
|
-
supported: true,
|
|
446
|
-
buttons: true,
|
|
447
|
-
selects: true,
|
|
448
|
-
context: true,
|
|
449
|
-
divider: true,
|
|
450
|
-
charts: true,
|
|
451
|
-
tables: true,
|
|
452
|
-
limits: {
|
|
453
|
-
actions: {
|
|
454
|
-
maxActionsPerRow: 25,
|
|
455
|
-
maxValueBytes: SLACK_BUTTON_VALUE_MAX,
|
|
456
|
-
supportsStyles: true
|
|
457
|
-
},
|
|
458
|
-
selects: {
|
|
459
|
-
maxOptions: 100,
|
|
460
|
-
maxValueBytes: 150
|
|
461
|
-
},
|
|
462
|
-
text: {
|
|
463
|
-
encoding: "characters",
|
|
464
|
-
markdownDialect: "slack-mrkdwn",
|
|
465
|
-
supportsEdit: true
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
};
|
|
469
|
-
//#endregion
|
|
470
|
-
//#region extensions/slack/src/approval-actions.ts
|
|
471
|
-
const SLACK_APPROVAL_VALUE_PREFIX = "openclaw:approval:v1:";
|
|
472
|
-
const SLACK_APPROVAL_HEADER_BLOCK_ID = "openclaw_approval_header";
|
|
473
|
-
function isApprovalDecision(value) {
|
|
474
|
-
return value === "allow-once" || value === "allow-always" || value === "deny";
|
|
475
|
-
}
|
|
476
|
-
/** Encode portable approval facts without exposing a slash command to Slack callbacks. */
|
|
477
|
-
function encodeSlackApprovalAction(action) {
|
|
478
|
-
const encode = (approvalId) => `${SLACK_APPROVAL_VALUE_PREFIX}${JSON.stringify({
|
|
479
|
-
approvalId,
|
|
480
|
-
approvalKind: action.approvalKind,
|
|
481
|
-
decision: action.decision
|
|
482
|
-
})}`;
|
|
483
|
-
const exact = encode(action.approvalId);
|
|
484
|
-
return exact.length <= 2e3 ? exact : encode(buildApprovalResolutionRef({
|
|
485
|
-
approvalId: action.approvalId,
|
|
486
|
-
approvalKind: action.approvalKind
|
|
487
|
-
}));
|
|
488
|
-
}
|
|
489
|
-
/** Decode only the exact Slack-owned approval envelope. Malformed callbacks fail closed. */
|
|
490
|
-
function decodeSlackApprovalAction(value) {
|
|
491
|
-
if (typeof value !== "string" || !value.startsWith(SLACK_APPROVAL_VALUE_PREFIX)) return null;
|
|
492
|
-
try {
|
|
493
|
-
const decoded = JSON.parse(value.slice(21));
|
|
494
|
-
if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) return null;
|
|
495
|
-
const record = decoded;
|
|
496
|
-
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;
|
|
497
|
-
return {
|
|
498
|
-
type: "approval",
|
|
499
|
-
approvalId: record.approvalId,
|
|
500
|
-
approvalKind: record.approvalKind,
|
|
501
|
-
decision: record.decision
|
|
502
|
-
};
|
|
503
|
-
} catch {
|
|
504
|
-
return null;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
453
|
//#endregion
|
|
508
454
|
//#region extensions/slack/src/monitor/mrkdwn.ts
|
|
509
455
|
function escapeSlackMrkdwn(value) {
|
|
510
|
-
return value.replaceAll("
|
|
456
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
511
457
|
}
|
|
512
458
|
//#endregion
|
|
513
459
|
//#region extensions/slack/src/presentation-fallback.ts
|
|
@@ -645,6 +591,7 @@ function renderSlackRichText(value, mode, separator = "") {
|
|
|
645
591
|
//#region extensions/slack/src/data-table.ts
|
|
646
592
|
const SLACK_DATA_TABLE_COLUMNS_MAX = 20;
|
|
647
593
|
const SLACK_DATA_TABLE_ROWS_MAX = 100;
|
|
594
|
+
const SLACK_DATA_TABLE_AGGREGATE_CELL_CHARACTERS_MAX = 1e4;
|
|
648
595
|
function countCharacters(value) {
|
|
649
596
|
return Array.from(value).length;
|
|
650
597
|
}
|
|
@@ -786,7 +733,7 @@ function escapeCompactFallbackCell(value) {
|
|
|
786
733
|
return value.replaceAll("\\", "\\\\").replaceAll(" ", "\\t").replaceAll("\r", "\\r").replaceAll("\n", "\\n");
|
|
787
734
|
}
|
|
788
735
|
function escapeSlackBasicTableCell(value, mrkdwnSafe) {
|
|
789
|
-
return (mrkdwnSafe ? escapeSlackMrkdwn(value) : value
|
|
736
|
+
return escapeCompactFallbackCell(mrkdwnSafe ? escapeSlackMrkdwn(value) : value);
|
|
790
737
|
}
|
|
791
738
|
function renderSlackBasicTableRows(value, mrkdwnSafe) {
|
|
792
739
|
return parseSlackBasicTableRows(value)?.map((row) => row.map((cell) => escapeSlackBasicTableCell(cell, mrkdwnSafe)).join(" ")).join("\n");
|
|
@@ -953,16 +900,14 @@ function renderSlackDataVisualizationMrkdwnFallbackText(value) {
|
|
|
953
900
|
}
|
|
954
901
|
//#endregion
|
|
955
902
|
//#region extensions/slack/src/format.ts
|
|
956
|
-
function escapeSlackMrkdwnSegment(text) {
|
|
957
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
958
|
-
}
|
|
959
903
|
const SLACK_ANGLE_TOKEN_RE = /<[^>\n]+>/g;
|
|
960
904
|
function isAllowedSlackAngleToken(token) {
|
|
961
905
|
if (!token.startsWith("<") || !token.endsWith(">")) return false;
|
|
962
906
|
const inner = token.slice(1, -1);
|
|
963
907
|
return inner.startsWith("@") || inner.startsWith("#") || inner.startsWith("!") || inner.startsWith("mailto:") || inner.startsWith("tel:") || inner.startsWith("http://") || inner.startsWith("https://") || inner.startsWith("slack://");
|
|
964
908
|
}
|
|
965
|
-
function escapeSlackMrkdwnContent(text) {
|
|
909
|
+
function escapeSlackMrkdwnContent(text, mentions) {
|
|
910
|
+
if (mentions === "escape") return escapeSlackMrkdwn(text);
|
|
966
911
|
if (!text) return "";
|
|
967
912
|
if (!text.includes("&") && !text.includes("<") && !text.includes(">")) return text;
|
|
968
913
|
SLACK_ANGLE_TOKEN_RE.lastIndex = 0;
|
|
@@ -970,20 +915,20 @@ function escapeSlackMrkdwnContent(text) {
|
|
|
970
915
|
let lastIndex = 0;
|
|
971
916
|
for (let match = SLACK_ANGLE_TOKEN_RE.exec(text); match; match = SLACK_ANGLE_TOKEN_RE.exec(text)) {
|
|
972
917
|
const matchIndex = match.index ?? 0;
|
|
973
|
-
out.push(
|
|
918
|
+
out.push(escapeSlackMrkdwn(text.slice(lastIndex, matchIndex)));
|
|
974
919
|
const token = match[0] ?? "";
|
|
975
|
-
out.push(isAllowedSlackAngleToken(token) ? token :
|
|
920
|
+
out.push(isAllowedSlackAngleToken(token) ? token : escapeSlackMrkdwn(token));
|
|
976
921
|
lastIndex = matchIndex + token.length;
|
|
977
922
|
}
|
|
978
|
-
out.push(
|
|
923
|
+
out.push(escapeSlackMrkdwn(text.slice(lastIndex)));
|
|
979
924
|
return out.join("");
|
|
980
925
|
}
|
|
981
|
-
function escapeSlackMrkdwnText(text) {
|
|
926
|
+
function escapeSlackMrkdwnText(text, mentions) {
|
|
982
927
|
if (!text) return "";
|
|
983
928
|
if (!text.includes("&") && !text.includes("<") && !text.includes(">")) return text;
|
|
984
929
|
return text.split("\n").map((line) => {
|
|
985
|
-
if (line.startsWith("> ")) return `> ${escapeSlackMrkdwnContent(line.slice(2))}`;
|
|
986
|
-
return escapeSlackMrkdwnContent(line);
|
|
930
|
+
if (line.startsWith("> ")) return `> ${escapeSlackMrkdwnContent(line.slice(2), mentions)}`;
|
|
931
|
+
return escapeSlackMrkdwnContent(line, mentions);
|
|
987
932
|
}).join("\n");
|
|
988
933
|
}
|
|
989
934
|
function buildSlackLink(link, text) {
|
|
@@ -992,7 +937,7 @@ function buildSlackLink(link, text) {
|
|
|
992
937
|
const trimmedLabel = text.slice(link.start, link.end).trim();
|
|
993
938
|
const comparableHref = href.startsWith("mailto:") ? href.slice(7) : href;
|
|
994
939
|
if (!(trimmedLabel.length > 0 && trimmedLabel !== href && trimmedLabel !== comparableHref)) return null;
|
|
995
|
-
const safeHref =
|
|
940
|
+
const safeHref = escapeSlackMrkdwn(href);
|
|
996
941
|
return {
|
|
997
942
|
start: link.start,
|
|
998
943
|
end: link.end,
|
|
@@ -1194,7 +1139,7 @@ function protectSlackAssistantTranscriptRoleHeaders(text) {
|
|
|
1194
1139
|
if (!slackProjectionHasRoleHeader(projectSlackMrkdwnVisibleText(text, "token")) && (!text.includes("<!date^") || !slackProjectionHasRoleHeader(projectSlackMrkdwnVisibleText(text, "fallback")))) return text;
|
|
1195
1140
|
return `${SLACK_ASSISTANT_TRANSCRIPT_PREFIX}${text}`;
|
|
1196
1141
|
}
|
|
1197
|
-
function buildSlackRenderOptions() {
|
|
1142
|
+
function buildSlackRenderOptions({ enclosingStyle, mentions } = {}) {
|
|
1198
1143
|
return {
|
|
1199
1144
|
annotationMarkers: { assistant_transcript_role: {
|
|
1200
1145
|
open: "`",
|
|
@@ -1202,14 +1147,14 @@ function buildSlackRenderOptions() {
|
|
|
1202
1147
|
suppressNestedFormatting: true
|
|
1203
1148
|
} },
|
|
1204
1149
|
styleMarkers: {
|
|
1205
|
-
bold: {
|
|
1150
|
+
...enclosingStyle !== "bold" ? { bold: {
|
|
1206
1151
|
open: "*",
|
|
1207
1152
|
close: "*"
|
|
1208
|
-
},
|
|
1209
|
-
italic: {
|
|
1153
|
+
} } : {},
|
|
1154
|
+
...enclosingStyle !== "italic" ? { italic: {
|
|
1210
1155
|
open: "_",
|
|
1211
1156
|
close: "_"
|
|
1212
|
-
},
|
|
1157
|
+
} } : {},
|
|
1213
1158
|
strikethrough: {
|
|
1214
1159
|
open: "~",
|
|
1215
1160
|
close: "~"
|
|
@@ -1223,7 +1168,7 @@ function buildSlackRenderOptions() {
|
|
|
1223
1168
|
close: "```"
|
|
1224
1169
|
}
|
|
1225
1170
|
},
|
|
1226
|
-
escapeText: escapeSlackMrkdwnText,
|
|
1171
|
+
escapeText: (text) => escapeSlackMrkdwnText(text, mentions),
|
|
1227
1172
|
buildLink: buildSlackLink
|
|
1228
1173
|
};
|
|
1229
1174
|
}
|
|
@@ -1236,7 +1181,7 @@ function normalizeSlackOutboundText(markdown, options = {}) {
|
|
|
1236
1181
|
blockquotePrefix: "> ",
|
|
1237
1182
|
tableMode: options.tableMode
|
|
1238
1183
|
}));
|
|
1239
|
-
return protectSlackAssistantTranscriptRoleHeaders(renderMarkdownWithMarkers(ir, buildSlackRenderOptions(), SLACK_FORMAT_PROFILE));
|
|
1184
|
+
return protectSlackAssistantTranscriptRoleHeaders(renderMarkdownWithMarkers(ir, buildSlackRenderOptions(options), SLACK_FORMAT_PROFILE));
|
|
1240
1185
|
}
|
|
1241
1186
|
/** Chunk already-rendered Slack mrkdwn without splitting entities or code markers. */
|
|
1242
1187
|
function chunkSlackMrkdwnText(text, limit) {
|
|
@@ -1270,7 +1215,7 @@ function chunkSlackMrkdwnText(text, limit) {
|
|
|
1270
1215
|
const marker = wrapper(activeMarker);
|
|
1271
1216
|
if (activeMarker && isAllowedSlackAngleToken(token)) {
|
|
1272
1217
|
if (marker) chunks.push(...chunkTextForOutbound(token, Math.max(1, Math.floor(contentLimit)), { preserveWhitespace: true }).map((fragment) => `${marker}${fragment}${marker}`));
|
|
1273
|
-
else chunks.push(...chunkTextForOutbound(
|
|
1218
|
+
else chunks.push(...chunkTextForOutbound(escapeSlackMrkdwn(token), Math.max(1, Math.floor(limit)), { preserveWhitespace: true }));
|
|
1274
1219
|
continue;
|
|
1275
1220
|
}
|
|
1276
1221
|
chunks.push(...token.length <= limit ? [token] : chunkTextForOutbound(token, limit));
|
|
@@ -1299,39 +1244,6 @@ function markdownToSlackMrkdwnChunks(markdown, limit, options = {}) {
|
|
|
1299
1244
|
}).map(({ rendered }) => rendered);
|
|
1300
1245
|
}
|
|
1301
1246
|
//#endregion
|
|
1302
|
-
//#region extensions/slack/src/question-actions.ts
|
|
1303
|
-
const SLACK_QUESTION_VALUE_PREFIX = "slq1:";
|
|
1304
|
-
const QUESTION_RECORD_ID_PATTERN = /^ask_[a-f0-9]{32}$/u;
|
|
1305
|
-
function encodeSlackQuestionAction(action) {
|
|
1306
|
-
if (!QUESTION_RECORD_ID_PATTERN.test(action.questionId) || !Number.isInteger(action.optionIndex) || action.optionIndex < 0 || action.optionIndex > 3) return;
|
|
1307
|
-
const value = `${SLACK_QUESTION_VALUE_PREFIX}${action.questionId}:${action.optionIndex}`;
|
|
1308
|
-
return value.length <= 2e3 ? value : void 0;
|
|
1309
|
-
}
|
|
1310
|
-
function decodeSlackQuestionAction(value) {
|
|
1311
|
-
if (typeof value !== "string" || value.length > 2e3) return null;
|
|
1312
|
-
const match = /^slq1:(ask_[a-f0-9]{32}):([0-3])$/u.exec(value);
|
|
1313
|
-
return match?.[1] && match[2] ? {
|
|
1314
|
-
questionId: match[1],
|
|
1315
|
-
optionIndex: Number(match[2])
|
|
1316
|
-
} : null;
|
|
1317
|
-
}
|
|
1318
|
-
async function resolveSlackQuestionAction(params) {
|
|
1319
|
-
let result;
|
|
1320
|
-
try {
|
|
1321
|
-
result = await (params.resolveQuestion ?? questionGatewayRuntime.resolveOption)({
|
|
1322
|
-
cfg: params.cfg,
|
|
1323
|
-
questionId: params.action.questionId,
|
|
1324
|
-
optionIndex: params.action.optionIndex,
|
|
1325
|
-
senderId: params.userId,
|
|
1326
|
-
clientDisplayName: `Slack question (${params.accountId})`
|
|
1327
|
-
});
|
|
1328
|
-
} catch {
|
|
1329
|
-
await params.respond("Could not submit this answer.").catch(() => {});
|
|
1330
|
-
return;
|
|
1331
|
-
}
|
|
1332
|
-
await params.respond(result.status === "answered" ? "Answer submitted." : "This question was already answered.").catch(() => {});
|
|
1333
|
-
}
|
|
1334
|
-
//#endregion
|
|
1335
1247
|
//#region extensions/slack/src/reply-action-ids.ts
|
|
1336
1248
|
const SLACK_REPLY_BUTTON_ACTION_ID = "openclaw:reply_button";
|
|
1337
1249
|
const SLACK_REPLY_LINK_ACTION_ID = "openclaw:reply_link";
|
|
@@ -1389,381 +1301,12 @@ function truncateSlackTextByUtf8Bytes(value, maxBytes) {
|
|
|
1389
1301
|
return maxBytes >= suffixBytes ? `${prefix}${suffix}` : prefix;
|
|
1390
1302
|
}
|
|
1391
1303
|
//#endregion
|
|
1392
|
-
//#region extensions/slack/src/blocks-render.ts
|
|
1393
|
-
const SLACK_BUTTON_URL_MAX = 3e3;
|
|
1394
|
-
function buildSlackReplyButtonActionId(buttonIndex, choiceIndex) {
|
|
1395
|
-
return `${SLACK_REPLY_BUTTON_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
|
|
1396
|
-
}
|
|
1397
|
-
function buildSlackReplyLinkActionId(buttonIndex, choiceIndex) {
|
|
1398
|
-
return `${SLACK_REPLY_LINK_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
|
|
1399
|
-
}
|
|
1400
|
-
function buildSlackReplySelectActionId(selectIndex) {
|
|
1401
|
-
return `${SLACK_REPLY_SELECT_ACTION_ID}:${String(selectIndex)}`;
|
|
1402
|
-
}
|
|
1403
|
-
function buildSlackApprovalButtonActionId(buttonIndex, choiceIndex) {
|
|
1404
|
-
return `${SLACK_APPROVAL_BUTTON_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
|
|
1405
|
-
}
|
|
1406
|
-
function buildSlackApprovalSelectActionId(selectIndex) {
|
|
1407
|
-
return `${SLACK_APPROVAL_SELECT_ACTION_ID}:${String(selectIndex)}`;
|
|
1408
|
-
}
|
|
1409
|
-
function buildSlackCallbackButtonActionId(buttonIndex, choiceIndex) {
|
|
1410
|
-
return `${SLACK_CALLBACK_BUTTON_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
|
|
1411
|
-
}
|
|
1412
|
-
function buildSlackCallbackSelectActionId(selectIndex) {
|
|
1413
|
-
return `${SLACK_CALLBACK_SELECT_ACTION_ID}:${String(selectIndex)}`;
|
|
1414
|
-
}
|
|
1415
|
-
function buildSlackQuestionButtonActionId(buttonIndex, choiceIndex) {
|
|
1416
|
-
return `${SLACK_QUESTION_BUTTON_ACTION_ID}:${String(buttonIndex)}:${String(choiceIndex + 1)}`;
|
|
1417
|
-
}
|
|
1418
|
-
function resolveSlackButtonStyle(style) {
|
|
1419
|
-
if (style === "primary" || style === "danger") return style;
|
|
1420
|
-
if (style === "success") return "primary";
|
|
1421
|
-
}
|
|
1422
|
-
function resolveSlackActionTarget(action, questionOptionIndices) {
|
|
1423
|
-
if (!action) return;
|
|
1424
|
-
if (action.type === "approval") return {
|
|
1425
|
-
kind: "approval",
|
|
1426
|
-
value: encodeSlackApprovalAction(action)
|
|
1427
|
-
};
|
|
1428
|
-
if (action.type === "question") {
|
|
1429
|
-
if ("intent" in action) return;
|
|
1430
|
-
const optionIndex = resolveAskUserQuestionOptionIndex({
|
|
1431
|
-
questionOptionIndices,
|
|
1432
|
-
questionId: action.questionId,
|
|
1433
|
-
optionValue: action.optionValue
|
|
1434
|
-
});
|
|
1435
|
-
const value = optionIndex === void 0 ? void 0 : encodeSlackQuestionAction({
|
|
1436
|
-
questionId: action.questionId,
|
|
1437
|
-
optionIndex
|
|
1438
|
-
});
|
|
1439
|
-
return value ? {
|
|
1440
|
-
kind: "question",
|
|
1441
|
-
value
|
|
1442
|
-
} : void 0;
|
|
1443
|
-
}
|
|
1444
|
-
if (action.type === "url" || action.type === "web-app") {
|
|
1445
|
-
const url = normalizeOptionalString(action.url);
|
|
1446
|
-
return url ? {
|
|
1447
|
-
kind: "link",
|
|
1448
|
-
url
|
|
1449
|
-
} : void 0;
|
|
1450
|
-
}
|
|
1451
|
-
if (action.type === "callback") {
|
|
1452
|
-
const value = normalizeOptionalString(action.value);
|
|
1453
|
-
return value ? {
|
|
1454
|
-
kind: "callback",
|
|
1455
|
-
value
|
|
1456
|
-
} : void 0;
|
|
1457
|
-
}
|
|
1458
|
-
const command = normalizeOptionalString(action.command);
|
|
1459
|
-
return command && parseExecApprovalCommandText(command) ? {
|
|
1460
|
-
kind: "reply",
|
|
1461
|
-
value: command
|
|
1462
|
-
} : void 0;
|
|
1463
|
-
}
|
|
1464
|
-
function resolveSlackButtonTarget(button, questionOptionIndices) {
|
|
1465
|
-
if (button.action !== void 0) {
|
|
1466
|
-
const action = resolveMessagePresentationButtonAction(button);
|
|
1467
|
-
return action ? resolveSlackActionTarget(action, questionOptionIndices) : void 0;
|
|
1468
|
-
}
|
|
1469
|
-
const legacyUrl = normalizeOptionalString(button.url ?? button.webApp?.url ?? button.web_app?.url);
|
|
1470
|
-
if (legacyUrl && isWithinSlackLimit(legacyUrl, SLACK_BUTTON_URL_MAX)) return {
|
|
1471
|
-
kind: "link",
|
|
1472
|
-
url: legacyUrl
|
|
1473
|
-
};
|
|
1474
|
-
const legacyValue = normalizeOptionalString(button.value);
|
|
1475
|
-
if (legacyValue) return {
|
|
1476
|
-
kind: "reply",
|
|
1477
|
-
value: legacyValue
|
|
1478
|
-
};
|
|
1479
|
-
return legacyUrl ? {
|
|
1480
|
-
kind: "link",
|
|
1481
|
-
url: legacyUrl
|
|
1482
|
-
} : void 0;
|
|
1483
|
-
}
|
|
1484
|
-
function isSlackTextFallbackButton(button) {
|
|
1485
|
-
const action = resolveMessagePresentationButtonAction(button);
|
|
1486
|
-
return action?.type === "question" && "intent" in action && action.intent === "custom-input";
|
|
1487
|
-
}
|
|
1488
|
-
function resolveSlackOptionTarget(option) {
|
|
1489
|
-
if (option.action !== void 0) {
|
|
1490
|
-
const action = resolveMessagePresentationOptionAction(option);
|
|
1491
|
-
const target = action ? resolveSlackActionTarget(action) : void 0;
|
|
1492
|
-
return target?.kind === "link" || target?.kind === "question" ? void 0 : target;
|
|
1493
|
-
}
|
|
1494
|
-
const value = normalizeOptionalString(option.value);
|
|
1495
|
-
return value ? {
|
|
1496
|
-
kind: "reply",
|
|
1497
|
-
value
|
|
1498
|
-
} : void 0;
|
|
1499
|
-
}
|
|
1500
|
-
function isWithinSlackLimit(value, maxLength) {
|
|
1501
|
-
return value.length <= maxLength;
|
|
1502
|
-
}
|
|
1503
|
-
function isRenderableSlackOption(option) {
|
|
1504
|
-
return isWithinSlackLimit(option.value, 150);
|
|
1505
|
-
}
|
|
1506
|
-
function readSlackBlockId(block) {
|
|
1507
|
-
const value = block.block_id;
|
|
1508
|
-
return typeof value === "string" ? value : void 0;
|
|
1509
|
-
}
|
|
1510
|
-
function readSlackOpenClawBlockIndex(blockId, prefix) {
|
|
1511
|
-
if (!blockId.startsWith(prefix)) return;
|
|
1512
|
-
const value = Number.parseInt(blockId.slice(prefix.length), 10);
|
|
1513
|
-
return Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
1514
|
-
}
|
|
1515
|
-
/** Resolve existing Block Kit indexes and native-data budgets before appending portable blocks. */
|
|
1516
|
-
function resolveSlackBlockOffsets(blocks) {
|
|
1517
|
-
let buttonIndexOffset = 0;
|
|
1518
|
-
const dataTableCellCharacterCountOffset = countSlackDataTableBlocksCellCharacters(blocks) ?? 10001;
|
|
1519
|
-
let dataVisualizationCountOffset = 0;
|
|
1520
|
-
let selectIndexOffset = 0;
|
|
1521
|
-
for (const block of blocks ?? []) {
|
|
1522
|
-
if (hasSlackDataVisualizationBlock([block])) dataVisualizationCountOffset += 1;
|
|
1523
|
-
const blockId = readSlackBlockId(block);
|
|
1524
|
-
if (!blockId) continue;
|
|
1525
|
-
buttonIndexOffset = Math.max(buttonIndexOffset, readSlackOpenClawBlockIndex(blockId, "openclaw_reply_buttons_") ?? 0);
|
|
1526
|
-
selectIndexOffset = Math.max(selectIndexOffset, readSlackOpenClawBlockIndex(blockId, "openclaw_reply_select_") ?? 0);
|
|
1527
|
-
}
|
|
1528
|
-
return {
|
|
1529
|
-
buttonIndexOffset,
|
|
1530
|
-
dataTableCellCharacterCountOffset,
|
|
1531
|
-
dataVisualizationCountOffset,
|
|
1532
|
-
selectIndexOffset
|
|
1533
|
-
};
|
|
1534
|
-
}
|
|
1535
|
-
/**
|
|
1536
|
-
* @deprecated Use buildSlackPresentationBlocks with MessagePresentation.
|
|
1537
|
-
*/
|
|
1538
|
-
function buildSlackInteractiveBlocks(interactive, options = {}) {
|
|
1539
|
-
return buildSlackPresentationBlocks(interactive ? legacyInteractiveReplyToPresentation(interactive) : void 0, options);
|
|
1540
|
-
}
|
|
1541
|
-
/** Render portable presentation blocks as Slack Block Kit blocks. */
|
|
1542
|
-
function buildSlackPresentationBlocks(presentation, options = {}) {
|
|
1543
|
-
if (!presentation) return [];
|
|
1544
|
-
const renderTablesNatively = canRenderSlackPresentationTables(presentation, options);
|
|
1545
|
-
const blocks = [];
|
|
1546
|
-
if (presentation.title) blocks.push({
|
|
1547
|
-
type: "header",
|
|
1548
|
-
text: {
|
|
1549
|
-
type: "plain_text",
|
|
1550
|
-
text: truncateSlackText(presentation.title, 150),
|
|
1551
|
-
emoji: true
|
|
1552
|
-
}
|
|
1553
|
-
});
|
|
1554
|
-
let buttonIndex = options.buttonIndexOffset ?? 0;
|
|
1555
|
-
let dataTableCellCharacterCount = options.dataTableCellCharacterCountOffset ?? 0;
|
|
1556
|
-
let dataVisualizationCount = options.dataVisualizationCountOffset ?? 0;
|
|
1557
|
-
let selectIndex = options.selectIndexOffset ?? 0;
|
|
1558
|
-
for (const block of presentation.blocks) {
|
|
1559
|
-
if (block.type === "text" || block.type === "context") {
|
|
1560
|
-
const text = block.text.trim();
|
|
1561
|
-
if (!text) continue;
|
|
1562
|
-
for (const chunk of chunkSlackMrkdwnText(text, SLACK_SECTION_TEXT_MAX)) blocks.push(block.type === "context" ? {
|
|
1563
|
-
type: "context",
|
|
1564
|
-
elements: [{
|
|
1565
|
-
type: "mrkdwn",
|
|
1566
|
-
text: chunk,
|
|
1567
|
-
verbatim: true
|
|
1568
|
-
}]
|
|
1569
|
-
} : {
|
|
1570
|
-
type: "section",
|
|
1571
|
-
text: {
|
|
1572
|
-
type: "mrkdwn",
|
|
1573
|
-
text: chunk
|
|
1574
|
-
}
|
|
1575
|
-
});
|
|
1576
|
-
continue;
|
|
1577
|
-
}
|
|
1578
|
-
if (block.type === "divider") {
|
|
1579
|
-
blocks.push({ type: "divider" });
|
|
1580
|
-
continue;
|
|
1581
|
-
}
|
|
1582
|
-
if (block.type === "buttons") {
|
|
1583
|
-
const rendered = buildSlackPresentationButtonBlock(block, buttonIndex + 1, options.questionOptionIndices);
|
|
1584
|
-
if (rendered) {
|
|
1585
|
-
buttonIndex += 1;
|
|
1586
|
-
blocks.push(rendered);
|
|
1587
|
-
}
|
|
1588
|
-
continue;
|
|
1589
|
-
}
|
|
1590
|
-
if (block.type === "chart") {
|
|
1591
|
-
const rendered = dataVisualizationCount < 2 ? buildSlackPresentationChartBlock(block) : void 0;
|
|
1592
|
-
if (rendered) {
|
|
1593
|
-
dataVisualizationCount += 1;
|
|
1594
|
-
blocks.push(rendered);
|
|
1595
|
-
} else {
|
|
1596
|
-
const fallback = renderSlackMessagePresentationChartFallbackText(block);
|
|
1597
|
-
blocks.push(...chunkTextForOutbound(fallback, SLACK_SECTION_TEXT_MAX).map((text) => ({
|
|
1598
|
-
type: "context",
|
|
1599
|
-
elements: [{
|
|
1600
|
-
type: "mrkdwn",
|
|
1601
|
-
text,
|
|
1602
|
-
verbatim: true
|
|
1603
|
-
}]
|
|
1604
|
-
})));
|
|
1605
|
-
}
|
|
1606
|
-
continue;
|
|
1607
|
-
}
|
|
1608
|
-
if (block.type === "table") {
|
|
1609
|
-
if (!renderTablesNatively) continue;
|
|
1610
|
-
const rendered = buildSlackDataTableBlock(block, { cellCharacterCountOffset: dataTableCellCharacterCount });
|
|
1611
|
-
if (rendered) {
|
|
1612
|
-
dataTableCellCharacterCount += countSlackDataTableCellCharacters(rendered);
|
|
1613
|
-
blocks.push(rendered);
|
|
1614
|
-
}
|
|
1615
|
-
continue;
|
|
1616
|
-
}
|
|
1617
|
-
if (block.type === "select") {
|
|
1618
|
-
const rendered = buildSlackPresentationSelectBlock(block, selectIndex + 1);
|
|
1619
|
-
if (rendered) {
|
|
1620
|
-
selectIndex += 1;
|
|
1621
|
-
blocks.push(rendered);
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
return blocks;
|
|
1626
|
-
}
|
|
1627
|
-
function buildSlackPresentationChartBlock(block) {
|
|
1628
|
-
return buildSlackDataVisualizationBlock(block);
|
|
1629
|
-
}
|
|
1630
|
-
function buildSlackPresentationButtonBlock(block, buttonIndex, questionOptionIndices) {
|
|
1631
|
-
const elements = block.buttons.flatMap((button, choiceIndex) => {
|
|
1632
|
-
const target = resolveSlackButtonTarget(button, questionOptionIndices);
|
|
1633
|
-
if (!target || (target.kind === "link" ? !isWithinSlackLimit(target.url, SLACK_BUTTON_URL_MAX) : !isWithinSlackLimit(target.value, 2e3))) return [];
|
|
1634
|
-
const style = resolveSlackButtonStyle(button.style);
|
|
1635
|
-
return [{
|
|
1636
|
-
type: "button",
|
|
1637
|
-
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),
|
|
1638
|
-
text: {
|
|
1639
|
-
type: "plain_text",
|
|
1640
|
-
text: truncateSlackText(button.label, 75),
|
|
1641
|
-
emoji: true
|
|
1642
|
-
},
|
|
1643
|
-
...target.kind === "link" ? { url: target.url } : { value: target.value },
|
|
1644
|
-
...style ? { style } : {}
|
|
1645
|
-
}];
|
|
1646
|
-
}).slice(0, 25);
|
|
1647
|
-
return elements.length > 0 ? {
|
|
1648
|
-
type: "actions",
|
|
1649
|
-
block_id: `openclaw_reply_buttons_${buttonIndex}`,
|
|
1650
|
-
elements
|
|
1651
|
-
} : void 0;
|
|
1652
|
-
}
|
|
1653
|
-
/** True when every portable table fits Slack's native per-message table budget. */
|
|
1654
|
-
function canRenderSlackPresentationTables(presentation, options = {}) {
|
|
1655
|
-
let cellCharacterCount = options.dataTableCellCharacterCountOffset ?? 0;
|
|
1656
|
-
for (const block of presentation.blocks) {
|
|
1657
|
-
if (block.type !== "table") continue;
|
|
1658
|
-
const rendered = buildSlackDataTableBlock(block, { cellCharacterCountOffset: cellCharacterCount });
|
|
1659
|
-
if (!rendered) return false;
|
|
1660
|
-
cellCharacterCount += countSlackDataTableCellCharacters(rendered);
|
|
1661
|
-
}
|
|
1662
|
-
return true;
|
|
1663
|
-
}
|
|
1664
|
-
/** True when native Slack rendering preserves every portable control. */
|
|
1665
|
-
function canRenderSlackPresentation(presentation, options = {}) {
|
|
1666
|
-
if (presentation.title && !isWithinSlackLimit(presentation.title.trim(), 150)) return false;
|
|
1667
|
-
if (!canRenderSlackPresentationTables(presentation, options)) return false;
|
|
1668
|
-
let dataVisualizationCount = options.dataVisualizationCountOffset ?? 0;
|
|
1669
|
-
for (const block of presentation.blocks) {
|
|
1670
|
-
if (block.type === "text" || block.type === "context") continue;
|
|
1671
|
-
if (block.type === "buttons") {
|
|
1672
|
-
let nativeButtonCount = 0;
|
|
1673
|
-
if (!(block.buttons.every((button) => {
|
|
1674
|
-
if (isSlackTextFallbackButton(button)) return true;
|
|
1675
|
-
nativeButtonCount += 1;
|
|
1676
|
-
if (!isWithinSlackLimit(button.label, 75)) return false;
|
|
1677
|
-
const target = resolveSlackButtonTarget(button, options.questionOptionIndices);
|
|
1678
|
-
return target ? target.kind === "link" ? isWithinSlackLimit(target.url, SLACK_BUTTON_URL_MAX) : isWithinSlackLimit(target.value, 2e3) : false;
|
|
1679
|
-
}) && nativeButtonCount <= 25)) return false;
|
|
1680
|
-
continue;
|
|
1681
|
-
}
|
|
1682
|
-
if (block.type === "select") {
|
|
1683
|
-
if (!(isWithinSlackLimit(normalizeOptionalString(block.placeholder) ?? "Choose an option", 75) && block.options.length <= 100 && (!block.placeholder || isWithinSlackLimit(block.placeholder, 75)) && block.options.every((option) => {
|
|
1684
|
-
if (!isWithinSlackLimit(option.label, 75)) return false;
|
|
1685
|
-
const target = resolveSlackOptionTarget(option);
|
|
1686
|
-
return target ? isRenderableSlackOption({
|
|
1687
|
-
label: option.label,
|
|
1688
|
-
...target
|
|
1689
|
-
}) : false;
|
|
1690
|
-
}) && new Set(block.options.map((option) => resolveSlackOptionTarget(option)?.kind)).size === 1)) return false;
|
|
1691
|
-
continue;
|
|
1692
|
-
}
|
|
1693
|
-
if (block.type === "chart") {
|
|
1694
|
-
if (dataVisualizationCount >= 2 || !canRenderSlackDataVisualization(block)) return false;
|
|
1695
|
-
dataVisualizationCount += 1;
|
|
1696
|
-
continue;
|
|
1697
|
-
}
|
|
1698
|
-
if (block.type === "table") continue;
|
|
1699
|
-
}
|
|
1700
|
-
return true;
|
|
1701
|
-
}
|
|
1702
|
-
function buildSlackPresentationSelectBlock(block, selectIndex) {
|
|
1703
|
-
const options = block.options.flatMap((option) => {
|
|
1704
|
-
const target = resolveSlackOptionTarget(option);
|
|
1705
|
-
return target ? [{
|
|
1706
|
-
label: option.label,
|
|
1707
|
-
...target
|
|
1708
|
-
}] : [];
|
|
1709
|
-
}).filter(isRenderableSlackOption).slice(0, 100);
|
|
1710
|
-
const optionKinds = new Set(options.map((option) => option.kind));
|
|
1711
|
-
return options.length > 0 && optionKinds.size === 1 ? {
|
|
1712
|
-
type: "actions",
|
|
1713
|
-
block_id: `openclaw_reply_select_${selectIndex}`,
|
|
1714
|
-
elements: [{
|
|
1715
|
-
type: "static_select",
|
|
1716
|
-
action_id: options[0]?.kind === "approval" ? buildSlackApprovalSelectActionId(selectIndex) : options[0]?.kind === "callback" ? buildSlackCallbackSelectActionId(selectIndex) : buildSlackReplySelectActionId(selectIndex),
|
|
1717
|
-
placeholder: {
|
|
1718
|
-
type: "plain_text",
|
|
1719
|
-
text: truncateSlackText(normalizeOptionalString(block.placeholder) ?? "Choose an option", 75),
|
|
1720
|
-
emoji: true
|
|
1721
|
-
},
|
|
1722
|
-
options: options.map((option) => ({
|
|
1723
|
-
text: {
|
|
1724
|
-
type: "plain_text",
|
|
1725
|
-
text: truncateSlackText(option.label, 75),
|
|
1726
|
-
emoji: true
|
|
1727
|
-
},
|
|
1728
|
-
value: option.value
|
|
1729
|
-
}))
|
|
1730
|
-
}]
|
|
1731
|
-
} : void 0;
|
|
1732
|
-
}
|
|
1733
|
-
//#endregion
|
|
1734
1304
|
//#region extensions/slack/src/limits.ts
|
|
1735
1305
|
const SLACK_TEXT_LIMIT = 8e3;
|
|
1736
1306
|
const SLACK_MESSAGE_TEXT_RECOMMENDED_LIMIT = 4e3;
|
|
1737
1307
|
const SLACK_EDIT_TEXT_MAX_BYTES = 4e3;
|
|
1738
1308
|
const SLACK_MESSAGE_TEXT_HARD_LIMIT = 4e4;
|
|
1739
1309
|
//#endregion
|
|
1740
|
-
//#region extensions/slack/src/authored-text.ts
|
|
1741
|
-
function normalizeComparableSlackText(text) {
|
|
1742
|
-
return text.trim().replace(/\s+/g, " ");
|
|
1743
|
-
}
|
|
1744
|
-
function isSlackAuthoredTextRepresentedInInteractive(text, interactive) {
|
|
1745
|
-
return isSlackAuthoredTextRepresentedInFragments(text, interactive?.blocks.flatMap((block) => block.type === "text" ? [block.text] : []) ?? []);
|
|
1746
|
-
}
|
|
1747
|
-
function isSlackAuthoredTextRepresentedInFragments(text, rawFragments) {
|
|
1748
|
-
const target = normalizeComparableSlackText(text);
|
|
1749
|
-
const fragments = rawFragments.map(normalizeComparableSlackText).filter(Boolean);
|
|
1750
|
-
for (let start = 0; start < fragments.length; start += 1) {
|
|
1751
|
-
let combined = "";
|
|
1752
|
-
for (let end = start; end < fragments.length; end += 1) {
|
|
1753
|
-
combined = normalizeComparableSlackText(`${combined} ${fragments[end]}`);
|
|
1754
|
-
if (combined === target) return true;
|
|
1755
|
-
if (combined.length > target.length) break;
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
return false;
|
|
1759
|
-
}
|
|
1760
|
-
/** Resolve placement from producer facts, before accessibility text changes the payload text. */
|
|
1761
|
-
function resolveSlackAuthoredTextPlacement(params) {
|
|
1762
|
-
const text = normalizeOptionalString(params.text);
|
|
1763
|
-
if (!text) return "none";
|
|
1764
|
-
return params.renderedInBlocks || isSlackAuthoredTextRepresentedInFragments(text, params.renderedTextFragments ?? []) || isSlackAuthoredTextRepresentedInInteractive(text, params.interactive) ? "blocks" : "outside-blocks";
|
|
1765
|
-
}
|
|
1766
|
-
//#endregion
|
|
1767
1310
|
//#region extensions/slack/src/blocks-fallback.ts
|
|
1768
1311
|
const SLACK_SELECT_ELEMENT_TYPES = /* @__PURE__ */ new Set([
|
|
1769
1312
|
"static_select",
|
|
@@ -1981,324 +1524,109 @@ function appendSlackNativeDataPlainTextFallback(text, blocks) {
|
|
|
1981
1524
|
return appendSlackNativeDataFallback(text, blocks, renderSlackNativeDataPlainTextBlock);
|
|
1982
1525
|
}
|
|
1983
1526
|
//#endregion
|
|
1984
|
-
//#region extensions/slack/src/
|
|
1985
|
-
function
|
|
1986
|
-
|
|
1987
|
-
if (!
|
|
1988
|
-
return value.map((raw) => {
|
|
1989
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("Slack rendered presentation segment must be an object");
|
|
1990
|
-
const segment = raw;
|
|
1991
|
-
if (segment.kind === "text" && typeof segment.text === "string" && segment.mrkdwn === false) return {
|
|
1992
|
-
kind: "text",
|
|
1993
|
-
text: segment.text,
|
|
1994
|
-
mrkdwn: false
|
|
1995
|
-
};
|
|
1996
|
-
if (segment.kind === "blocks") {
|
|
1997
|
-
const blocks = parseSlackBlocksInput(segment.blocks);
|
|
1998
|
-
if (blocks?.length) return {
|
|
1999
|
-
kind: "blocks",
|
|
2000
|
-
blocks
|
|
2001
|
-
};
|
|
2002
|
-
}
|
|
2003
|
-
throw new Error("Slack rendered presentation segment is invalid");
|
|
2004
|
-
});
|
|
2005
|
-
}
|
|
2006
|
-
/** Convert compiled segments into ordered sender calls without re-inferring text placement. */
|
|
2007
|
-
function resolveSlackReplyDeliveryMessages(params) {
|
|
2008
|
-
const messages = [];
|
|
2009
|
-
let outsideText = params.authoredTextPlacement === "outside-blocks" ? params.text?.trim() ?? "" : "";
|
|
2010
|
-
for (const segment of params.segments) {
|
|
2011
|
-
if (segment.kind === "text") {
|
|
2012
|
-
const text = [outsideText, segment.text].filter(Boolean).join("\n\n");
|
|
2013
|
-
outsideText = "";
|
|
2014
|
-
if (text) messages.push({
|
|
2015
|
-
text,
|
|
2016
|
-
textIsSlackPlainText: true
|
|
2017
|
-
});
|
|
2018
|
-
continue;
|
|
2019
|
-
}
|
|
2020
|
-
const baseText = outsideText;
|
|
2021
|
-
outsideText = "";
|
|
2022
|
-
const text = buildSlackNativeDataAccessibilityText(baseText, segment.blocks) || buildSlackBlocksFallbackText(segment.blocks);
|
|
2023
|
-
const authoredTextPlacement = baseText ? "outside-blocks" : params.authoredTextPlacement === "blocks" ? "blocks" : "none";
|
|
2024
|
-
messages.push({
|
|
2025
|
-
text,
|
|
2026
|
-
blocks: segment.blocks,
|
|
2027
|
-
authoredTextPlacement,
|
|
2028
|
-
...baseText ? { nativeDataFallbackBaseText: baseText } : {}
|
|
2029
|
-
});
|
|
2030
|
-
}
|
|
2031
|
-
if (outsideText) messages.push({
|
|
2032
|
-
text: outsideText,
|
|
2033
|
-
authoredTextPlacement: "outside-blocks"
|
|
2034
|
-
});
|
|
2035
|
-
return messages;
|
|
2036
|
-
}
|
|
2037
|
-
function resolveSlackReplyText(payload, text = payload.text) {
|
|
2038
|
-
const presentation = normalizeMessagePresentation(payload.presentation);
|
|
2039
|
-
return presentation ? renderSlackMessagePresentationFallbackText({
|
|
2040
|
-
text,
|
|
2041
|
-
presentation
|
|
2042
|
-
}) : 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");
|
|
2043
1531
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
if (hasSlackNativeDataBlock(blocks)) {
|
|
2063
|
-
renderedText = appendSlackNativeDataFallbackText(sourceText, blocks) || renderedText;
|
|
2064
|
-
textIsSlackMrkdwn = true;
|
|
2065
|
-
} else if (message?.authoredTextPlacement === "blocks") {
|
|
2066
|
-
renderedText = sourceText;
|
|
2067
|
-
textIsSlackMrkdwn = false;
|
|
2068
|
-
}
|
|
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;
|
|
2069
1550
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
}
|
|
2077
|
-
const blockPart = messages.find((message) => message.blocks?.length);
|
|
2078
|
-
return {
|
|
2079
|
-
mode: "split",
|
|
2080
|
-
fallbackText: messages.map((message) => message.text).filter(Boolean).join("\n\n"),
|
|
2081
|
-
...blockPart ? { blockPart: {
|
|
2082
|
-
text: blockPart.text,
|
|
2083
|
-
blocks: blockPart.blocks
|
|
2084
|
-
} } : {}
|
|
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
|
|
2085
1557
|
};
|
|
2086
1558
|
}
|
|
2087
|
-
function
|
|
2088
|
-
const
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
});
|
|
2101
|
-
}
|
|
2102
|
-
function buildSlackAuthoredTextBlocks(text) {
|
|
2103
|
-
return markdownToSlackMrkdwnChunks(text, SLACK_SECTION_TEXT_MAX).map((chunk) => ({
|
|
2104
|
-
type: "section",
|
|
2105
|
-
text: {
|
|
2106
|
-
type: "mrkdwn",
|
|
2107
|
-
text: chunk,
|
|
2108
|
-
verbatim: true
|
|
2109
|
-
}
|
|
2110
|
-
}));
|
|
2111
|
-
}
|
|
2112
|
-
function addPreviewVerbatimToAuthoredTextBlocks(blocks, sourceText) {
|
|
2113
|
-
if (!blocks?.length || !sourceText) return blocks;
|
|
2114
|
-
const authoredChunks = new Set(markdownToSlackMrkdwnChunks(sourceText, SLACK_SECTION_TEXT_MAX));
|
|
2115
|
-
return blocks.map((block) => {
|
|
2116
|
-
const text = block.text;
|
|
2117
|
-
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;
|
|
2118
1572
|
return {
|
|
2119
|
-
...
|
|
2120
|
-
|
|
2121
|
-
...
|
|
2122
|
-
|
|
1573
|
+
...binding,
|
|
1574
|
+
match: {
|
|
1575
|
+
...binding.match,
|
|
1576
|
+
peer: normalizedPeer
|
|
2123
1577
|
}
|
|
2124
1578
|
};
|
|
2125
1579
|
});
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
}
|
|
2134
|
-
function appendTextSegment(segments, text) {
|
|
2135
|
-
const trimmed = text.trim();
|
|
2136
|
-
if (!trimmed) return;
|
|
2137
|
-
const last = segments.at(-1);
|
|
2138
|
-
if (last?.kind === "text") {
|
|
2139
|
-
last.text = `${last.text}\n\n${trimmed}`;
|
|
2140
|
-
return;
|
|
2141
|
-
}
|
|
2142
|
-
segments.push({
|
|
2143
|
-
kind: "text",
|
|
2144
|
-
text: trimmed,
|
|
2145
|
-
mrkdwn: false
|
|
2146
|
-
});
|
|
2147
|
-
}
|
|
2148
|
-
function appendBlockSegment(segments, blocks, startNew = false) {
|
|
2149
|
-
let shouldStartNew = startNew;
|
|
2150
|
-
for (const block of blocks) {
|
|
2151
|
-
const last = segments.at(-1);
|
|
2152
|
-
if (!shouldStartNew && last?.kind === "blocks" && last.blocks.length < 50) last.blocks.push(block);
|
|
2153
|
-
else segments.push({
|
|
2154
|
-
kind: "blocks",
|
|
2155
|
-
blocks: [block]
|
|
2156
|
-
});
|
|
2157
|
-
shouldStartNew = false;
|
|
2158
|
-
}
|
|
2159
|
-
}
|
|
2160
|
-
function resolvePresentationRenderOptions(segments, mode) {
|
|
2161
|
-
const allOffsets = resolveSlackBlockOffsets(readAllNativeBlocks(segments));
|
|
2162
|
-
return {
|
|
2163
|
-
...mode === "current" ? resolveSlackBlockOffsets(readLastBlockSegment(segments)) : {},
|
|
2164
|
-
buttonIndexOffset: allOffsets.buttonIndexOffset,
|
|
2165
|
-
selectIndexOffset: allOffsets.selectIndexOffset
|
|
2166
|
-
};
|
|
2167
|
-
}
|
|
2168
|
-
function renderNativePresentation(presentation, options) {
|
|
2169
|
-
if (!canRenderSlackPresentation(presentation, options)) return;
|
|
2170
|
-
const blocks = buildSlackPresentationBlocks(presentation, options);
|
|
2171
|
-
return blocks.length > 0 ? blocks : void 0;
|
|
2172
|
-
}
|
|
2173
|
-
function appendPresentationPart(segments, presentation, questionOptionIndices) {
|
|
2174
|
-
const currentBlocks = readLastBlockSegment(segments);
|
|
2175
|
-
const currentRendered = renderNativePresentation(presentation, {
|
|
2176
|
-
...resolvePresentationRenderOptions(segments, "current"),
|
|
2177
|
-
questionOptionIndices
|
|
2178
|
-
});
|
|
2179
|
-
if (currentRendered && currentBlocks.length + currentRendered.length <= 50) {
|
|
2180
|
-
appendBlockSegment(segments, currentRendered);
|
|
2181
|
-
return;
|
|
2182
|
-
}
|
|
2183
|
-
const freshRendered = renderNativePresentation(presentation, {
|
|
2184
|
-
...resolvePresentationRenderOptions(segments, "new-message"),
|
|
2185
|
-
questionOptionIndices
|
|
2186
|
-
});
|
|
2187
|
-
if (freshRendered) {
|
|
2188
|
-
appendBlockSegment(segments, freshRendered, true);
|
|
2189
|
-
return;
|
|
2190
|
-
}
|
|
2191
|
-
appendTextSegment(segments, renderMessagePresentationFallbackText({ presentation }));
|
|
2192
|
-
}
|
|
2193
|
-
const SLACK_BUTTON_CONTROL_ACTION_IDS = [
|
|
2194
|
-
SLACK_APPROVAL_BUTTON_ACTION_ID,
|
|
2195
|
-
SLACK_CALLBACK_BUTTON_ACTION_ID,
|
|
2196
|
-
SLACK_QUESTION_BUTTON_ACTION_ID,
|
|
2197
|
-
SLACK_REPLY_BUTTON_ACTION_ID,
|
|
2198
|
-
SLACK_REPLY_LINK_ACTION_ID
|
|
2199
|
-
];
|
|
2200
|
-
const SLACK_SELECT_CONTROL_ACTION_IDS = [
|
|
2201
|
-
SLACK_APPROVAL_SELECT_ACTION_ID,
|
|
2202
|
-
SLACK_CALLBACK_SELECT_ACTION_ID,
|
|
2203
|
-
SLACK_REPLY_SELECT_ACTION_ID
|
|
2204
|
-
];
|
|
2205
|
-
function readGeneratedSlackControlRowKey(block) {
|
|
2206
|
-
const record = block;
|
|
2207
|
-
if (record.type !== "actions" || typeof record.block_id !== "string") return;
|
|
2208
|
-
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;
|
|
2209
|
-
if (!expectedElementType || !Array.isArray(record.elements) || record.elements.length === 0) return;
|
|
2210
|
-
const actionIds = expectedElementType === "button" ? SLACK_BUTTON_CONTROL_ACTION_IDS : SLACK_SELECT_CONTROL_ACTION_IDS;
|
|
2211
|
-
const elements = record.elements.map((element) => {
|
|
2212
|
-
if (!element || typeof element !== "object" || Array.isArray(element)) return;
|
|
2213
|
-
const { action_id: actionId, ...content } = element;
|
|
2214
|
-
const actionFamily = typeof actionId === "string" ? actionIds.find((candidate) => actionId.startsWith(`${candidate}:`)) : void 0;
|
|
2215
|
-
return actionFamily && content.type === expectedElementType ? [actionFamily, content] : void 0;
|
|
2216
|
-
});
|
|
2217
|
-
return elements.some((element) => element === void 0) ? void 0 : JSON.stringify(elements);
|
|
2218
|
-
}
|
|
2219
|
-
function subtractMirroredSlackControlRows(params) {
|
|
2220
|
-
const remainingMirrors = /* @__PURE__ */ new Map();
|
|
2221
|
-
for (const block of params.presentationBlocks) {
|
|
2222
|
-
const key = readGeneratedSlackControlRowKey(block);
|
|
2223
|
-
if (key) remainingMirrors.set(key, (remainingMirrors.get(key) ?? 0) + 1);
|
|
2224
|
-
}
|
|
2225
|
-
return params.interactiveBlocks.filter((block) => {
|
|
2226
|
-
const key = readGeneratedSlackControlRowKey(block);
|
|
2227
|
-
const remaining = key ? remainingMirrors.get(key) ?? 0 : 0;
|
|
2228
|
-
if (!key || remaining === 0) return true;
|
|
2229
|
-
remainingMirrors.set(key, remaining - 1);
|
|
2230
|
-
return false;
|
|
1580
|
+
const normalizedCfg = changed ? {
|
|
1581
|
+
...cfg,
|
|
1582
|
+
bindings: normalizedBindings
|
|
1583
|
+
} : cfg;
|
|
1584
|
+
slackRouteBindingConfigCache.set(cfg, {
|
|
1585
|
+
bindingsRef: bindings,
|
|
1586
|
+
normalizedCfg
|
|
2231
1587
|
});
|
|
1588
|
+
return normalizedCfg;
|
|
2232
1589
|
}
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
}) !== "blocks") compiledChannelBlocks = [...channelBlocks, ...textBlocks];
|
|
2257
|
-
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
|
|
2258
1613
|
}
|
|
2259
|
-
}
|
|
2260
|
-
if (compiledChannelBlocks.length > 0) appendBlockSegment(segments, compiledChannelBlocks);
|
|
2261
|
-
const presentation = normalizeMessagePresentation(payload.presentation);
|
|
2262
|
-
const questionOptionIndices = resolveAskUserQuestionOptionIndices(payload);
|
|
2263
|
-
const presentationBlockOffset = readAllNativeBlocks(segments).length;
|
|
2264
|
-
if (presentation?.title) appendPresentationPart(segments, {
|
|
2265
|
-
title: presentation.title,
|
|
2266
|
-
blocks: []
|
|
2267
|
-
}, questionOptionIndices);
|
|
2268
|
-
for (const block of presentation?.blocks ?? []) appendPresentationPart(segments, { blocks: [block] }, questionOptionIndices);
|
|
2269
|
-
const renderedPresentationBlocks = readAllNativeBlocks(segments).slice(presentationBlockOffset);
|
|
2270
|
-
appendBlockSegment(segments, subtractMirroredSlackControlRows({
|
|
2271
|
-
interactiveBlocks: buildSlackInteractiveBlocks(payload.interactive, {
|
|
2272
|
-
...resolveSlackBlockOffsets(readAllNativeBlocks(segments)),
|
|
2273
|
-
questionOptionIndices
|
|
2274
|
-
}),
|
|
2275
|
-
presentationBlocks: renderedPresentationBlocks
|
|
2276
|
-
}));
|
|
2277
|
-
const renderedTextFragments = segments.flatMap((segment) => {
|
|
2278
|
-
if (segment.kind === "text") return [segment.text];
|
|
2279
|
-
return renderSlackAuthoredTextFragments(segment.blocks);
|
|
2280
|
-
});
|
|
2281
|
-
const authoredTextPlacement = resolveSlackAuthoredTextPlacement({
|
|
2282
|
-
text: payload.text,
|
|
2283
|
-
interactive: payload.interactive,
|
|
2284
|
-
renderedTextFragments
|
|
2285
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;
|
|
2286
1624
|
return {
|
|
2287
|
-
|
|
2288
|
-
|
|
1625
|
+
runtimeRoute,
|
|
1626
|
+
configuredRoute,
|
|
1627
|
+
route: runtimeRoute.boundSessionKey ? runtimeRoute.route : configuredRoute?.route ?? params.route
|
|
2289
1628
|
};
|
|
2290
1629
|
}
|
|
2291
|
-
/** Return the single-message native shape when no ordered text fallback is required. */
|
|
2292
|
-
function resolveSlackReplyBlocks(payload) {
|
|
2293
|
-
const { segments } = resolveSlackReplyBlockResolution(payload);
|
|
2294
|
-
return segments.length === 1 && segments[0]?.kind === "blocks" ? segments[0].blocks : void 0;
|
|
2295
|
-
}
|
|
2296
|
-
//#endregion
|
|
2297
|
-
//#region extensions/slack/src/detached-target-admission.ts
|
|
2298
|
-
function assertSlackDetachedTargetAllowed(accountId, teamId) {
|
|
2299
|
-
const installationKind = getSlackInstallationKind(accountId);
|
|
2300
|
-
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");
|
|
2301
|
-
}
|
|
2302
1630
|
//#endregion
|
|
2303
1631
|
//#region extensions/slack/src/monitor/workspace-routing.ts
|
|
2304
1632
|
function resolveSlackEnterpriseMainDmSessionKey(params) {
|
|
@@ -2366,4 +1694,46 @@ function resolveSlackGroupToolPolicy(params) {
|
|
|
2366
1694
|
});
|
|
2367
1695
|
}
|
|
2368
1696
|
//#endregion
|
|
2369
|
-
|
|
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 };
|