@openclaw/slack 2026.7.2-beta.2 → 2026.7.2-beta.4

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.
Files changed (161) hide show
  1. package/dist/{account-configured-DzC407uv.js → account-configured-BwfXBo_F.js} +1 -1
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/{account-inspect-Di7ditqZ.js → account-inspect-jn1xLaVi.js} +9 -2
  4. package/dist/account-reply-mode-DrUXogvO.js +14 -0
  5. package/dist/{accounts-BOJJiHSr.js → accounts-BSr252c_.js} +13 -25
  6. package/dist/accounts.runtime-CjULMLTa.js +2 -0
  7. package/dist/{action-runtime-Cee2ih0s.js → action-runtime-CFU52n4W.js} +11 -17
  8. package/dist/action-runtime.runtime-9-17zvKH.js +2 -0
  9. package/dist/{actions-BZiuSTIq.js → actions-D51foiwR.js} +5 -5
  10. package/dist/{actions.runtime-DoD0-gxw.js → actions.runtime-BMJwtf5C.js} +1 -1
  11. package/dist/api.js +15 -15
  12. package/dist/{approval-handler.runtime-CUPtIM4k.js → approval-handler.runtime-3b4-wxMl.js} +2 -2
  13. package/dist/{channel-DaIiGUOp.js → channel-Ds8NKvfr.js} +75 -41
  14. package/dist/channel-config-api.js +1 -1
  15. package/dist/channel-plugin-api.js +1 -1
  16. package/dist/{channel-type-Cfm6_6n1.js → channel-type-DXkUajFP.js} +3 -3
  17. package/dist/{channel.setup-C-mLz50O.js → channel.setup-DOSTEDfA.js} +8 -5
  18. package/dist/{config-schema-xPSnK2VK.js → config-schema-B4j4tCOI.js} +236 -30
  19. package/dist/{directory-config-DRkFujwG.js → directory-config-B08pm5ZT.js} +1 -1
  20. package/dist/directory-contract-api.js +1 -1
  21. package/dist/{directory-live-D_yZhjgp.js → directory-live-DpPKzPrn.js} +2 -2
  22. package/dist/doctor-contract-U6oVntbM.js +173 -0
  23. package/dist/doctor-contract-api.js +1 -1
  24. package/dist/{group-policy-85UWiriS.js → group-policy-CdnQzQBI.js} +98 -125
  25. package/dist/{interactive-replies-BilWUdcI.js → interactive-replies-C3ffk6oN.js} +1 -1
  26. package/dist/interactive-replies-api.js +1 -1
  27. package/dist/{message-tool-api-CA9jZrCY.js → message-tool-api-ByVyIVOh.js} +3 -3
  28. package/dist/message-tool-api.js +1 -1
  29. package/dist/{monitor-CalGciTs.js → monitor-CnB_WKvF.js} +3 -3
  30. package/dist/{outbound-adapter-BiLKcnyH.js → outbound-adapter-DuPz2HHt.js} +49 -5
  31. package/dist/{pipeline.runtime-DofAvTtb.js → pipeline.runtime-5rRYZuX5.js} +490 -345
  32. package/dist/{policy-BVs4LiSa.js → policy-C31RYSuT.js} +1 -1
  33. package/dist/probe-CVRuArHA.js +340 -0
  34. package/dist/{provider-CmeoUiIM.js → provider-htuIJGFD.js} +791 -359
  35. package/dist/{relay-source-DMmgtvg4.js → relay-source-Df9EaiTF.js} +6 -11
  36. package/dist/{replies-CJJCp6qz.js → replies-Cgr2Gli4.js} +3 -3
  37. package/dist/{resolve-channels-Rfts6POK.js → resolve-channels-DodP2TFx.js} +1 -1
  38. package/dist/{resolve-users-y6wAxR8E.js → resolve-users-M9eF8891.js} +1 -1
  39. package/dist/{runtime-api-BGDyOt1D.js → runtime-api-D5XYMMsf.js} +3 -2
  40. package/dist/runtime-api.js +11 -11
  41. package/dist/{scopes-CuxNQZ6v.js → scopes-CJRpHkDC.js} +1 -2
  42. package/dist/{secret-contract-CMohXFWS.js → secret-contract-DxmvbxBF.js} +1 -0
  43. package/dist/secret-contract-api.js +1 -1
  44. package/dist/{security-audit-CikQhBUY.js → security-audit-Cz6erklv.js} +0 -10
  45. package/dist/security-contract-api.js +1 -1
  46. package/dist/{send-DYp8ca-l.js → send-BZ1tPv7-.js} +28 -11
  47. package/dist/send.runtime-a2pilFor.js +2 -0
  48. package/dist/setup-core-tPxMuFez.js +600 -0
  49. package/dist/setup-plugin-api.js +1 -1
  50. package/dist/{setup-surface-oSd3NX9J.js → setup-surface-wV-S1dE_.js} +58 -32
  51. package/dist/{shared-D9yz5Otg.js → shared-BfMskaMx.js} +57 -10
  52. package/dist/{slash-dispatch.runtime-CSja9VuD.js → slash-dispatch.runtime-lfeIsXTX.js} +1 -1
  53. package/node_modules/@types/node/README.md +1 -1
  54. package/node_modules/@types/node/fs/promises.d.ts +16 -6
  55. package/node_modules/@types/node/package.json +2 -2
  56. package/node_modules/agent-base/README.md +145 -0
  57. package/node_modules/agent-base/dist/src/index.d.ts +78 -0
  58. package/node_modules/agent-base/dist/src/index.js +203 -0
  59. package/node_modules/agent-base/dist/src/index.js.map +1 -0
  60. package/node_modules/agent-base/dist/src/promisify.d.ts +4 -0
  61. package/node_modules/agent-base/dist/src/promisify.js +18 -0
  62. package/node_modules/agent-base/dist/src/promisify.js.map +1 -0
  63. package/node_modules/agent-base/package.json +64 -0
  64. package/node_modules/agent-base/src/index.ts +345 -0
  65. package/node_modules/agent-base/src/promisify.ts +33 -0
  66. package/node_modules/axios/CHANGELOG.md +197 -1
  67. package/node_modules/axios/README.md +424 -256
  68. package/node_modules/axios/dist/axios.js +589 -204
  69. package/node_modules/axios/dist/axios.min.js +3 -3
  70. package/node_modules/axios/dist/axios.min.js.map +1 -1
  71. package/node_modules/axios/dist/browser/axios.cjs +630 -185
  72. package/node_modules/axios/dist/esm/axios.js +630 -185
  73. package/node_modules/axios/dist/esm/axios.min.js +2 -2
  74. package/node_modules/axios/dist/esm/axios.min.js.map +1 -1
  75. package/node_modules/axios/dist/node/axios.cjs +949 -301
  76. package/node_modules/axios/index.d.cts +28 -5
  77. package/node_modules/axios/index.d.ts +24 -3
  78. package/node_modules/axios/lib/adapters/adapters.js +1 -1
  79. package/node_modules/axios/lib/adapters/fetch.js +223 -49
  80. package/node_modules/axios/lib/adapters/http.js +408 -193
  81. package/node_modules/axios/lib/adapters/xhr.js +3 -1
  82. package/node_modules/axios/lib/core/Axios.js +4 -2
  83. package/node_modules/axios/lib/core/AxiosError.js +13 -1
  84. package/node_modules/axios/lib/core/AxiosHeaders.js +13 -42
  85. package/node_modules/axios/lib/core/buildFullPath.js +29 -1
  86. package/node_modules/axios/lib/core/mergeConfig.js +35 -0
  87. package/node_modules/axios/lib/defaults/transitional.js +2 -0
  88. package/node_modules/axios/lib/env/data.js +1 -1
  89. package/node_modules/axios/lib/helpers/AxiosURLSearchParams.js +1 -3
  90. package/node_modules/axios/lib/helpers/Http2Sessions.js +119 -0
  91. package/node_modules/axios/lib/helpers/buildURL.js +7 -4
  92. package/node_modules/axios/lib/helpers/composeSignals.js +48 -47
  93. package/node_modules/axios/lib/helpers/cookies.js +5 -1
  94. package/node_modules/axios/lib/helpers/estimateDataURLDecodedBytes.js +16 -11
  95. package/node_modules/axios/lib/helpers/formDataToJSON.js +26 -4
  96. package/node_modules/axios/lib/helpers/formDataToStream.js +2 -2
  97. package/node_modules/axios/lib/helpers/fromDataURI.js +20 -5
  98. package/node_modules/axios/lib/helpers/progressEventReducer.js +3 -0
  99. package/node_modules/axios/lib/helpers/resolveConfig.js +26 -13
  100. package/node_modules/axios/lib/helpers/sanitizeHeaderValue.js +60 -0
  101. package/node_modules/axios/lib/helpers/shouldBypassProxy.js +33 -1
  102. package/node_modules/axios/lib/helpers/toFormData.js +48 -12
  103. package/node_modules/axios/lib/helpers/validator.js +1 -1
  104. package/node_modules/axios/lib/utils.js +105 -19
  105. package/node_modules/axios/package.json +31 -13
  106. package/node_modules/https-proxy-agent/README.md +137 -0
  107. package/node_modules/https-proxy-agent/dist/agent.d.ts +30 -0
  108. package/node_modules/https-proxy-agent/dist/agent.js +177 -0
  109. package/node_modules/https-proxy-agent/dist/agent.js.map +1 -0
  110. package/node_modules/https-proxy-agent/dist/index.d.ts +23 -0
  111. package/node_modules/https-proxy-agent/dist/index.js +14 -0
  112. package/node_modules/https-proxy-agent/dist/index.js.map +1 -0
  113. package/node_modules/https-proxy-agent/dist/parse-proxy-response.d.ts +7 -0
  114. package/node_modules/https-proxy-agent/dist/parse-proxy-response.js +66 -0
  115. package/node_modules/https-proxy-agent/dist/parse-proxy-response.js.map +1 -0
  116. package/node_modules/https-proxy-agent/package.json +56 -0
  117. package/node_modules/iconv-lite/encodings/sbcs-data.js +2 -0
  118. package/node_modules/iconv-lite/encodings/utf32.js +10 -3
  119. package/node_modules/iconv-lite/package.json +2 -2
  120. package/node_modules/iconv-lite/types/encodings.d.ts +2 -0
  121. package/node_modules/path-to-regexp/Readme.md +3 -3
  122. package/node_modules/path-to-regexp/dist/index.js +135 -158
  123. package/node_modules/path-to-regexp/dist/index.js.map +1 -1
  124. package/node_modules/path-to-regexp/package.json +2 -2
  125. package/node_modules/qs/CHANGELOG.md +11 -0
  126. package/node_modules/qs/dist/qs.js +17 -17
  127. package/node_modules/qs/lib/parse.js +19 -8
  128. package/node_modules/qs/lib/utils.js +47 -8
  129. package/node_modules/qs/package.json +6 -5
  130. package/node_modules/qs/test/parse.js +230 -0
  131. package/node_modules/qs/test/utils.js +194 -0
  132. package/node_modules/typebox/build/compile/validator.d.mts +1 -4
  133. package/node_modules/typebox/build/guard/guard.d.mts +1 -1
  134. package/node_modules/typebox/build/guard/guard.mjs +5 -2
  135. package/node_modules/typebox/build/guard/string.mjs +16 -4
  136. package/node_modules/typebox/build/schema/engine/_functions.mjs +34 -15
  137. package/node_modules/typebox/build/type/action/readonly_object.d.mts +5 -0
  138. package/node_modules/typebox/build/type/action/readonly_object.mjs +8 -0
  139. package/node_modules/typebox/build/typebox.d.mts +1 -1
  140. package/node_modules/typebox/build/typebox.mjs +1 -1
  141. package/node_modules/typebox/package.json +1 -1
  142. package/node_modules/typebox/readme.md +29 -32
  143. package/node_modules/ws/lib/receiver.js +15 -32
  144. package/node_modules/ws/lib/websocket-server.js +4 -4
  145. package/node_modules/ws/lib/websocket.js +4 -4
  146. package/node_modules/ws/package.json +5 -1
  147. package/npm-shrinkwrap.json +54 -27
  148. package/openclaw.plugin.json +1331 -1424
  149. package/package.json +92 -14
  150. package/dist/accounts.runtime-KvftRRek.js +0 -2
  151. package/dist/action-runtime.runtime-DZfd7IFr.js +0 -2
  152. package/dist/client-WIPAS3Yr.js +0 -160
  153. package/dist/config-api-Df9a1B8C.js +0 -3
  154. package/dist/doctor-contract-DddfkMlF.js +0 -134
  155. package/dist/probe-CN0ssfLN.js +0 -48
  156. package/dist/send.runtime-CJLxAt26.js +0 -2
  157. package/dist/setup-core-CkwVLzFO.js +0 -337
  158. package/node_modules/axios/dist/axios.js.map +0 -1
  159. package/node_modules/axios/dist/browser/axios.cjs.map +0 -1
  160. package/node_modules/axios/dist/esm/axios.js.map +0 -1
  161. package/node_modules/axios/dist/node/axios.cjs.map +0 -1
@@ -1,17 +1,17 @@
1
1
  import { n as registerSlackHttpHandler, r as normalizeSlackWebhookPath } from "./registry-bbjH7IHX.js";
