@openclaw/slack 2026.11.3 → 2026.11.5-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.setup/{account-inspect-CmkKiSb9.mjs → account-inspect-BLVha-ov.mjs} +11 -8
- package/dist/.setup/{accounts-msihSF3-.mjs → accounts-CGJScKtk.mjs} +10 -1
- package/dist/.setup/{action-runtime-ChRfnnBu.mjs → action-runtime-D01wBhx5.mjs} +38 -45
- package/dist/.setup/action-runtime.runtime-VQGaR6g8.mjs +1 -0
- package/dist/.setup/{actions-By7tvpvv.mjs → actions-D5S6D_BU.mjs} +89 -76
- package/dist/.setup/{approval-handler.runtime-D8ArtDQt.mjs → approval-handler.runtime-Buw5jh2Y.mjs} +26 -30
- package/dist/.setup/{approval-message-updates-DT7XBPbV.mjs → approval-message-updates-pmXcve5E.mjs} +1 -1
- package/dist/.setup/{channel-DLFiMJm2.mjs → channel-C_R98Nbv.mjs} +569 -339
- package/dist/.setup/{channel-type-BEFP2ORi.mjs → channel-type-B5P3w9eA.mjs} +4 -8
- package/dist/.setup/{channel.setup-CzFw73mU.mjs → channel.setup-Lq2OxKkF.mjs} +13 -13
- package/dist/.setup/{config-schema-DNnh6bWU.mjs → config-schema-BWAaa5au.mjs} +3 -7
- package/dist/.setup/cursor-pages-CATAzbTT.mjs +313 -0
- package/dist/.setup/{directory-config-Cjj0N8R0.mjs → directory-config-D-hf9KGQ.mjs} +3 -3
- package/dist/.setup/{directory-live-BIKD-5B_.mjs → directory-live-onKAFI1E.mjs} +4 -4
- package/dist/.setup/{doctor-contract-Cd4-VzIM.mjs → doctor-contract-9-QJRCXk.mjs} +37 -45
- package/dist/.setup/{message-tool-api-BS5ekEA_.mjs → message-tool-api-LGIgVXJf.mjs} +4 -3
- package/dist/.setup/{monitor-CV6Z6FKP.mjs → monitor-Cm1TuqxN.mjs} +3424 -3703
- package/dist/.setup/{outbound-adapter-9dbnYtL1.mjs → outbound-adapter-C-95EWl2.mjs} +30 -36
- package/dist/.setup/paths-B1u59oLK.mjs +8 -0
- package/dist/.setup/{pipeline.runtime-DLIQf1Bo.mjs → pipeline.runtime-DGnV6akc.mjs} +416 -534
- package/dist/.setup/{policy-yhn48xf5.mjs → policy-DxhGRvc2.mjs} +1 -2
- package/dist/.setup/{session-status-Dv8wNBEt.mjs → probe-DANukR3O.mjs} +1206 -839
- package/dist/.setup/question-delivery-CWC7sRhH.mjs +39 -0
- package/dist/.setup/{relay-source-CxDcrn16.mjs → relay-source-DoyOlTbK.mjs} +5 -3
- package/dist/.setup/{replies-MRkrg3rC.mjs → replies-BcmUKord.mjs} +34 -19
- package/dist/.setup/{reply-blocks-Ca-raz2z.mjs → reply-blocks-BzsXUUUL.mjs} +201 -206
- package/dist/.setup/{resolve-channels-DRRhElwz.mjs → resolve-channels-XDPgnesf.mjs} +4 -7
- package/dist/.setup/{resolve-users-Cz8e17ky.mjs → resolve-users-aDAv24U2.mjs} +8 -16
- package/dist/.setup/runtime-setter-api-Cx_PV-xu.mjs +2 -0
- package/dist/.setup/{scopes-DfQVi8Jw.mjs → scopes-BocHwImV.mjs} +2 -1
- package/dist/.setup/secret-contract-api-BlLz88mj.mjs +2 -0
- package/dist/.setup/{secret-contract-vVRMoE1D.mjs → secret-contract-uiQtoNlu.mjs} +2 -2
- package/dist/.setup/{send-results-eeOu_HYm.mjs → send-results-BGxlroJ-.mjs} +3 -4
- package/dist/.setup/{send-C-8wY0lK.mjs → send-yK6_-px6.mjs} +297 -334
- package/dist/.setup/setup-plugin-api-Cfz58xoh.mjs +2 -0
- package/dist/.setup/{setup-surface--n8VsDvV.mjs → setup-surface-C0KRtyqa.mjs} +5 -6
- package/dist/.setup/{slash-dispatch.runtime-D3Wq45ER.mjs → slash-dispatch.runtime-CyVC3bYx.mjs} +2 -2
- package/dist/.setup/{target-parsing-FFE26kOV.mjs → target-parsing-Bx2UZniD.mjs} +1 -2
- package/dist/.setup/{targets-C6LZQL1Q.mjs → targets-D-ASeWHy.mjs} +1 -1
- package/dist/.source-module-urls/c5ffd0957b234c7c12090e7de4eca2482ef339b8561e078da55b734211b0ba58.json +1 -0
- package/dist/account-inspect-api.js +1 -1
- package/dist/api.js +16 -17
- package/dist/channel-config-api.js +1 -2
- package/dist/channel-plugin-api.js +1 -2
- package/dist/directory-contract-api.js +1 -2
- package/dist/doctor-contract-api.js +1 -2
- package/dist/index.js +0 -6
- package/dist/message-tool-api.js +1 -2
- package/dist/runtime-api.js +10 -12
- package/dist/runtime-setter-api.js +1 -2
- package/dist/secret-contract-api.js +1 -2
- package/dist/security-contract-api.js +1 -2
- package/dist/setup-entry.js +3 -3
- package/dist/setup-plugin-api.js +1 -2
- package/node_modules/@slack/bolt/node_modules/@types/node/README.md +1 -1
- package/node_modules/@slack/bolt/node_modules/@types/node/package.json +2 -2
- package/node_modules/@slack/bolt/node_modules/@types/node/util.d.ts +6 -3
- package/node_modules/@slack/bolt/node_modules/content-disposition/README.md +105 -35
- package/node_modules/@slack/bolt/node_modules/content-disposition/dist/index.d.ts +72 -0
- package/node_modules/@slack/bolt/node_modules/content-disposition/dist/index.js +388 -0
- package/node_modules/@slack/bolt/node_modules/content-disposition/dist/index.js.map +1 -0
- package/node_modules/@slack/bolt/node_modules/content-disposition/package.json +31 -19
- package/node_modules/@slack/bolt/node_modules/content-type/README.md +34 -57
- package/node_modules/@slack/bolt/node_modules/content-type/package.json +35 -25
- package/node_modules/@slack/bolt/node_modules/express/Readme.md +15 -9
- package/node_modules/@slack/bolt/node_modules/express/lib/application.js +2 -2
- package/node_modules/@slack/bolt/node_modules/express/lib/request.js +32 -19
- package/node_modules/@slack/bolt/node_modules/express/lib/response.js +27 -30
- package/node_modules/@slack/bolt/node_modules/express/package.json +10 -10
- package/node_modules/@slack/bolt/node_modules/qs/.github/THREAT_MODEL.md +4 -2
- package/node_modules/@slack/bolt/node_modules/qs/CHANGELOG.md +38 -14
- package/node_modules/@slack/bolt/node_modules/qs/README.md +31 -3
- package/node_modules/@slack/bolt/node_modules/qs/dist/qs.js +20 -20
- package/node_modules/@slack/bolt/node_modules/qs/lib/parse.js +94 -4
- package/node_modules/@slack/bolt/node_modules/qs/lib/stringify.js +4 -4
- package/node_modules/@slack/bolt/node_modules/qs/lib/utils.js +130 -6
- package/node_modules/@slack/bolt/node_modules/qs/package.json +4 -4
- package/node_modules/@slack/bolt/node_modules/qs/test/parse.js +174 -0
- package/node_modules/@slack/bolt/node_modules/qs/test/stringify.js +263 -33
- package/node_modules/@slack/bolt/node_modules/qs/test/utils.js +184 -0
- package/node_modules/@slack/web-api/node_modules/@types/node/README.md +1 -1
- package/node_modules/@slack/web-api/node_modules/@types/node/package.json +2 -2
- package/node_modules/@slack/web-api/node_modules/@types/node/util.d.ts +6 -3
- package/node_modules/typebox/build/format/duration.d.mts +2 -2
- package/node_modules/typebox/build/format/duration.mjs +2 -2
- package/node_modules/typebox/build/format/idn_email.d.mts +1 -0
- package/node_modules/typebox/build/format/idn_email.mjs +1 -0
- package/node_modules/typebox/build/format/idn_hostname.d.mts +1 -1
- package/node_modules/typebox/build/format/idn_hostname.mjs +1 -1
- package/node_modules/typebox/build/format/ipv4.d.mts +2 -2
- package/node_modules/typebox/build/format/ipv4.mjs +2 -2
- package/node_modules/typebox/build/format/ipv6.d.mts +1 -1
- package/node_modules/typebox/build/format/ipv6.mjs +1 -1
- 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/schema/build.mjs +1 -0
- package/node_modules/typebox/build/schema/engine/_unique.d.mts +4 -2
- package/node_modules/typebox/build/schema/engine/_unique.mjs +7 -3
- package/node_modules/typebox/build/schema/engine/additionalItems.mjs +3 -3
- package/node_modules/typebox/build/schema/engine/additionalProperties.d.mts +0 -3
- package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +15 -17
- package/node_modules/typebox/build/schema/engine/contains.mjs +3 -3
- package/node_modules/typebox/build/schema/engine/index.d.mts +1 -0
- package/node_modules/typebox/build/schema/engine/index.mjs +1 -0
- package/node_modules/typebox/build/schema/engine/items.mjs +7 -4
- package/node_modules/typebox/build/schema/engine/maxContains.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/minContains.mjs +3 -3
- package/node_modules/typebox/build/schema/engine/oneOf.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/patternProperties.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/unevaluatedItems.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/unevaluatedProperties.mjs +2 -2
- package/node_modules/typebox/build/system/hashing/hash.mjs +63 -34
- package/node_modules/typebox/build/system/locale/en_US.mjs +2 -1
- package/node_modules/typebox/build/system/memory/assign.d.mts +2 -8
- package/node_modules/typebox/build/system/memory/assign.mjs +13 -6
- package/node_modules/typebox/build/system/memory/clone.d.mts +4 -5
- package/node_modules/typebox/build/system/memory/clone.mjs +8 -96
- package/node_modules/typebox/build/system/memory/create.d.mts +1 -5
- package/node_modules/typebox/build/system/memory/create.mjs +18 -13
- package/node_modules/typebox/build/system/memory/deep.d.mts +2 -0
- package/node_modules/typebox/build/system/memory/deep.mjs +108 -0
- package/node_modules/typebox/build/system/memory/discard.d.mts +1 -1
- package/node_modules/typebox/build/system/memory/discard.mjs +9 -8
- package/node_modules/typebox/build/system/memory/finalize.d.mts +4 -0
- package/node_modules/typebox/build/system/memory/finalize.mjs +30 -0
- package/node_modules/typebox/build/system/memory/keys.d.mts +2 -0
- package/node_modules/typebox/build/system/memory/keys.mjs +8 -0
- package/node_modules/typebox/build/system/memory/update.d.mts +1 -4
- package/node_modules/typebox/build/system/memory/update.mjs +43 -16
- package/node_modules/typebox/build/type/engine/call/index.d.mts +1 -0
- package/node_modules/typebox/build/type/engine/call/index.mjs +1 -0
- package/node_modules/typebox/build/type/engine/evaluate/composite.d.mts +2 -24
- package/node_modules/typebox/build/type/engine/evaluate/composite.mjs +2 -29
- package/node_modules/typebox/build/type/engine/evaluate/index.d.mts +1 -0
- package/node_modules/typebox/build/type/engine/evaluate/index.mjs +1 -0
- package/node_modules/typebox/build/type/engine/evaluate/narrow.d.mts +5 -1
- package/node_modules/typebox/build/type/engine/evaluate/narrow.mjs +6 -1
- package/node_modules/typebox/build/type/engine/evaluate/property.d.mts +30 -0
- package/node_modules/typebox/build/type/engine/evaluate/property.mjs +29 -0
- package/node_modules/typebox/build/type/engine/immutable/instantiate_add.d.mts +1 -3
- package/node_modules/typebox/build/type/engine/immutable/instantiate_add.mjs +1 -4
- package/node_modules/typebox/build/type/engine/object/from_intersect.d.mts +2 -2
- package/node_modules/typebox/build/type/engine/object/from_intersect.mjs +2 -2
- package/node_modules/typebox/build/type/engine/object/from_union.d.mts +2 -2
- package/node_modules/typebox/build/type/engine/object/from_union.mjs +2 -2
- package/node_modules/typebox/build/type/engine/optional/instantiate_add.d.mts +1 -3
- package/node_modules/typebox/build/type/engine/optional/instantiate_add.mjs +1 -4
- package/node_modules/typebox/build/type/engine/readonly/instantiate_add.d.mts +1 -3
- package/node_modules/typebox/build/type/engine/readonly/instantiate_add.mjs +1 -4
- package/node_modules/typebox/build/type/engine/this/index.d.mts +1 -0
- package/node_modules/typebox/build/type/engine/this/index.mjs +1 -0
- package/node_modules/typebox/build/value/clone/clone.d.mts +4 -3
- package/node_modules/typebox/build/value/clone/clone.mjs +5 -7
- package/node_modules/typebox/build/value/repair/from_cyclic.d.mts +2 -0
- package/node_modules/typebox/build/value/repair/from_cyclic.mjs +6 -0
- package/node_modules/typebox/build/value/shared/union_priority_sort.mjs +25 -14
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +1 -1
- package/node_modules/undici/docs/docs/api/Interceptors.md +15 -2
- package/node_modules/undici/lib/dispatcher/client-h2.js +78 -3
- package/node_modules/undici/lib/dispatcher/pool-base.js +20 -3
- package/node_modules/undici/package.json +3 -3
- package/node_modules/undici/types/api.d.ts +1 -1
- package/node_modules/undici/types/utility.d.ts +4 -4
- package/node_modules/zod/package.json +1 -1
- package/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
- package/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
- package/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
- package/node_modules/zod/src/v4/core/compile.ts +9 -0
- package/node_modules/zod/src/v4/core/schemas.ts +35 -1
- package/node_modules/zod/src/v4/core/util.ts +13 -0
- package/node_modules/zod/src/v4/core/versions.ts +1 -1
- package/node_modules/zod/v4/core/compile.cjs +8 -0
- package/node_modules/zod/v4/core/compile.js +8 -0
- package/node_modules/zod/v4/core/schemas.cjs +30 -0
- package/node_modules/zod/v4/core/schemas.d.cts +3 -1
- package/node_modules/zod/v4/core/schemas.d.ts +3 -1
- package/node_modules/zod/v4/core/schemas.js +30 -0
- package/node_modules/zod/v4/core/util.cjs +15 -0
- package/node_modules/zod/v4/core/util.d.cts +2 -0
- package/node_modules/zod/v4/core/util.d.ts +2 -0
- package/node_modules/zod/v4/core/util.js +14 -0
- package/node_modules/zod/v4/core/versions.cjs +1 -1
- package/node_modules/zod/v4/core/versions.js +1 -1
- package/openclaw.plugin.json +6 -30
- package/package.json +7 -7
- package/dist/.setup/action-runtime.runtime-vLxx_7s2.mjs +0 -2
- package/dist/.setup/http-C9sM-FXe.mjs +0 -2
- package/dist/.setup/plugin-routes-D4qozHVn.mjs +0 -22
- package/dist/.setup/probe-IiHogLQN.mjs +0 -694
- package/dist/.setup/registry-DizWcnNp.mjs +0 -36
- package/dist/.setup/runtime-setter-api-CMFvhrAr.mjs +0 -3
- package/dist/.setup/secret-contract-api-DK9gZldc.mjs +0 -3
- package/dist/.setup/setup-plugin-api-BGD1TYnK.mjs +0 -3
- package/dist/.setup/shared-BhJHIZs1.mjs +0 -259
- package/dist/http-routes-api.js +0 -2
- package/node_modules/@slack/bolt/node_modules/body-parser/node_modules/content-type/LICENSE +0 -22
- package/node_modules/@slack/bolt/node_modules/body-parser/node_modules/content-type/README.md +0 -71
- package/node_modules/@slack/bolt/node_modules/body-parser/node_modules/content-type/package.json +0 -52
- package/node_modules/@slack/bolt/node_modules/content-disposition/index.js +0 -536
- package/node_modules/@slack/bolt/node_modules/content-type/HISTORY.md +0 -29
- package/node_modules/@slack/bolt/node_modules/content-type/index.js +0 -225
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/LICENSE +0 -22
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/README.md +0 -71
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/dist/index.d.ts +0 -46
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/dist/index.js +0 -176
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/dist/index.js.map +0 -1
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/package.json +0 -52
- package/node_modules/@slack/bolt/node_modules/type-is/node_modules/content-type/LICENSE +0 -22
- package/node_modules/@slack/bolt/node_modules/type-is/node_modules/content-type/README.md +0 -71
- package/node_modules/@slack/bolt/node_modules/type-is/node_modules/content-type/dist/index.d.ts +0 -46
- package/node_modules/@slack/bolt/node_modules/type-is/node_modules/content-type/dist/index.js +0 -176
- package/node_modules/@slack/bolt/node_modules/type-is/node_modules/content-type/dist/index.js.map +0 -1
- package/node_modules/@slack/bolt/node_modules/type-is/node_modules/content-type/package.json +0 -52
- package/node_modules/typebox/build/system/memory/freeze.d.mts +0 -4
- package/node_modules/typebox/build/system/memory/freeze.mjs +0 -6
- /package/node_modules/@slack/bolt/node_modules/{body-parser/node_modules/content-type → content-type}/dist/index.d.ts +0 -0
- /package/node_modules/@slack/bolt/node_modules/{body-parser/node_modules/content-type → content-type}/dist/index.js +0 -0
- /package/node_modules/@slack/bolt/node_modules/{body-parser/node_modules/content-type → content-type}/dist/index.js.map +0 -0
|
@@ -1,30 +1,36 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-B4FMCO8f.mjs";
|
|
1
2
|
import { r as isSlackPluginAccountConfigured } from "./account-configured-zcR9TJJN.mjs";
|
|
2
|
-
import { a as mergeSlackAccountConfig, c as resolveSlackAccountAllowFrom, i as listSlackAccountIds, o as resolveDefaultSlackAccountId, s as resolveSlackAccount } from "./accounts-
|
|
3
|
-
import { a as parseSlackTarget, n as formatSlackTarget, t as canonicalizeSlackApiTargetId } from "./target-parsing-
|
|
4
|
-
import { t as slackContextTargetsMatch } from "./targets-
|
|
5
|
-
import {
|
|
6
|
-
import { resolveGlobalMap } from "openclaw/plugin-sdk/global-singleton";
|
|
3
|
+
import { a as mergeSlackAccountConfig, c as resolveSlackAccountAllowFrom, i as listSlackAccountIds, m as formatSlackBotTokenIdentityWarning, o as resolveDefaultSlackAccountId, s as resolveSlackAccount } from "./accounts-CGJScKtk.mjs";
|
|
4
|
+
import { a as parseSlackTarget, n as formatSlackTarget, t as canonicalizeSlackApiTargetId } from "./target-parsing-Bx2UZniD.mjs";
|
|
5
|
+
import { t as slackContextTargetsMatch } from "./targets-D-ASeWHy.mjs";
|
|
6
|
+
import { _ as createSlackProbeDispatcher, i as createSlackReadClient } from "./cursor-pages-CATAzbTT.mjs";
|
|
7
7
|
import { normalizeAccountId } from "openclaw/plugin-sdk/account-resolution";
|
|
8
|
-
import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringifiedOptionalString, readNonBlankString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
8
|
+
import { asOptionalRecord, isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringifiedOptionalString, readNonBlankString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
9
9
|
import { normalizeMessageChannel, resolveAgentRoute } from "openclaw/plugin-sdk/routing";
|
|
10
10
|
import { defaultRuntime, logVerbose, warn } from "openclaw/plugin-sdk/runtime-env";
|
|
11
11
|
import { FormatCapabilityProfile, chunkTextForOutbound, markdownToIR, renderMarkdownIRChunksWithinLimit, renderMarkdownWithMarkers } from "openclaw/plugin-sdk/text-chunking";
|
|
12
|
-
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
12
|
+
import { runChannelProbe, sliceUtf16Safe, truncateUtf16Safe, truncateUtf8Prefix } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
13
13
|
import { isSingleUseReplyToMode } from "openclaw/plugin-sdk/reply-reference";
|
|
14
14
|
import { buildChannelKeyCandidates } from "openclaw/plugin-sdk/channel-targets";
|
|
15
15
|
import { createNativeApprovalChannelRouteGatesAsync, doesApprovalRequestSelectChannelAccountAsync, resolveApprovalKind, resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
|
|
16
16
|
import { createChannelApprovalAuth, resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
|
|
17
|
-
import { normalizeHyphenSlug, normalizeStringEntriesLower } from "openclaw/plugin-sdk/string-normalization-runtime";
|
|
17
|
+
import { normalizeHyphenSlug, normalizeStringEntriesLower, truncateWithMarker } from "openclaw/plugin-sdk/string-normalization-runtime";
|
|
18
18
|
import { createChannelExecApprovalProfileAsync, isChannelExecApprovalClientEnabledFromConfig, isChannelExecApprovalTargetRecipient, matchesApprovalRequestFilters } from "openclaw/plugin-sdk/approval-client-runtime";
|
|
19
|
+
import { resolveGlobalMap } from "openclaw/plugin-sdk/global-singleton";
|
|
20
|
+
import { compileAllowlist, resolveCompiledAllowlistMatch } from "openclaw/plugin-sdk/allow-from";
|
|
21
|
+
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
19
22
|
import { channelRouteTargetsMatchExact, stringifyRouteThreadId } from "openclaw/plugin-sdk/channel-route";
|
|
20
23
|
import { normalizeMessagePresentation, renderMessagePresentationChartFallbackText, renderMessagePresentationFallbackText, renderMessagePresentationTableFallbackText } from "openclaw/plugin-sdk/interactive-runtime";
|
|
21
|
-
import { buildApprovalResolutionRef } from "openclaw/plugin-sdk/approval-reference-runtime";
|
|
22
24
|
import { eastAsianWidthType } from "get-east-asian-width";
|
|
23
25
|
import { resolveIntegerOption } from "openclaw/plugin-sdk/number-runtime";
|
|
26
|
+
import { isStringArray } from "openclaw/plugin-sdk/collection-input-runtime";
|
|
27
|
+
import { buildApprovalResolutionRef } from "openclaw/plugin-sdk/approval-reference-runtime";
|
|
24
28
|
import { questionGatewayRuntime } from "openclaw/plugin-sdk/question-gateway-runtime";
|
|
25
29
|
import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
|
26
30
|
import { inspectRuntimeConversationBindingRoute, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRouteAsync } from "openclaw/plugin-sdk/conversation-binding-runtime";
|
|
31
|
+
import { redactSensitiveText } from "openclaw/plugin-sdk/logging-core";
|
|
27
32
|
import { resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
|
|
33
|
+
import { defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
28
34
|
//#region extensions/slack/src/action-threading.ts
|
|
29
35
|
const SLACK_PRIVATE_ACTION_DELIVERY_RESULT = Symbol("slack.action.delivery-result");
|
|
30
36
|
function resolveSlackAutoThreadId(params) {
|
|
@@ -39,6 +45,16 @@ function resolveSlackAutoThreadId(params) {
|
|
|
39
45
|
if (isSingleUseReplyToMode(context.replyToMode ?? "off") && context.hasRepliedRef?.value) return;
|
|
40
46
|
return context.currentThreadTs;
|
|
41
47
|
}
|
|
48
|
+
function resolveSlackThreadTsFromContext(explicitThreadTs, targetChannel, context, opts) {
|
|
49
|
+
if (explicitThreadTs) return explicitThreadTs;
|
|
50
|
+
if (opts?.suppressImplicitThread) return;
|
|
51
|
+
const threadTs = resolveSlackAutoThreadId({
|
|
52
|
+
to: targetChannel,
|
|
53
|
+
toolContext: context
|
|
54
|
+
});
|
|
55
|
+
if (isSingleUseReplyToMode(context?.replyToMode ?? "off") && !context?.hasRepliedRef) return;
|
|
56
|
+
return threadTs;
|
|
57
|
+
}
|
|
42
58
|
//#endregion
|
|
43
59
|
//#region extensions/slack/src/approval-plugin-policy.ts
|
|
44
60
|
/** Returns undefined only when Slack plugin approvals retain legacy account authorization. */
|
|
@@ -140,6 +156,104 @@ function isSlackWorkspaceInstallation(accountId) {
|
|
|
140
156
|
return getSlackInstallationKind(accountId) === "workspace";
|
|
141
157
|
}
|
|
142
158
|
//#endregion
|
|
159
|
+
//#region extensions/slack/src/monitor/allow-list.ts
|
|
160
|
+
const SLACK_SLUG_CACHE_MAX = 512;
|
|
161
|
+
const SLACK_STABLE_USER_ID_RE = /^[ubw][a-z0-9]+$/;
|
|
162
|
+
const slackSlugCache = /* @__PURE__ */ new Map();
|
|
163
|
+
function normalizeSlackSlug(raw) {
|
|
164
|
+
const key = raw ?? "";
|
|
165
|
+
const cached = slackSlugCache.get(key);
|
|
166
|
+
if (cached !== void 0) return cached;
|
|
167
|
+
const normalized = normalizeHyphenSlug(raw);
|
|
168
|
+
slackSlugCache.set(key, normalized);
|
|
169
|
+
pruneMapToMaxSize(slackSlugCache, SLACK_SLUG_CACHE_MAX);
|
|
170
|
+
return normalized;
|
|
171
|
+
}
|
|
172
|
+
function normalizeSlackAllowOwnerEntry(entry) {
|
|
173
|
+
const trimmed = normalizeOptionalLowercaseString(entry);
|
|
174
|
+
if (!trimmed || trimmed === "*") return;
|
|
175
|
+
try {
|
|
176
|
+
const target = parseSlackTarget(trimmed);
|
|
177
|
+
if (target?.kind === "user" && target.teamId) return target.id.toLowerCase();
|
|
178
|
+
} catch {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const withoutPrefix = trimmed.replace(/^(slack:|user:)/, "");
|
|
182
|
+
return SLACK_STABLE_USER_ID_RE.test(withoutPrefix) ? withoutPrefix : void 0;
|
|
183
|
+
}
|
|
184
|
+
function resolveSlackAllowListMatch(params) {
|
|
185
|
+
const compiledAllowList = compileAllowlist(params.allowList);
|
|
186
|
+
const teamId = normalizeOptionalLowercaseString(params.teamId);
|
|
187
|
+
const id = normalizeOptionalLowercaseString(params.id);
|
|
188
|
+
const name = normalizeOptionalLowercaseString(params.name);
|
|
189
|
+
const slug = normalizeSlackSlug(name);
|
|
190
|
+
const scopedCandidates = [{
|
|
191
|
+
value: teamId && id ? `team:${teamId}:user:${id}` : void 0,
|
|
192
|
+
source: "workspace-id"
|
|
193
|
+
}];
|
|
194
|
+
const unscopedCandidates = [
|
|
195
|
+
{
|
|
196
|
+
value: id,
|
|
197
|
+
source: "id"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
value: id ? `slack:${id}` : void 0,
|
|
201
|
+
source: "prefixed-id"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
value: id ? `user:${id}` : void 0,
|
|
205
|
+
source: "prefixed-user"
|
|
206
|
+
},
|
|
207
|
+
...params.allowNameMatching === true ? [
|
|
208
|
+
{
|
|
209
|
+
value: name,
|
|
210
|
+
source: "name"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
value: name ? `slack:${name}` : void 0,
|
|
214
|
+
source: "prefixed-name"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
value: slug,
|
|
218
|
+
source: "slug"
|
|
219
|
+
}
|
|
220
|
+
] : []
|
|
221
|
+
];
|
|
222
|
+
return resolveCompiledAllowlistMatch({
|
|
223
|
+
compiledAllowlist: compiledAllowList,
|
|
224
|
+
candidates: [...scopedCandidates, ...unscopedCandidates]
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function allowListMatches(params) {
|
|
228
|
+
return resolveSlackAllowListMatch(params).allowed;
|
|
229
|
+
}
|
|
230
|
+
function resolveSlackUserAllowed(params) {
|
|
231
|
+
const allowList = normalizeStringEntriesLower(params.allowList);
|
|
232
|
+
if (allowList.length === 0) return true;
|
|
233
|
+
return allowListMatches({
|
|
234
|
+
allowList,
|
|
235
|
+
teamId: params.teamId,
|
|
236
|
+
id: params.userId,
|
|
237
|
+
name: params.userName,
|
|
238
|
+
allowNameMatching: params.allowNameMatching
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function resolveSlackUserAllowListForTeam(params) {
|
|
242
|
+
const allowList = normalizeStringEntriesLower(params.allowList);
|
|
243
|
+
const teamId = normalizeOptionalLowercaseString(params.teamId);
|
|
244
|
+
return allowList.flatMap((entry) => {
|
|
245
|
+
if (entry === "*") return [entry];
|
|
246
|
+
if (!entry.startsWith("team:")) return [entry];
|
|
247
|
+
try {
|
|
248
|
+
const target = parseSlackTarget(entry);
|
|
249
|
+
if (target?.kind === "user" && target.teamId?.toLowerCase() === teamId) return [entry];
|
|
250
|
+
return params.preserveUnmatchedScopedEntries ? [entry] : [];
|
|
251
|
+
} catch {
|
|
252
|
+
return params.preserveUnmatchedScopedEntries ? [entry] : [];
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
143
257
|
//#region extensions/slack/src/approval-auth.ts
|
|
144
258
|
function resolveSlackApprovalInputs(params) {
|
|
145
259
|
const account = resolveSlackAccount(params).config;
|
|
@@ -420,351 +534,603 @@ async function shouldHandleSlackNativeApprovalRequest(params) {
|
|
|
420
534
|
});
|
|
421
535
|
}
|
|
422
536
|
//#endregion
|
|
423
|
-
//#region extensions/slack/src/blocks-input.ts
|
|
424
|
-
const SLACK_MAX_BLOCKS = 50;
|
|
425
|
-
function parseBlocksJson(raw) {
|
|
426
|
-
try {
|
|
427
|
-
return JSON.parse(raw);
|
|
428
|
-
} catch {
|
|
429
|
-
throw new Error("blocks must be valid JSON");
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
function validateSlackBlocksArray(raw) {
|
|
433
|
-
if (!Array.isArray(raw)) throw new Error("blocks must be an array");
|
|
434
|
-
if (raw.length === 0) throw new Error("blocks must contain at least one block");
|
|
435
|
-
if (raw.length > 50) throw new Error(`blocks cannot exceed 50 items`);
|
|
436
|
-
for (const block of raw) {
|
|
437
|
-
if (!block || typeof block !== "object" || Array.isArray(block)) throw new Error("each block must be an object");
|
|
438
|
-
const type = block.type;
|
|
439
|
-
if (typeof type !== "string" || type.trim().length === 0) throw new Error("each block must include a non-empty string type");
|
|
440
|
-
}
|
|
441
|
-
return raw;
|
|
442
|
-
}
|
|
443
|
-
function parseSlackBlocksInput(raw) {
|
|
444
|
-
if (raw == null) return;
|
|
445
|
-
return validateSlackBlocksArray(typeof raw === "string" ? parseBlocksJson(raw) : raw);
|
|
446
|
-
}
|
|
447
|
-
const SLACK_BUTTON_VALUE_MAX = 2e3;
|
|
448
|
-
const SLACK_SECTION_TEXT_MAX = 3e3;
|
|
449
|
-
const SLACK_PRESENTATION_CAPABILITIES = {
|
|
450
|
-
supported: true,
|
|
451
|
-
buttons: true,
|
|
452
|
-
selects: true,
|
|
453
|
-
context: true,
|
|
454
|
-
divider: true,
|
|
455
|
-
charts: true,
|
|
456
|
-
tables: true,
|
|
457
|
-
limits: {
|
|
458
|
-
actions: {
|
|
459
|
-
maxActionsPerRow: 25,
|
|
460
|
-
maxValueBytes: SLACK_BUTTON_VALUE_MAX,
|
|
461
|
-
supportsStyles: true
|
|
462
|
-
},
|
|
463
|
-
selects: {
|
|
464
|
-
maxOptions: 100,
|
|
465
|
-
maxValueBytes: 150
|
|
466
|
-
},
|
|
467
|
-
text: {
|
|
468
|
-
encoding: "characters",
|
|
469
|
-
markdownDialect: "slack-mrkdwn",
|
|
470
|
-
supportsEdit: true
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
};
|
|
474
|
-
//#endregion
|
|
475
|
-
//#region extensions/slack/src/approval-actions.ts
|
|
476
|
-
const SLACK_APPROVAL_VALUE_PREFIX = "openclaw:approval:v1:";
|
|
477
|
-
const SLACK_APPROVAL_HEADER_BLOCK_ID = "openclaw_approval_header";
|
|
478
|
-
function isApprovalDecision(value) {
|
|
479
|
-
return value === "allow-once" || value === "allow-always" || value === "deny";
|
|
480
|
-
}
|
|
481
|
-
/** Encode portable approval facts without exposing a slash command to Slack callbacks. */
|
|
482
|
-
function encodeSlackApprovalAction(action) {
|
|
483
|
-
const encode = (approvalId) => `${SLACK_APPROVAL_VALUE_PREFIX}${JSON.stringify({
|
|
484
|
-
approvalId,
|
|
485
|
-
approvalKind: action.approvalKind,
|
|
486
|
-
decision: action.decision
|
|
487
|
-
})}`;
|
|
488
|
-
const exact = encode(action.approvalId);
|
|
489
|
-
return exact.length <= 2e3 ? exact : encode(buildApprovalResolutionRef({
|
|
490
|
-
approvalId: action.approvalId,
|
|
491
|
-
approvalKind: action.approvalKind
|
|
492
|
-
}));
|
|
493
|
-
}
|
|
494
|
-
/** Decode only the exact Slack-owned approval envelope. Malformed callbacks fail closed. */
|
|
495
|
-
function decodeSlackApprovalAction(value) {
|
|
496
|
-
if (typeof value !== "string" || !value.startsWith(SLACK_APPROVAL_VALUE_PREFIX)) return null;
|
|
497
|
-
try {
|
|
498
|
-
const decoded = JSON.parse(value.slice(21));
|
|
499
|
-
if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) return null;
|
|
500
|
-
const record = decoded;
|
|
501
|
-
if (Object.keys(record).length !== 3 || typeof record.approvalId !== "string" || record.approvalId.length === 0 || record.approvalKind !== "exec" && record.approvalKind !== "plugin" && record.approvalKind !== "system-agent" || !isApprovalDecision(record.decision)) return null;
|
|
502
|
-
return {
|
|
503
|
-
type: "approval",
|
|
504
|
-
approvalId: record.approvalId,
|
|
505
|
-
approvalKind: record.approvalKind,
|
|
506
|
-
decision: record.decision
|
|
507
|
-
};
|
|
508
|
-
} catch {
|
|
509
|
-
return null;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
//#endregion
|
|
513
537
|
//#region extensions/slack/src/monitor/mrkdwn.ts
|
|
514
538
|
function escapeSlackMrkdwn(value) {
|
|
515
539
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
516
540
|
}
|
|
517
541
|
//#endregion
|
|
518
|
-
//#region extensions/slack/src/
|
|
519
|
-
const
|
|
520
|
-
function
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
if (
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
if (escaped.yLabel) escaped.yLabel = escapeSlackMrkdwn(escaped.yLabel);
|
|
542
|
+
//#region extensions/slack/src/format.ts
|
|
543
|
+
const SLACK_ANGLE_TOKEN_RE = /<[^>\n]+>/g;
|
|
544
|
+
function isAllowedSlackAngleToken(token) {
|
|
545
|
+
const inner = token.slice(1, -1);
|
|
546
|
+
return inner.startsWith("@") || inner.startsWith("#") || inner.startsWith("!") || inner.startsWith("mailto:") || inner.startsWith("tel:") || inner.startsWith("http://") || inner.startsWith("https://") || inner.startsWith("slack://");
|
|
547
|
+
}
|
|
548
|
+
function escapeSlackMrkdwnContent(text, mentions) {
|
|
549
|
+
if (mentions === "escape") return escapeSlackMrkdwn(text);
|
|
550
|
+
if (!text.includes("&") && !text.includes("<") && !text.includes(">")) return text;
|
|
551
|
+
const out = [];
|
|
552
|
+
let lastIndex = 0;
|
|
553
|
+
for (const match of text.matchAll(SLACK_ANGLE_TOKEN_RE)) {
|
|
554
|
+
const matchIndex = match.index;
|
|
555
|
+
out.push(escapeSlackMrkdwn(text.slice(lastIndex, matchIndex)));
|
|
556
|
+
const token = match[0];
|
|
557
|
+
const escapeControl = mentions === "escape-controls" && /^<[@#!]/u.test(token);
|
|
558
|
+
out.push(isAllowedSlackAngleToken(token) && !escapeControl ? token : escapeSlackMrkdwn(token));
|
|
559
|
+
lastIndex = matchIndex + token.length;
|
|
537
560
|
}
|
|
538
|
-
|
|
561
|
+
out.push(escapeSlackMrkdwn(text.slice(lastIndex)));
|
|
562
|
+
return out.join("");
|
|
563
|
+
}
|
|
564
|
+
function escapeSlackMrkdwnText(text, mentions) {
|
|
565
|
+
if (!text.includes("&") && !text.includes("<") && !text.includes(">")) return text;
|
|
566
|
+
return text.split("\n").map((line) => {
|
|
567
|
+
if (line.startsWith("> ")) return `> ${escapeSlackMrkdwnContent(line.slice(2), mentions)}`;
|
|
568
|
+
return escapeSlackMrkdwnContent(line, mentions);
|
|
569
|
+
}).join("\n");
|
|
539
570
|
}
|
|
540
|
-
function
|
|
571
|
+
function buildSlackLink(link, text) {
|
|
572
|
+
const href = link.href.trim();
|
|
573
|
+
if (!href || /^[@#!]/u.test(href)) return null;
|
|
574
|
+
const trimmedLabel = text.slice(link.start, link.end).trim();
|
|
575
|
+
const comparableHref = href.startsWith("mailto:") ? href.slice(7) : href;
|
|
576
|
+
if (!(trimmedLabel.length > 0 && trimmedLabel !== href && trimmedLabel !== comparableHref)) return null;
|
|
577
|
+
const safeHref = escapeSlackMrkdwn(href);
|
|
541
578
|
return {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
579
|
+
start: link.start,
|
|
580
|
+
end: link.end,
|
|
581
|
+
open: `<${safeHref}|`,
|
|
582
|
+
close: ">"
|
|
546
583
|
};
|
|
547
584
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
...button.webApp ? { webApp: { url: escapeSlackMrkdwn(button.webApp.url) } } : {},
|
|
562
|
-
...button.web_app ? { web_app: { url: escapeSlackMrkdwn(button.web_app.url) } } : {},
|
|
563
|
-
...commandAction ? { action: {
|
|
564
|
-
...commandAction,
|
|
565
|
-
command: commandAction.command.replaceAll("`", "[backtick]")
|
|
566
|
-
} } : {}
|
|
567
|
-
};
|
|
568
|
-
})
|
|
569
|
-
};
|
|
570
|
-
if (block.type === "select") return {
|
|
571
|
-
...block,
|
|
572
|
-
...block.placeholder ? { placeholder: escapeSlackMrkdwn(block.placeholder) } : {},
|
|
573
|
-
options: block.options.map((option) => ({
|
|
574
|
-
...option,
|
|
575
|
-
label: escapeSlackMrkdwn(option.label)
|
|
576
|
-
}))
|
|
577
|
-
};
|
|
578
|
-
return block;
|
|
585
|
+
const SLACK_MRKDWN_WORD_CHARACTER_RE = /[\p{L}\p{M}\p{N}_]/u;
|
|
586
|
+
const SLACK_MRKDWN_PUNCTUATION_RE = /\p{P}/u;
|
|
587
|
+
const SLACK_MRKDWN_SYMBOL_RE = /\p{S}/u;
|
|
588
|
+
const SLACK_MRKDWN_CJK_SCRIPT_RE = /[\p{Script_Extensions=Han}\p{Script_Extensions=Hiragana}\p{Script_Extensions=Katakana}\p{Script_Extensions=Hangul}]/u;
|
|
589
|
+
const SLACK_MRKDWN_EMOJI_PRESENTATION_RE = /\p{Emoji_Presentation}/u;
|
|
590
|
+
function getCodePointBefore(text, index) {
|
|
591
|
+
if (index <= 0) return "";
|
|
592
|
+
const lastCodeUnit = text.charCodeAt(index - 1);
|
|
593
|
+
if (lastCodeUnit >= 56320 && lastCodeUnit <= 57343 && index > 1) {
|
|
594
|
+
const previousCodeUnit = text.charCodeAt(index - 2);
|
|
595
|
+
if (previousCodeUnit >= 55296 && previousCodeUnit <= 56319) return text.slice(index - 2, index);
|
|
596
|
+
}
|
|
597
|
+
return text[index - 1] ?? "";
|
|
579
598
|
}
|
|
580
|
-
function
|
|
581
|
-
|
|
599
|
+
function getCodePointAt(text, index) {
|
|
600
|
+
const codePoint = text.codePointAt(index);
|
|
601
|
+
return codePoint === void 0 ? "" : String.fromCodePoint(codePoint);
|
|
582
602
|
}
|
|
583
|
-
function
|
|
584
|
-
|
|
603
|
+
function isSlackCjkPunctuation(character) {
|
|
604
|
+
if (!SLACK_MRKDWN_PUNCTUATION_RE.test(character)) return false;
|
|
605
|
+
if (SLACK_MRKDWN_CJK_SCRIPT_RE.test(character)) return true;
|
|
606
|
+
const codePoint = character.codePointAt(0);
|
|
607
|
+
if (codePoint === void 0) return false;
|
|
608
|
+
const width = eastAsianWidthType(codePoint);
|
|
609
|
+
return width === "fullwidth" || width === "halfwidth" || width === "wide" && !SLACK_MRKDWN_EMOJI_PRESENTATION_RE.test(character);
|
|
585
610
|
}
|
|
586
|
-
function
|
|
587
|
-
if (
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
blocks: params.presentation.blocks.map(escapeSlackPresentationFallbackBlock)
|
|
592
|
-
};
|
|
593
|
-
return renderMessagePresentationFallbackText({
|
|
594
|
-
...params,
|
|
595
|
-
presentation
|
|
596
|
-
});
|
|
611
|
+
function isUnsafeSlackEmphasisBoundary(character) {
|
|
612
|
+
if (SLACK_MRKDWN_WORD_CHARACTER_RE.test(character)) return true;
|
|
613
|
+
const codePoint = character.codePointAt(0);
|
|
614
|
+
if (codePoint === void 0 || codePoint <= 127) return false;
|
|
615
|
+
return SLACK_MRKDWN_SYMBOL_RE.test(character) || isSlackCjkPunctuation(character);
|
|
597
616
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
function renderSlackRichText(value, mode, separator = "") {
|
|
607
|
-
if (!Array.isArray(value)) return "";
|
|
608
|
-
const table = mode === "table";
|
|
609
|
-
const read = table ? readNonBlankString : normalizeOptionalString;
|
|
610
|
-
const literal = table ? (text) => text : escapeSlackMrkdwn;
|
|
611
|
-
const reference = mode === "escaped" ? escapeSlackMrkdwn : (text) => text;
|
|
612
|
-
const formatReference = (raw, prefix, suffix) => {
|
|
613
|
-
const text = read(raw);
|
|
614
|
-
return text ? reference(`${prefix}${text}${suffix}`) : "";
|
|
617
|
+
function makeSlackEmphasisStylesSafe(ir) {
|
|
618
|
+
const styles = ir.styles.filter((span) => {
|
|
619
|
+
if (span.style !== "italic" && span.style !== "bold") return true;
|
|
620
|
+
return !isUnsafeSlackEmphasisBoundary(getCodePointBefore(ir.text, span.start)) && !isUnsafeSlackEmphasisBoundary(getCodePointAt(ir.text, span.end));
|
|
621
|
+
});
|
|
622
|
+
return styles.length === ir.styles.length ? ir : {
|
|
623
|
+
...ir,
|
|
624
|
+
styles
|
|
615
625
|
};
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
626
|
+
}
|
|
627
|
+
const SLACK_FORMAT_PROFILE = FormatCapabilityProfile.define({
|
|
628
|
+
mechanism: "markdown",
|
|
629
|
+
constructs: {
|
|
630
|
+
underline: "strip",
|
|
631
|
+
spoiler: "fallback",
|
|
632
|
+
codeLanguage: "fallback",
|
|
633
|
+
heading: "fallback",
|
|
634
|
+
bulletList: "fallback",
|
|
635
|
+
orderedList: "fallback",
|
|
636
|
+
taskList: "fallback",
|
|
637
|
+
table: "fallback",
|
|
638
|
+
image: "fallback"
|
|
639
|
+
},
|
|
640
|
+
chunk: {
|
|
641
|
+
limit: 4e3,
|
|
642
|
+
unit: "chars",
|
|
643
|
+
hardCap: 4e4
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
const SLACK_ASSISTANT_TRANSCRIPT_PREFIX = "`Assistant:` ";
|
|
647
|
+
function tokenizeSlackMrkdwn(text, graphemes) {
|
|
648
|
+
const tokens = [];
|
|
649
|
+
for (let index = 0; index < text.length;) {
|
|
650
|
+
if (text.startsWith("```", index)) {
|
|
651
|
+
tokens.push("```");
|
|
652
|
+
index += 3;
|
|
653
|
+
continue;
|
|
624
654
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
655
|
+
const entity = text[index] === "&" ? [
|
|
656
|
+
"&",
|
|
657
|
+
"<",
|
|
658
|
+
">"
|
|
659
|
+
].find((candidate) => text.startsWith(candidate, index)) : void 0;
|
|
660
|
+
if (entity) {
|
|
661
|
+
tokens.push(entity);
|
|
662
|
+
index += entity.length;
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
if (text[index] === "<") {
|
|
666
|
+
const end = text.indexOf(">", index + 1);
|
|
667
|
+
const angleToken = end >= 0 ? text.slice(index, end + 1) : void 0;
|
|
668
|
+
if (angleToken && !angleToken.includes("\n") && isAllowedSlackAngleToken(angleToken)) {
|
|
669
|
+
tokens.push(angleToken);
|
|
670
|
+
index += angleToken.length;
|
|
671
|
+
continue;
|
|
641
672
|
}
|
|
642
|
-
default: return "";
|
|
643
673
|
}
|
|
644
|
-
|
|
674
|
+
const codePoint = text.codePointAt(index);
|
|
675
|
+
if (codePoint === void 0) break;
|
|
676
|
+
const grapheme = graphemes?.containing(index + (codePoint > 65535 ? 1 : 0));
|
|
677
|
+
const character = grapheme && text.slice(index, grapheme.index + grapheme.segment.length).match(/^[^`*_~<>&]+/u)?.[0] || String.fromCodePoint(codePoint);
|
|
678
|
+
index += character.length;
|
|
679
|
+
tokens.push(character);
|
|
680
|
+
}
|
|
681
|
+
return tokens;
|
|
645
682
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const SLACK_DATA_TABLE_ROWS_MAX = 100;
|
|
650
|
-
const SLACK_DATA_TABLE_AGGREGATE_CELL_CHARACTERS_MAX = 1e4;
|
|
651
|
-
function countCharacters(value) {
|
|
652
|
-
return Array.from(value).length;
|
|
683
|
+
function resolveSlackCodeMarkerTransition(active, token) {
|
|
684
|
+
if ((token === "`" || token === "```") && (active === void 0 || active === token)) return active === token ? void 0 : token;
|
|
685
|
+
return null;
|
|
653
686
|
}
|
|
654
|
-
function
|
|
655
|
-
|
|
656
|
-
if (!cell) return;
|
|
657
|
-
const basic = mode === "basic";
|
|
658
|
-
let text;
|
|
659
|
-
if (cell.type === "raw_text") text = cell.text;
|
|
660
|
-
else if (cell.type === "raw_number") {
|
|
661
|
-
if (basic && typeof cell.text === "string" && cell.text.length > 0) return cell.text;
|
|
662
|
-
if (typeof cell.value === "number" && Number.isFinite(cell.value)) text = basic ? String(cell.value) : cell.text;
|
|
663
|
-
else if (basic && typeof cell.value === "string") text = cell.value;
|
|
664
|
-
} else if (cell.type === "rich_text" && mode !== "native-header") text = renderSlackRichText(cell.elements, "table", basic ? "\n" : "");
|
|
665
|
-
return basic ? typeof text === "string" ? text : void 0 : readNonBlankString(text);
|
|
687
|
+
function maskSlackExcludedText(text) {
|
|
688
|
+
return text.split("\n").map((line) => line.trim() ? `x${" ".repeat(Math.max(0, line.length - 1))}` : " ".repeat(line.length)).join("\n");
|
|
666
689
|
}
|
|
667
|
-
function
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
if (!Array.isArray(rawRow) || rawRow.length < 1 || rawRow.length > SLACK_DATA_TABLE_COLUMNS_MAX) return;
|
|
675
|
-
const row = rawRow.map((cell) => readSlackTableCell(cell, "basic") ?? "");
|
|
676
|
-
characterCount += row.reduce((total, cell) => total + countCharacters(cell), 0);
|
|
677
|
-
if (characterCount > 1e4) return;
|
|
678
|
-
rows.push(row);
|
|
690
|
+
function maskSlackExcludedRanges(projection) {
|
|
691
|
+
let masked = "";
|
|
692
|
+
let cursor = 0;
|
|
693
|
+
for (const range of projection.excludedRanges) {
|
|
694
|
+
masked += projection.text.slice(cursor, range.start);
|
|
695
|
+
masked += maskSlackExcludedText(projection.text.slice(range.start, range.end));
|
|
696
|
+
cursor = range.end;
|
|
679
697
|
}
|
|
680
|
-
return
|
|
698
|
+
return masked + projection.text.slice(cursor);
|
|
681
699
|
}
|
|
682
|
-
function
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
const rows = block.rows.slice(1).map((rawRow) => {
|
|
692
|
-
if (!Array.isArray(rawRow) || rawRow.length !== headers.length) return;
|
|
693
|
-
const cells = rawRow.map((cell) => readSlackTableCell(cell, "native-cell"));
|
|
694
|
-
return cells.every((cell) => Boolean(cell)) ? cells : void 0;
|
|
695
|
-
});
|
|
696
|
-
if (!rows.every((row) => Boolean(row))) return;
|
|
697
|
-
return {
|
|
698
|
-
caption,
|
|
699
|
-
headers,
|
|
700
|
-
rows
|
|
701
|
-
};
|
|
700
|
+
function slackProjectionHasRoleHeader(projection) {
|
|
701
|
+
return Boolean(markdownToIR(maskSlackExcludedRanges(projection), {
|
|
702
|
+
assistantTranscriptRoleHeaders: true,
|
|
703
|
+
autolink: false,
|
|
704
|
+
blockquotePrefix: "",
|
|
705
|
+
headingStyle: "none",
|
|
706
|
+
linkify: false,
|
|
707
|
+
tableMode: "off"
|
|
708
|
+
}).annotations?.some((annotation) => annotation.type === "assistant_transcript_role"));
|
|
702
709
|
}
|
|
703
|
-
function
|
|
704
|
-
|
|
705
|
-
if (!parsed || parsed.rows.length > SLACK_DATA_TABLE_ROWS_MAX || parsed.headers.length > SLACK_DATA_TABLE_COLUMNS_MAX) return;
|
|
706
|
-
const cellCharacterCount = [...parsed.headers, ...parsed.rows.flat()].reduce((total, cell) => total + countCharacters(cell), 0);
|
|
707
|
-
return cellCharacterCount > 1e4 ? void 0 : cellCharacterCount;
|
|
710
|
+
function decodeSlackMrkdwnEntities(text) {
|
|
711
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
708
712
|
}
|
|
709
|
-
function
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
713
|
+
function projectSlackAngleToken(token, dateDisplay) {
|
|
714
|
+
const inner = token.slice(1, -1);
|
|
715
|
+
if (inner.startsWith("!date^")) {
|
|
716
|
+
const fallbackSeparator = inner.indexOf("|");
|
|
717
|
+
const tokenString = (fallbackSeparator === -1 ? inner : inner.slice(0, fallbackSeparator)).split("^")[2] ?? "";
|
|
718
|
+
const fallback = fallbackSeparator === -1 ? "" : inner.slice(fallbackSeparator + 1);
|
|
719
|
+
return decodeSlackMrkdwnEntities(dateDisplay === "fallback" ? fallback || tokenString : tokenString || fallback);
|
|
716
720
|
}
|
|
717
|
-
|
|
721
|
+
const labelSeparator = inner.indexOf("|");
|
|
722
|
+
if (labelSeparator >= 0) return decodeSlackMrkdwnEntities(inner.slice(labelSeparator + 1));
|
|
723
|
+
const prefix = inner.charAt(0);
|
|
724
|
+
if (prefix === "@" || prefix === "#" || prefix === "!") return prefix;
|
|
725
|
+
return decodeSlackMrkdwnEntities(inner);
|
|
718
726
|
}
|
|
719
|
-
function
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
text: cell
|
|
744
|
-
});
|
|
727
|
+
function appendSlackVisibleProjection(projection, visible, excluded) {
|
|
728
|
+
if (!visible) return;
|
|
729
|
+
const start = projection.text.length;
|
|
730
|
+
projection.text += visible;
|
|
731
|
+
if (!excluded) return;
|
|
732
|
+
const previous = projection.excludedRanges.at(-1);
|
|
733
|
+
if (previous?.end === start) previous.end = projection.text.length;
|
|
734
|
+
else projection.excludedRanges.push({
|
|
735
|
+
start,
|
|
736
|
+
end: projection.text.length
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
function projectSlackMrkdwnVisibleText(text, dateDisplay) {
|
|
740
|
+
const projection = {
|
|
741
|
+
text: "",
|
|
742
|
+
excludedRanges: []
|
|
743
|
+
};
|
|
744
|
+
let activeMarker;
|
|
745
|
+
let lineHasVisibleContent = false;
|
|
746
|
+
for (const token of tokenizeSlackMrkdwn(text)) {
|
|
747
|
+
const transition = resolveSlackCodeMarkerTransition(activeMarker, token);
|
|
748
|
+
if (transition !== null) {
|
|
749
|
+
activeMarker = transition;
|
|
750
|
+
continue;
|
|
745
751
|
}
|
|
746
|
-
|
|
752
|
+
let visible = token;
|
|
753
|
+
if (isAllowedSlackAngleToken(token)) visible = activeMarker ? token : projectSlackAngleToken(token, dateDisplay);
|
|
754
|
+
else if (token === "&" || token === "<" || token === ">") visible = decodeSlackMrkdwnEntities(token);
|
|
755
|
+
else if (!activeMarker && (token === "*" || token === "_" || token === "~")) visible = "";
|
|
756
|
+
else if (!activeMarker && token === ">" && !lineHasVisibleContent) visible = "";
|
|
757
|
+
appendSlackVisibleProjection(projection, visible, activeMarker !== void 0);
|
|
758
|
+
for (const character of visible) if (character === "\n") lineHasVisibleContent = false;
|
|
759
|
+
else if (character !== " " && character !== " " && character !== "\r") lineHasVisibleContent = true;
|
|
747
760
|
}
|
|
748
|
-
|
|
749
|
-
return {
|
|
750
|
-
type: "data_table",
|
|
751
|
-
caption: block.caption,
|
|
752
|
-
rows,
|
|
753
|
-
...block.rowHeaderColumnIndex !== void 0 ? { row_header_column_index: block.rowHeaderColumnIndex } : {}
|
|
754
|
-
};
|
|
761
|
+
return projection;
|
|
755
762
|
}
|
|
756
|
-
function
|
|
757
|
-
|
|
758
|
-
if (
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
763
|
+
function protectSlackAssistantTranscriptRoleHeaders(text) {
|
|
764
|
+
if (text.startsWith(SLACK_ASSISTANT_TRANSCRIPT_PREFIX)) return text;
|
|
765
|
+
if (!slackProjectionHasRoleHeader(projectSlackMrkdwnVisibleText(text, "token")) && (!text.includes("<!date^") || !slackProjectionHasRoleHeader(projectSlackMrkdwnVisibleText(text, "fallback")))) return text;
|
|
766
|
+
return `${SLACK_ASSISTANT_TRANSCRIPT_PREFIX}${text}`;
|
|
767
|
+
}
|
|
768
|
+
function buildSlackRenderOptions({ enclosingStyle, mentions } = {}) {
|
|
769
|
+
return {
|
|
770
|
+
annotationMarkers: { assistant_transcript_role: {
|
|
771
|
+
open: "`",
|
|
772
|
+
close: "`",
|
|
773
|
+
suppressNestedFormatting: true
|
|
774
|
+
} },
|
|
775
|
+
styleMarkers: {
|
|
776
|
+
...enclosingStyle !== "bold" ? { bold: {
|
|
777
|
+
open: "*",
|
|
778
|
+
close: "*"
|
|
779
|
+
} } : {},
|
|
780
|
+
...enclosingStyle !== "italic" ? { italic: {
|
|
781
|
+
open: "_",
|
|
782
|
+
close: "_"
|
|
783
|
+
} } : {},
|
|
784
|
+
strikethrough: {
|
|
785
|
+
open: "~",
|
|
786
|
+
close: "~"
|
|
787
|
+
},
|
|
788
|
+
code: {
|
|
789
|
+
open: "`",
|
|
790
|
+
close: "`"
|
|
791
|
+
},
|
|
792
|
+
code_block: {
|
|
793
|
+
open: "```\n",
|
|
794
|
+
close: "```"
|
|
795
|
+
}
|
|
796
|
+
},
|
|
797
|
+
escapeText: (text) => escapeSlackMrkdwnText(text, mentions),
|
|
798
|
+
buildLink: buildSlackLink
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
function prepareSlackMarkdownIR(markdown, options) {
|
|
802
|
+
return makeSlackEmphasisStylesSafe(markdownToIR(markdown ?? "", {
|
|
803
|
+
assistantTranscriptRoleHeaders: true,
|
|
804
|
+
linkify: false,
|
|
805
|
+
autolink: false,
|
|
806
|
+
headingStyle: "rich",
|
|
807
|
+
blockquotePrefix: "> ",
|
|
808
|
+
tableMode: options.tableMode
|
|
809
|
+
}));
|
|
810
|
+
}
|
|
811
|
+
function normalizeSlackOutboundText(markdown, options = {}) {
|
|
812
|
+
const ir = prepareSlackMarkdownIR(markdown, options);
|
|
813
|
+
return protectSlackAssistantTranscriptRoleHeaders(renderMarkdownWithMarkers(ir, buildSlackRenderOptions(options), SLACK_FORMAT_PROFILE));
|
|
814
|
+
}
|
|
815
|
+
/** Chunk already-rendered Slack mrkdwn without splitting entities or code markers. */
|
|
816
|
+
function chunkSlackMrkdwnText(text, limit) {
|
|
817
|
+
if (text.length <= limit) return [text];
|
|
818
|
+
if (!(text.includes("`") || text.includes("&") || text.includes("<") || text.includes(">") || (text.match(/<[^>\n]+>/gu)?.some(isAllowedSlackAngleToken) ?? false))) return chunkTextForOutbound(text, limit, { preserveWhitespace: true });
|
|
819
|
+
const chunks = [];
|
|
820
|
+
let activeMarker;
|
|
821
|
+
let content = "";
|
|
822
|
+
const wrapper = (marker) => marker && limit > marker.length * 2 ? marker : void 0;
|
|
823
|
+
const capacity = (marker) => limit - (wrapper(marker)?.length ?? 0);
|
|
824
|
+
const flush = () => {
|
|
825
|
+
const marker = wrapper(activeMarker);
|
|
826
|
+
if (content && content !== marker) chunks.push(marker ? `${content}${marker}` : content);
|
|
827
|
+
content = "";
|
|
828
|
+
};
|
|
829
|
+
for (const token of tokenizeSlackMrkdwn(text, new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(text))) {
|
|
830
|
+
const transition = resolveSlackCodeMarkerTransition(activeMarker, token);
|
|
831
|
+
const nextMarker = transition === null ? activeMarker : transition;
|
|
832
|
+
const sourceMarker = token === "`" || token === "```" ? token : void 0;
|
|
833
|
+
if (transition !== null && sourceMarker && !wrapper(sourceMarker)) {
|
|
834
|
+
activeMarker = nextMarker;
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
if (content && content.length + token.length > capacity(nextMarker)) flush();
|
|
838
|
+
activeMarker = nextMarker;
|
|
839
|
+
if (!content && transition === void 0) continue;
|
|
840
|
+
content ||= transition === null ? wrapper(activeMarker) ?? "" : "";
|
|
841
|
+
const contentLimit = capacity(activeMarker) - (wrapper(activeMarker)?.length ?? 0);
|
|
842
|
+
if (token.length > contentLimit) {
|
|
843
|
+
flush();
|
|
844
|
+
const marker = wrapper(activeMarker);
|
|
845
|
+
if (activeMarker) {
|
|
846
|
+
const fragments = chunkTextForOutbound(marker ? token : escapeSlackMrkdwn(token), Math.max(1, Math.floor(marker ? contentLimit : limit)), { preserveWhitespace: true });
|
|
847
|
+
chunks.push(...marker ? fragments.map((fragment) => `${marker}${fragment}${marker}`) : fragments);
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
chunks.push(...token.length <= limit ? [token] : chunkTextForOutbound(token, limit));
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
content += token;
|
|
854
|
+
}
|
|
855
|
+
flush();
|
|
856
|
+
return chunks;
|
|
857
|
+
}
|
|
858
|
+
function markdownToSlackMrkdwnChunks(markdown, limit, options = {}) {
|
|
859
|
+
const ir = prepareSlackMarkdownIR(markdown, options);
|
|
860
|
+
const renderOptions = buildSlackRenderOptions();
|
|
861
|
+
const normalizedLimit = limit === Number.POSITIVE_INFINITY ? limit : resolveIntegerOption(limit, 1, { min: 1 });
|
|
862
|
+
return renderMarkdownIRChunksWithinLimit({
|
|
863
|
+
ir,
|
|
864
|
+
limit: normalizedLimit,
|
|
865
|
+
renderChunk: (chunk) => {
|
|
866
|
+
const rendered = renderMarkdownWithMarkers(chunk, renderOptions, SLACK_FORMAT_PROFILE);
|
|
867
|
+
return rendered.length > normalizedLimit ? rendered : protectSlackAssistantTranscriptRoleHeaders(rendered);
|
|
868
|
+
},
|
|
869
|
+
measureRendered: (rendered) => rendered.length
|
|
870
|
+
}).map(({ rendered }) => rendered.length > normalizedLimit ? protectSlackAssistantTranscriptRoleHeaders(rendered) : rendered);
|
|
871
|
+
}
|
|
872
|
+
//#endregion
|
|
873
|
+
//#region extensions/slack/src/limits.ts
|
|
874
|
+
const SLACK_TEXT_LIMIT = 8e3;
|
|
875
|
+
const SLACK_MESSAGE_TEXT_RECOMMENDED_LIMIT = 4e3;
|
|
876
|
+
const SLACK_EDIT_TEXT_MAX_BYTES = 4e3;
|
|
877
|
+
const SLACK_MESSAGE_TEXT_HARD_LIMIT = 4e4;
|
|
878
|
+
//#endregion
|
|
879
|
+
//#region extensions/slack/src/presentation-fallback.ts
|
|
880
|
+
const SLACK_UNCOPYABLE_COMMAND_WARNING = "not copyable: contains backtick";
|
|
881
|
+
function escapeSlackPresentationFallbackBlock(block) {
|
|
882
|
+
if (block.type === "chart") return {
|
|
883
|
+
type: "text",
|
|
884
|
+
text: renderSlackMessagePresentationChartFallbackText(block)
|
|
885
|
+
};
|
|
886
|
+
if (block.type === "table") return {
|
|
887
|
+
type: "text",
|
|
888
|
+
text: renderSlackMessagePresentationTableFallbackText(block)
|
|
889
|
+
};
|
|
890
|
+
if (block.type === "buttons") return {
|
|
891
|
+
...block,
|
|
892
|
+
buttons: block.buttons.map((button) => {
|
|
893
|
+
const commandAction = button.action?.type === "command" ? button.action : void 0;
|
|
894
|
+
const label = commandAction?.command.includes("`") ? `${button.label} [${SLACK_UNCOPYABLE_COMMAND_WARNING}]` : button.label;
|
|
895
|
+
return {
|
|
896
|
+
...button,
|
|
897
|
+
label: escapeSlackMrkdwn(label),
|
|
898
|
+
...button.value ? { value: escapeSlackMrkdwn(button.value) } : {},
|
|
899
|
+
...button.url ? { url: escapeSlackMrkdwn(button.url) } : {},
|
|
900
|
+
...button.webApp ? { webApp: { url: escapeSlackMrkdwn(button.webApp.url) } } : {},
|
|
901
|
+
...button.web_app ? { web_app: { url: escapeSlackMrkdwn(button.web_app.url) } } : {},
|
|
902
|
+
...commandAction ? { action: {
|
|
903
|
+
...commandAction,
|
|
904
|
+
command: commandAction.command.replaceAll("`", "[backtick]")
|
|
905
|
+
} } : {}
|
|
906
|
+
};
|
|
907
|
+
})
|
|
908
|
+
};
|
|
909
|
+
if (block.type === "select") return {
|
|
910
|
+
...block,
|
|
911
|
+
...block.placeholder ? { placeholder: escapeSlackMrkdwn(block.placeholder) } : {},
|
|
912
|
+
options: block.options.map((option) => ({
|
|
913
|
+
...option,
|
|
914
|
+
label: escapeSlackMrkdwn(option.label)
|
|
915
|
+
}))
|
|
916
|
+
};
|
|
917
|
+
return block;
|
|
918
|
+
}
|
|
919
|
+
function renderSlackMessagePresentationChartFallbackText(block) {
|
|
920
|
+
return escapeSlackMrkdwn(renderMessagePresentationChartFallbackText(block));
|
|
921
|
+
}
|
|
922
|
+
function renderSlackMessagePresentationTableFallbackText(block) {
|
|
923
|
+
return escapeSlackMrkdwn(renderMessagePresentationTableFallbackText(block));
|
|
924
|
+
}
|
|
925
|
+
function renderSlackMessagePresentationFallbackText(params) {
|
|
926
|
+
if (!params.presentation) return renderMessagePresentationFallbackText(params);
|
|
927
|
+
const presentation = {
|
|
928
|
+
...params.presentation,
|
|
929
|
+
...params.presentation.title ? { title: escapeSlackMrkdwn(params.presentation.title) } : {},
|
|
930
|
+
blocks: params.presentation.blocks.map(escapeSlackPresentationFallbackBlock)
|
|
931
|
+
};
|
|
932
|
+
return renderMessagePresentationFallbackText({
|
|
933
|
+
...params,
|
|
934
|
+
presentation
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
const SLACK_BUTTON_VALUE_MAX = 2e3;
|
|
938
|
+
const SLACK_SECTION_TEXT_MAX = 3e3;
|
|
939
|
+
const SLACK_PRESENTATION_CAPABILITIES = {
|
|
940
|
+
supported: true,
|
|
941
|
+
buttons: true,
|
|
942
|
+
selects: true,
|
|
943
|
+
context: true,
|
|
944
|
+
divider: true,
|
|
945
|
+
charts: true,
|
|
946
|
+
tables: true,
|
|
947
|
+
limits: {
|
|
948
|
+
actions: {
|
|
949
|
+
maxActionsPerRow: 25,
|
|
950
|
+
maxValueBytes: SLACK_BUTTON_VALUE_MAX,
|
|
951
|
+
supportsStyles: true
|
|
952
|
+
},
|
|
953
|
+
selects: {
|
|
954
|
+
maxOptions: 100,
|
|
955
|
+
maxValueBytes: 150
|
|
956
|
+
},
|
|
957
|
+
text: {
|
|
958
|
+
encoding: "characters",
|
|
959
|
+
markdownDialect: "slack-mrkdwn",
|
|
960
|
+
supportsEdit: true
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
//#endregion
|
|
965
|
+
//#region extensions/slack/src/rich-text.ts
|
|
966
|
+
const RICH_TEXT_CONTAINER_TYPES = /* @__PURE__ */ new Set([
|
|
967
|
+
"rich_text_section",
|
|
968
|
+
"rich_text_preformatted",
|
|
969
|
+
"rich_text_quote",
|
|
970
|
+
"rich_text_list"
|
|
971
|
+
]);
|
|
972
|
+
function renderSlackRichText(value, mode, separator = "") {
|
|
973
|
+
if (!Array.isArray(value)) return "";
|
|
974
|
+
const table = mode === "table";
|
|
975
|
+
const read = table ? readNonBlankString : normalizeOptionalString;
|
|
976
|
+
const literal = table ? (text) => text : escapeSlackMrkdwn;
|
|
977
|
+
const reference = mode === "escaped" ? escapeSlackMrkdwn : (text) => text;
|
|
978
|
+
const formatReference = (raw, prefix, suffix) => {
|
|
979
|
+
const text = read(raw);
|
|
980
|
+
return text ? reference(`${prefix}${text}${suffix}`) : "";
|
|
981
|
+
};
|
|
982
|
+
return value.map((rawElement) => {
|
|
983
|
+
const element = asOptionalRecord(rawElement);
|
|
984
|
+
if (!element) return "";
|
|
985
|
+
if (Array.isArray(element.elements) && (table || RICH_TEXT_CONTAINER_TYPES.has(element.type))) return renderSlackRichText(element.elements, mode, element.type === "rich_text_list" ? "\n" : "");
|
|
986
|
+
if (element.type === "text" && typeof element.text === "string") return literal(element.text);
|
|
987
|
+
if (table) {
|
|
988
|
+
const text = readNonBlankString(element.text);
|
|
989
|
+
if (text) return text;
|
|
990
|
+
}
|
|
991
|
+
switch (element.type) {
|
|
992
|
+
case "link": return literal(read(element.text) ?? read(element.url) ?? "");
|
|
993
|
+
case "user": return formatReference(element.user_id, "<@", ">");
|
|
994
|
+
case "channel": return formatReference(element.channel_id, "<#", ">");
|
|
995
|
+
case "usergroup": return formatReference(element.usergroup_id, "<!subteam^", ">");
|
|
996
|
+
case "broadcast": return formatReference(element.range, "<!", ">");
|
|
997
|
+
case "emoji": {
|
|
998
|
+
const name = read(element.name);
|
|
999
|
+
return name ? `:${name}:` : "";
|
|
1000
|
+
}
|
|
1001
|
+
case "date": {
|
|
1002
|
+
const fallback = read(element.fallback);
|
|
1003
|
+
if (fallback) return literal(fallback);
|
|
1004
|
+
if (typeof element.timestamp !== "number") return "";
|
|
1005
|
+
const date = /* @__PURE__ */ new Date(element.timestamp * 1e3);
|
|
1006
|
+
return Number.isNaN(date.getTime()) ? "" : date.toISOString();
|
|
1007
|
+
}
|
|
1008
|
+
default: return "";
|
|
1009
|
+
}
|
|
1010
|
+
}).filter(Boolean).join(separator);
|
|
1011
|
+
}
|
|
1012
|
+
//#endregion
|
|
1013
|
+
//#region extensions/slack/src/data-table.ts
|
|
1014
|
+
const SLACK_DATA_TABLE_COLUMNS_MAX = 20;
|
|
1015
|
+
const SLACK_DATA_TABLE_ROWS_MAX = 100;
|
|
1016
|
+
const SLACK_DATA_TABLE_AGGREGATE_CELL_CHARACTERS_MAX = 1e4;
|
|
1017
|
+
function countCharacters(value) {
|
|
1018
|
+
return Array.from(value).length;
|
|
1019
|
+
}
|
|
1020
|
+
function readSlackTableCell(value, mode) {
|
|
1021
|
+
const cell = asOptionalRecord(value);
|
|
1022
|
+
if (!cell) return;
|
|
1023
|
+
const basic = mode === "basic";
|
|
1024
|
+
let text;
|
|
1025
|
+
if (cell.type === "raw_text") text = cell.text;
|
|
1026
|
+
else if (cell.type === "raw_number") {
|
|
1027
|
+
if (basic && typeof cell.text === "string" && cell.text.length > 0) return cell.text;
|
|
1028
|
+
if (typeof cell.value === "number" && Number.isFinite(cell.value)) text = basic ? String(cell.value) : cell.text;
|
|
1029
|
+
else if (basic && typeof cell.value === "string") text = cell.value;
|
|
1030
|
+
} else if (cell.type === "rich_text" && mode !== "native-header") text = renderSlackRichText(cell.elements, "table", basic ? "\n" : "");
|
|
1031
|
+
return basic ? typeof text === "string" ? text : void 0 : readNonBlankString(text);
|
|
1032
|
+
}
|
|
1033
|
+
function parseSlackBasicTableRows(value) {
|
|
1034
|
+
const block = asOptionalRecord(value);
|
|
1035
|
+
if (block?.type !== "table" || !Array.isArray(block.rows)) return;
|
|
1036
|
+
if (block.rows.length < 1 || block.rows.length > SLACK_DATA_TABLE_ROWS_MAX) return;
|
|
1037
|
+
let characterCount = 0;
|
|
1038
|
+
const rows = [];
|
|
1039
|
+
for (const rawRow of block.rows) {
|
|
1040
|
+
if (!Array.isArray(rawRow) || rawRow.length < 1 || rawRow.length > SLACK_DATA_TABLE_COLUMNS_MAX) return;
|
|
1041
|
+
const row = rawRow.map((cell) => readSlackTableCell(cell, "basic") ?? "");
|
|
1042
|
+
characterCount += row.reduce((total, cell) => total + countCharacters(cell), 0);
|
|
1043
|
+
if (characterCount > 1e4) return;
|
|
1044
|
+
rows.push(row);
|
|
1045
|
+
}
|
|
1046
|
+
return rows.some((row) => row.some((cell) => cell.length > 0)) ? rows : void 0;
|
|
1047
|
+
}
|
|
1048
|
+
function parseSlackDataTable(value) {
|
|
1049
|
+
const block = asOptionalRecord(value);
|
|
1050
|
+
const caption = readNonBlankString(block?.caption);
|
|
1051
|
+
if (block?.type !== "data_table" || !caption || !Array.isArray(block.rows)) return;
|
|
1052
|
+
if (block.rows.length < 2) return;
|
|
1053
|
+
const rawHeader = block.rows[0];
|
|
1054
|
+
if (!Array.isArray(rawHeader) || rawHeader.length < 1) return;
|
|
1055
|
+
const headers = Array.from(rawHeader, (cell) => readSlackTableCell(cell, "native-header"));
|
|
1056
|
+
if (!headers.every((header) => Boolean(header))) return;
|
|
1057
|
+
const rows = block.rows.slice(1).map((rawRow) => {
|
|
1058
|
+
if (!Array.isArray(rawRow) || rawRow.length !== headers.length) return;
|
|
1059
|
+
const cells = rawRow.map((cell) => readSlackTableCell(cell, "native-cell"));
|
|
1060
|
+
return cells.every((cell) => Boolean(cell)) ? cells : void 0;
|
|
1061
|
+
});
|
|
1062
|
+
if (!rows.every((row) => Boolean(row))) return;
|
|
1063
|
+
return {
|
|
1064
|
+
caption,
|
|
1065
|
+
headers,
|
|
1066
|
+
rows
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
function countSlackDataTableCellCharacters(value) {
|
|
1070
|
+
const parsed = parseSlackDataTable(value);
|
|
1071
|
+
if (!parsed || parsed.rows.length > SLACK_DATA_TABLE_ROWS_MAX || parsed.headers.length > SLACK_DATA_TABLE_COLUMNS_MAX) return;
|
|
1072
|
+
const cellCharacterCount = [...parsed.headers, ...parsed.rows.flat()].reduce((total, cell) => total + countCharacters(cell), 0);
|
|
1073
|
+
return cellCharacterCount > 1e4 ? void 0 : cellCharacterCount;
|
|
1074
|
+
}
|
|
1075
|
+
function countSlackDataTableBlocksCellCharacters(blocks) {
|
|
1076
|
+
let total = 0;
|
|
1077
|
+
for (const block of blocks ?? []) {
|
|
1078
|
+
if (asOptionalRecord(block)?.type !== "data_table") continue;
|
|
1079
|
+
const cellCharacterCount = countSlackDataTableCellCharacters(block);
|
|
1080
|
+
if (cellCharacterCount === void 0) return;
|
|
1081
|
+
total += cellCharacterCount;
|
|
1082
|
+
}
|
|
1083
|
+
return total;
|
|
1084
|
+
}
|
|
1085
|
+
function buildSlackDataTableBlock(block, options = {}) {
|
|
1086
|
+
const cellCharacterCountOffset = options.cellCharacterCountOffset ?? 0;
|
|
1087
|
+
if (!Number.isSafeInteger(cellCharacterCountOffset) || cellCharacterCountOffset < 0) return;
|
|
1088
|
+
if (block.caption.trim().length === 0 || block.headers.length < 1 || block.headers.length > SLACK_DATA_TABLE_COLUMNS_MAX || block.rows.length < 1 || block.rows.length > SLACK_DATA_TABLE_ROWS_MAX || new Set(block.headers).size !== block.headers.length || !block.headers.every((header) => header.trim().length > 0) || block.rowHeaderColumnIndex !== void 0 && (!Number.isInteger(block.rowHeaderColumnIndex) || block.rowHeaderColumnIndex < 0 || block.rowHeaderColumnIndex >= block.headers.length)) return;
|
|
1089
|
+
const rows = [Array.from(block.headers, (text) => ({
|
|
1090
|
+
type: "raw_text",
|
|
1091
|
+
text
|
|
1092
|
+
}))];
|
|
1093
|
+
for (const row of block.rows) {
|
|
1094
|
+
if (row.length !== block.headers.length) return;
|
|
1095
|
+
const cells = [];
|
|
1096
|
+
for (const cell of row) {
|
|
1097
|
+
if (typeof cell === "number") {
|
|
1098
|
+
if (!Number.isFinite(cell)) return;
|
|
1099
|
+
cells.push({
|
|
1100
|
+
type: "raw_number",
|
|
1101
|
+
value: cell,
|
|
1102
|
+
text: String(cell)
|
|
1103
|
+
});
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
if (cell.trim().length === 0) return;
|
|
1107
|
+
cells.push({
|
|
1108
|
+
type: "raw_text",
|
|
1109
|
+
text: cell
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
rows.push(cells);
|
|
1113
|
+
}
|
|
1114
|
+
if (cellCharacterCountOffset + rows.flat().reduce((total, cell) => total + countCharacters(cell.text), 0) > 1e4) return;
|
|
1115
|
+
return {
|
|
1116
|
+
type: "data_table",
|
|
1117
|
+
caption: block.caption,
|
|
1118
|
+
rows,
|
|
1119
|
+
...block.rowHeaderColumnIndex !== void 0 ? { row_header_column_index: block.rowHeaderColumnIndex } : {}
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
function renderSlackDataTable(value, render, mrkdwnSafe = false) {
|
|
1123
|
+
const block = asOptionalRecord(value);
|
|
1124
|
+
if (block?.type !== "data_table") return;
|
|
1125
|
+
const parsed = parseSlackDataTable(block);
|
|
1126
|
+
if (parsed) return render({
|
|
1127
|
+
type: "table",
|
|
1128
|
+
caption: parsed.caption,
|
|
1129
|
+
headers: parsed.headers,
|
|
1130
|
+
rows: parsed.rows
|
|
1131
|
+
});
|
|
1132
|
+
const caption = readNonBlankString(block.caption)?.trim();
|
|
1133
|
+
return caption && mrkdwnSafe ? escapeSlackMrkdwn(caption) : caption;
|
|
768
1134
|
}
|
|
769
1135
|
function renderSlackDataTableFallbackText(value, mrkdwnSafe = false) {
|
|
770
1136
|
return renderSlackDataTable(value, mrkdwnSafe ? renderSlackMessagePresentationTableFallbackText : renderMessagePresentationTableFallbackText, mrkdwnSafe);
|
|
@@ -791,441 +1157,250 @@ const SLACK_CHART_LABEL_MAX = 20;
|
|
|
791
1157
|
const SLACK_CHART_AXIS_LABEL_MAX = 50;
|
|
792
1158
|
const SLACK_CHART_SERIES_MAX = 12;
|
|
793
1159
|
const SLACK_CHART_DATA_POINTS_MAX = 20;
|
|
794
|
-
/** Detect native chart blocks without depending on unreleased Slack SDK types. */
|
|
795
|
-
function hasSlackDataVisualizationBlock(blocks) {
|
|
796
|
-
return blocks?.some((block) => asOptionalRecord(block)?.type === "data_visualization") ?? false;
|
|
797
|
-
}
|
|
798
|
-
function isStringWithin(value, maxLength) {
|
|
799
|
-
return
|
|
800
|
-
}
|
|
801
|
-
function hasUniqueStrings(values) {
|
|
802
|
-
return new Set(values).size === values.length;
|
|
803
|
-
}
|
|
804
|
-
function buildSlackDataVisualizationBlock(block) {
|
|
805
|
-
if (!isStringWithin(block.title, SLACK_CHART_TITLE_MAX)) return;
|
|
806
|
-
if (block.chartType === "pie") {
|
|
807
|
-
if (block.segments.length < 1 || block.segments.length > SLACK_CHART_SERIES_MAX || !block.segments.every((segment) => isStringWithin(segment.label, SLACK_CHART_LABEL_MAX) && Number.isFinite(segment.value) && segment.value > 0)) return;
|
|
808
|
-
return {
|
|
809
|
-
type: "data_visualization",
|
|
810
|
-
title: block.title,
|
|
811
|
-
chart: {
|
|
812
|
-
type: "pie",
|
|
813
|
-
segments: block.segments
|
|
814
|
-
}
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
if (block.categories.length < 1 || block.categories.length > SLACK_CHART_DATA_POINTS_MAX || !block.categories.every((category) => isStringWithin(category, SLACK_CHART_LABEL_MAX)) || !hasUniqueStrings(block.categories) || block.series.length < 1 || block.series.length > SLACK_CHART_SERIES_MAX || !hasUniqueStrings(block.series.map((series) => series.name)) || block.xLabel !== void 0 && !isStringWithin(block.xLabel, SLACK_CHART_AXIS_LABEL_MAX) || block.yLabel !== void 0 && !isStringWithin(block.yLabel, SLACK_CHART_AXIS_LABEL_MAX) || !block.series.every((series) => isStringWithin(series.name, SLACK_CHART_LABEL_MAX) && series.values.length === block.categories.length && series.values.every((value) => Number.isFinite(value)))) return;
|
|
818
|
-
return {
|
|
819
|
-
type: "data_visualization",
|
|
820
|
-
title: block.title,
|
|
821
|
-
chart: {
|
|
822
|
-
type: block.chartType,
|
|
823
|
-
series: block.series.map((series) => ({
|
|
824
|
-
name: series.name,
|
|
825
|
-
data: block.categories.map((label, index) => ({
|
|
826
|
-
label,
|
|
827
|
-
value: series.values[index]
|
|
828
|
-
}))
|
|
829
|
-
})),
|
|
830
|
-
axis_config: {
|
|
831
|
-
categories: [...block.categories],
|
|
832
|
-
...block.xLabel ? { x_label: block.xLabel } : {},
|
|
833
|
-
...block.yLabel ? { y_label: block.yLabel } : {}
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
};
|
|
837
|
-
}
|
|
838
|
-
function readSlackChartDatum(value) {
|
|
839
|
-
const record = asOptionalRecord(value);
|
|
840
|
-
const label = record?.label;
|
|
841
|
-
const datumValue = record?.value;
|
|
842
|
-
return typeof label === "string" && typeof datumValue === "number" ? {
|
|
843
|
-
label,
|
|
844
|
-
value: datumValue
|
|
845
|
-
} : void 0;
|
|
846
|
-
}
|
|
847
|
-
function parseSlackDataVisualizationBlock(block) {
|
|
848
|
-
const title = block.title;
|
|
849
|
-
const chart = asOptionalRecord(block.chart);
|
|
850
|
-
if (typeof title !== "string" || !chart) return;
|
|
851
|
-
if (chart.type === "pie") {
|
|
852
|
-
if (!Array.isArray(chart.segments)) return;
|
|
853
|
-
const segments = chart.segments.map(readSlackChartDatum);
|
|
854
|
-
if (segments.some((segment) => !segment)) return;
|
|
855
|
-
return {
|
|
856
|
-
type: "chart",
|
|
857
|
-
chartType: "pie",
|
|
858
|
-
title,
|
|
859
|
-
segments
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
if (chart.type !== "bar" && chart.type !== "area" && chart.type !== "line") return;
|
|
863
|
-
const axisConfig = asOptionalRecord(chart.axis_config);
|
|
864
|
-
const categories = axisConfig?.categories;
|
|
865
|
-
if (!Array.isArray(categories) || !categories.every((category) => typeof category === "string")) return;
|
|
866
|
-
if (!Array.isArray(chart.series)) return;
|
|
867
|
-
const series = chart.series.map((rawSeries) => {
|
|
868
|
-
const seriesRecord = asOptionalRecord(rawSeries);
|
|
869
|
-
if (typeof seriesRecord?.name !== "string" || !Array.isArray(seriesRecord.data)) return;
|
|
870
|
-
const data = seriesRecord.data.map(readSlackChartDatum);
|
|
871
|
-
if (data.some((datum) => !datum) || data.length !== categories.length) return;
|
|
872
|
-
const dataByLabel = new Map(data.map((datum) => [datum.label, datum.value]));
|
|
873
|
-
if (dataByLabel.size !== data.length || categories.some((category) => !dataByLabel.has(category))) return;
|
|
874
|
-
return {
|
|
875
|
-
name: seriesRecord.name,
|
|
876
|
-
values: categories.map((category) => dataByLabel.get(category))
|
|
877
|
-
};
|
|
878
|
-
});
|
|
879
|
-
if (series.some((entry) => !entry)) return;
|
|
880
|
-
return {
|
|
881
|
-
type: "chart",
|
|
882
|
-
chartType: chart.type,
|
|
883
|
-
title,
|
|
884
|
-
categories,
|
|
885
|
-
series,
|
|
886
|
-
xLabel: axisConfig?.x_label,
|
|
887
|
-
yLabel: axisConfig?.y_label
|
|
888
|
-
};
|
|
889
|
-
}
|
|
890
|
-
/** Extract an accessible summary, escaping mrkdwn control tokens when requested. */
|
|
891
|
-
function renderSlackDataVisualizationFallbackText(value, mrkdwnSafe = false) {
|
|
892
|
-
const block = asOptionalRecord(value);
|
|
893
|
-
if (block?.type !== "data_visualization") return;
|
|
894
|
-
const parsed = normalizeMessagePresentation({ blocks: [parseSlackDataVisualizationBlock(block)] })?.blocks[0];
|
|
895
|
-
if (parsed?.type === "chart") return mrkdwnSafe ? renderSlackMessagePresentationChartFallbackText(parsed) : renderMessagePresentationChartFallbackText(parsed);
|
|
896
|
-
const title = typeof block.title === "string" ? block.title.trim() : "";
|
|
897
|
-
return title ? mrkdwnSafe ? escapeSlackMrkdwn(title) : title : void 0;
|
|
898
|
-
}
|
|
899
|
-
//#endregion
|
|
900
|
-
//#region extensions/slack/src/format.ts
|
|
901
|
-
const SLACK_ANGLE_TOKEN_RE = /<[^>\n]+>/g;
|
|
902
|
-
function isAllowedSlackAngleToken(token) {
|
|
903
|
-
const inner = token.slice(1, -1);
|
|
904
|
-
return inner.startsWith("@") || inner.startsWith("#") || inner.startsWith("!") || inner.startsWith("mailto:") || inner.startsWith("tel:") || inner.startsWith("http://") || inner.startsWith("https://") || inner.startsWith("slack://");
|
|
905
|
-
}
|
|
906
|
-
function escapeSlackMrkdwnContent(text, mentions) {
|
|
907
|
-
if (mentions === "escape") return escapeSlackMrkdwn(text);
|
|
908
|
-
if (!text.includes("&") && !text.includes("<") && !text.includes(">")) return text;
|
|
909
|
-
const out = [];
|
|
910
|
-
let lastIndex = 0;
|
|
911
|
-
for (const match of text.matchAll(SLACK_ANGLE_TOKEN_RE)) {
|
|
912
|
-
const matchIndex = match.index;
|
|
913
|
-
out.push(escapeSlackMrkdwn(text.slice(lastIndex, matchIndex)));
|
|
914
|
-
const token = match[0];
|
|
915
|
-
const escapeControl = mentions === "escape-controls" && /^<[@#!]/u.test(token);
|
|
916
|
-
out.push(isAllowedSlackAngleToken(token) && !escapeControl ? token : escapeSlackMrkdwn(token));
|
|
917
|
-
lastIndex = matchIndex + token.length;
|
|
918
|
-
}
|
|
919
|
-
out.push(escapeSlackMrkdwn(text.slice(lastIndex)));
|
|
920
|
-
return out.join("");
|
|
921
|
-
}
|
|
922
|
-
function escapeSlackMrkdwnText(text, mentions) {
|
|
923
|
-
if (!text.includes("&") && !text.includes("<") && !text.includes(">")) return text;
|
|
924
|
-
return text.split("\n").map((line) => {
|
|
925
|
-
if (line.startsWith("> ")) return `> ${escapeSlackMrkdwnContent(line.slice(2), mentions)}`;
|
|
926
|
-
return escapeSlackMrkdwnContent(line, mentions);
|
|
927
|
-
}).join("\n");
|
|
928
|
-
}
|
|
929
|
-
function buildSlackLink(link, text) {
|
|
930
|
-
const href = link.href.trim();
|
|
931
|
-
if (!href || /^[@#!]/u.test(href)) return null;
|
|
932
|
-
const trimmedLabel = text.slice(link.start, link.end).trim();
|
|
933
|
-
const comparableHref = href.startsWith("mailto:") ? href.slice(7) : href;
|
|
934
|
-
if (!(trimmedLabel.length > 0 && trimmedLabel !== href && trimmedLabel !== comparableHref)) return null;
|
|
935
|
-
const safeHref = escapeSlackMrkdwn(href);
|
|
936
|
-
return {
|
|
937
|
-
start: link.start,
|
|
938
|
-
end: link.end,
|
|
939
|
-
open: `<${safeHref}|`,
|
|
940
|
-
close: ">"
|
|
941
|
-
};
|
|
942
|
-
}
|
|
943
|
-
const SLACK_MRKDWN_WORD_CHARACTER_RE = /[\p{L}\p{M}\p{N}_]/u;
|
|
944
|
-
const SLACK_MRKDWN_PUNCTUATION_RE = /\p{P}/u;
|
|
945
|
-
const SLACK_MRKDWN_SYMBOL_RE = /\p{S}/u;
|
|
946
|
-
const SLACK_MRKDWN_CJK_SCRIPT_RE = /[\p{Script_Extensions=Han}\p{Script_Extensions=Hiragana}\p{Script_Extensions=Katakana}\p{Script_Extensions=Hangul}]/u;
|
|
947
|
-
const SLACK_MRKDWN_EMOJI_PRESENTATION_RE = /\p{Emoji_Presentation}/u;
|
|
948
|
-
function getCodePointBefore(text, index) {
|
|
949
|
-
if (index <= 0) return "";
|
|
950
|
-
const lastCodeUnit = text.charCodeAt(index - 1);
|
|
951
|
-
if (lastCodeUnit >= 56320 && lastCodeUnit <= 57343 && index > 1) {
|
|
952
|
-
const previousCodeUnit = text.charCodeAt(index - 2);
|
|
953
|
-
if (previousCodeUnit >= 55296 && previousCodeUnit <= 56319) return text.slice(index - 2, index);
|
|
954
|
-
}
|
|
955
|
-
return text[index - 1] ?? "";
|
|
956
|
-
}
|
|
957
|
-
function getCodePointAt(text, index) {
|
|
958
|
-
const codePoint = text.codePointAt(index);
|
|
959
|
-
return codePoint === void 0 ? "" : String.fromCodePoint(codePoint);
|
|
960
|
-
}
|
|
961
|
-
function isSlackCjkPunctuation(character) {
|
|
962
|
-
if (!SLACK_MRKDWN_PUNCTUATION_RE.test(character)) return false;
|
|
963
|
-
if (SLACK_MRKDWN_CJK_SCRIPT_RE.test(character)) return true;
|
|
964
|
-
const codePoint = character.codePointAt(0);
|
|
965
|
-
if (codePoint === void 0) return false;
|
|
966
|
-
const width = eastAsianWidthType(codePoint);
|
|
967
|
-
return width === "fullwidth" || width === "halfwidth" || width === "wide" && !SLACK_MRKDWN_EMOJI_PRESENTATION_RE.test(character);
|
|
968
|
-
}
|
|
969
|
-
function isUnsafeSlackEmphasisBoundary(character) {
|
|
970
|
-
if (SLACK_MRKDWN_WORD_CHARACTER_RE.test(character)) return true;
|
|
971
|
-
const codePoint = character.codePointAt(0);
|
|
972
|
-
if (codePoint === void 0 || codePoint <= 127) return false;
|
|
973
|
-
return SLACK_MRKDWN_SYMBOL_RE.test(character) || isSlackCjkPunctuation(character);
|
|
974
|
-
}
|
|
975
|
-
function makeSlackEmphasisStylesSafe(ir) {
|
|
976
|
-
const styles = ir.styles.filter((span) => {
|
|
977
|
-
if (span.style !== "italic" && span.style !== "bold") return true;
|
|
978
|
-
return !isUnsafeSlackEmphasisBoundary(getCodePointBefore(ir.text, span.start)) && !isUnsafeSlackEmphasisBoundary(getCodePointAt(ir.text, span.end));
|
|
979
|
-
});
|
|
980
|
-
return styles.length === ir.styles.length ? ir : {
|
|
981
|
-
...ir,
|
|
982
|
-
styles
|
|
983
|
-
};
|
|
984
|
-
}
|
|
985
|
-
const SLACK_FORMAT_PROFILE = FormatCapabilityProfile.define({
|
|
986
|
-
mechanism: "markdown",
|
|
987
|
-
constructs: {
|
|
988
|
-
underline: "strip",
|
|
989
|
-
spoiler: "fallback",
|
|
990
|
-
codeLanguage: "fallback",
|
|
991
|
-
heading: "fallback",
|
|
992
|
-
bulletList: "fallback",
|
|
993
|
-
orderedList: "fallback",
|
|
994
|
-
taskList: "fallback",
|
|
995
|
-
table: "fallback",
|
|
996
|
-
image: "fallback"
|
|
997
|
-
},
|
|
998
|
-
chunk: {
|
|
999
|
-
limit: 4e3,
|
|
1000
|
-
unit: "chars",
|
|
1001
|
-
hardCap: 4e4
|
|
1002
|
-
}
|
|
1003
|
-
});
|
|
1004
|
-
const SLACK_ASSISTANT_TRANSCRIPT_PREFIX = "`Assistant:` ";
|
|
1005
|
-
function tokenizeSlackMrkdwn(text, graphemes) {
|
|
1006
|
-
const tokens = [];
|
|
1007
|
-
for (let index = 0; index < text.length;) {
|
|
1008
|
-
if (text.startsWith("```", index)) {
|
|
1009
|
-
tokens.push("```");
|
|
1010
|
-
index += 3;
|
|
1011
|
-
continue;
|
|
1012
|
-
}
|
|
1013
|
-
const entity = text[index] === "&" ? [
|
|
1014
|
-
"&",
|
|
1015
|
-
"<",
|
|
1016
|
-
">"
|
|
1017
|
-
].find((candidate) => text.startsWith(candidate, index)) : void 0;
|
|
1018
|
-
if (entity) {
|
|
1019
|
-
tokens.push(entity);
|
|
1020
|
-
index += entity.length;
|
|
1021
|
-
continue;
|
|
1022
|
-
}
|
|
1023
|
-
if (text[index] === "<") {
|
|
1024
|
-
const end = text.indexOf(">", index + 1);
|
|
1025
|
-
const angleToken = end >= 0 ? text.slice(index, end + 1) : void 0;
|
|
1026
|
-
if (angleToken && !angleToken.includes("\n") && isAllowedSlackAngleToken(angleToken)) {
|
|
1027
|
-
tokens.push(angleToken);
|
|
1028
|
-
index += angleToken.length;
|
|
1029
|
-
continue;
|
|
1160
|
+
/** Detect native chart blocks without depending on unreleased Slack SDK types. */
|
|
1161
|
+
function hasSlackDataVisualizationBlock(blocks) {
|
|
1162
|
+
return blocks?.some((block) => asOptionalRecord(block)?.type === "data_visualization") ?? false;
|
|
1163
|
+
}
|
|
1164
|
+
function isStringWithin(value, maxLength) {
|
|
1165
|
+
return value.trim().length > 0 && Array.from(value).length <= maxLength;
|
|
1166
|
+
}
|
|
1167
|
+
function hasUniqueStrings(values) {
|
|
1168
|
+
return new Set(values).size === values.length;
|
|
1169
|
+
}
|
|
1170
|
+
function buildSlackDataVisualizationBlock(block) {
|
|
1171
|
+
if (!isStringWithin(block.title, SLACK_CHART_TITLE_MAX)) return;
|
|
1172
|
+
if (block.chartType === "pie") {
|
|
1173
|
+
if (block.segments.length < 1 || block.segments.length > SLACK_CHART_SERIES_MAX || !block.segments.every((segment) => isStringWithin(segment.label, SLACK_CHART_LABEL_MAX) && Number.isFinite(segment.value) && segment.value > 0)) return;
|
|
1174
|
+
return {
|
|
1175
|
+
type: "data_visualization",
|
|
1176
|
+
title: block.title,
|
|
1177
|
+
chart: {
|
|
1178
|
+
type: "pie",
|
|
1179
|
+
segments: block.segments
|
|
1030
1180
|
}
|
|
1031
|
-
}
|
|
1032
|
-
const codePoint = text.codePointAt(index);
|
|
1033
|
-
if (codePoint === void 0) break;
|
|
1034
|
-
const grapheme = graphemes?.containing(index + (codePoint > 65535 ? 1 : 0));
|
|
1035
|
-
const character = grapheme && text.slice(index, grapheme.index + grapheme.segment.length).match(/^[^`*_~<>&]+/u)?.[0] || String.fromCodePoint(codePoint);
|
|
1036
|
-
index += character.length;
|
|
1037
|
-
tokens.push(character);
|
|
1181
|
+
};
|
|
1038
1182
|
}
|
|
1039
|
-
return
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1183
|
+
if (block.categories.length < 1 || block.categories.length > SLACK_CHART_DATA_POINTS_MAX || !block.categories.every((category) => isStringWithin(category, SLACK_CHART_LABEL_MAX)) || !hasUniqueStrings(block.categories) || block.series.length < 1 || block.series.length > SLACK_CHART_SERIES_MAX || !hasUniqueStrings(block.series.map((series) => series.name)) || block.xLabel !== void 0 && !isStringWithin(block.xLabel, SLACK_CHART_AXIS_LABEL_MAX) || block.yLabel !== void 0 && !isStringWithin(block.yLabel, SLACK_CHART_AXIS_LABEL_MAX) || !block.series.every((series) => isStringWithin(series.name, SLACK_CHART_LABEL_MAX) && series.values.length === block.categories.length && series.values.every((value) => Number.isFinite(value)))) return;
|
|
1184
|
+
return {
|
|
1185
|
+
type: "data_visualization",
|
|
1186
|
+
title: block.title,
|
|
1187
|
+
chart: {
|
|
1188
|
+
type: block.chartType,
|
|
1189
|
+
series: block.series.map((series) => ({
|
|
1190
|
+
name: series.name,
|
|
1191
|
+
data: block.categories.map((label, index) => ({
|
|
1192
|
+
label,
|
|
1193
|
+
value: series.values[index]
|
|
1194
|
+
}))
|
|
1195
|
+
})),
|
|
1196
|
+
axis_config: {
|
|
1197
|
+
categories: block.categories,
|
|
1198
|
+
...block.xLabel ? { x_label: block.xLabel } : {},
|
|
1199
|
+
...block.yLabel ? { y_label: block.yLabel } : {}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1044
1203
|
}
|
|
1045
|
-
function
|
|
1046
|
-
|
|
1204
|
+
function readSlackChartDatum(value) {
|
|
1205
|
+
const record = asOptionalRecord(value);
|
|
1206
|
+
const label = record?.label;
|
|
1207
|
+
const datumValue = record?.value;
|
|
1208
|
+
return typeof label === "string" && typeof datumValue === "number" ? {
|
|
1209
|
+
label,
|
|
1210
|
+
value: datumValue
|
|
1211
|
+
} : void 0;
|
|
1047
1212
|
}
|
|
1048
|
-
function
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1213
|
+
function parseSlackDataVisualizationBlock(block) {
|
|
1214
|
+
const title = block.title;
|
|
1215
|
+
const chart = asOptionalRecord(block.chart);
|
|
1216
|
+
if (typeof title !== "string" || !chart) return;
|
|
1217
|
+
if (chart.type === "pie") {
|
|
1218
|
+
if (!Array.isArray(chart.segments)) return;
|
|
1219
|
+
const segments = chart.segments.map(readSlackChartDatum);
|
|
1220
|
+
if (segments.some((segment) => !segment)) return;
|
|
1221
|
+
return {
|
|
1222
|
+
type: "chart",
|
|
1223
|
+
chartType: "pie",
|
|
1224
|
+
title,
|
|
1225
|
+
segments
|
|
1226
|
+
};
|
|
1055
1227
|
}
|
|
1056
|
-
|
|
1228
|
+
if (chart.type !== "bar" && chart.type !== "area" && chart.type !== "line") return;
|
|
1229
|
+
const axisConfig = asOptionalRecord(chart.axis_config);
|
|
1230
|
+
const categories = axisConfig?.categories;
|
|
1231
|
+
if (!isStringArray(categories)) return;
|
|
1232
|
+
if (!Array.isArray(chart.series)) return;
|
|
1233
|
+
const series = chart.series.map((rawSeries) => {
|
|
1234
|
+
const seriesRecord = asOptionalRecord(rawSeries);
|
|
1235
|
+
if (typeof seriesRecord?.name !== "string" || !Array.isArray(seriesRecord.data)) return;
|
|
1236
|
+
const data = seriesRecord.data.map(readSlackChartDatum);
|
|
1237
|
+
if (data.some((datum) => !datum) || data.length !== categories.length) return;
|
|
1238
|
+
const dataByLabel = new Map(data.map((datum) => [datum.label, datum.value]));
|
|
1239
|
+
if (dataByLabel.size !== data.length || categories.some((category) => !dataByLabel.has(category))) return;
|
|
1240
|
+
return {
|
|
1241
|
+
name: seriesRecord.name,
|
|
1242
|
+
values: categories.map((category) => dataByLabel.get(category))
|
|
1243
|
+
};
|
|
1244
|
+
});
|
|
1245
|
+
if (series.some((entry) => !entry)) return;
|
|
1246
|
+
return {
|
|
1247
|
+
type: "chart",
|
|
1248
|
+
chartType: chart.type,
|
|
1249
|
+
title,
|
|
1250
|
+
categories,
|
|
1251
|
+
series,
|
|
1252
|
+
xLabel: axisConfig?.x_label,
|
|
1253
|
+
yLabel: axisConfig?.y_label
|
|
1254
|
+
};
|
|
1057
1255
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
}).annotations?.some((annotation) => annotation.type === "assistant_transcript_role"));
|
|
1256
|
+
/** Extract an accessible summary, escaping mrkdwn control tokens when requested. */
|
|
1257
|
+
function renderSlackDataVisualizationFallbackText(value, mrkdwnSafe = false) {
|
|
1258
|
+
const block = asOptionalRecord(value);
|
|
1259
|
+
if (block?.type !== "data_visualization") return;
|
|
1260
|
+
const parsed = normalizeMessagePresentation({ blocks: [parseSlackDataVisualizationBlock(block)] })?.blocks[0];
|
|
1261
|
+
if (parsed?.type === "chart") return mrkdwnSafe ? renderSlackMessagePresentationChartFallbackText(parsed) : renderMessagePresentationChartFallbackText(parsed);
|
|
1262
|
+
const title = typeof block.title === "string" ? block.title.trim() : "";
|
|
1263
|
+
return title ? mrkdwnSafe ? escapeSlackMrkdwn(title) : title : void 0;
|
|
1067
1264
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1265
|
+
//#endregion
|
|
1266
|
+
//#region extensions/slack/src/blocks-fallback.ts
|
|
1267
|
+
const SLACK_SELECT_ELEMENT_TYPES = /* @__PURE__ */ new Set([
|
|
1268
|
+
"static_select",
|
|
1269
|
+
"multi_static_select",
|
|
1270
|
+
"external_select",
|
|
1271
|
+
"multi_external_select",
|
|
1272
|
+
"users_select",
|
|
1273
|
+
"multi_users_select",
|
|
1274
|
+
"conversations_select",
|
|
1275
|
+
"multi_conversations_select",
|
|
1276
|
+
"channels_select",
|
|
1277
|
+
"multi_channels_select"
|
|
1278
|
+
]);
|
|
1279
|
+
function readTextObject(value, options = {}) {
|
|
1280
|
+
const record = asOptionalRecord(value);
|
|
1281
|
+
const text = normalizeOptionalString(record?.text);
|
|
1282
|
+
if (!text) return;
|
|
1283
|
+
return record?.type === "plain_text" && options.nativeDataFormat !== "plain" ? escapeSlackMrkdwn(text) : text;
|
|
1070
1284
|
}
|
|
1071
|
-
function
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1074
|
-
const fallbackSeparator = inner.indexOf("|");
|
|
1075
|
-
const tokenString = (fallbackSeparator === -1 ? inner : inner.slice(0, fallbackSeparator)).split("^")[2] ?? "";
|
|
1076
|
-
const fallback = fallbackSeparator === -1 ? "" : inner.slice(fallbackSeparator + 1);
|
|
1077
|
-
return decodeSlackMrkdwnEntities(dateDisplay === "fallback" ? fallback || tokenString : tokenString || fallback);
|
|
1078
|
-
}
|
|
1079
|
-
const labelSeparator = inner.indexOf("|");
|
|
1080
|
-
if (labelSeparator >= 0) return decodeSlackMrkdwnEntities(inner.slice(labelSeparator + 1));
|
|
1081
|
-
const prefix = inner.charAt(0);
|
|
1082
|
-
if (prefix === "@" || prefix === "#" || prefix === "!") return prefix;
|
|
1083
|
-
return decodeSlackMrkdwnEntities(inner);
|
|
1285
|
+
function readAltText(value) {
|
|
1286
|
+
const text = normalizeOptionalString(value);
|
|
1287
|
+
return text ? escapeSlackMrkdwn(text) : void 0;
|
|
1084
1288
|
}
|
|
1085
|
-
function
|
|
1086
|
-
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1089
|
-
if (
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
end: projection.text.length
|
|
1095
|
-
});
|
|
1289
|
+
function readControlElementText(value, options = {}) {
|
|
1290
|
+
const element = asOptionalRecord(value);
|
|
1291
|
+
const type = normalizeOptionalString(element?.type);
|
|
1292
|
+
if (type === "button" || type === "workflow_button") return normalizeOptionalString(element?.text) ?? readTextObject(element?.text, options);
|
|
1293
|
+
if (type && (SLACK_SELECT_ELEMENT_TYPES.has(type) || type === "overflow")) {
|
|
1294
|
+
if (!options.includeSelectOptions) return readTextObject(element?.placeholder, options);
|
|
1295
|
+
const choices = Array.isArray(element?.options) ? element.options : [];
|
|
1296
|
+
return [readTextObject(element?.placeholder, options), ...choices.map((choice) => readTextObject(asOptionalRecord(choice)?.text, options))].filter(Boolean).join("\n");
|
|
1297
|
+
}
|
|
1096
1298
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1299
|
+
/** Read only user-visible text from one Slack block. */
|
|
1300
|
+
function renderSlackBlockFallbackText(raw, options = {}) {
|
|
1301
|
+
const block = asOptionalRecord(raw);
|
|
1302
|
+
if (!block) return;
|
|
1303
|
+
switch (block.type) {
|
|
1304
|
+
case "rich_text": return normalizeOptionalString(renderSlackRichText(block.elements, options.nativeReferenceFormat === "plain" ? "native-reference" : "escaped", "\n"));
|
|
1305
|
+
case "header": return readTextObject(block.text, options);
|
|
1306
|
+
case "section":
|
|
1307
|
+
case "context": {
|
|
1308
|
+
const context = block.type === "context";
|
|
1309
|
+
return (context ? (Array.isArray(block.elements) ? block.elements : []).map((element) => {
|
|
1310
|
+
const record = asOptionalRecord(element);
|
|
1311
|
+
return readTextObject(record, options) ?? readAltText(record?.alt_text);
|
|
1312
|
+
}) : [
|
|
1313
|
+
readTextObject(block.text, options),
|
|
1314
|
+
...Array.isArray(block.fields) ? block.fields.map((field) => readTextObject(field, options)) : [],
|
|
1315
|
+
readControlElementText(block.accessory, options)
|
|
1316
|
+
]).filter(Boolean).join(context ? " " : "\n") || void 0;
|
|
1109
1317
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1318
|
+
case "image": return readAltText(block.alt_text) ?? readTextObject(block.title) ?? "Shared an image";
|
|
1319
|
+
case "video": return readTextObject(block.title, options) ?? readAltText(block.alt_text) ?? "Shared a video";
|
|
1320
|
+
case "file": return "Shared a file";
|
|
1321
|
+
case "actions": {
|
|
1322
|
+
if (!Array.isArray(block.elements)) return;
|
|
1323
|
+
const labels = block.elements.map((value) => readControlElementText(value, options)).filter(Boolean);
|
|
1324
|
+
return [...new Set(labels)].join("\n") || void 0;
|
|
1325
|
+
}
|
|
1326
|
+
case "data_visualization": return renderSlackDataVisualizationFallbackText(block, options.nativeDataFormat !== "plain");
|
|
1327
|
+
case "data_table": return renderSlackDataTableFallbackText(block, options.nativeDataFormat !== "plain");
|
|
1328
|
+
case "table": return renderSlackTableFallbackText(block, options.nativeDataFormat !== "plain");
|
|
1329
|
+
default: return;
|
|
1118
1330
|
}
|
|
1119
|
-
return projection;
|
|
1120
|
-
}
|
|
1121
|
-
function protectSlackAssistantTranscriptRoleHeaders(text) {
|
|
1122
|
-
if (text.startsWith(SLACK_ASSISTANT_TRANSCRIPT_PREFIX)) return text;
|
|
1123
|
-
if (!slackProjectionHasRoleHeader(projectSlackMrkdwnVisibleText(text, "token")) && (!text.includes("<!date^") || !slackProjectionHasRoleHeader(projectSlackMrkdwnVisibleText(text, "fallback")))) return text;
|
|
1124
|
-
return `${SLACK_ASSISTANT_TRANSCRIPT_PREFIX}${text}`;
|
|
1125
1331
|
}
|
|
1126
|
-
function
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
} },
|
|
1133
|
-
styleMarkers: {
|
|
1134
|
-
...enclosingStyle !== "bold" ? { bold: {
|
|
1135
|
-
open: "*",
|
|
1136
|
-
close: "*"
|
|
1137
|
-
} } : {},
|
|
1138
|
-
...enclosingStyle !== "italic" ? { italic: {
|
|
1139
|
-
open: "_",
|
|
1140
|
-
close: "_"
|
|
1141
|
-
} } : {},
|
|
1142
|
-
strikethrough: {
|
|
1143
|
-
open: "~",
|
|
1144
|
-
close: "~"
|
|
1145
|
-
},
|
|
1146
|
-
code: {
|
|
1147
|
-
open: "`",
|
|
1148
|
-
close: "`"
|
|
1149
|
-
},
|
|
1150
|
-
code_block: {
|
|
1151
|
-
open: "```\n",
|
|
1152
|
-
close: "```"
|
|
1153
|
-
}
|
|
1154
|
-
},
|
|
1155
|
-
escapeText: (text) => escapeSlackMrkdwnText(text, mentions),
|
|
1156
|
-
buildLink: buildSlackLink
|
|
1157
|
-
};
|
|
1332
|
+
function buildSlackBlocksFallbackText(blocks) {
|
|
1333
|
+
for (const block of blocks) {
|
|
1334
|
+
const text = renderSlackBlockFallbackText(block);
|
|
1335
|
+
if (text) return text;
|
|
1336
|
+
}
|
|
1337
|
+
return "Shared a Block Kit message";
|
|
1158
1338
|
}
|
|
1159
|
-
function
|
|
1160
|
-
return
|
|
1161
|
-
assistantTranscriptRoleHeaders: true,
|
|
1162
|
-
linkify: false,
|
|
1163
|
-
autolink: false,
|
|
1164
|
-
headingStyle: "rich",
|
|
1165
|
-
blockquotePrefix: "> ",
|
|
1166
|
-
tableMode: options.tableMode
|
|
1167
|
-
}));
|
|
1339
|
+
function buildSlackCompleteBlocksFallbackText(blocks, options = {}) {
|
|
1340
|
+
return blocks.map((block) => renderSlackBlockFallbackText(block, options)).filter(Boolean).join("\n\n").trim() || buildSlackBlocksFallbackText(blocks);
|
|
1168
1341
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1342
|
+
//#endregion
|
|
1343
|
+
//#region extensions/slack/src/blocks-input.ts
|
|
1344
|
+
const SLACK_MAX_BLOCKS = 50;
|
|
1345
|
+
function parseBlocksJson(raw) {
|
|
1346
|
+
try {
|
|
1347
|
+
return JSON.parse(raw);
|
|
1348
|
+
} catch {
|
|
1349
|
+
throw new Error("blocks must be valid JSON");
|
|
1350
|
+
}
|
|
1172
1351
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
if (
|
|
1176
|
-
if (
|
|
1177
|
-
const
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
const capacity = (marker) => limit - (wrapper(marker)?.length ?? 0);
|
|
1182
|
-
const flush = () => {
|
|
1183
|
-
const marker = wrapper(activeMarker);
|
|
1184
|
-
if (content && content !== marker) chunks.push(marker ? `${content}${marker}` : content);
|
|
1185
|
-
content = "";
|
|
1186
|
-
};
|
|
1187
|
-
for (const token of tokenizeSlackMrkdwn(text, new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(text))) {
|
|
1188
|
-
const transition = resolveSlackCodeMarkerTransition(activeMarker, token);
|
|
1189
|
-
const nextMarker = transition === null ? activeMarker : transition;
|
|
1190
|
-
const sourceMarker = token === "`" || token === "```" ? token : void 0;
|
|
1191
|
-
if (transition !== null && sourceMarker && !wrapper(sourceMarker)) {
|
|
1192
|
-
activeMarker = nextMarker;
|
|
1193
|
-
continue;
|
|
1194
|
-
}
|
|
1195
|
-
if (content && content.length + token.length > capacity(nextMarker)) flush();
|
|
1196
|
-
activeMarker = nextMarker;
|
|
1197
|
-
if (!content && transition === void 0) continue;
|
|
1198
|
-
content ||= transition === null ? wrapper(activeMarker) ?? "" : "";
|
|
1199
|
-
const contentLimit = capacity(activeMarker) - (wrapper(activeMarker)?.length ?? 0);
|
|
1200
|
-
if (token.length > contentLimit) {
|
|
1201
|
-
flush();
|
|
1202
|
-
const marker = wrapper(activeMarker);
|
|
1203
|
-
if (activeMarker) {
|
|
1204
|
-
const fragments = chunkTextForOutbound(marker ? token : escapeSlackMrkdwn(token), Math.max(1, Math.floor(marker ? contentLimit : limit)), { preserveWhitespace: true });
|
|
1205
|
-
chunks.push(...marker ? fragments.map((fragment) => `${marker}${fragment}${marker}`) : fragments);
|
|
1206
|
-
continue;
|
|
1207
|
-
}
|
|
1208
|
-
chunks.push(...token.length <= limit ? [token] : chunkTextForOutbound(token, limit));
|
|
1209
|
-
continue;
|
|
1210
|
-
}
|
|
1211
|
-
content += token;
|
|
1352
|
+
function validateSlackBlocksArray(raw) {
|
|
1353
|
+
if (!Array.isArray(raw)) throw new Error("blocks must be an array");
|
|
1354
|
+
if (raw.length === 0) throw new Error("blocks must contain at least one block");
|
|
1355
|
+
if (raw.length > 50) throw new Error(`blocks cannot exceed 50 items`);
|
|
1356
|
+
for (const block of raw) {
|
|
1357
|
+
if (!block || typeof block !== "object" || Array.isArray(block)) throw new Error("each block must be an object");
|
|
1358
|
+
const type = block.type;
|
|
1359
|
+
if (typeof type !== "string" || type.trim().length === 0) throw new Error("each block must include a non-empty string type");
|
|
1212
1360
|
}
|
|
1213
|
-
|
|
1214
|
-
return chunks;
|
|
1361
|
+
return raw;
|
|
1215
1362
|
}
|
|
1216
|
-
function
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1363
|
+
function parseSlackBlocksInput(raw) {
|
|
1364
|
+
if (raw == null) return;
|
|
1365
|
+
return validateSlackBlocksArray(typeof raw === "string" ? parseBlocksJson(raw) : raw);
|
|
1366
|
+
}
|
|
1367
|
+
//#endregion
|
|
1368
|
+
//#region extensions/slack/src/approval-actions.ts
|
|
1369
|
+
const SLACK_APPROVAL_VALUE_PREFIX = "openclaw:approval:v1:";
|
|
1370
|
+
const SLACK_APPROVAL_HEADER_BLOCK_ID = "openclaw_approval_header";
|
|
1371
|
+
function isApprovalDecision(value) {
|
|
1372
|
+
return value === "allow-once" || value === "allow-always" || value === "deny";
|
|
1373
|
+
}
|
|
1374
|
+
/** Encode portable approval facts without exposing a slash command to Slack callbacks. */
|
|
1375
|
+
function encodeSlackApprovalAction(action) {
|
|
1376
|
+
const encode = (approvalId) => `${SLACK_APPROVAL_VALUE_PREFIX}${JSON.stringify({
|
|
1377
|
+
approvalId,
|
|
1378
|
+
approvalKind: action.approvalKind,
|
|
1379
|
+
decision: action.decision
|
|
1380
|
+
})}`;
|
|
1381
|
+
const exact = encode(action.approvalId);
|
|
1382
|
+
return exact.length <= 2e3 ? exact : encode(buildApprovalResolutionRef({
|
|
1383
|
+
approvalId: action.approvalId,
|
|
1384
|
+
approvalKind: action.approvalKind
|
|
1385
|
+
}));
|
|
1386
|
+
}
|
|
1387
|
+
/** Decode only the exact Slack-owned approval envelope. Malformed callbacks fail closed. */
|
|
1388
|
+
function decodeSlackApprovalAction(value) {
|
|
1389
|
+
if (typeof value !== "string" || !value.startsWith(SLACK_APPROVAL_VALUE_PREFIX)) return null;
|
|
1390
|
+
try {
|
|
1391
|
+
const decoded = JSON.parse(value.slice(21));
|
|
1392
|
+
if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) return null;
|
|
1393
|
+
const record = decoded;
|
|
1394
|
+
if (Object.keys(record).length !== 3 || typeof record.approvalId !== "string" || record.approvalId.length === 0 || record.approvalKind !== "exec" && record.approvalKind !== "plugin" && record.approvalKind !== "system-agent" || !isApprovalDecision(record.decision)) return null;
|
|
1395
|
+
return {
|
|
1396
|
+
type: "approval",
|
|
1397
|
+
approvalId: record.approvalId,
|
|
1398
|
+
approvalKind: record.approvalKind,
|
|
1399
|
+
decision: record.decision
|
|
1400
|
+
};
|
|
1401
|
+
} catch {
|
|
1402
|
+
return null;
|
|
1403
|
+
}
|
|
1229
1404
|
}
|
|
1230
1405
|
//#endregion
|
|
1231
1406
|
//#region extensions/slack/src/question-actions.ts
|
|
@@ -1246,7 +1421,7 @@ function decodeSlackQuestionAction(value) {
|
|
|
1246
1421
|
async function resolveSlackQuestionAction(params) {
|
|
1247
1422
|
let result;
|
|
1248
1423
|
try {
|
|
1249
|
-
result = await
|
|
1424
|
+
result = await questionGatewayRuntime.resolveOption({
|
|
1250
1425
|
cfg: params.cfg,
|
|
1251
1426
|
questionId: params.action.questionId,
|
|
1252
1427
|
optionIndex: params.action.optionIndex,
|
|
@@ -1284,11 +1459,14 @@ function createSlackActionIdMatcher(...prefixes) {
|
|
|
1284
1459
|
const isSlackQuestionActionId = createSlackActionIdMatcher(SLACK_BUTTON_ACTION_IDS.question);
|
|
1285
1460
|
const isSlackApprovalActionId = createSlackActionIdMatcher(SLACK_BUTTON_ACTION_IDS.approval, SLACK_SELECT_ACTION_IDS.approval);
|
|
1286
1461
|
const isSlackCallbackActionId = createSlackActionIdMatcher(SLACK_BUTTON_ACTION_IDS.callback, SLACK_SELECT_ACTION_IDS.callback);
|
|
1462
|
+
function isSlackQuestionActionForQuestion(action, questionId) {
|
|
1463
|
+
return Boolean(action.action_id && isSlackQuestionActionId(action.action_id) && decodeSlackQuestionAction(action.value)?.questionId === questionId);
|
|
1464
|
+
}
|
|
1287
1465
|
/** Read only question control identities from the blocks actually sent to Slack. */
|
|
1288
|
-
function resolveSlackQuestionActionIds(blocks) {
|
|
1466
|
+
function resolveSlackQuestionActionIds(blocks, questionId) {
|
|
1289
1467
|
return (blocks ?? []).flatMap((block) => {
|
|
1290
1468
|
if (block.type !== "actions") return [];
|
|
1291
|
-
return (block.elements ?? []).flatMap((
|
|
1469
|
+
return (block.elements ?? []).flatMap((element) => element.action_id && (questionId ? isSlackQuestionActionForQuestion(element, questionId) : isSlackQuestionActionId(element.action_id)) ? [element.action_id] : []);
|
|
1292
1470
|
});
|
|
1293
1471
|
}
|
|
1294
1472
|
//#endregion
|
|
@@ -1297,7 +1475,11 @@ function truncateSlackText(value, max, whitespace = "trim") {
|
|
|
1297
1475
|
const text = whitespace === "preserve" ? value : value.trim();
|
|
1298
1476
|
if (text.length <= max) return text;
|
|
1299
1477
|
if (max <= 1) return sliceUtf16Safe(text, 0, max);
|
|
1300
|
-
return
|
|
1478
|
+
return truncateWithMarker(text, max, {
|
|
1479
|
+
marker: "…",
|
|
1480
|
+
reserve: 1,
|
|
1481
|
+
trimEnd: false
|
|
1482
|
+
});
|
|
1301
1483
|
}
|
|
1302
1484
|
function countSlackTextUtf8Bytes(value) {
|
|
1303
1485
|
return Buffer.byteLength(value, "utf8");
|
|
@@ -1310,101 +1492,10 @@ function truncateSlackTextByUtf8Bytes(value, maxBytes) {
|
|
|
1310
1492
|
const suffix = "…";
|
|
1311
1493
|
const suffixBytes = countSlackTextUtf8Bytes(suffix);
|
|
1312
1494
|
const prefixBudget = maxBytes >= suffixBytes ? maxBytes - suffixBytes : maxBytes;
|
|
1313
|
-
|
|
1314
|
-
let prefixBytes = 0;
|
|
1315
|
-
for (const character of trimmed) {
|
|
1316
|
-
const characterBytes = countSlackTextUtf8Bytes(character);
|
|
1317
|
-
if (prefixBytes + characterBytes > prefixBudget) break;
|
|
1318
|
-
prefix += character;
|
|
1319
|
-
prefixBytes += characterBytes;
|
|
1320
|
-
}
|
|
1495
|
+
const prefix = truncateUtf8Prefix(trimmed, prefixBudget);
|
|
1321
1496
|
return maxBytes >= suffixBytes ? `${prefix}${suffix}` : prefix;
|
|
1322
1497
|
}
|
|
1323
1498
|
//#endregion
|
|
1324
|
-
//#region extensions/slack/src/limits.ts
|
|
1325
|
-
const SLACK_TEXT_LIMIT = 8e3;
|
|
1326
|
-
const SLACK_MESSAGE_TEXT_RECOMMENDED_LIMIT = 4e3;
|
|
1327
|
-
const SLACK_EDIT_TEXT_MAX_BYTES = 4e3;
|
|
1328
|
-
const SLACK_MESSAGE_TEXT_HARD_LIMIT = 4e4;
|
|
1329
|
-
//#endregion
|
|
1330
|
-
//#region extensions/slack/src/blocks-fallback.ts
|
|
1331
|
-
const SLACK_SELECT_ELEMENT_TYPES = /* @__PURE__ */ new Set([
|
|
1332
|
-
"static_select",
|
|
1333
|
-
"multi_static_select",
|
|
1334
|
-
"external_select",
|
|
1335
|
-
"multi_external_select",
|
|
1336
|
-
"users_select",
|
|
1337
|
-
"multi_users_select",
|
|
1338
|
-
"conversations_select",
|
|
1339
|
-
"multi_conversations_select",
|
|
1340
|
-
"channels_select",
|
|
1341
|
-
"multi_channels_select"
|
|
1342
|
-
]);
|
|
1343
|
-
function readTextObject(value, options = {}) {
|
|
1344
|
-
const record = asOptionalRecord(value);
|
|
1345
|
-
const text = normalizeOptionalString(record?.text);
|
|
1346
|
-
if (!text) return;
|
|
1347
|
-
return record?.type === "plain_text" && options.nativeDataFormat !== "plain" ? escapeSlackMrkdwn(text) : text;
|
|
1348
|
-
}
|
|
1349
|
-
function readAltText(value) {
|
|
1350
|
-
const text = normalizeOptionalString(value);
|
|
1351
|
-
return text ? escapeSlackMrkdwn(text) : void 0;
|
|
1352
|
-
}
|
|
1353
|
-
function readControlElementText(value, options = {}) {
|
|
1354
|
-
const element = asOptionalRecord(value);
|
|
1355
|
-
const type = normalizeOptionalString(element?.type);
|
|
1356
|
-
if (type === "button" || type === "workflow_button") return normalizeOptionalString(element?.text) ?? readTextObject(element?.text, options);
|
|
1357
|
-
if (type && (SLACK_SELECT_ELEMENT_TYPES.has(type) || type === "overflow")) {
|
|
1358
|
-
if (!options.includeSelectOptions) return readTextObject(element?.placeholder, options);
|
|
1359
|
-
const choices = Array.isArray(element?.options) ? element.options : [];
|
|
1360
|
-
return [readTextObject(element?.placeholder, options), ...choices.map((choice) => readTextObject(asOptionalRecord(choice)?.text, options))].filter(Boolean).join("\n");
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
function readControlElementsText(values, options = {}) {
|
|
1364
|
-
const labels = values.map((value) => readControlElementText(value, options)).filter(Boolean);
|
|
1365
|
-
return [...new Set(labels)].join("\n") || void 0;
|
|
1366
|
-
}
|
|
1367
|
-
function readTextLayout(block, options = {}) {
|
|
1368
|
-
const context = block.type === "context";
|
|
1369
|
-
return (context ? (Array.isArray(block.elements) ? block.elements : []).map((element) => {
|
|
1370
|
-
const record = asOptionalRecord(element);
|
|
1371
|
-
return readTextObject(record, options) ?? readAltText(record?.alt_text);
|
|
1372
|
-
}) : [
|
|
1373
|
-
readTextObject(block.text, options),
|
|
1374
|
-
...Array.isArray(block.fields) ? block.fields.map((field) => readTextObject(field, options)) : [],
|
|
1375
|
-
readControlElementText(block.accessory, options)
|
|
1376
|
-
]).filter(Boolean).join(context ? " " : "\n") || void 0;
|
|
1377
|
-
}
|
|
1378
|
-
/** Read only user-visible text from one Slack block. */
|
|
1379
|
-
function renderSlackBlockFallbackText(raw, options = {}) {
|
|
1380
|
-
const block = asOptionalRecord(raw);
|
|
1381
|
-
if (!block) return;
|
|
1382
|
-
switch (block.type) {
|
|
1383
|
-
case "rich_text": return normalizeOptionalString(renderSlackRichText(block.elements, options.nativeReferenceFormat === "plain" ? "native-reference" : "escaped", "\n"));
|
|
1384
|
-
case "header": return readTextObject(block.text, options);
|
|
1385
|
-
case "section":
|
|
1386
|
-
case "context": return readTextLayout(block, options);
|
|
1387
|
-
case "image": return readAltText(block.alt_text) ?? readTextObject(block.title) ?? "Shared an image";
|
|
1388
|
-
case "video": return readTextObject(block.title, options) ?? readAltText(block.alt_text) ?? "Shared a video";
|
|
1389
|
-
case "file": return "Shared a file";
|
|
1390
|
-
case "actions": return Array.isArray(block.elements) ? readControlElementsText(block.elements, options) : void 0;
|
|
1391
|
-
case "data_visualization": return renderSlackDataVisualizationFallbackText(block, options.nativeDataFormat !== "plain");
|
|
1392
|
-
case "data_table": return renderSlackDataTableFallbackText(block, options.nativeDataFormat !== "plain");
|
|
1393
|
-
case "table": return renderSlackTableFallbackText(block, options.nativeDataFormat !== "plain");
|
|
1394
|
-
default: return;
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
function buildSlackBlocksFallbackText(blocks) {
|
|
1398
|
-
for (const block of blocks) {
|
|
1399
|
-
const text = renderSlackBlockFallbackText(block);
|
|
1400
|
-
if (text) return text;
|
|
1401
|
-
}
|
|
1402
|
-
return "Shared a Block Kit message";
|
|
1403
|
-
}
|
|
1404
|
-
function buildSlackCompleteBlocksFallbackText(blocks, options = {}) {
|
|
1405
|
-
return blocks.map((block) => renderSlackBlockFallbackText(block, options)).filter(Boolean).join("\n\n").trim() || buildSlackBlocksFallbackText(blocks);
|
|
1406
|
-
}
|
|
1407
|
-
//#endregion
|
|
1408
1499
|
//#region extensions/slack/src/native-data-blocks.ts
|
|
1409
1500
|
const SLACK_MALFORMED_NATIVE_DATA_FALLBACK = "Slack could not render this chart or table data.";
|
|
1410
1501
|
const SLACK_RESPONSE_URL_BODY_LIMIT_BYTES = 16384;
|
|
@@ -1536,7 +1627,7 @@ function normalizeSlackRouteBindingConfig(cfg) {
|
|
|
1536
1627
|
const bindings = cfg.bindings;
|
|
1537
1628
|
const cached = slackRouteBindingConfigCache.get(cfg);
|
|
1538
1629
|
if (cached && cached.bindingsRef === bindings) return cached.normalizedCfg;
|
|
1539
|
-
if (!
|
|
1630
|
+
if (!bindings) return cfg;
|
|
1540
1631
|
let changed = false;
|
|
1541
1632
|
const normalizedBindings = bindings.map((binding) => {
|
|
1542
1633
|
if (binding.type === "acp" || binding.match.channel.trim().toLowerCase() !== "slack") return binding;
|
|
@@ -1688,6 +1779,114 @@ function qualifySlackConversationId(conversationId, eventScope) {
|
|
|
1688
1779
|
return eventScope ? `team:${encodeURIComponent(eventScope.teamId)}:${conversationId}` : conversationId;
|
|
1689
1780
|
}
|
|
1690
1781
|
//#endregion
|
|
1782
|
+
//#region extensions/slack/src/errors.ts
|
|
1783
|
+
const NO_ERROR_DETAIL = "no error detail";
|
|
1784
|
+
const MAX_ERROR_CAUSE_DEPTH = 32;
|
|
1785
|
+
function getSlackWebApiErrorData(error) {
|
|
1786
|
+
if (!(error instanceof Error)) return;
|
|
1787
|
+
const data = error.data;
|
|
1788
|
+
return data && typeof data === "object" ? data : void 0;
|
|
1789
|
+
}
|
|
1790
|
+
function addStringDetail(details, label, value) {
|
|
1791
|
+
if (typeof value !== "string") return;
|
|
1792
|
+
const trimmed = redactSensitiveText(value.trim());
|
|
1793
|
+
if (trimmed) details.push(label ? `${label}: ${trimmed}` : trimmed);
|
|
1794
|
+
}
|
|
1795
|
+
function addScalarDetail(details, label, value) {
|
|
1796
|
+
if (typeof value === "string") {
|
|
1797
|
+
addStringDetail(details, label, value);
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
if (typeof value === "number" || typeof value === "boolean") details.push(`${label}: ${String(value)}`);
|
|
1801
|
+
}
|
|
1802
|
+
function addStringListDetail(details, label, value) {
|
|
1803
|
+
if (!Array.isArray(value)) return;
|
|
1804
|
+
const entries = [];
|
|
1805
|
+
for (const entry of value) addStringDetail(entries, "", entry);
|
|
1806
|
+
if (entries.length) details.push(`${label}: ${entries.join(", ")}`);
|
|
1807
|
+
}
|
|
1808
|
+
function safeStringify(value) {
|
|
1809
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
1810
|
+
try {
|
|
1811
|
+
const result = JSON.stringify(value, (_key, nested) => {
|
|
1812
|
+
if (typeof nested !== "object" || nested === null) return nested;
|
|
1813
|
+
if (seen.has(nested)) return "[Circular]";
|
|
1814
|
+
seen.add(nested);
|
|
1815
|
+
return nested;
|
|
1816
|
+
});
|
|
1817
|
+
return result ? redactSensitiveText(result) : void 0;
|
|
1818
|
+
} catch {
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
function addSlackResponseMetadata(details, value) {
|
|
1823
|
+
if (!isRecord(value)) return;
|
|
1824
|
+
addStringListDetail(details, "scopes", value.scopes);
|
|
1825
|
+
addStringListDetail(details, "accepted", value.acceptedScopes);
|
|
1826
|
+
for (const [key, label] of [["messages", "slack message"], ["warnings", "slack warning"]]) {
|
|
1827
|
+
const entries = value[key];
|
|
1828
|
+
if (Array.isArray(entries)) for (const entry of entries) addStringDetail(details, label, entry);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
function addSlackDataDetails(details, value) {
|
|
1832
|
+
if (!isRecord(value)) return;
|
|
1833
|
+
addScalarDetail(details, "slack error", value.error);
|
|
1834
|
+
addScalarDetail(details, "needed", value.needed);
|
|
1835
|
+
addScalarDetail(details, "provided", value.provided);
|
|
1836
|
+
addSlackResponseMetadata(details, value.response_metadata);
|
|
1837
|
+
}
|
|
1838
|
+
function addRecordDetails(details, value) {
|
|
1839
|
+
addScalarDetail(details, "code", value.code);
|
|
1840
|
+
addScalarDetail(details, "status", value.status);
|
|
1841
|
+
addScalarDetail(details, "statusCode", value.statusCode);
|
|
1842
|
+
addScalarDetail(details, "statusMessage", value.statusMessage);
|
|
1843
|
+
addScalarDetail(details, "retryAfter", value.retryAfter);
|
|
1844
|
+
addScalarDetail(details, "errno", value.errno);
|
|
1845
|
+
addScalarDetail(details, "syscall", value.syscall);
|
|
1846
|
+
addScalarDetail(details, "hostname", value.hostname);
|
|
1847
|
+
addScalarDetail(details, "type", value.type);
|
|
1848
|
+
addStringDetail(details, "statusText", value.statusText);
|
|
1849
|
+
addStringDetail(details, "body", value.body);
|
|
1850
|
+
addSlackDataDetails(details, value.data);
|
|
1851
|
+
if (isRecord(value.response)) {
|
|
1852
|
+
addScalarDetail(details, "response status", value.response.status);
|
|
1853
|
+
addStringDetail(details, "response statusText", value.response.statusText);
|
|
1854
|
+
addSlackDataDetails(details, value.response.data);
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
function collectSlackErrorDetails(error, seen) {
|
|
1858
|
+
const details = [];
|
|
1859
|
+
if (error === void 0 || error === null) return details;
|
|
1860
|
+
if (typeof error === "string") {
|
|
1861
|
+
addStringDetail(details, "", error);
|
|
1862
|
+
return details;
|
|
1863
|
+
}
|
|
1864
|
+
if (error instanceof Error) {
|
|
1865
|
+
addStringDetail(details, "", error.message || error.name);
|
|
1866
|
+
if (error.cause !== void 0) {
|
|
1867
|
+
const cause = formatSlackErrorWithCauses(error.cause, "", seen);
|
|
1868
|
+
if (cause) details.push(`cause: ${cause}`);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
if (isRecord(error)) addRecordDetails(details, error);
|
|
1872
|
+
return details;
|
|
1873
|
+
}
|
|
1874
|
+
function formatSlackErrorWithCauses(error, fallback, seen) {
|
|
1875
|
+
if (error instanceof Error) {
|
|
1876
|
+
if (seen.has(error)) return "[Circular]";
|
|
1877
|
+
if (seen.size >= MAX_ERROR_CAUSE_DEPTH) return "[Cause chain truncated]";
|
|
1878
|
+
seen.add(error);
|
|
1879
|
+
}
|
|
1880
|
+
const details = collectSlackErrorDetails(error, seen);
|
|
1881
|
+
if (details.length > 0) return details.join("; ");
|
|
1882
|
+
if (error === void 0 || error === null) return fallback;
|
|
1883
|
+
if (typeof error === "string" && !error.trim()) return fallback;
|
|
1884
|
+
return safeStringify(error) ?? fallback;
|
|
1885
|
+
}
|
|
1886
|
+
function formatSlackError(error, fallback = NO_ERROR_DETAIL) {
|
|
1887
|
+
return formatSlackErrorWithCauses(error, fallback, /* @__PURE__ */ new Set());
|
|
1888
|
+
}
|
|
1889
|
+
//#endregion
|
|
1691
1890
|
//#region extensions/slack/src/channel-policy-state.ts
|
|
1692
1891
|
const slackChannelPolicyBindings = resolveGlobalMap(Symbol.for("openclaw.slack.channel-policy-bindings"), "close-and-restart");
|
|
1693
1892
|
function setSlackChannelPolicyBindings(accountId, bindings) {
|
|
@@ -1700,6 +1899,15 @@ function getSlackChannelPolicyKeys(accountId, channelId) {
|
|
|
1700
1899
|
}
|
|
1701
1900
|
//#endregion
|
|
1702
1901
|
//#region extensions/slack/src/group-policy.ts
|
|
1902
|
+
function hasPotentialSlackNamedPolicy(params) {
|
|
1903
|
+
if (params.allowNameMatching !== true) return false;
|
|
1904
|
+
return Object.entries(params.channels ?? {}).some(([key, entry]) => {
|
|
1905
|
+
if (entry == null || key === "*") return false;
|
|
1906
|
+
const named = !/^(?:channel:)?[CDG][A-Z0-9]+$/i.test(key);
|
|
1907
|
+
const entryAllows = entry.enabled !== false;
|
|
1908
|
+
return named && (params.decision === "allow" ? entryAllows : !entryAllows);
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1703
1911
|
function buildSlackChannelIdCandidates(channelId, teamId, options) {
|
|
1704
1912
|
const trimmedId = channelId?.trim();
|
|
1705
1913
|
if (!trimmedId) return [];
|
|
@@ -1752,6 +1960,104 @@ function resolveSlackGroupToolPolicy(params) {
|
|
|
1752
1960
|
});
|
|
1753
1961
|
}
|
|
1754
1962
|
//#endregion
|
|
1963
|
+
//#region extensions/slack/src/monitor/ingress-identity.ts
|
|
1964
|
+
const SLACK_USER_NAME_KIND = "plugin:slack-user-name";
|
|
1965
|
+
const SLACK_WORKSPACE_USER_ID_KIND = "plugin:slack-workspace-user-id";
|
|
1966
|
+
function normalizeSlackUserId(raw) {
|
|
1967
|
+
const value = (raw ?? "").trim().toLowerCase();
|
|
1968
|
+
if (!value) return "";
|
|
1969
|
+
const mention = value.match(/^<@([a-z0-9_]+)>$/i);
|
|
1970
|
+
if (mention?.[1]) return mention[1];
|
|
1971
|
+
return value.replace(/^(slack:|user:)/, "");
|
|
1972
|
+
}
|
|
1973
|
+
function normalizeSlackWorkspaceUserEntry(entry) {
|
|
1974
|
+
const normalized = entry.trim().toLowerCase();
|
|
1975
|
+
if (!normalized) return null;
|
|
1976
|
+
try {
|
|
1977
|
+
const target = parseSlackTarget(normalized);
|
|
1978
|
+
if (target?.kind === "user" && target.teamId) return target.normalized;
|
|
1979
|
+
} catch {
|
|
1980
|
+
return null;
|
|
1981
|
+
}
|
|
1982
|
+
return null;
|
|
1983
|
+
}
|
|
1984
|
+
function normalizeSlackBareUserEntry(entry) {
|
|
1985
|
+
const normalized = entry.trim().toLowerCase();
|
|
1986
|
+
if (!normalized || normalizeSlackWorkspaceUserEntry(normalized)) return null;
|
|
1987
|
+
const userId = normalizeSlackUserId(normalized);
|
|
1988
|
+
return /^[ubw][a-z0-9_]+$/i.test(userId) ? userId : null;
|
|
1989
|
+
}
|
|
1990
|
+
function normalizeSlackStableEntry(entry) {
|
|
1991
|
+
return normalizeSlackBareUserEntry(entry) ?? normalizeSlackWorkspaceUserEntry(entry);
|
|
1992
|
+
}
|
|
1993
|
+
function normalizeSlackNameEntry(entry) {
|
|
1994
|
+
const normalized = entry.trim().toLowerCase();
|
|
1995
|
+
if (!normalized || normalizeSlackStableEntry(normalized)) return null;
|
|
1996
|
+
return normalized.replace(/^slack:/, "") || null;
|
|
1997
|
+
}
|
|
1998
|
+
function normalizeSlackNameSubject(value) {
|
|
1999
|
+
return value.trim().toLowerCase() || null;
|
|
2000
|
+
}
|
|
2001
|
+
function normalizeSlackNameSlugEntry(entry) {
|
|
2002
|
+
const name = normalizeSlackNameEntry(entry);
|
|
2003
|
+
if (!name) return null;
|
|
2004
|
+
return normalizeSlackSlug(name) || null;
|
|
2005
|
+
}
|
|
2006
|
+
const slackIngressIdentity = defineStableChannelIngressIdentity({
|
|
2007
|
+
resolveParticipant: (subject) => {
|
|
2008
|
+
const qualified = subject.aliases?.workspaceSenderId;
|
|
2009
|
+
if (typeof qualified !== "string") return;
|
|
2010
|
+
const normalized = normalizeSlackWorkspaceUserEntry(qualified);
|
|
2011
|
+
const target = normalized ? parseSlackTarget(normalized) : void 0;
|
|
2012
|
+
return target?.teamId ? {
|
|
2013
|
+
domain: target.teamId,
|
|
2014
|
+
idKind: target.id.startsWith("b") ? "bot-id" : "user-id",
|
|
2015
|
+
id: target.id
|
|
2016
|
+
} : void 0;
|
|
2017
|
+
},
|
|
2018
|
+
key: "senderId",
|
|
2019
|
+
kind: "stable-id",
|
|
2020
|
+
authentication: "asserted",
|
|
2021
|
+
normalizeEntry: normalizeSlackBareUserEntry,
|
|
2022
|
+
normalizeSubject: normalizeSlackUserId,
|
|
2023
|
+
sensitivity: "pii",
|
|
2024
|
+
aliases: [{
|
|
2025
|
+
key: "workspaceSenderId",
|
|
2026
|
+
kind: SLACK_WORKSPACE_USER_ID_KIND,
|
|
2027
|
+
authentication: "asserted",
|
|
2028
|
+
normalizeEntry: normalizeSlackWorkspaceUserEntry,
|
|
2029
|
+
normalizeSubject: normalizeSlackWorkspaceUserEntry,
|
|
2030
|
+
sensitivity: "pii"
|
|
2031
|
+
}, ...[["senderName", normalizeSlackNameEntry], ["senderNameSlug", normalizeSlackNameSlugEntry]].map(([key, normalizeEntry]) => ({
|
|
2032
|
+
key,
|
|
2033
|
+
kind: SLACK_USER_NAME_KIND,
|
|
2034
|
+
normalizeEntry,
|
|
2035
|
+
normalizeSubject: normalizeSlackNameSubject,
|
|
2036
|
+
authentication: "mutable",
|
|
2037
|
+
sensitivity: "pii"
|
|
2038
|
+
}))]
|
|
2039
|
+
});
|
|
2040
|
+
function createSlackIngressSubject(params) {
|
|
2041
|
+
const senderId = normalizeSlackUserId(params.senderId);
|
|
2042
|
+
const teamId = normalizeOptionalLowercaseString(params.teamId);
|
|
2043
|
+
const senderName = params.senderName?.trim().toLowerCase();
|
|
2044
|
+
const senderNameSlug = senderName ? normalizeSlackSlug(senderName) : void 0;
|
|
2045
|
+
return {
|
|
2046
|
+
stableId: params.senderId,
|
|
2047
|
+
authentication: {
|
|
2048
|
+
senderId: params.senderAuthentication ?? "asserted",
|
|
2049
|
+
workspaceSenderId: params.senderAuthentication ?? "asserted",
|
|
2050
|
+
senderName: "mutable",
|
|
2051
|
+
senderNameSlug: "mutable"
|
|
2052
|
+
},
|
|
2053
|
+
aliases: {
|
|
2054
|
+
workspaceSenderId: teamId && senderId ? `team:${teamId}:user:${senderId}` : void 0,
|
|
2055
|
+
senderName,
|
|
2056
|
+
senderNameSlug
|
|
2057
|
+
}
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
//#endregion
|
|
1755
2061
|
//#region extensions/slack/src/session-status.ts
|
|
1756
2062
|
const MISSING_STOP_SUBSCRIPTION = "missing_agent_session_stopped_event_subscription";
|
|
1757
2063
|
let warnedMissingStopSubscription = false;
|
|
@@ -1794,4 +2100,65 @@ async function renameSlackSession(params) {
|
|
|
1794
2100
|
}
|
|
1795
2101
|
}
|
|
1796
2102
|
//#endregion
|
|
1797
|
-
|
|
2103
|
+
//#region extensions/slack/src/probe.ts
|
|
2104
|
+
var probe_exports = /* @__PURE__ */ __exportAll({ probeSlack: () => probeSlack });
|
|
2105
|
+
async function probeSlack(token, timeoutMs = 2500, opts) {
|
|
2106
|
+
const dispatcher = createSlackProbeDispatcher(timeoutMs);
|
|
2107
|
+
const client = createSlackReadClient(token, {
|
|
2108
|
+
rejectRateLimitedCalls: true,
|
|
2109
|
+
retryConfig: { retries: 0 },
|
|
2110
|
+
timeout: timeoutMs
|
|
2111
|
+
}, dispatcher);
|
|
2112
|
+
const probeResult = runChannelProbe(timeoutMs, async () => {
|
|
2113
|
+
const result = await client.auth.test();
|
|
2114
|
+
if (!result.ok) return {
|
|
2115
|
+
ok: false,
|
|
2116
|
+
status: 200,
|
|
2117
|
+
error: result.error ?? "unknown"
|
|
2118
|
+
};
|
|
2119
|
+
const userIdentity = opts?.identity === "user";
|
|
2120
|
+
let userId = result.user_id;
|
|
2121
|
+
if (userIdentity) {
|
|
2122
|
+
if (result.bot_id?.trim()) return {
|
|
2123
|
+
ok: false,
|
|
2124
|
+
status: 200,
|
|
2125
|
+
error: "Slack auth.test identified a bot token; user identity requires a user OAuth token"
|
|
2126
|
+
};
|
|
2127
|
+
userId = result.user_id?.trim();
|
|
2128
|
+
if (!userId) return {
|
|
2129
|
+
ok: false,
|
|
2130
|
+
status: 200,
|
|
2131
|
+
error: "Slack auth.test returned no human user_id for user identity"
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
const warning = userIdentity ? void 0 : formatSlackBotTokenIdentityWarning({
|
|
2135
|
+
auth: result,
|
|
2136
|
+
accountId: opts?.accountId
|
|
2137
|
+
});
|
|
2138
|
+
const authIdentity = {
|
|
2139
|
+
id: userId,
|
|
2140
|
+
name: result.user
|
|
2141
|
+
};
|
|
2142
|
+
return {
|
|
2143
|
+
ok: true,
|
|
2144
|
+
status: 200,
|
|
2145
|
+
...userIdentity ? { user: authIdentity } : { bot: authIdentity },
|
|
2146
|
+
team: {
|
|
2147
|
+
id: result.team_id,
|
|
2148
|
+
name: result.team
|
|
2149
|
+
},
|
|
2150
|
+
...warning ? { warning } : {}
|
|
2151
|
+
};
|
|
2152
|
+
}, (error) => ({
|
|
2153
|
+
ok: false,
|
|
2154
|
+
status: typeof error.statusCode === "number" ? error.statusCode : null,
|
|
2155
|
+
error: formatSlackError(error)
|
|
2156
|
+
}));
|
|
2157
|
+
try {
|
|
2158
|
+
return await probeResult;
|
|
2159
|
+
} finally {
|
|
2160
|
+
await dispatcher.destroy().catch(() => void 0);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
//#endregion
|
|
2164
|
+
export { decodeSlackApprovalAction as $, resolveSlackExecApprovalTarget as $t, isSlackInvalidBlocksResponse as A, resolveSessionSlackOriginTarget as At, SLACK_REPLY_LINK_ACTION_ID as B, allowListMatches as Bt, SLACK_MALFORMED_NATIVE_DATA_FALLBACK as C, markdownToSlackMrkdwnChunks as Ct, createSlackNativeDataBaseTextConsumer as D, isSlackPluginApprovalRequest as Dt, buildSlackNativeDataAccessibilityText as E, isSlackAnyNativeApprovalClientEnabled as Et, truncateSlackText as F, shouldHandleSlackPluginViaForwardingSession as Ft, isSlackCallbackActionId as G, resolveSlackUserAllowed as Gt, SLACK_SELECT_ACTION_IDS as H, normalizeSlackSlug as Ht, truncateSlackTextByUtf8Bytes as I, slackTargetsMatch as It, resolveSlackQuestionActionIds as J, registerSlackInstallationState as Jt, isSlackQuestionActionForQuestion as K, getSlackInstallationKind as Kt, SLACK_BUTTON_ACTION_IDS as L, getSlackApprovalApproversForTeam as Lt, renderSlackNativeDataPlainTextBlock as M, resolveSlackFallbackOriginTarget as Mt, stripSlackNativeDataBlocks as N, resolveTurnSourceSlackOriginTarget as Nt, hasSlackNativeDataBlock as O, normalizeSlackForwardTarget as Ot, countSlackTextUtf8Bytes as P, shouldHandleSlackNativeApprovalRequest as Pt, SLACK_APPROVAL_HEADER_BLOCK_ID as Q, normalizeSlackApproverId as Qt, SLACK_QUESTION_FINALIZATION_BLOCKS as R, isSlackPluginApprovalAuthorizedSender as Rt, assertSlackDetachedTargetAllowed as S, chunkSlackMrkdwnText as St, appendSlackNativeDataPlainTextFallback as T, escapeSlackMrkdwn as Tt, SLACK_SESSION_LINK_ACTION_ID as U, resolveSlackAllowListMatch as Ut, SLACK_REPLY_SELECT_ACTION_ID as V, normalizeSlackAllowOwnerEntry as Vt, isSlackApprovalActionId as W, resolveSlackUserAllowListForTeam as Wt, encodeSlackQuestionAction as X, isSlackExecApprovalAuthorizedSender as Xt, decodeSlackQuestionAction as Y, getSlackExecApprovalApprovers as Yt, resolveSlackQuestionAction as Z, isSlackExecApprovalClientEnabled as Zt, qualifySlackRoutePeerId as _, renderSlackMessagePresentationFallbackText as _t, SLACK_USER_NAME_KIND as a, buildSlackCompleteBlocksFallbackText as at, resolveSlackConversationBindingRoute as b, SLACK_MESSAGE_TEXT_RECOMMENDED_LIMIT as bt, buildSlackChannelIdCandidates as c, hasSlackDataVisualizationBlock as ct, resolveSlackGroupRequireMention as d, countSlackDataTableBlocksCellCharacters as dt, shouldSuppressLocalSlackExecApprovalPrompt as en, encodeSlackApprovalAction as et, resolveSlackGroupToolPolicy as f, countSlackDataTableCellCharacters as ft, qualifySlackConversationId as g, renderSlackMessagePresentationChartFallbackText as gt, getSlackWebApiErrorData as h, SLACK_SECTION_TEXT_MAX as ht, setSlackSessionStatus as i, resolveSlackThreadTsFromContext as in, buildSlackBlocksFallbackText as it, isSlackNativeResponseUrlRejection as j, resolveSlackApproverDmTargets as jt, isSlackInvalidBlocksError as k, normalizeSlackOriginTarget as kt, buildSlackChannelPolicyScope as l, SLACK_DATA_TABLE_AGGREGATE_CELL_CHARACTERS_MAX as lt, formatSlackError as m, SLACK_PRESENTATION_CAPABILITIES as mt, probe_exports as n, SLACK_PRIVATE_ACTION_DELIVERY_RESULT as nn, parseSlackBlocksInput as nt, createSlackIngressSubject as o, renderSlackBlockFallbackText as ot, setSlackChannelPolicyBindings as p, SLACK_BUTTON_VALUE_MAX as pt, isSlackQuestionActionId as q, isSlackWorkspaceInstallation as qt, renameSlackSession as r, resolveSlackAutoThreadId as rn, validateSlackBlocksArray as rt, slackIngressIdentity as s, buildSlackDataVisualizationBlock as st, probeSlack as t, resolvePluginApprovalSlackApprovers as tn, SLACK_MAX_BLOCKS as tt, hasPotentialSlackNamedPolicy as u, buildSlackDataTableBlock as ut, resolveSlackAgentRoute as v, SLACK_EDIT_TEXT_MAX_BYTES as vt, appendSlackNativeDataFallbackText as w, normalizeSlackOutboundText as wt, resolveSlackConversationBindingRouteAsync as x, SLACK_TEXT_LIMIT as xt, normalizeSlackRouteBindingConfig as y, SLACK_MESSAGE_TEXT_HARD_LIMIT as yt, SLACK_REPLY_BUTTON_ACTION_ID as z, resolveSlackApprovalTeamId as zt };
|