2
- import { a as resolveSlackAccount, d as formatSlackBotTokenIdentityWarning, f as resolveSlackAppToken, i as resolveDefaultSlackAccountId, o as resolveSlackAccountAllowFrom, p as resolveSlackBotToken, s as resolveSlackAccountDmPolicy } from "./accounts-BOJJiHSr.js";
3
- import { B as truncateSlackText, G as isSlackCallbackActionId, I as SLACK_TEXT_LIMIT, J as decodeSlackApprovalAction, W as isSlackApprovalActionId, c as normalizeAllowListLower, d as resolveSlackAllowListMatch, et as isSlackAnyNativeApprovalClientEnabled, ft as isSlackExecApprovalAuthorizedSender, i as formatSlackError, l as normalizeSlackAllowOwnerEntry, o as allowListMatches, q as escapeSlackMrkdwn, s as normalizeAllowList, u as normalizeSlackSlug, ut as isSlackApprovalAuthorizedSender } from "./group-policy-85UWiriS.js";
4
- import { n as isSlackInteractiveRepliesEnabled, t as compileSlackInteractiveReplies } from "./interactive-replies-BilWUdcI.js";
5
- import { d as resolveSlackWebClientOptions, r as createSlackStartupAuthClient, s as getSlackListenerUploadCompletionClient } from "./client-WIPAS3Yr.js";
2
+ import { a as resolveSlackAccount, d as resolveSlackAppToken, f as resolveSlackBotToken, i as resolveDefaultSlackAccountId, o as resolveSlackAccountAllowFrom, s as resolveSlackAccountDmPolicy, u as formatSlackBotTokenIdentityWarning } from "./accounts-BSr252c_.js";
3
+ import { F as SLACK_TEXT_LIMIT, G as isSlackQuestionActionId, K as decodeSlackQuestionAction, U as isSlackApprovalActionId, W as isSlackCallbackActionId, X as decodeSlackApprovalAction, Y as escapeSlackMrkdwn, a as allowListMatches, c as normalizeSlackAllowOwnerEntry, ft as isSlackApprovalAuthorizedSender, l as normalizeSlackSlug, mt as isSlackExecApprovalAuthorizedSender, nt as isSlackAnyNativeApprovalClientEnabled, o as normalizeAllowList, q as resolveSlackQuestionAction, s as normalizeAllowListLower, u as resolveSlackAllowListMatch, z as truncateSlackText } from "./group-policy-CdnQzQBI.js";
4
+ import { n as isSlackInteractiveRepliesEnabled, t as compileSlackInteractiveReplies } from "./interactive-replies-C3ffk6oN.js";
5
+ import { m as resolveSlackWebClientOptions, o as createSlackStartupAuthClient, r as formatSlackError, u as getSlackListenerUploadCompletionClient } from "./probe-CVRuArHA.js";
6
6
  import { n as getSlackRuntime, t as getOptionalSlackRuntime } from "./runtime-JSVZSWAj.js";
7
- import { i as setSlackDefaultSendIdentity } from "./send-DYp8ca-l.js";
8
- import { t as resolveSlackChannelAllowlist } from "./resolve-channels-Rfts6POK.js";
9
- import { t as resolveSlackUserAllowlist } from "./resolve-users-y6wAxR8E.js";
10
- import { n as resolveSlackChannelConfig, r as resolveSlackChannelLabel, t as isSlackChannelAllowedByPolicy } from "./policy-BVs4LiSa.js";
7
+ import { i as setSlackDefaultSendIdentity } from "./send-BZ1tPv7-.js";
8
+ import { t as resolveSlackChannelAllowlist } from "./resolve-channels-DodP2TFx.js";
9
+ import { t as resolveSlackUserAllowlist } from "./resolve-users-M9eF8891.js";
10
+ import { n as resolveSlackChannelConfig, r as resolveSlackChannelLabel, t as isSlackChannelAllowedByPolicy } from "./policy-C31RYSuT.js";
11
11
  import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
12
12
  import { asOptionalRecord, isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, normalizeStringEntriesLower, normalizeUniqueTrimmedStringList } from "openclaw/plugin-sdk/string-coerce-runtime";
13
13
  import { normalizeAccountId, normalizeMainKey, resolveAgentRoute, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
14
- import { createChannelMessageReplyPipeline } from "openclaw/plugin-sdk/channel-outbound";
14
+ import { createChannelIngressMonitor, createChannelMessageReplyPipeline } from "openclaw/plugin-sdk/channel-outbound";
15
15
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
16
16
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
17
17
  import { computeBackoff, createNonExitingRuntime, createSubsystemLogger, danger, getChildLogger, logVerbose, shouldLogVerbose, sleepWithAbort, warn } from "openclaw/plugin-sdk/runtime-env";
@@ -20,12 +20,12 @@ import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plu
20
20
  import { parseExecApprovalCommandText } from "openclaw/plugin-sdk/approval-reply-runtime";
21
21
  import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
22
22
  import { addAllowlistUserEntriesFromConfigEntry, buildAllowlistResolutionSummary, formatAllowlistMatchMeta, mergeAllowlist, patchAllowlistUsersInConfigEntries, summarizeMapping } from "openclaw/plugin-sdk/allow-from";
23
- import { collectErrorGraphCandidates, formatErrorMessage, isApprovalNotFoundError } from "openclaw/plugin-sdk/error-runtime";
23
+ import { collectErrorGraphCandidates, extractErrorCode, formatErrorMessage, isApprovalNotFoundError } from "openclaw/plugin-sdk/error-runtime";
24
24
  import { asDateTimestampMs, parseFiniteNumber, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
25
25
  import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
26
26
  import { buildUntrustedChannelMetadata } from "openclaw/plugin-sdk/security-runtime";
27
- import { createDedupeCache, createPersistentDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
28
- import { buildPluginBindingResolvedText, parsePluginBindingApprovalCustomId, recordInboundSession, resolveConversationLabel as resolveConversationLabel$1, resolvePluginConversationBindingApproval, resolveRuntimeConversationBindingRoute, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
27
+ import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
28
+ import { buildPluginBindingResolvedText, parsePluginBindingApprovalCustomId, resolveConversationLabel as resolveConversationLabel$1, resolvePluginConversationBindingApproval, resolveRuntimeConversationBindingRoute, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
29
29
  import { resolveNativeCommandsEnabled, resolveNativeSkillsEnabled } from "openclaw/plugin-sdk/native-command-config-runtime";
30
30
  import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
31
31
  import { DEFAULT_GROUP_HISTORY_LIMIT } from "openclaw/plugin-sdk/reply-history";
@@ -35,7 +35,7 @@ import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-na
35
35
  import { getSessionEntry, readSessionUpdatedAt, resolveChannelResetConfig, resolveSessionKey, resolveStorePath, resolveStorePath as resolveStorePath$1, updateLastRoute } from "openclaw/plugin-sdk/session-store-runtime";
36
36
  import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
37
37
  import { resolveDefaultGroupPolicy, resolveOpenProviderRuntimeGroupPolicy as resolveOpenProviderRuntimeGroupPolicy$1, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
38
- import { loadModelCatalog, resolveDefaultAgentId, resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
38
+ import { loadPreparedModelCatalog, resolveAgentDir, resolveDefaultAgentId, resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
39
39
  import { resolveChannelConfigWrites } from "openclaw/plugin-sdk/channel-config-writes";
40
40
  import { mutateConfigFile } from "openclaw/plugin-sdk/config-mutation";
41
41
  import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
@@ -45,6 +45,7 @@ import { requestHeartbeat } from "openclaw/plugin-sdk/heartbeat-runtime";
45
45
  import { createInteractiveConversationBindingHelpers, dispatchPluginInteractiveHandler } from "openclaw/plugin-sdk/plugin-runtime";
46
46
  import { createChannelIngressResolver, defineStableChannelIngressIdentity, readChannelIngressStoreAllowFromForDmPolicy } from "openclaw/plugin-sdk/channel-ingress-runtime";
47
47
  import { createChannelInboundDebouncer, shouldDebounceTextInbound } from "openclaw/plugin-sdk/channel-inbound";
48
+ import { createChannelReplayGuard, runClaimableDedupeClaimLoop } from "openclaw/plugin-sdk/persistent-dedupe";
48
49
  import { generateSecureToken } from "openclaw/plugin-sdk/secure-random-runtime";
49
50
  //#region extensions/slack/src/monitor/commands.ts
50
51
  /**
@@ -71,6 +72,151 @@ function buildSlackSlashCommandMatcher(name) {
71
72
  return new RegExp(`^/?${escaped}$`);
72
73
  }
73
74
  //#endregion
75
+ //#region extensions/slack/src/monitor/agent-view-state.ts
76
+ const SLACK_AGENT_VIEW_STATE_NAMESPACE = "agent-view-workspaces";
77
+ const SLACK_AGENT_VIEW_THREAD_STATE_NAMESPACE = "agent-view-threads";
78
+ const SLACK_AGENT_VIEW_STATE_MAX_ENTRIES = 4096;
79
+ const SLACK_AGENT_VIEW_THREAD_STATE_MAX_ENTRIES = 4096;
80
+ const SLACK_MANAGED_THREAD_CACHE_MAX_ENTRIES = 4096;
81
+ function createSlackAgentViewState(params) {
82
+ let enabled = false;
83
+ let loaded = false;
84
+ let persisted = false;
85
+ let workspaceStore;
86
+ let threadStore;
87
+ let warned = false;
88
+ const managedThreads = /* @__PURE__ */ new Map();
89
+ const warnOnce = (action, error) => {
90
+ if (warned) return;
91
+ warned = true;
92
+ params.warn(action, error);
93
+ };
94
+ const openWorkspaceStore = () => {
95
+ if (workspaceStore) return workspaceStore;
96
+ const runtime = getOptionalSlackRuntime();
97
+ if (!runtime) return;
98
+ try {
99
+ workspaceStore = runtime.state.openKeyedStore({
100
+ namespace: SLACK_AGENT_VIEW_STATE_NAMESPACE,
101
+ maxEntries: SLACK_AGENT_VIEW_STATE_MAX_ENTRIES
102
+ });
103
+ return workspaceStore;
104
+ } catch (error) {
105
+ warnOnce("open", error);
106
+ return;
107
+ }
108
+ };
109
+ const openThreadStore = () => {
110
+ if (threadStore) return threadStore;
111
+ const runtime = getOptionalSlackRuntime();
112
+ if (!runtime) return;
113
+ try {
114
+ threadStore = runtime.state.openKeyedStore({
115
+ namespace: SLACK_AGENT_VIEW_THREAD_STATE_NAMESPACE,
116
+ maxEntries: SLACK_AGENT_VIEW_THREAD_STATE_MAX_ENTRIES
117
+ });
118
+ return threadStore;
119
+ } catch (error) {
120
+ warnOnce("open", error);
121
+ return;
122
+ }
123
+ };
124
+ const workspaceStateKey = params.apiAppId ? JSON.stringify([
125
+ "workspace",
126
+ params.accountId,
127
+ params.teamId,
128
+ params.apiAppId
129
+ ]) : void 0;
130
+ const record = async () => {
131
+ enabled = true;
132
+ loaded = true;
133
+ if (persisted || !workspaceStateKey) return;
134
+ const openedStore = openWorkspaceStore();
135
+ if (!openedStore) return;
136
+ try {
137
+ await openedStore.register(workspaceStateKey, {
138
+ experience: "agent",
139
+ observedAt: Date.now()
140
+ });
141
+ persisted = true;
142
+ } catch (error) {
143
+ warnOnce("persist", error);
144
+ }
145
+ };
146
+ const isEnabled = async () => {
147
+ if (enabled) return true;
148
+ if (loaded) return false;
149
+ if (!workspaceStateKey) {
150
+ loaded = true;
151
+ return false;
152
+ }
153
+ const openedStore = openWorkspaceStore();
154
+ if (!openedStore) return false;
155
+ try {
156
+ const stored = await openedStore.lookup(workspaceStateKey);
157
+ loaded = true;
158
+ enabled = stored?.experience === "agent";
159
+ persisted = enabled;
160
+ return enabled;
161
+ } catch (error) {
162
+ warnOnce("load", error);
163
+ return false;
164
+ }
165
+ };
166
+ const managedThreadKey = (channelId, threadTs) => JSON.stringify([channelId, threadTs]);
167
+ const managedThreadStateKey = (channelId, threadTs) => params.apiAppId ? JSON.stringify([
168
+ "thread",
169
+ params.accountId,
170
+ params.teamId,
171
+ params.apiAppId,
172
+ channelId,
173
+ threadTs
174
+ ]) : void 0;
175
+ const rememberManagedThread = (key) => {
176
+ managedThreads.delete(key);
177
+ managedThreads.set(key, true);
178
+ if (managedThreads.size <= SLACK_MANAGED_THREAD_CACHE_MAX_ENTRIES) return;
179
+ const oldestKey = managedThreads.keys().next().value;
180
+ if (oldestKey !== void 0) managedThreads.delete(oldestKey);
181
+ };
182
+ const recordManagedThread = async (channelId, threadTs) => {
183
+ const key = managedThreadKey(channelId, threadTs);
184
+ rememberManagedThread(key);
185
+ const stateKey = managedThreadStateKey(channelId, threadTs);
186
+ const openedStore = stateKey ? openThreadStore() : void 0;
187
+ if (!openedStore || !stateKey) return;
188
+ try {
189
+ await openedStore.register(stateKey, {
190
+ experience: "managed-thread",
191
+ observedAt: Date.now()
192
+ });
193
+ } catch (error) {
194
+ warnOnce("persist", error);
195
+ }
196
+ };
197
+ const isManagedThread = async (channelId, threadTs) => {
198
+ const key = managedThreadKey(channelId, threadTs);
199
+ if (managedThreads.has(key)) return true;
200
+ const stateKey = managedThreadStateKey(channelId, threadTs);
201
+ const openedStore = stateKey ? openThreadStore() : void 0;
202
+ if (!openedStore || !stateKey) return false;
203
+ try {
204
+ const found = (await openedStore.lookup(stateKey))?.experience === "managed-thread";
205
+ if (found) rememberManagedThread(key);
206
+ return found;
207
+ } catch (error) {
208
+ warnOnce("load", error);
209
+ return false;
210
+ }
211
+ };
212
+ return {
213
+ isEnabled,
214
+ isManagedThread,
215
+ record,
216
+ recordManagedThread
217
+ };
218
+ }
219
+ //#endregion
74
220
  //#region extensions/slack/src/monitor/channel-type.ts
75
221
  function inferSlackChannelType(channelId) {
76
222
  const trimmed = channelId?.trim();
@@ -110,6 +256,42 @@ function writeLruMapEntry(cache, cacheKey, entry, maxEntries) {
110
256
  pruneMapToMaxSize(cache, maxEntries);
111
257
  }
112
258
  //#endregion
259
+ //#region extensions/slack/src/monitor/suggested-prompts.ts
260
+ const DEFAULT_SLACK_SUGGESTED_PROMPTS = [
261
+ {
262
+ title: "What can you do?",
263
+ message: "What can you help me with?"
264
+ },
265
+ {
266
+ title: "Summarize this channel",
267
+ message: "Summarize the recent activity in this channel."
268
+ },
269
+ {
270
+ title: "Draft a reply",
271
+ message: "Help me draft a reply."
272
+ }
273
+ ];
274
+ async function updateSlackSuggestedPrompts(params) {
275
+ const prompts = params.prompts.map((prompt) => ({
276
+ title: prompt.title.trim(),
277
+ message: prompt.message.trim()
278
+ })).filter((prompt) => prompt.title && prompt.message).slice(0, 4);
279
+ if (prompts.length === 0) return false;
280
+ try {
281
+ await params.client.assistant.threads.setSuggestedPrompts({
282
+ token: params.botToken,
283
+ channel_id: params.channelId,
284
+ ...params.threadTs ? { thread_ts: params.threadTs } : {},
285
+ ...params.title?.trim() ? { title: params.title.trim() } : {},
286
+ prompts
287
+ });
288
+ return true;
289
+ } catch (error) {
290
+ logVerbose(`slack suggested prompts update failed for channel ${params.channelId}: ${formatSlackError(error)}`);
291
+ return false;
292
+ }
293
+ }
294
+ //#endregion
113
295
  //#region extensions/slack/src/monitor/context.ts
114
296
  const SLACK_ASSISTANT_THREAD_CONTEXT_METADATA_EVENT = "assistant_thread_context";
115
297
  const SLACK_CHANNEL_CACHE_MAX_ENTRIES = 1024;
@@ -150,16 +332,18 @@ function createSlackMonitorContext(params) {
150
332
  const logger = getChildLogger({ module: "slack-auto-reply" });
151
333
  const channelCache = /* @__PURE__ */ new Map();
152
334
  const userCache = /* @__PURE__ */ new Map();
153
- const seenMessages = createDedupeCache({
154
- ttlMs: 6e4,
155
- maxSize: 500
156
- });
157
335
  const channelDenialWarnings = createDedupeCache({
158
336
  ttlMs: SLACK_CHANNEL_DENIAL_WARNING_TTL_MS,
159
337
  maxSize: SLACK_CHANNEL_DENIAL_WARNING_MAX_ENTRIES
160
338
  });
161
339
  const assistantThreadContexts = /* @__PURE__ */ new Map();
162
340
  let lastAssistantContextCleanupAt = Date.now();
341
+ const agentViewState = createSlackAgentViewState({
342
+ accountId: params.accountId,
343
+ teamId: params.teamId,
344
+ apiAppId: params.apiAppId,
345
+ warn: (action, error) => logger.warn({ error: formatSlackError(error) }, `Slack Agent View state failed to ${action}`)
346
+ });
163
347
  const allowFrom = normalizeAllowList(params.allowFrom);
164
348
  const groupDmChannels = normalizeAllowList(params.groupDmChannels);
165
349
  const groupDmChannelsLower = new Set(normalizeAllowListLower(groupDmChannels).map((entry) => entry.replace(/^channel:/, "")));
@@ -187,14 +371,6 @@ function createSlackMonitorContext(params) {
187
371
  const id = normalizeOptionalString(channelId);
188
372
  return id ? readLruMapEntry(channelCache, scopedKey(id, eventScope))?.info.type : void 0;
189
373
  };
190
- const markMessageSeen = (channelId, ts, eventScope) => {
191
- if (!channelId || !ts) return false;
192
- return seenMessages.check(scopedKey(`${channelId}:${ts}`, eventScope));
193
- };
194
- const releaseSeenMessage = (channelId, ts, eventScope) => {
195
- if (!channelId || !ts) return;
196
- seenMessages.delete(scopedKey(`${channelId}:${ts}`, eventScope));
197
- };
198
374
  const assistantContextKey = (channelId, threadTs, eventScope) => scopedKey(`${channelId}:${threadTs}`, eventScope);
199
375
  const cleanupAssistantThreadContexts = () => {
200
376
  const now = Date.now();
@@ -342,26 +518,11 @@ function createSlackMonitorContext(params) {
342
518
  logVerbose(`slack status update failed for channel ${p.channelId}: ${formatSlackError(err)}`);
343
519
  }
344
520
  };
345
- const setSlackAssistantSuggestedPrompts = async (p) => {
346
- const prompts = p.prompts.map((prompt) => ({
347
- title: prompt.title.trim(),
348
- message: prompt.message.trim()
349
- })).filter((prompt) => prompt.title && prompt.message).slice(0, 4);
350
- if (prompts.length === 0) return false;
351
- try {
352
- await params.app.client.assistant.threads.setSuggestedPrompts({
353
- token: params.botToken,
354
- channel_id: p.channelId,
355
- thread_ts: p.threadTs,
356
- ...p.title?.trim() ? { title: p.title.trim() } : {},
357
- prompts
358
- });
359
- return true;
360
- } catch (err) {
361
- logVerbose(`slack suggested prompts update failed for channel ${p.channelId}: ${formatSlackError(err)}`);
362
- return false;
363
- }
364
- };
521
+ const setSlackSuggestedPrompts = (input) => updateSlackSuggestedPrompts({
522
+ ...input,
523
+ botToken: params.botToken,
524
+ client: params.app.client
525
+ });
365
526
  const isChannelAllowed = (p) => {
366
527
  const channelType = normalizeSlackChannelType(p.channelType, p.channelId);
367
528
  const isDirectMessage = channelType === "im";
@@ -472,16 +633,12 @@ function createSlackMonitorContext(params) {
472
633
  replyToMode: params.replyToMode,
473
634
  threadHistoryScope: params.threadHistoryScope,
474
635
  threadInheritParent: params.threadInheritParent,
475
- threadRequireExplicitMention: params.threadRequireExplicitMention,
476
636
  slashCommand: params.slashCommand,
477
637
  textLimit: params.textLimit,
478
638
  ackReactionScope: params.ackReactionScope,
479
639
  typingReaction: params.typingReaction,
480
640
  mediaMaxBytes: params.mediaMaxBytes,
481
- removeAckAfterReply: params.removeAckAfterReply,
482
641
  logger,
483
- markMessageSeen,
484
- releaseSeenMessage,
485
642
  shouldDropMismatchedSlackEvent,
486
643
  resolveSlackSystemEventSessionKey,
487
644
  isChannelAllowed,
@@ -492,7 +649,11 @@ function createSlackMonitorContext(params) {
492
649
  setSlackThreadStatus,
493
650
  getSlackAssistantThreadContext,
494
651
  saveSlackAssistantThreadContext,
495
- setSlackAssistantSuggestedPrompts
652
+ setSlackSuggestedPrompts,
653
+ recordSlackAgentView: agentViewState.record,
654
+ isSlackAgentView: agentViewState.isEnabled,
655
+ recordSlackManagedViewThread: agentViewState.recordManagedThread,
656
+ isSlackManagedViewThread: agentViewState.isManagedThread
496
657
  };
497
658
  }
498
659
  //#endregion
@@ -535,8 +696,8 @@ function assertEnterpriseSlackPolicyConfig(params) {
535
696
  predicate: isStableSlackAllowlistUserEntry
536
697
  });
537
698
  assertStableEntries({
538
- values: config.dm?.allowFrom,
539
- path: `channels.slack.accounts.${accountId}.dm.allowFrom`,
699
+ values: config.allowFrom,
700
+ path: `channels.slack.accounts.${accountId}.allowFrom`,
540
701
  predicate: isStableSlackAllowlistUserEntry
541
702
  });
542
703
  assertStableEntries({
@@ -625,21 +786,21 @@ function resolveSlackIdentityHealth(params) {
625
786
  };
626
787
  }
627
788
  //#endregion
789
+ //#region extensions/slack/src/monitor/events/agent.ts
790
+ function registerSlackAgentEvents(params) {
791
+ const { ctx, trackEvent } = params;
792
+ ctx.app.event("app_context_changed", async ({ body }) => {
793
+ try {
794
+ if (ctx.shouldDropMismatchedSlackEvent(body)) return;
795
+ trackEvent?.();
796
+ await ctx.recordSlackAgentView();
797
+ } catch (error) {
798
+ ctx.runtime.error?.(danger(`slack app_context_changed handler failed: ${formatErrorMessage(error)}`));
799
+ }
800
+ });
801
+ }
802
+ //#endregion
628
803
  //#region extensions/slack/src/monitor/events/assistant.ts
629
- const DEFAULT_ASSISTANT_PROMPTS = [
630
- {
631
- title: "What can you do?",
632
- message: "What can you help me with?"
633
- },
634
- {
635
- title: "Summarize this channel",
636
- message: "Summarize the recent activity in this channel."
637
- },
638
- {
639
- title: "Draft a reply",
640
- message: "Help me draft a reply."
641
- }
642
- ];
643
804
  function normalizeAssistantThread(event, getPrevious) {
644
805
  const thread = event.assistant_thread;
645
806
  if (!thread) return null;
@@ -701,11 +862,11 @@ function registerSlackAssistantEvents(params) {
701
862
  return;
702
863
  }
703
864
  ctx.saveSlackAssistantThreadContext(assistantThread);
704
- await ctx.setSlackAssistantSuggestedPrompts({
865
+ await ctx.setSlackSuggestedPrompts({
705
866
  channelId: assistantThread.assistantChannelId,
706
867
  threadTs: assistantThread.threadTs,
707
868
  title: "Try asking",
708
- prompts: DEFAULT_ASSISTANT_PROMPTS
869
+ prompts: DEFAULT_SLACK_SUGGESTED_PROMPTS
709
870
  });
710
871
  } catch (err) {
711
872
  ctx.runtime.error?.(danger(`slack assistant_thread_started handler failed: ${formatErrorMessage(err)}`));
@@ -945,7 +1106,16 @@ function registerSlackHomeEvents(params) {
945
1106
  if (ctx.shouldDropMismatchedSlackEvent(body)) return;
946
1107
  trackEvent?.();
947
1108
  const payload = event;
948
- if (!payload.user || payload.tab === "messages") return;
1109
+ if (!payload.user) return;
1110
+ if (payload.tab === "messages") {
1111
+ if (!payload.channel) return;
1112
+ if (await ctx.setSlackSuggestedPrompts({
1113
+ channelId: payload.channel,
1114
+ title: "Try asking",
1115
+ prompts: DEFAULT_SLACK_SUGGESTED_PROMPTS
1116
+ })) await ctx.recordSlackAgentView();
1117
+ return;
1118
+ }
949
1119
  await ctx.app.client.views.publish({
950
1120
  token: ctx.botToken,
951
1121
  user_id: payload.user,
@@ -1927,6 +2097,26 @@ async function handleSlackBlockAction(params) {
1927
2097
  });
1928
2098
  return;
1929
2099
  }
2100
+ if (isSlackQuestionActionId(parsed.actionId)) {
2101
+ const question = decodeSlackQuestionAction(parsed.actionSummary.value);
2102
+ if (!question) {
2103
+ await respondEphemeral(respond, "This question action is invalid or expired.");
2104
+ return;
2105
+ }
2106
+ if (!(await authorizeSlackBlockAction({
2107
+ ctx: params.ctx,
2108
+ parsed,
2109
+ respond
2110
+ })).allowed) return;
2111
+ await resolveSlackQuestionAction({
2112
+ action: question,
2113
+ cfg: params.ctx.cfg,
2114
+ accountId: params.ctx.accountId,
2115
+ userId: parsed.userId,
2116
+ respond: async (text) => await respondEphemeral(respond, text)
2117
+ });
2118
+ return;
2119
+ }
1930
2120
  const pluginInteractionData = buildSlackPluginInteractionData({
1931
2121
  actionId: parsed.actionId,
1932
2122
  summary: parsed.actionSummary
@@ -2649,6 +2839,334 @@ function resolveSlackEventScope(params) {
2649
2839
  };
2650
2840
  }
2651
2841
  //#endregion
2842
+ //#region extensions/slack/src/monitor/reconnect-policy.ts
2843
+ const SLACK_AUTH_ERROR_RE = /account_inactive|invalid_auth|token_revoked|token_expired|not_authed|org_login_required|team_access_not_granted|user_removed_from_team|team_disabled|missing_scope|cannot_find_service|invalid_token/i;
2844
+ const NO_ERROR_DETAIL = "no error detail";
2845
+ const SLACK_SOCKET_RECONNECT_POLICY = {
2846
+ initialMs: 2e3,
2847
+ maxMs: 3e4,
2848
+ factor: 1.8,
2849
+ jitter: .25
2850
+ };
2851
+ const SLACK_SOCKET_SHARED_CONNECTION_DOCS_URL = "https://docs.slack.dev/apis/events-api/using-socket-mode#using-multiple-connections";
2852
+ const SLACK_SOCKET_HELLO_MARKER = Buffer.from("\"hello\"");
2853
+ function getSocketEmitter(app) {
2854
+ const receiver = app.receiver;
2855
+ const client = receiver && typeof receiver === "object" ? receiver.client : void 0;
2856
+ if (!client || typeof client !== "object") return null;
2857
+ const on = client.on;
2858
+ const off = client.off;
2859
+ if (typeof on !== "function" || typeof off !== "function") return null;
2860
+ return {
2861
+ on: (event, listener) => on.call(client, event, listener),
2862
+ off: (event, listener) => off.call(client, event, listener)
2863
+ };
2864
+ }
2865
+ function isBufferArray(value) {
2866
+ return Array.isArray(value) && value.every((entry) => Buffer.isBuffer(entry));
2867
+ }
2868
+ function resolveSlackSocketModeConnectionCount(message) {
2869
+ const buffer = Buffer.isBuffer(message) ? message : message instanceof ArrayBuffer ? Buffer.from(message) : isBufferArray(message) ? Buffer.concat(message) : void 0;
2870
+ if (!buffer?.includes(SLACK_SOCKET_HELLO_MARKER)) return;
2871
+ let payload;
2872
+ try {
2873
+ payload = JSON.parse(buffer.toString("utf8"));
2874
+ } catch {
2875
+ return;
2876
+ }
2877
+ const count = isRecord(payload) && payload.type === "hello" ? payload.num_connections : void 0;
2878
+ return typeof count === "number" && Number.isSafeInteger(count) && count >= 0 ? count : void 0;
2879
+ }
2880
+ function formatSlackSocketModeSharedConnectionWarning(activeConnections) {
2881
+ return [
2882
+ `slack socket mode reports ${activeConnections} active connections for this Slack app`,
2883
+ "Slack may deliver each event to any one connection",
2884
+ "ensure every OpenClaw gateway sharing this app has equivalent routing and authorization, or use a separate Slack app per gateway, one relay ingress, or HTTP Request URLs behind a load balancer",
2885
+ `See ${SLACK_SOCKET_SHARED_CONNECTION_DOCS_URL}`
2886
+ ].join("; ");
2887
+ }
2888
+ function registerSlackSocketModeConnectionDiagnostics(params) {
2889
+ const emitter = getSocketEmitter(params.app);
2890
+ if (!emitter) return () => {};
2891
+ let hasWarned = false;
2892
+ const listener = (message, isBinary) => {
2893
+ if (isBinary === true || hasWarned) return;
2894
+ const activeConnections = resolveSlackSocketModeConnectionCount(message);
2895
+ if (activeConnections === void 0 || activeConnections <= 1) return;
2896
+ hasWarned = true;
2897
+ params.onSharedConnection(activeConnections);
2898
+ };
2899
+ emitter.on("ws_message", listener);
2900
+ return () => {
2901
+ emitter.off("ws_message", listener);
2902
+ };
2903
+ }
2904
+ function waitForSlackSocketDisconnect(app, abortSignal) {
2905
+ return new Promise((resolve) => {
2906
+ const emitter = getSocketEmitter(app);
2907
+ if (!emitter) {
2908
+ abortSignal?.addEventListener("abort", () => resolve({ event: "disconnect" }), { once: true });
2909
+ return;
2910
+ }
2911
+ const disconnectListener = () => resolveOnce({ event: "disconnect" });
2912
+ const startFailListener = (error) => resolveOnce({
2913
+ event: "unable_to_socket_mode_start",
2914
+ error
2915
+ });
2916
+ const errorListener = (error) => resolveOnce({
2917
+ event: "error",
2918
+ error
2919
+ });
2920
+ const abortListener = () => resolveOnce({ event: "disconnect" });
2921
+ const cleanup = () => {
2922
+ emitter.off("disconnected", disconnectListener);
2923
+ emitter.off("unable_to_socket_mode_start", startFailListener);
2924
+ emitter.off("error", errorListener);
2925
+ abortSignal?.removeEventListener("abort", abortListener);
2926
+ };
2927
+ const resolveOnce = (value) => {
2928
+ cleanup();
2929
+ resolve(value);
2930
+ };
2931
+ emitter.on("disconnected", disconnectListener);
2932
+ emitter.on("unable_to_socket_mode_start", startFailListener);
2933
+ emitter.on("error", errorListener);
2934
+ abortSignal?.addEventListener("abort", abortListener, { once: true });
2935
+ });
2936
+ }
2937
+ /**
2938
+ * Detect permanent Slack account and credential failures.
2939
+ * Transient request and HTTP failures stay in OpenClaw's reconnect loop.
2940
+ */
2941
+ function isNonRecoverableSlackAuthError(error) {
2942
+ return SLACK_AUTH_ERROR_RE.test(formatUnknownError(error, ""));
2943
+ }
2944
+ function formatUnknownError(error, fallback = NO_ERROR_DETAIL) {
2945
+ return formatSlackError(error, fallback);
2946
+ }
2947
+ //#endregion
2948
+ //#region extensions/slack/src/monitor/ingress.ts
2949
+ const SLACK_INGRESS_PAYLOAD_VERSION = 1;
2950
+ const SLACK_INGRESS_POLL_INTERVAL_MS = 1e3;
2951
+ const SLACK_INGRESS_PRUNE_INTERVAL_MS = 3600 * 1e3;
2952
+ const SLACK_INGRESS_COMPLETED_TTL_MS = 720 * 60 * 60 * 1e3;
2953
+ const SLACK_INGRESS_COMPLETED_MAX_ENTRIES = 2e4;
2954
+ const SLACK_INGRESS_FAILED_TTL_MS = 720 * 60 * 60 * 1e3;
2955
+ const SLACK_INGRESS_FAILED_MAX_ENTRIES = 2e4;
2956
+ const SLACK_BOLT_AUTHORIZATION_ERROR = "slack_bolt_authorization_error";
2957
+ const SLACK_INGRESS_LIFECYCLE_CONTEXT_KEY = "openclawIngressLifecycle";
2958
+ /** Logical message identity: mirrors the retired guard key (team:channel:ts). */
2959
+ function resolveSlackRelayIngressEventId(event) {
2960
+ const ts = event.message.ts?.trim();
2961
+ if (!event.message.channel?.trim() || !ts) return `relay:${event.deliveryId}`;
2962
+ const team = event.message.team?.trim();
2963
+ return `message:${team ? `${team}:` : ""}${event.message.channel.trim()}:${ts}`;
2964
+ }
2965
+ var SlackIngressPayloadError = class extends Error {
2966
+ constructor(message) {
2967
+ super(message);
2968
+ this.name = "SlackIngressPayloadError";
2969
+ }
2970
+ };
2971
+ function resolveSlackEventId(body) {
2972
+ const eventId = asOptionalRecord(body)?.event_id;
2973
+ return typeof eventId === "string" && eventId.trim() ? eventId.trim() : null;
2974
+ }
2975
+ function resolveSlackIngressLane(body, eventId) {
2976
+ const envelope = asOptionalRecord(body);
2977
+ const event = asOptionalRecord(envelope?.event);
2978
+ const item = asOptionalRecord(event?.item);
2979
+ const assistantThread = asOptionalRecord(event?.assistant_thread);
2980
+ const team = asOptionalRecord(envelope?.team);
2981
+ const teamId = [
2982
+ envelope?.team_id,
2983
+ team?.id,
2984
+ event?.team
2985
+ ].find((value) => typeof value === "string" && value.trim())?.toString().trim() || "workspace";
2986
+ const channelId = [
2987
+ event?.channel,
2988
+ event?.channel_id,
2989
+ item?.channel,
2990
+ assistantThread?.channel_id
2991
+ ].find((value) => typeof value === "string" && value.trim())?.toString().trim();
2992
+ if (channelId) return `team:${teamId}:conversation:${channelId}`;
2993
+ const userId = [event?.user, event?.user_id].find((value) => typeof value === "string" && value.trim())?.toString().trim();
2994
+ return userId ? `team:${teamId}:user:${userId}` : `event:${eventId}`;
2995
+ }
2996
+ function isSlackEventCallback(body) {
2997
+ return asOptionalRecord(body)?.type === "event_callback";
2998
+ }
2999
+ function decodeSlackIngressPayload(payload, eventId) {
3000
+ if (payload.kind === "relay") {
3001
+ if (!asOptionalRecord(payload.message)) throw new SlackIngressPayloadError(`Slack relay ingress payload ${eventId} was invalid.`);
3002
+ return {
3003
+ version: payload.version,
3004
+ body: payload
3005
+ };
3006
+ }
3007
+ if (!asOptionalRecord(payload.body) || resolveSlackEventId(payload.body) !== eventId) throw new SlackIngressPayloadError(`Slack ingress payload ${eventId} was invalid.`);
3008
+ return {
3009
+ version: payload.version,
3010
+ body: payload
3011
+ };
3012
+ }
3013
+ function inspectSlackIngress(raw) {
3014
+ if (raw.kind === "relay") {
3015
+ const eventId = resolveSlackRelayIngressEventId({
3016
+ deliveryId: raw.deliveryId,
3017
+ message: raw.message
3018
+ });
3019
+ return {
3020
+ eventId,
3021
+ laneKey: resolveSlackIngressLane({ event: raw.message }, eventId)
3022
+ };
3023
+ }
3024
+ const eventId = resolveSlackEventId(raw.body);
3025
+ if (!eventId) throw new SlackIngressPayloadError("Slack Events API envelope missing event_id.");
3026
+ return {
3027
+ eventId,
3028
+ laneKey: resolveSlackIngressLane(raw.body, eventId)
3029
+ };
3030
+ }
3031
+ function resolveSlackIngressNonRetryableFailure(error) {
3032
+ for (const candidate of collectErrorGraphCandidates(error, (current) => [
3033
+ current.cause,
3034
+ current.error,
3035
+ current.original
3036
+ ])) {
3037
+ if (candidate instanceof SlackIngressPayloadError || candidate instanceof SyntaxError) return {
3038
+ reason: "invalid-event",
3039
+ message: formatErrorMessage(candidate)
3040
+ };
3041
+ if (extractErrorCode(candidate) === SLACK_BOLT_AUTHORIZATION_ERROR || isNonRecoverableSlackAuthError(candidate)) return {
3042
+ reason: "slack-auth",
3043
+ message: formatErrorMessage(candidate)
3044
+ };
3045
+ }
3046
+ return null;
3047
+ }
3048
+ function resolveSlackIngressTurnLifecycle(context) {
3049
+ const candidate = asOptionalRecord(context)?.[SLACK_INGRESS_LIFECYCLE_CONTEXT_KEY];
3050
+ if (!candidate || typeof candidate !== "object") return null;
3051
+ const lifecycle = candidate;
3052
+ return typeof lifecycle.onAdopted === "function" && lifecycle.abortSignal instanceof AbortSignal ? lifecycle : null;
3053
+ }
3054
+ function createSlackDurableIngress(options) {
3055
+ let app;
3056
+ let relayDispatch;
3057
+ const monitor = createChannelIngressMonitor({
3058
+ queue: options.queue ?? (() => getSlackRuntime().state.openChannelIngressQueue({ accountId: options.accountId })),
3059
+ inspect: inspectSlackIngress,
3060
+ payload: {
3061
+ version: SLACK_INGRESS_PAYLOAD_VERSION,
3062
+ serialize: (raw, { receivedAt }) => raw.kind === "relay" ? {
3063
+ kind: "relay",
3064
+ receivedAt,
3065
+ message: raw.message
3066
+ } : {
3067
+ kind: "events-api",
3068
+ receivedAt,
3069
+ body: raw.body,
3070
+ ...raw.retryNum === void 0 ? {} : { retryNum: raw.retryNum },
3071
+ ...raw.retryReason === void 0 ? {} : { retryReason: raw.retryReason }
3072
+ },
3073
+ deserialize: (body, { claim }) => body.kind === "relay" ? {
3074
+ kind: "relay",
3075
+ deliveryId: claim.id.startsWith("relay:") ? claim.id.slice(6) : claim.id,
3076
+ message: body.message
3077
+ } : {
3078
+ kind: "events-api",
3079
+ body: body.body,
3080
+ ...body.retryNum === void 0 ? {} : { retryNum: body.retryNum },
3081
+ ...body.retryReason === void 0 ? {} : { retryReason: body.retryReason }
3082
+ },
3083
+ encode: ({ body }) => ({
3084
+ version: SLACK_INGRESS_PAYLOAD_VERSION,
3085
+ ...body
3086
+ }),
3087
+ decode: (payload, { claim }) => decodeSlackIngressPayload(payload, claim.id),
3088
+ createClaimError: (_kind, claim) => new SlackIngressPayloadError(`Slack ingress payload ${claim.id} was invalid.`)
3089
+ },
3090
+ onDurableAdmission: async (raw) => {
3091
+ if (raw.kind === "events-api") await raw.afterDurableAdmission?.();
3092
+ },
3093
+ deliver: async (raw, lifecycle) => {
3094
+ if (raw.kind === "relay") {
3095
+ if (!relayDispatch) throw new Error("Slack relay ingress dispatcher is not attached.");
3096
+ await relayDispatch(raw.message, lifecycle);
3097
+ return;
3098
+ }
3099
+ if (!app) throw new Error("Slack ingress receiver is not attached to a Bolt app.");
3100
+ await app.processEvent({
3101
+ body: raw.body,
3102
+ ack: async () => {},
3103
+ ...raw.retryNum === void 0 ? {} : { retryNum: raw.retryNum },
3104
+ ...raw.retryReason === void 0 ? {} : { retryReason: raw.retryReason },
3105
+ customProperties: { [SLACK_INGRESS_LIFECYCLE_CONTEXT_KEY]: lifecycle }
3106
+ });
3107
+ },
3108
+ pollIntervalMs: options.pollIntervalMs ?? SLACK_INGRESS_POLL_INTERVAL_MS,
3109
+ retention: {
3110
+ pruneIntervalMs: SLACK_INGRESS_PRUNE_INTERVAL_MS,
3111
+ completedTtlMs: SLACK_INGRESS_COMPLETED_TTL_MS,
3112
+ completedMaxEntries: SLACK_INGRESS_COMPLETED_MAX_ENTRIES,
3113
+ failedTtlMs: SLACK_INGRESS_FAILED_TTL_MS,
3114
+ failedMaxEntries: SLACK_INGRESS_FAILED_MAX_ENTRIES
3115
+ },
3116
+ appendRetryDelaysMs: [0],
3117
+ drain: {
3118
+ resolveNonRetryableFailure: resolveSlackIngressNonRetryableFailure,
3119
+ deriveLaneKey: (record) => record.payload.kind === "relay" ? resolveSlackIngressLane({ event: record.payload.message }, record.id) : resolveSlackIngressLane(record.payload.body, record.id),
3120
+ ...options.adoptionStallTimeoutMs === void 0 ? {} : { adoptionStallTimeoutMs: options.adoptionStallTimeoutMs },
3121
+ ...options.onLog ? { onLog: options.onLog } : {}
3122
+ },
3123
+ ...options.abortSignal ? { abortSignal: options.abortSignal } : {},
3124
+ onError: (error) => options.onLog?.(`slack ingress drain failed: ${formatErrorMessage(error)}`)
3125
+ });
3126
+ const acceptReceiverEvent = async (event) => {
3127
+ if (!isSlackEventCallback(event.body)) {
3128
+ if (!app) throw new Error("Slack ingress receiver is not attached to a Bolt app.");
3129
+ await app.processEvent(event);
3130
+ return;
3131
+ }
3132
+ await monitor.admit({
3133
+ kind: "events-api",
3134
+ body: event.body,
3135
+ ...event.retryNum === void 0 ? {} : { retryNum: event.retryNum },
3136
+ ...event.retryReason === void 0 ? {} : { retryReason: event.retryReason },
3137
+ afterDurableAdmission: () => event.ack()
3138
+ });
3139
+ };
3140
+ const acceptRelayEvent = async (event) => {
3141
+ await monitor.admit({
3142
+ kind: "relay",
3143
+ deliveryId: event.deliveryId,
3144
+ message: event.message
3145
+ });
3146
+ };
3147
+ return {
3148
+ wrapReceiver: (receiver) => {
3149
+ const client = Reflect.get(receiver, "client");
3150
+ return {
3151
+ init: (nextApp) => {
3152
+ app = nextApp;
3153
+ receiver.init({ processEvent: acceptReceiverEvent });
3154
+ },
3155
+ start: (...args) => receiver.start(...args),
3156
+ stop: (...args) => receiver.stop(...args),
3157
+ ...client === void 0 ? {} : { client }
3158
+ };
3159
+ },
3160
+ acceptRelayEvent,
3161
+ attachRelayDispatch: (dispatch) => {
3162
+ relayDispatch = dispatch;
3163
+ },
3164
+ start: monitor.start,
3165
+ stop: monitor.stop,
3166
+ waitForIdle: monitor.waitForIdle
3167
+ };
3168
+ }
3169
+ //#endregion
2652
3170
  //#region extensions/slack/src/monitor/events/message-subtype-handlers.ts
2653
3171
  const SUBTYPE_HANDLER_REGISTRY = {
2654
3172
  message_changed: {
@@ -2776,6 +3294,7 @@ function registerSlackMessageEvents(params) {
2776
3294
  return resolved.scope;
2777
3295
  };
2778
3296
  const handleIncomingMessageEvent = async ({ event, body, context, client }) => {
3297
+ const turnAdoptionLifecycle = resolveSlackIngressTurnLifecycle(context);
2779
3298
  try {
2780
3299
  const eventScope = resolveEventScope({
2781
3300
  body,
@@ -2801,10 +3320,9 @@ function registerSlackMessageEvents(params) {
2801
3320
  if (assistantChangedInbound) {
2802
3321
  await handleSlackMessage(assistantChangedInbound, {
2803
3322
  source: "message",
2804
- ...eventScope ? {
2805
- eventScope,
2806
- awaitDispatch: true
2807
- } : {}
3323
+ ...eventScope ? { eventScope } : {},
3324
+ ...turnAdoptionLifecycle ? { turnAdoptionLifecycle } : {},
3325
+ ...eventScope || turnAdoptionLifecycle ? { awaitDispatch: true } : {}
2808
3326
  });
2809
3327
  return;
2810
3328
  }
@@ -2832,12 +3350,12 @@ function registerSlackMessageEvents(params) {
2832
3350
  }
2833
3351
  await handleSlackMessage(message, {
2834
3352
  source: "message",
2835
- ...eventScope ? {
2836
- eventScope,
2837
- awaitDispatch: true
2838
- } : {}
3353
+ ...eventScope ? { eventScope } : {},
3354
+ ...turnAdoptionLifecycle ? { turnAdoptionLifecycle } : {},
3355
+ ...eventScope || turnAdoptionLifecycle ? { awaitDispatch: true } : {}
2839
3356
  });
2840
3357
  } catch (err) {
3358
+ if (turnAdoptionLifecycle) throw err;
2841
3359
  ctx.runtime.error?.(danger(`slack handler failed: ${formatErrorMessage(err)}`));
2842
3360
  }
2843
3361
  };
@@ -2846,6 +3364,7 @@ function registerSlackMessageEvents(params) {
2846
3364
  });
2847
3365
  ctx.app.event("app_mention", async (args) => {
2848
3366
  const { event, body, context, client } = args;
3367
+ const turnAdoptionLifecycle = resolveSlackIngressTurnLifecycle(context);
2849
3368
  try {
2850
3369
  const eventScope = resolveEventScope({
2851
3370
  body,
@@ -2872,12 +3391,12 @@ function registerSlackMessageEvents(params) {
2872
3391
  await handleSlackMessage(mention, {
2873
3392
  source: "app_mention",
2874
3393
  wasMentioned: true,
2875
- ...eventScope ? {
2876
- eventScope,
2877
- awaitDispatch: true
2878
- } : {}
3394
+ ...eventScope ? { eventScope } : {},
3395
+ ...turnAdoptionLifecycle ? { turnAdoptionLifecycle } : {},
3396
+ ...eventScope || turnAdoptionLifecycle ? { awaitDispatch: true } : {}
2879
3397
  });
2880
3398
  } catch (err) {
3399
+ if (turnAdoptionLifecycle) throw err;
2881
3400
  ctx.runtime.error?.(danger(`slack mention handler failed: ${formatErrorMessage(err)}`));
2882
3401
  }
2883
3402
  });
@@ -3026,6 +3545,10 @@ function registerSlackMonitorEvents(params) {
3026
3545
  slashCommandName: params.appHomeSlashCommandName,
3027
3546
  trackEvent: params.trackEvent
3028
3547
  });
3548
+ registerSlackAgentEvents({
3549
+ ctx: params.ctx,
3550
+ trackEvent: params.trackEvent
3551
+ });
3029
3552
  registerSlackInteractionEvents({
3030
3553
  ctx: params.ctx,
3031
3554
  trackEvent: params.trackEvent
@@ -3036,41 +3559,47 @@ function registerSlackMonitorEvents(params) {
3036
3559
  });
3037
3560
  }
3038
3561
  //#endregion
3039
- //#region extensions/slack/src/monitor/inbound-delivery-state.ts
3040
- const deliveredMessages = createPersistentDedupeCache({
3041
- globalKey: Symbol.for("openclaw.slackInboundDeliveries"),
3042
- ttlMs: 1440 * 60 * 1e3,
3043
- maxSize: 2e4,
3044
- persistent: {
3045
- namespace: "slack.inbound-deliveries",
3046
- maxEntries: 2e4,
3047
- openStore: (options) => getOptionalSlackRuntime()?.state.openKeyedStore(options),
3048
- logError: (error) => {
3049
- try {
3050
- getOptionalSlackRuntime()?.logging.getChildLogger({
3051
- plugin: "slack",
3052
- feature: "inbound-delivery-state"
3053
- }).warn("Slack persistent inbound delivery state failed", { error: String(error) });
3054
- } catch {}
3055
- }
3056
- }
3057
- });
3058
- function makeKey(accountId, channelId, ts, teamId) {
3059
- return `${accountId}:${teamId ? `${teamId}:` : ""}${channelId}:${ts}`;
3060
- }
3061
- async function hasSlackInboundMessageDelivery(params) {
3062
- if (!params.accountId || !params.channelId || !params.ts) return false;
3063
- return await deliveredMessages.lookup(makeKey(params.accountId, params.channelId, params.ts, params.teamId));
3064
- }
3065
- async function recordSlackInboundMessageDeliveries(params) {
3066
- if (!params.accountId || params.messages.length === 0) return;
3067
- const deliveredAt = Date.now();
3068
- const keys = /* @__PURE__ */ new Set();
3069
- for (const message of params.messages) {
3070
- if (!message.channel || !message.ts) continue;
3071
- keys.add(makeKey(params.accountId, message.channel, message.ts, params.teamId));
3072
- }
3073
- await Promise.all(Array.from(keys, (key) => deliveredMessages.register(key, { deliveredAt }, { at: deliveredAt })));
3562
+ //#region extensions/slack/src/monitor/message-dispatch-dedupe.ts
3563
+ const SLACK_MESSAGE_DISPATCH_DEDUPE_TTL_MS = 1440 * 60 * 1e3;
3564
+ const SLACK_MESSAGE_DISPATCH_DEDUPE_MEMORY_MAX_ENTRIES = 2e4;
3565
+ const SLACK_MESSAGE_DISPATCH_DEDUPE_STATE_MAX_ENTRIES = 2e4;
3566
+ const SLACK_MESSAGE_DISPATCH_DEDUPE_NAMESPACE = "global";
3567
+ const SLACK_MESSAGE_DISPATCH_DEDUPE_NAMESPACE_PREFIX = "slack.message-dispatch-dedupe";
3568
+ const SLACK_MESSAGE_DISPATCH_DEDUPE_STATE_PLUGIN_ID = "slack-message-dispatch-dedupe";
3569
+ function buildSlackMessageDispatchReplayKey(params) {
3570
+ const channelId = params.channelId?.trim();
3571
+ const ts = params.ts?.trim();
3572
+ if (!channelId || !ts) return null;
3573
+ const teamId = params.teamId?.trim();
3574
+ return JSON.stringify([
3575
+ "message",
3576
+ params.accountId,
3577
+ teamId ?? "",
3578
+ channelId,
3579
+ ts
3580
+ ]);
3581
+ }
3582
+ function createSlackMessageDispatchReplayGuard(params = {}) {
3583
+ return createChannelReplayGuard({
3584
+ dedupe: {
3585
+ ttlMs: SLACK_MESSAGE_DISPATCH_DEDUPE_TTL_MS,
3586
+ memoryMaxSize: SLACK_MESSAGE_DISPATCH_DEDUPE_MEMORY_MAX_ENTRIES,
3587
+ pluginId: SLACK_MESSAGE_DISPATCH_DEDUPE_STATE_PLUGIN_ID,
3588
+ namespacePrefix: SLACK_MESSAGE_DISPATCH_DEDUPE_NAMESPACE_PREFIX,
3589
+ stateMaxEntries: SLACK_MESSAGE_DISPATCH_DEDUPE_STATE_MAX_ENTRIES,
3590
+ ...params.onDiskError ? { onDiskError: params.onDiskError } : {}
3591
+ },
3592
+ buildReplayKey: (event) => event.keys,
3593
+ namespace: () => SLACK_MESSAGE_DISPATCH_DEDUPE_NAMESPACE
3594
+ });
3595
+ }
3596
+ /** Claim one logical message key; an in-flight sibling claim settles to duplicate. */
3597
+ async function claimSlackMessageDispatchReplay(params) {
3598
+ const claim = await runClaimableDedupeClaimLoop(() => params.guard.claim({ keys: [params.key] }), (_error, rejectionCount) => rejectionCount <= 1);
3599
+ return claim.kind === "claimed" ? {
3600
+ kind: "claimed",
3601
+ handle: claim.handle
3602
+ } : { kind: "duplicate" };
3074
3603
  }
3075
3604
  //#endregion
3076
3605
  //#region extensions/slack/src/monitor/message-handler/debounce-key.ts
@@ -3198,7 +3727,7 @@ function createSlackThreadTsResolver(params) {
3198
3727
  }
3199
3728
  //#endregion
3200
3729
  //#region extensions/slack/src/monitor/message-handler.ts
3201
- const loadSlackMessagePipeline = createLazyRuntimeModule(() => import("./pipeline.runtime-DofAvTtb.js"));
3730
+ const loadSlackMessagePipeline = createLazyRuntimeModule(() => import("./pipeline.runtime-5rRYZuX5.js"));
3202
3731
  function createSlackDispatchCompletion() {
3203
3732
  let resolve;
3204
3733
  let reject;
@@ -3211,7 +3740,6 @@ function createSlackDispatchCompletion() {
3211
3740
  reject
3212
3741
  };
3213
3742
  }
3214
- const APP_MENTION_RETRY_TTL_MS = 6e4;
3215
3743
  const RETRYABLE_FLUSH_MAX_ATTEMPTS = 3;
3216
3744
  const RETRYABLE_FLUSH_RETRY_DELAY_MS = 1e3;
3217
3745
  const REPLY_SESSION_INIT_CONFLICT_MESSAGE_RE = /reply session initialization conflicted for \S+/u;
@@ -3225,12 +3753,9 @@ function shouldDebounceSlackMessage(message, cfg) {
3225
3753
  hasMedia: Boolean(message.files && message.files.length > 0)
3226
3754
  });
3227
3755
  }
3228
- function buildSeenMessageKey(channelId, ts, teamId) {
3229
- if (!channelId || !ts) return null;
3230
- return `${teamId ? `${teamId}:` : ""}${channelId}:${ts}`;
3231
- }
3232
3756
  function createSlackMessageHandler(params) {
3233
3757
  const { ctx, account, trackEvent, onPrepared } = params;
3758
+ const dispatchReplayGuard = params.dispatchReplayGuard ?? createSlackMessageDispatchReplayGuard({ onDiskError: (error) => ctx.runtime.error?.(`slack message dispatch dedupe persistence failed: ${formatErrorMessage(error)}`) });
3234
3759
  const { debounceMs, debouncer } = createChannelInboundDebouncer({
3235
3760
  cfg: ctx.cfg,
3236
3761
  channel: "slack",
@@ -3263,8 +3788,41 @@ function createSlackMessageHandler(params) {
3263
3788
  const completions = entries.map((entry) => entry.opts.dispatchCompletion).filter((completion) => completion !== void 0);
3264
3789
  try {
3265
3790
  await (async () => {
3266
- const last = entries.at(-1);
3267
- if (!last) return;
3791
+ const claims = [];
3792
+ const claimedKeys = /* @__PURE__ */ new Set();
3793
+ const surviving = [];
3794
+ for (const entry of entries) {
3795
+ const replayKey = buildSlackMessageDispatchReplayKey({
3796
+ accountId: ctx.accountId,
3797
+ channelId: entry.message.channel,
3798
+ ts: entry.message.ts,
3799
+ teamId: entry.opts.eventScope?.teamId
3800
+ });
3801
+ if (!replayKey || claimedKeys.has(replayKey)) {
3802
+ surviving.push(entry);
3803
+ continue;
3804
+ }
3805
+ const claim = await claimSlackMessageDispatchReplay({
3806
+ guard: dispatchReplayGuard,
3807
+ key: replayKey
3808
+ });
3809
+ if (claim.kind === "claimed") {
3810
+ claims.push(claim.handle);
3811
+ claimedKeys.add(replayKey);
3812
+ surviving.push(entry);
3813
+ }
3814
+ }
3815
+ const releaseClaims = (error) => {
3816
+ for (const handle of claims) handle.release(error === void 0 ? {} : { error });
3817
+ };
3818
+ const commitClaims = async () => {
3819
+ for (const handle of claims) await handle.commit();
3820
+ };
3821
+ const last = surviving.at(-1);
3822
+ if (!last) {
3823
+ releaseClaims();
3824
+ return;
3825
+ }
3268
3826
  const teamId = last.opts.eventScope?.teamId;
3269
3827
  const flushedKey = buildSlackDebounceKey(last.message, ctx.accountId, teamId);
3270
3828
  const topLevelConversationKey = buildTopLevelSlackConversationKey(last.message, ctx.accountId, teamId);
@@ -3275,76 +3833,60 @@ function createSlackMessageHandler(params) {
3275
3833
  if (pendingKeys.size === 0) pendingTopLevelDebounceKeys.delete(topLevelConversationKey);
3276
3834
  }
3277
3835
  }
3278
- const combinedText = entries.length === 1 ? last.message.text ?? "" : entries.map((entry) => entry.message.text ?? "").filter(Boolean).join("\n");
3279
- const combinedMentioned = entries.some((entry) => Boolean(entry.opts.wasMentioned));
3836
+ const combinedText = surviving.length === 1 ? last.message.text ?? "" : surviving.map((entry) => entry.message.text ?? "").filter(Boolean).join("\n");
3837
+ const combinedMentioned = surviving.some((entry) => Boolean(entry.opts.wasMentioned));
3280
3838
  const syntheticMessage = {
3281
3839
  ...last.message,
3282
3840
  text: combinedText
3283
3841
  };
3284
- const seenMessageKey = buildSeenMessageKey(last.message.channel, last.message.ts, last.opts.eventScope?.teamId);
3842
+ const { prepareSlackMessage, dispatchPreparedSlackMessage } = await loadSlackMessagePipeline();
3843
+ const { dispatchCompletion: _completion, awaitDispatch: _awaitDispatch, turnAdoptionLifecycle, ...lastOpts } = last.opts;
3844
+ let prepared;
3845
+ let settlementHandedOff = false;
3285
3846
  try {
3286
- const { prepareSlackMessage, dispatchPreparedSlackMessage } = await loadSlackMessagePipeline();
3287
- const { dispatchCompletion: _completion, awaitDispatch: _awaitDispatch, ...lastOpts } = last.opts;
3288
- const appMentionRetryKey = seenMessageKey && lastOpts.source === "app_mention" && !ctx.botUserId ? seenMessageKey : void 0;
3289
- if (appMentionRetryKey) appMentionPreparingKeys.add(appMentionRetryKey);
3290
- const prepared = await (async () => {
3291
- try {
3292
- const result = await prepareSlackMessage({
3293
- ctx,
3294
- account,
3295
- message: syntheticMessage,
3296
- opts: {
3297
- ...lastOpts,
3298
- wasMentioned: combinedMentioned || last.opts.wasMentioned,
3299
- ...seenMessageKey && lastOpts.source === "message" ? { shouldRecordDroppedHistory: () => !appMentionPreparingKeys.has(seenMessageKey) && !appMentionDispatchedKeys.has(seenMessageKey) } : {}
3300
- }
3301
- });
3302
- if (result && seenMessageKey) {
3303
- pruneAppMentionRetryKeys(Date.now());
3304
- if (last.opts.source === "app_mention") rememberExpiringAppMentionKey(appMentionDispatchedKeys, seenMessageKey);
3305
- else if (last.opts.source === "message" && appMentionDispatchedKeys.has(seenMessageKey)) {
3306
- appMentionDispatchedKeys.delete(seenMessageKey);
3307
- appMentionRetryKeys.delete(seenMessageKey);
3308
- return null;
3309
- }
3310
- appMentionRetryKeys.delete(seenMessageKey);
3311
- }
3312
- return result;
3313
- } finally {
3314
- if (appMentionRetryKey) appMentionPreparingKeys.delete(appMentionRetryKey);
3847
+ prepared = await prepareSlackMessage({
3848
+ ctx,
3849
+ account,
3850
+ message: syntheticMessage,
3851
+ opts: {
3852
+ ...lastOpts,
3853
+ wasMentioned: combinedMentioned || last.opts.wasMentioned
3315
3854
  }
3316
- })();
3317
- if (!prepared) return;
3855
+ });
3856
+ if (!prepared) {
3857
+ releaseClaims();
3858
+ return;
3859
+ }
3860
+ prepared.turnAdoptionLifecycle = turnAdoptionLifecycle ? {
3861
+ ...turnAdoptionLifecycle,
3862
+ onAdopted: async () => {
3863
+ settlementHandedOff = true;
3864
+ await commitClaims();
3865
+ await turnAdoptionLifecycle.onAdopted();
3866
+ },
3867
+ onDeferred: () => {
3868
+ settlementHandedOff = true;
3869
+ turnAdoptionLifecycle.onDeferred();
3870
+ },
3871
+ onAbandoned: () => {
3872
+ releaseClaims();
3873
+ turnAdoptionLifecycle.onAbandoned();
3874
+ }
3875
+ } : turnAdoptionLifecycle;
3318
3876
  onPrepared?.(prepared);
3319
- if (entries.length > 1) {
3320
- const ids = entries.map((entry) => entry.message.ts).filter(Boolean);
3877
+ if (surviving.length > 1) {
3878
+ const ids = surviving.map((entry) => entry.message.ts).filter(Boolean);
3321
3879
  if (ids.length > 0) {
3322
3880
  prepared.ctxPayload.MessageSids = ids;
3323
3881
  prepared.ctxPayload.MessageSidFirst = ids[0];
3324
3882
  prepared.ctxPayload.MessageSidLast = ids[ids.length - 1];
3325
3883
  }
3326
3884
  }
3327
- try {
3328
- await dispatchPreparedSlackMessage(prepared);
3329
- await recordSlackInboundMessageDeliveries({
3330
- accountId: ctx.accountId,
3331
- ...last.opts.eventScope ? { teamId: last.opts.eventScope.teamId } : {},
3332
- messages: entries.map((entry) => entry.message)
3333
- });
3334
- } catch (error) {
3335
- if (!isRetryableSlackInboundError(error)) await recordSlackInboundMessageDeliveries({
3336
- accountId: ctx.accountId,
3337
- ...last.opts.eventScope ? { teamId: last.opts.eventScope.teamId } : {},
3338
- messages: entries.map((entry) => entry.message)
3339
- });
3340
- throw error;
3341
- }
3885
+ await dispatchPreparedSlackMessage(prepared);
3886
+ if (!turnAdoptionLifecycle) await commitClaims();
3887
+ else if (!settlementHandedOff) releaseClaims();
3342
3888
  } catch (error) {
3343
- if (isRetryableSlackInboundError(error)) for (const entry of entries) {
3344
- const entrySeenKey = buildSeenMessageKey(entry.message.channel, entry.message.ts, entry.opts.eventScope?.teamId);
3345
- if (entrySeenKey) appMentionDispatchedKeys.delete(entrySeenKey);
3346
- ctx.releaseSeenMessage(entry.message.channel, entry.message.ts, entry.opts.eventScope);
3347
- }
3889
+ releaseClaims(error);
3348
3890
  throw error;
3349
3891
  }
3350
3892
  })();
@@ -3361,51 +3903,10 @@ function createSlackMessageHandler(params) {
3361
3903
  });
3362
3904
  const threadTsResolver = createSlackThreadTsResolver({ client: ctx.app.client });
3363
3905
  const pendingTopLevelDebounceKeys = /* @__PURE__ */ new Map();
3364
- const appMentionRetryKeys = /* @__PURE__ */ new Map();
3365
- const appMentionPreparingKeys = /* @__PURE__ */ new Set();
3366
- const appMentionDispatchedKeys = /* @__PURE__ */ new Map();
3367
- const pruneAppMentionRetryKeys = (rawNow) => {
3368
- const now = asDateTimestampMs(rawNow);
3369
- if (now === void 0) {
3370
- appMentionRetryKeys.clear();
3371
- appMentionDispatchedKeys.clear();
3372
- return false;
3373
- }
3374
- for (const [key, expiresAt] of appMentionRetryKeys) if (asDateTimestampMs(expiresAt) === void 0 || expiresAt <= now) appMentionRetryKeys.delete(key);
3375
- for (const [key, expiresAt] of appMentionDispatchedKeys) if (asDateTimestampMs(expiresAt) === void 0 || expiresAt <= now) appMentionDispatchedKeys.delete(key);
3376
- return true;
3377
- };
3378
- const rememberExpiringAppMentionKey = (map, key) => {
3379
- const now = Date.now();
3380
- if (!pruneAppMentionRetryKeys(now)) return;
3381
- const expiresAt = resolveExpiresAtMsFromDurationMs(APP_MENTION_RETRY_TTL_MS, { nowMs: now });
3382
- if (expiresAt !== void 0) map.set(key, expiresAt);
3383
- };
3384
- const rememberAppMentionRetryKey = (key) => {
3385
- rememberExpiringAppMentionKey(appMentionRetryKeys, key);
3386
- };
3387
- const consumeAppMentionRetryKey = (key) => {
3388
- if (!pruneAppMentionRetryKeys(Date.now())) return false;
3389
- if (!appMentionRetryKeys.has(key)) return false;
3390
- appMentionRetryKeys.delete(key);
3391
- return true;
3392
- };
3393
3906
  async function enqueueSlackMessage(message, opts) {
3394
3907
  if (opts.source === "message" && message.type !== "message") return;
3395
3908
  if (opts.source === "message" && message.subtype && message.subtype !== "file_share" && message.subtype !== "bot_message" && message.subtype !== "thread_broadcast") return;
3396
3909
  ctx.rememberSlackChannelType(message.channel, message.channel_type, opts.eventScope);
3397
- const seenMessageKey = buildSeenMessageKey(message.channel, message.ts, opts.eventScope?.teamId);
3398
- if (seenMessageKey && await hasSlackInboundMessageDelivery({
3399
- accountId: ctx.accountId,
3400
- channelId: message.channel,
3401
- ts: message.ts,
3402
- ...opts.eventScope ? { teamId: opts.eventScope.teamId } : {}
3403
- })) return;
3404
- const wasSeen = seenMessageKey ? ctx.markMessageSeen(message.channel, message.ts, opts.eventScope) : false;
3405
- if (seenMessageKey && opts.source === "message" && !wasSeen) rememberAppMentionRetryKey(seenMessageKey);
3406
- if (seenMessageKey && wasSeen) {
3407
- if (opts.source !== "app_mention" || !consumeAppMentionRetryKey(seenMessageKey)) return;
3408
- }
3409
3910
  trackEvent?.();
3410
3911
  const resolvedMessage = await (opts.eventScope ? createSlackThreadTsResolver({ client: opts.eventScope.client }) : threadTsResolver).resolve({
3411
3912
  message,
@@ -3642,112 +4143,6 @@ function openSlackPresenceCooldownStore() {
3642
4143
  });
3643
4144
  }
3644
4145
  //#endregion
3645
- //#region extensions/slack/src/monitor/reconnect-policy.ts
3646
- const SLACK_AUTH_ERROR_RE = /account_inactive|invalid_auth|token_revoked|token_expired|not_authed|org_login_required|team_access_not_granted|user_removed_from_team|team_disabled|missing_scope|cannot_find_service|invalid_token/i;
3647
- const NO_ERROR_DETAIL = "no error detail";
3648
- const SLACK_SOCKET_RECONNECT_POLICY = {
3649
- initialMs: 2e3,
3650
- maxMs: 3e4,
3651
- factor: 1.8,
3652
- jitter: .25
3653
- };
3654
- const SLACK_SOCKET_SHARED_CONNECTION_DOCS_URL = "https://docs.slack.dev/apis/events-api/using-socket-mode#using-multiple-connections";
3655
- const SLACK_SOCKET_HELLO_MARKER = Buffer.from("\"hello\"");
3656
- function getSocketEmitter(app) {
3657
- const receiver = app.receiver;
3658
- const client = receiver && typeof receiver === "object" ? receiver.client : void 0;
3659
- if (!client || typeof client !== "object") return null;
3660
- const on = client.on;
3661
- const off = client.off;
3662
- if (typeof on !== "function" || typeof off !== "function") return null;
3663
- return {
3664
- on: (event, listener) => on.call(client, event, listener),
3665
- off: (event, listener) => off.call(client, event, listener)
3666
- };
3667
- }
3668
- function isBufferArray(value) {
3669
- return Array.isArray(value) && value.every((entry) => Buffer.isBuffer(entry));
3670
- }
3671
- function resolveSlackSocketModeConnectionCount(message) {
3672
- const buffer = Buffer.isBuffer(message) ? message : message instanceof ArrayBuffer ? Buffer.from(message) : isBufferArray(message) ? Buffer.concat(message) : void 0;
3673
- if (!buffer?.includes(SLACK_SOCKET_HELLO_MARKER)) return;
3674
- let payload;
3675
- try {
3676
- payload = JSON.parse(buffer.toString("utf8"));
3677
- } catch {
3678
- return;
3679
- }
3680
- const count = isRecord(payload) && payload.type === "hello" ? payload.num_connections : void 0;
3681
- return typeof count === "number" && Number.isSafeInteger(count) && count >= 0 ? count : void 0;
3682
- }
3683
- function formatSlackSocketModeSharedConnectionWarning(activeConnections) {
3684
- return [
3685
- `slack socket mode reports ${activeConnections} active connections for this Slack app`,
3686
- "Slack may deliver each event to any one connection",
3687
- "ensure every OpenClaw gateway sharing this app has equivalent routing and authorization, or use a separate Slack app per gateway, one relay ingress, or HTTP Request URLs behind a load balancer",
3688
- `See ${SLACK_SOCKET_SHARED_CONNECTION_DOCS_URL}`
3689
- ].join("; ");
3690
- }
3691
- function registerSlackSocketModeConnectionDiagnostics(params) {
3692
- const emitter = getSocketEmitter(params.app);
3693
- if (!emitter) return () => {};
3694
- let hasWarned = false;
3695
- const listener = (message, isBinary) => {
3696
- if (isBinary === true || hasWarned) return;
3697
- const activeConnections = resolveSlackSocketModeConnectionCount(message);
3698
- if (activeConnections === void 0 || activeConnections <= 1) return;
3699
- hasWarned = true;
3700
- params.onSharedConnection(activeConnections);
3701
- };
3702
- emitter.on("ws_message", listener);
3703
- return () => {
3704
- emitter.off("ws_message", listener);
3705
- };
3706
- }
3707
- function waitForSlackSocketDisconnect(app, abortSignal) {
3708
- return new Promise((resolve) => {
3709
- const emitter = getSocketEmitter(app);
3710
- if (!emitter) {
3711
- abortSignal?.addEventListener("abort", () => resolve({ event: "disconnect" }), { once: true });
3712
- return;
3713
- }
3714
- const disconnectListener = () => resolveOnce({ event: "disconnect" });
3715
- const startFailListener = (error) => resolveOnce({
3716
- event: "unable_to_socket_mode_start",
3717
- error
3718
- });
3719
- const errorListener = (error) => resolveOnce({
3720
- event: "error",
3721
- error
3722
- });
3723
- const abortListener = () => resolveOnce({ event: "disconnect" });
3724
- const cleanup = () => {
3725
- emitter.off("disconnected", disconnectListener);
3726
- emitter.off("unable_to_socket_mode_start", startFailListener);
3727
- emitter.off("error", errorListener);
3728
- abortSignal?.removeEventListener("abort", abortListener);
3729
- };
3730
- const resolveOnce = (value) => {
3731
- cleanup();
3732
- resolve(value);
3733
- };
3734
- emitter.on("disconnected", disconnectListener);
3735
- emitter.on("unable_to_socket_mode_start", startFailListener);
3736
- emitter.on("error", errorListener);
3737
- abortSignal?.addEventListener("abort", abortListener, { once: true });
3738
- });
3739
- }
3740
- /**
3741
- * Detect permanent Slack account and credential failures.
3742
- * Transient request and HTTP failures stay in OpenClaw's reconnect loop.
3743
- */
3744
- function isNonRecoverableSlackAuthError(error) {
3745
- return SLACK_AUTH_ERROR_RE.test(formatUnknownError(error, ""));
3746
- }
3747
- function formatUnknownError(error, fallback = NO_ERROR_DETAIL) {
3748
- return formatSlackError(error, fallback);
3749
- }
3750
- //#endregion
3751
4146
  //#region extensions/slack/src/monitor/provider-support.ts
3752
4147
  const OPENCLAW_SLACK_CLIENT_PING_TIMEOUT_MS = 15e3;
3753
4148
  const OPENCLAW_SLACK_SOCKET_START_FAILED_EVENT = "unable_to_socket_mode_start";
@@ -3910,27 +4305,28 @@ function createSlackBoltApp(params) {
3910
4305
  const socketModeReceiverOptions = {
3911
4306
  appToken: params.appToken ?? "",
3912
4307
  autoReconnectEnabled: true,
3913
- clientPingTimeout: params.socketMode?.clientPingTimeout ?? OPENCLAW_SLACK_CLIENT_PING_TIMEOUT_MS,
4308
+ clientPingTimeout: OPENCLAW_SLACK_CLIENT_PING_TIMEOUT_MS,
3914
4309
  logger: socketModeLogger,
3915
- installerOptions: { clientOptions: params.clientOptions }
4310
+ installerOptions: { clientOptions: params.clientOptions },
4311
+ ...params.wrapReceiver ? { processEventErrorHandler: async () => false } : {}
3916
4312
  };
3917
- if (params.socketMode?.serverPingTimeout !== void 0) socketModeReceiverOptions.serverPingTimeout = params.socketMode.serverPingTimeout;
3918
- if (params.socketMode?.pingPongLoggingEnabled !== void 0) socketModeReceiverOptions.pingPongLoggingEnabled = params.socketMode.pingPongLoggingEnabled;
3919
4313
  let receiver;
3920
4314
  if (params.slackMode === "socket") {
3921
4315
  receiver = new params.interop.SocketModeReceiver(socketModeReceiverOptions);
3922
4316
  installSlackNativeReconnectFailureObserver(receiver);
3923
4317
  } else if (params.slackMode === "http") receiver = new params.interop.HTTPReceiver({
3924
4318
  signingSecret: params.signingSecret ?? "",
3925
- endpoints: params.slackWebhookPath
4319
+ endpoints: params.slackWebhookPath,
4320
+ ...params.wrapReceiver ? { processEventErrorHandler: async () => false } : {}
3926
4321
  });
3927
4322
  else receiver = createSlackRelayReceiver();
4323
+ const appReceiver = receiver && params.wrapReceiver ? params.wrapReceiver(receiver) : receiver;
3928
4324
  const app = new params.interop.App({
3929
- token: params.botToken,
4325
+ token: params.token,
3930
4326
  clientOptions: params.clientOptions,
3931
4327
  ignoreSelf: false,
3932
4328
  tokenVerificationEnabled: false,
3933
- ...receiver ? { receiver } : {}
4329
+ ...appReceiver ? { receiver: appReceiver } : {}
3934
4330
  });
3935
4331
  app.use(async (args) => {
3936
4332
  if (shouldSkipOpenClawSlackSelfEvent(args)) return;
@@ -4178,7 +4574,7 @@ const SLACK_COMMAND_ARG_CONFIRM_TEXT_MAX = 300;
4178
4574
  const SLACK_HEADER_TEXT_MAX = 150;
4179
4575
  const SLACK_COMMAND_ARG_ACTION_BLOCKS_MAX = 47;
4180
4576
  const loadSlashCommandsRuntime = createLazyRuntimeModule(() => import("./slash-commands.runtime-BTTMlmwy.js"));
4181
- const loadSlashDispatchRuntime = createLazyRuntimeModule(() => import("./slash-dispatch.runtime-CSja9VuD.js"));
4577
+ const loadSlashDispatchRuntime = createLazyRuntimeModule(() => import("./slash-dispatch.runtime-lfeIsXTX.js"));
4182
4578
  const loadSlackPluginCommandsRuntime = createLazyRuntimeModule(() => import("./slash-plugin-commands.runtime-Dosfcx3X.js"));
4183
4579
  const loadSlashSkillCommandsRuntime = createLazyRuntimeModule(() => import("./slash-skill-commands.runtime-CD1J2ODY.js"));
4184
4580
  function resolveSlackCommandMenuModelContext(params) {
@@ -4571,8 +4967,12 @@ async function registerSlackMonitorSlashCommands(params) {
4571
4967
  agentId: menuRoute.agentId,
4572
4968
  sessionKey: menuRoute.sessionKey
4573
4969
  }) : {};
4574
- const menuModelCatalog = commandDefinition.key === "think" && menuNeedsModelContext ? await loadModelCatalog({
4970
+ const menuModelCatalog = commandDefinition.key === "think" && menuNeedsModelContext ? await loadPreparedModelCatalog({
4575
4971
  config: cfg,
4972
+ ...menuRoute ? {
4973
+ agentId: menuRoute.agentId,
4974
+ agentDir: resolveAgentDir(cfg, menuRoute.agentId)
4975
+ } : {},
4576
4976
  readOnly: true
4577
4977
  }) : void 0;
4578
4978
  const menu = resolveCommandArgMenu({
@@ -4630,7 +5030,7 @@ async function registerSlackMonitorSlashCommands(params) {
4630
5030
  sessionPrefix: slashCommand.sessionPrefix,
4631
5031
  userId: command.user_id,
4632
5032
  targetSessionKey: route.sessionKey,
4633
- lowercaseSessionKey: true
5033
+ sessionKeyCase: "lowercase"
4634
5034
  });
4635
5035
  const slashReplyTarget = !slashCommand.ephemeral && isRoomish ? `channel:${command.channel_id}` : `user:${command.user_id}`;
4636
5036
  const ctxPayload = finalizeInboundContext({
@@ -4709,12 +5109,17 @@ async function registerSlackMonitorSlashCommands(params) {
4709
5109
  });
4710
5110
  };
4711
5111
  const pendingSlashReplies = [];
5112
+ const shouldDeliverBlockImmediately = commandDefinition?.key === "login";
4712
5113
  const { counts } = await dispatchReplyWithDispatcher({
4713
5114
  ctx: ctxPayload,
4714
5115
  cfg,
4715
5116
  dispatcherOptions: {
4716
5117
  ...replyPipeline,
4717
- deliver: async (payload) => {
5118
+ deliver: async (payload, info) => {
5119
+ if (info.kind === "block" && shouldDeliverBlockImmediately) {
5120
+ await deliverSlashPayloads([payload]);
5121
+ return;
5122
+ }
4718
5123
  pendingSlashReplies.push(payload);
4719
5124
  },
4720
5125
  onError: (err, info) => {
@@ -4918,7 +5323,7 @@ async function getSlackBoltInterop() {
4918
5323
  }
4919
5324
  return slackBoltInterop;
4920
5325
  }
4921
- const loadSlackRelaySource = createLazyRuntimeModule(() => import("./relay-source-DMmgtvg4.js"));
5326
+ const loadSlackRelaySource = createLazyRuntimeModule(() => import("./relay-source-Df9EaiTF.js"));
4922
5327
  const SLACK_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
4923
5328
  const SLACK_WEBHOOK_BODY_TIMEOUT_MS = 3e4;
4924
5329
  function resolveStableSlackUserIdEntry(raw) {
@@ -5011,16 +5416,26 @@ async function monitorSlackProvider(opts = {}) {
5011
5416
  path: `channels.slack.accounts.${account.accountId}.signingSecret`
5012
5417
  });
5013
5418
  const botToken = resolveSlackBotToken(opts.botToken ?? account.botToken);
5419
+ const userToken = account.userToken;
5014
5420
  const appToken = resolveSlackAppToken(opts.appToken ?? account.appToken);
5015
5421
  const relayConfig = slackMode === "relay" ? resolveSlackRelayConfig({
5016
5422
  relay: account.config.relay,
5017
5423
  accountId: account.accountId
5018
5424
  }) : void 0;
5019
- if (!botToken || slackMode === "socket" && !appToken) {
5020
- const missing = slackMode === "http" ? `Slack bot token missing for account "${account.accountId}" (set channels.slack.accounts.${account.accountId}.botToken or SLACK_BOT_TOKEN for default).` : slackMode === "relay" ? `Slack bot token missing for account "${account.accountId}" (set channels.slack.accounts.${account.accountId}.botToken or SLACK_BOT_TOKEN for default).` : `Slack bot + app tokens missing for account "${account.accountId}" (set channels.slack.accounts.${account.accountId}.botToken/appToken or SLACK_BOT_TOKEN/SLACK_APP_TOKEN for default).`;
5021
- throw new Error(missing);
5425
+ let token;
5426
+ if (account.identity === "user") {
5427
+ if (!userToken) throw new Error(`Slack user token missing for account "${account.accountId}" (set channels.slack.accounts.${account.accountId}.userToken or SLACK_USER_TOKEN for default).`);
5428
+ if (slackMode === "socket" && !appToken) throw new Error(`Slack app token missing for user-identity socket mode account "${account.accountId}" (set channels.slack.accounts.${account.accountId}.appToken or SLACK_APP_TOKEN for default).`);
5429
+ if (slackMode === "http" && !signingSecret) throw new Error(`Slack signing secret missing for user-identity HTTP mode account "${account.accountId}" (set channels.slack.signingSecret or channels.slack.accounts.${account.accountId}.signingSecret).`);
5430
+ token = userToken;
5431
+ } else {
5432
+ if (!botToken || slackMode === "socket" && !appToken) {
5433
+ const missing = slackMode === "http" ? `Slack bot token missing for account "${account.accountId}" (set channels.slack.accounts.${account.accountId}.botToken or SLACK_BOT_TOKEN for default).` : slackMode === "relay" ? `Slack bot token missing for account "${account.accountId}" (set channels.slack.accounts.${account.accountId}.botToken or SLACK_BOT_TOKEN for default).` : `Slack bot + app tokens missing for account "${account.accountId}" (set channels.slack.accounts.${account.accountId}.botToken/appToken or SLACK_BOT_TOKEN/SLACK_APP_TOKEN for default).`;
5434
+ throw new Error(missing);
5435
+ }
5436
+ if (slackMode === "http" && !signingSecret) throw new Error(`Slack signing secret missing for account "${account.accountId}" (set channels.slack.signingSecret or channels.slack.accounts.${account.accountId}.signingSecret).`);
5437
+ token = botToken;
5022
5438
  }
5023
- if (slackMode === "http" && !signingSecret) throw new Error(`Slack signing secret missing for account "${account.accountId}" (set channels.slack.signingSecret or channels.slack.accounts.${account.accountId}.signingSecret).`);
5024
5439
  const slackCfg = account.config;
5025
5440
  const dmConfig = slackCfg.dm;
5026
5441
  const dmEnabled = dmConfig?.enabled ?? true;
@@ -5054,30 +5469,33 @@ async function monitorSlackProvider(opts = {}) {
5054
5469
  log: (message) => runtime.log?.(warn(message))
5055
5470
  });
5056
5471
  const resolveToken = account.userToken || botToken;
5057
- const useAccessGroups = cfg.commands?.useAccessGroups !== false;
5472
+ const useAccessGroups = true;
5058
5473
  const reactionMode = slackCfg.reactionNotifications ?? "own";
5059
5474
  const reactionAllowlist = slackCfg.reactionAllowlist ?? [];
5060
5475
  const replyToMode = slackCfg.replyToMode ?? "off";
5061
5476
  const threadHistoryScope = slackCfg.thread?.historyScope ?? "thread";
5062
5477
  const threadInheritParent = slackCfg.thread?.inheritParent ?? false;
5063
- const threadRequireExplicitMention = slackCfg.thread?.requireExplicitMention ?? false;
5064
5478
  const slashCommand = resolveSlackSlashCommandConfig(opts.slashCommand ?? slackCfg.slashCommand);
5065
5479
  const allowNameMatching = isDangerousNameMatchingEnabled(slackCfg);
5066
5480
  const textLimit = resolveTextChunkLimit(cfg, "slack", account.accountId, { fallbackLimit: SLACK_TEXT_LIMIT });
5067
5481
  const ackReactionScope = cfg.messages?.ackReactionScope ?? "group-mentions";
5068
5482
  const typingReaction = slackCfg.typingReaction?.trim() ?? "";
5069
5483
  const mediaMaxBytes = (opts.mediaMaxMb ?? slackCfg.mediaMaxMb ?? 20) * 1024 * 1024;
5070
- const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
5071
5484
  const clientOptions = resolveSlackWebClientOptions();
5485
+ const durableIngress = createSlackDurableIngress({
5486
+ accountId: account.accountId,
5487
+ ...runtime.log ? { onLog: runtime.log } : {},
5488
+ ...opts.abortSignal ? { abortSignal: opts.abortSignal } : {}
5489
+ });
5072
5490
  const { app, receiver, socketModeLogger } = createSlackBoltApp({
5073
5491
  interop: await getSlackBoltInterop(),
5074
5492
  slackMode,
5075
- botToken,
5493
+ token,
5076
5494
  appToken: slackMode === "socket" ? appToken ?? void 0 : void 0,
5077
5495
  signingSecret: slackMode === "http" ? signingSecret ?? void 0 : void 0,
5078
5496
  slackWebhookPath,
5079
5497
  clientOptions,
5080
- ...slackCfg.socketMode ? { socketMode: slackCfg.socketMode } : {}
5498
+ wrapReceiver: durableIngress.wrapReceiver
5081
5499
  });
5082
5500
  const gracefulStop = async () => {
5083
5501
  await gracefulStopSlackApp(app);
@@ -5112,12 +5530,12 @@ async function monitorSlackProvider(opts = {}) {
5112
5530
  let authIdentityWarning;
5113
5531
  let authTestIdentity;
5114
5532
  try {
5115
- const auth = await createSlackStartupAuthClient(botToken, clientOptions).auth.test();
5533
+ const auth = await createSlackStartupAuthClient(token, clientOptions).auth.test();
5116
5534
  const authUserId = normalizeOptionalString(auth.user_id) ?? "";
5117
5535
  botId = normalizeOptionalString(auth.bot_id) ?? "";
5118
- botUserId = botId ? authUserId : "";
5536
+ botUserId = account.identity === "user" ? authUserId : botId ? authUserId : "";
5119
5537
  authTestIdentity = auth;
5120
- authIdentityWarning = formatSlackBotTokenIdentityWarning({
5538
+ if (account.identity === "bot") authIdentityWarning = formatSlackBotTokenIdentityWarning({
5121
5539
  auth,
5122
5540
  accountId: account.accountId
5123
5541
  });
@@ -5133,7 +5551,10 @@ async function monitorSlackProvider(opts = {}) {
5133
5551
  });
5134
5552
  const teamId = installationIdentity.kind === "workspace" ? installationIdentity.teamId : "";
5135
5553
  const apiAppId = installationIdentity.kind === "degraded" ? "" : installationIdentity.apiAppId ?? "";
5136
- if (authTestError !== void 0) runtime.log?.(warn(`[${account.accountId}] slack auth.test failed at boot (${authTestError}); explicit bot-mention detection will be disabled until restart with a valid bot token; required-mention channels will fail closed without another trusted activation signal`));
5554
+ if (authTestError !== void 0) {
5555
+ const identityFailureDetail = account.identity === "user" ? "explicit self-mention detection will be disabled until restart with a valid user token" : "explicit bot-mention detection will be disabled until restart with a valid bot token";
5556
+ runtime.log?.(warn(`[${account.accountId}] slack auth.test failed at boot (${authTestError}); ${identityFailureDetail}; required-mention channels will fail closed without another trusted activation signal`));
5557
+ }
5137
5558
  if (authIdentityWarning) runtime.log?.(warn(authIdentityWarning));
5138
5559
  const identityHealth = resolveSlackIdentityHealth({
5139
5560
  installationIdentity,
@@ -5141,11 +5562,14 @@ async function monitorSlackProvider(opts = {}) {
5141
5562
  authTestError,
5142
5563
  authIdentityWarning
5143
5564
  });
5144
- if (apiAppId && expectedApiAppIdFromAppToken && apiAppId !== expectedApiAppIdFromAppToken) runtime.error?.(`slack token mismatch: bot token app_id=${apiAppId} but app token looks like app_id=${expectedApiAppIdFromAppToken}`);
5565
+ if (apiAppId && expectedApiAppIdFromAppToken && apiAppId !== expectedApiAppIdFromAppToken) {
5566
+ const identityTokenLabel = account.identity === "user" ? "user token" : "bot token";
5567
+ runtime.error?.(`slack token mismatch: ${identityTokenLabel} app_id=${apiAppId} but app token looks like app_id=${expectedApiAppIdFromAppToken}`);
5568
+ }
5145
5569
  const ctx = createSlackMonitorContext({
5146
5570
  cfg,
5147
5571
  accountId: account.accountId,
5148
- botToken,
5572
+ botToken: token,
5149
5573
  app,
5150
5574
  runtime,
5151
5575
  channelRuntime: opts.channelRuntime,
@@ -5173,13 +5597,11 @@ async function monitorSlackProvider(opts = {}) {
5173
5597
  replyToMode,
5174
5598
  threadHistoryScope,
5175
5599
  threadInheritParent,
5176
- threadRequireExplicitMention,
5177
5600
  slashCommand,
5178
5601
  textLimit,
5179
5602
  ackReactionScope,
5180
5603
  typingReaction,
5181
- mediaMaxBytes,
5182
- removeAckAfterReply
5604
+ mediaMaxBytes
5183
5605
  });
5184
5606
  const trackEvent = opts.setStatus ? () => {
5185
5607
  opts.setStatus({
@@ -5232,6 +5654,7 @@ async function monitorSlackProvider(opts = {}) {
5232
5654
  appHomeSlashCommandName: commandRegistration.mode === "single" ? commandRegistration.name : void 0,
5233
5655
  trackEvent
5234
5656
  });
5657
+ durableIngress.start();
5235
5658
  presenceMonitor?.start();
5236
5659
  if (slackMode === "http" && slackHttpHandler) unregisterHttpHandler = registerSlackHttpHandler({
5237
5660
  path: slackWebhookPath,
@@ -5397,9 +5820,17 @@ async function monitorSlackProvider(opts = {}) {
5397
5820
  }
5398
5821
  } else if (slackMode === "relay" && relayConfig) {
5399
5822
  runtime.log?.(`slack relay mode connecting to ${relayConfig.url} gateway_id:${relayConfig.gatewayId}`);
5823
+ durableIngress.attachRelayDispatch(async (message, turnAdoptionLifecycle) => {
5824
+ await handleSlackMessage(message, {
5825
+ source: "message",
5826
+ wasMentioned: true,
5827
+ awaitDispatch: true,
5828
+ turnAdoptionLifecycle
5829
+ });
5830
+ });
5400
5831
  await (await loadSlackRelaySource()).monitorSlackRelaySource({
5401
5832
  config: relayConfig,
5402
- handleSlackMessage,
5833
+ acceptRelayEvent: durableIngress.acceptRelayEvent,
5403
5834
  runtime,
5404
5835
  abortSignal: opts.abortSignal,
5405
5836
  setStatus: opts.setStatus,
@@ -5417,9 +5848,10 @@ async function monitorSlackProvider(opts = {}) {
5417
5848
  opts.abortSignal?.removeEventListener("abort", stopOnAbort);
5418
5849
  unregisterSocketModeConnectionDiagnostics();
5419
5850
  unregisterHttpHandler?.();
5851
+ await durableIngress.stop();
5420
5852
  await gracefulStop();
5421
5853
  }
5422
5854
  }
5423
5855
  const resolveSlackRuntimeGroupPolicy = resolveOpenProviderRuntimeGroupPolicy$1;
5424
5856
  //#endregion
5425
- export { buildSlackSlashCommandMatcher as C, updateLastRoute as S, resolveSlackChatType as _, createSlackResponseUrlBudget as a, resolveChannelResetConfig as b, formatUnknownError as c, authorizeSlackBotRoomMessage as d, resolveSlackCommandIngress as f, normalizeSlackChannelType as g, parseSlackAssistantThreadMetadata as h, SlackResponseAlreadyReportedError as i, recordInboundSession as l, buildSlackAssistantThreadMetadata as m, resolveSlackRuntimeGroupPolicy as n, authorizeSlackDirectMessage as o, resolveSlackEffectiveAllowFrom as p, resolveSlackRoomContextHints as r, SLACK_SOCKET_RECONNECT_POLICY as s, monitorSlackProvider as t, resolveConversationLabel$1 as u, readSessionUpdatedAt as v, stripSlackMentionsForCommandDetection as w, resolveStorePath$1 as x, resolveChannelContextVisibilityMode as y };
5857
+ export { stripSlackMentionsForCommandDetection as C, buildSlackSlashCommandMatcher as S, readSessionUpdatedAt as _, createSlackResponseUrlBudget as a, resolveStorePath$1 as b, formatUnknownError as c, resolveSlackCommandIngress as d, resolveSlackEffectiveAllowFrom as f, resolveSlackChatType as g, normalizeSlackChannelType as h, SlackResponseAlreadyReportedError as i, resolveConversationLabel$1 as l, parseSlackAssistantThreadMetadata as m, resolveSlackRuntimeGroupPolicy as n, authorizeSlackDirectMessage as o, buildSlackAssistantThreadMetadata as p, resolveSlackRoomContextHints as r, SLACK_SOCKET_RECONNECT_POLICY as s, monitorSlackProvider as t, authorizeSlackBotRoomMessage as u, resolveChannelContextVisibilityMode as v, updateLastRoute as x, resolveChannelResetConfig as y };