@openclaw/slack 2026.7.2-beta.3 → 2026.7.2-beta.5

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 (627) hide show
  1. package/README.md +1 -1
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/{account-inspect-Bvbtu3iH.js → account-inspect-jn1xLaVi.js} +2 -2
  4. package/dist/account-reply-mode-DrUXogvO.js +14 -0
  5. package/dist/{accounts-DQEbYKh9.js → accounts-BSr252c_.js} +6 -27
  6. package/dist/{accounts.runtime-BIKmJdtJ.js → accounts.runtime-CjULMLTa.js} +1 -1
  7. package/dist/{action-runtime-DqkRwO3Z.js → action-runtime-BO3HK_50.js} +7 -7
  8. package/dist/action-runtime.runtime-DbEcqEXC.js +2 -0
  9. package/dist/{actions-CF0tUY5q.js → actions-BkOaiDSX.js} +6 -6
  10. package/dist/{actions.runtime-hCbx4RXg.js → actions.runtime-Do-iOL51.js} +1 -1
  11. package/dist/api.js +15 -15
  12. package/dist/{approval-handler.runtime-Em8I1-8-.js → approval-handler.runtime-BAnGN7w0.js} +2 -2
  13. package/dist/{channel-4-TRi1D6.js → channel-Cnrlie9S.js} +26 -36
  14. package/dist/channel-config-api.js +1 -1
  15. package/dist/channel-plugin-api.js +1 -1
  16. package/dist/{channel-type-Cu1yjNg2.js → channel-type-CPDX8EV-.js} +4 -4
  17. package/dist/{channel.setup-CgkADtZg.js → channel.setup-DyZdLwUB.js} +6 -25
  18. package/dist/{setup-core-CeiAVwQ3.js → config-adapter-Bb8hn2tM.js} +141 -72
  19. package/dist/{config-schema-XjJGfwQC.js → config-schema-BajbuJGL.js} +229 -30
  20. package/dist/{directory-config-Bv0lcUlH.js → directory-config-CeixfVuC.js} +2 -2
  21. package/dist/directory-contract-api.js +1 -1
  22. package/dist/{directory-live-CzUIB7th.js → directory-live-CUwGPX2d.js} +3 -3
  23. package/dist/doctor-contract-CsGH1Dfc.js +158 -0
  24. package/dist/doctor-contract-api.js +1 -1
  25. package/dist/{group-policy-Bw-65m3V.js → group-policy-CthIXE2T.js} +48 -7
  26. package/dist/{message-tool-api-C2pcf5du.js → message-tool-api-CvKoWYYQ.js} +1 -6
  27. package/dist/message-tool-api.js +1 -1
  28. package/dist/{monitor-CNFeF30h.js → monitor-Cb5ZsOTY.js} +4 -4
  29. package/dist/{outbound-adapter-CVi2uok0.js → outbound-adapter-sGrozuRg.js} +11 -68
  30. package/dist/{pipeline.runtime-CTvokbty.js → pipeline.runtime-mFCCSllA.js} +2173 -1999
  31. package/dist/{policy-b_dOPDCq.js → policy-DVg6FyMP.js} +1 -1
  32. package/dist/{probe-Borhn9PQ.js → probe-BhQTDVBQ.js} +51 -58
  33. package/dist/{provider-B-HVL5ZE.js → provider-PSppCU_K.js} +435 -252
  34. package/dist/{relay-source-Ea_P9VcO.js → relay-source-D0mb_JBk.js} +1 -1
  35. package/dist/{replies-B4bOdrYb.js → replies-BkPPM2Ri.js} +50 -13
  36. package/dist/{resolve-channels-KTc568SU.js → resolve-channels-B3RlJKyV.js} +2 -2
  37. package/dist/{resolve-users-DeGJf1yp.js → resolve-users-DTm7PEMa.js} +2 -2
  38. package/dist/{rolldown-runtime-D7D4PA-g.js → rolldown-runtime-8H4AJuhK.js} +1 -0
  39. package/dist/{runtime-api-BFfNpzDe.js → runtime-api-jpn3vQ7e.js} +3 -2
  40. package/dist/runtime-api.js +11 -11
  41. package/dist/{scopes-lx08L56K.js → scopes-B2SqZ--4.js} +1 -1
  42. package/dist/{security-audit-CikQhBUY.js → security-audit-DR097212.js} +1 -11
  43. package/dist/security-contract-api.js +1 -1
  44. package/dist/{send-KuTDky7i.js → send-2IoDXPeB.js} +8 -22
  45. package/dist/{send.runtime-CXyxws1D.js → send.runtime-BKd_PAEw.js} +1 -1
  46. package/dist/setup-plugin-api.js +1 -1
  47. package/dist/{setup-surface-DLA0vHXp.js → setup-surface-Dyam58nO.js} +47 -39
  48. package/dist/{shared-C4yVB0Q-.js → shared-g55DV75U.js} +16 -37
  49. package/dist/{slash-dispatch.runtime-BT-mMm-f.js → slash-dispatch.runtime-BRQbvn2e.js} +10 -9
  50. package/node_modules/@slack/bolt/dist/App.d.ts +4 -19
  51. package/node_modules/@slack/bolt/dist/App.d.ts.map +1 -1
  52. package/node_modules/@slack/bolt/dist/App.js +21 -40
  53. package/node_modules/@slack/bolt/dist/App.js.map +1 -1
  54. package/node_modules/@slack/bolt/dist/context/create-respond.d.ts +3 -3
  55. package/node_modules/@slack/bolt/dist/context/create-respond.d.ts.map +1 -1
  56. package/node_modules/@slack/bolt/dist/context/create-respond.js +13 -2
  57. package/node_modules/@slack/bolt/dist/context/create-respond.js.map +1 -1
  58. package/node_modules/@slack/bolt/dist/errors.d.ts +6 -8
  59. package/node_modules/@slack/bolt/dist/errors.d.ts.map +1 -1
  60. package/node_modules/@slack/bolt/dist/errors.js +11 -11
  61. package/node_modules/@slack/bolt/dist/errors.js.map +1 -1
  62. package/node_modules/@slack/bolt/dist/helpers.d.ts.map +1 -1
  63. package/node_modules/@slack/bolt/dist/helpers.js +1 -2
  64. package/node_modules/@slack/bolt/dist/helpers.js.map +1 -1
  65. package/node_modules/@slack/bolt/dist/index.d.ts +0 -1
  66. package/node_modules/@slack/bolt/dist/index.d.ts.map +1 -1
  67. package/node_modules/@slack/bolt/dist/index.js +1 -3
  68. package/node_modules/@slack/bolt/dist/index.js.map +1 -1
  69. package/node_modules/@slack/bolt/dist/receivers/AwsLambdaReceiver.d.ts +10 -1
  70. package/node_modules/@slack/bolt/dist/receivers/AwsLambdaReceiver.d.ts.map +1 -1
  71. package/node_modules/@slack/bolt/dist/receivers/AwsLambdaReceiver.js +5 -1
  72. package/node_modules/@slack/bolt/dist/receivers/AwsLambdaReceiver.js.map +1 -1
  73. package/node_modules/@slack/bolt/dist/receivers/HTTPModuleFunctions.d.ts.map +1 -1
  74. package/node_modules/@slack/bolt/dist/receivers/HTTPModuleFunctions.js +10 -14
  75. package/node_modules/@slack/bolt/dist/receivers/HTTPModuleFunctions.js.map +1 -1
  76. package/node_modules/@slack/bolt/dist/receivers/SocketModeFunctions.d.ts.map +1 -1
  77. package/node_modules/@slack/bolt/dist/receivers/SocketModeFunctions.js +1 -1
  78. package/node_modules/@slack/bolt/dist/receivers/SocketModeFunctions.js.map +1 -1
  79. package/node_modules/@slack/bolt/dist/receivers/SocketModeReceiver.d.ts +3 -1
  80. package/node_modules/@slack/bolt/dist/receivers/SocketModeReceiver.d.ts.map +1 -1
  81. package/node_modules/@slack/bolt/dist/receivers/SocketModeReceiver.js +2 -1
  82. package/node_modules/@slack/bolt/dist/receivers/SocketModeReceiver.js.map +1 -1
  83. package/node_modules/@slack/bolt/dist/types/actions/index.d.ts +2 -4
  84. package/node_modules/@slack/bolt/dist/types/actions/index.d.ts.map +1 -1
  85. package/node_modules/@slack/bolt/dist/types/actions/index.js +0 -2
  86. package/node_modules/@slack/bolt/dist/types/actions/index.js.map +1 -1
  87. package/node_modules/@slack/bolt/dist/types/events/index.d.ts +2 -0
  88. package/node_modules/@slack/bolt/dist/types/events/index.d.ts.map +1 -1
  89. package/node_modules/@slack/bolt/dist/types/utilities.d.ts +2 -2
  90. package/node_modules/@slack/bolt/dist/types/utilities.d.ts.map +1 -1
  91. package/node_modules/@slack/bolt/dist/types/view/index.d.ts +1 -31
  92. package/node_modules/@slack/bolt/dist/types/view/index.d.ts.map +1 -1
  93. package/node_modules/@slack/bolt/package.json +10 -11
  94. package/node_modules/@slack/logger/README.md +1 -1
  95. package/node_modules/@slack/logger/dist/index.js +17 -13
  96. package/node_modules/@slack/logger/dist/index.js.map +1 -1
  97. package/node_modules/@slack/logger/package.json +6 -7
  98. package/node_modules/@slack/oauth/README.md +1 -1
  99. package/node_modules/@slack/oauth/dist/errors.d.ts +25 -14
  100. package/node_modules/@slack/oauth/dist/errors.d.ts.map +1 -1
  101. package/node_modules/@slack/oauth/dist/errors.js +24 -33
  102. package/node_modules/@slack/oauth/dist/errors.js.map +1 -1
  103. package/node_modules/@slack/oauth/dist/install-provider.js +58 -16
  104. package/node_modules/@slack/oauth/dist/install-provider.js.map +1 -1
  105. package/node_modules/@slack/oauth/dist/installation-stores/file-store.js +4 -2
  106. package/node_modules/@slack/oauth/dist/installation-stores/file-store.js.map +1 -1
  107. package/node_modules/@slack/oauth/dist/installation-stores/memory-store.js +3 -16
  108. package/node_modules/@slack/oauth/dist/installation-stores/memory-store.js.map +1 -1
  109. package/node_modules/@slack/oauth/dist/state-stores/clear-state-store.js +2 -0
  110. package/node_modules/@slack/oauth/dist/state-stores/clear-state-store.js.map +1 -1
  111. package/node_modules/@slack/oauth/dist/state-stores/file-state-store.js +3 -0
  112. package/node_modules/@slack/oauth/dist/state-stores/file-state-store.js.map +1 -1
  113. package/node_modules/@slack/oauth/package.json +8 -9
  114. package/node_modules/@slack/socket-mode/README.md +1 -1
  115. package/node_modules/@slack/socket-mode/dist/package.json +21 -13
  116. package/node_modules/@slack/socket-mode/dist/src/SlackWebSocket.d.ts +14 -8
  117. package/node_modules/@slack/socket-mode/dist/src/SlackWebSocket.d.ts.map +1 -1
  118. package/node_modules/@slack/socket-mode/dist/src/SlackWebSocket.js +136 -48
  119. package/node_modules/@slack/socket-mode/dist/src/SlackWebSocket.js.map +1 -1
  120. package/node_modules/@slack/socket-mode/dist/src/SocketModeClient.d.ts +12 -6
  121. package/node_modules/@slack/socket-mode/dist/src/SocketModeClient.d.ts.map +1 -1
  122. package/node_modules/@slack/socket-mode/dist/src/SocketModeClient.js +87 -38
  123. package/node_modules/@slack/socket-mode/dist/src/SocketModeClient.js.map +1 -1
  124. package/node_modules/@slack/socket-mode/dist/src/SocketModeOptions.d.ts +31 -1
  125. package/node_modules/@slack/socket-mode/dist/src/SocketModeOptions.d.ts.map +1 -1
  126. package/node_modules/@slack/socket-mode/dist/src/errors.d.ts +31 -37
  127. package/node_modules/@slack/socket-mode/dist/src/errors.d.ts.map +1 -1
  128. package/node_modules/@slack/socket-mode/dist/src/errors.js +43 -47
  129. package/node_modules/@slack/socket-mode/dist/src/errors.js.map +1 -1
  130. package/node_modules/@slack/socket-mode/dist/src/index.d.ts +2 -2
  131. package/node_modules/@slack/socket-mode/dist/src/index.d.ts.map +1 -1
  132. package/node_modules/@slack/socket-mode/dist/src/index.js +7 -1
  133. package/node_modules/@slack/socket-mode/dist/src/index.js.map +1 -1
  134. package/node_modules/@slack/socket-mode/package.json +21 -13
  135. package/node_modules/@slack/types/README.md +1 -1
  136. package/node_modules/@slack/types/dist/events/app.d.ts +28 -0
  137. package/node_modules/@slack/types/dist/events/app.d.ts.map +1 -1
  138. package/node_modules/@slack/types/dist/events/index.d.ts +2 -2
  139. package/node_modules/@slack/types/dist/events/index.d.ts.map +1 -1
  140. package/node_modules/@slack/types/dist/events/index.js.map +1 -1
  141. package/node_modules/@slack/types/dist/events/message.d.ts +2 -0
  142. package/node_modules/@slack/types/dist/events/message.d.ts.map +1 -1
  143. package/node_modules/@slack/types/package.json +3 -3
  144. package/node_modules/@slack/web-api/README.md +1 -1
  145. package/node_modules/@slack/web-api/dist/WebClient.d.ts +42 -71
  146. package/node_modules/@slack/web-api/dist/WebClient.d.ts.map +1 -1
  147. package/node_modules/@slack/web-api/dist/WebClient.js +180 -248
  148. package/node_modules/@slack/web-api/dist/WebClient.js.map +1 -1
  149. package/node_modules/@slack/web-api/dist/chat-stream.js +37 -22
  150. package/node_modules/@slack/web-api/dist/chat-stream.js.map +1 -1
  151. package/node_modules/@slack/web-api/dist/errors.d.ts +32 -51
  152. package/node_modules/@slack/web-api/dist/errors.d.ts.map +1 -1
  153. package/node_modules/@slack/web-api/dist/errors.js +64 -55
  154. package/node_modules/@slack/web-api/dist/errors.js.map +1 -1
  155. package/node_modules/@slack/web-api/dist/file-upload.d.ts +0 -9
  156. package/node_modules/@slack/web-api/dist/file-upload.d.ts.map +1 -1
  157. package/node_modules/@slack/web-api/dist/file-upload.js +41 -46
  158. package/node_modules/@slack/web-api/dist/file-upload.js.map +1 -1
  159. package/node_modules/@slack/web-api/dist/index.d.ts +2 -2
  160. package/node_modules/@slack/web-api/dist/index.d.ts.map +1 -1
  161. package/node_modules/@slack/web-api/dist/index.js +8 -1
  162. package/node_modules/@slack/web-api/dist/index.js.map +1 -1
  163. package/node_modules/@slack/web-api/dist/methods.d.ts +2 -38
  164. package/node_modules/@slack/web-api/dist/methods.d.ts.map +1 -1
  165. package/node_modules/@slack/web-api/dist/methods.js +1347 -1384
  166. package/node_modules/@slack/web-api/dist/methods.js.map +1 -1
  167. package/node_modules/@slack/web-api/dist/types/request/files.d.ts +0 -1
  168. package/node_modules/@slack/web-api/dist/types/request/files.d.ts.map +1 -1
  169. package/node_modules/@slack/web-api/dist/types/request/index.d.ts +3 -3
  170. package/node_modules/@slack/web-api/dist/types/request/index.d.ts.map +1 -1
  171. package/node_modules/@slack/web-api/dist/types/request/manifest.d.ts +66 -1
  172. package/node_modules/@slack/web-api/dist/types/request/manifest.d.ts.map +1 -1
  173. package/node_modules/@slack/web-api/dist/types/request/rtm.d.ts +1 -14
  174. package/node_modules/@slack/web-api/dist/types/request/rtm.d.ts.map +1 -1
  175. package/node_modules/@slack/web-api/dist/types/request/workflows.d.ts +0 -29
  176. package/node_modules/@slack/web-api/dist/types/request/workflows.d.ts.map +1 -1
  177. package/node_modules/@slack/web-api/package.json +6 -10
  178. package/node_modules/@types/node/README.md +1 -1
  179. package/node_modules/@types/node/fs/promises.d.ts +16 -6
  180. package/node_modules/@types/node/package.json +2 -2
  181. package/node_modules/@types/retry/LICENSE +21 -21
  182. package/node_modules/@types/retry/README.md +5 -4
  183. package/node_modules/@types/retry/index.d.ts +20 -13
  184. package/node_modules/@types/retry/package.json +9 -11
  185. package/node_modules/iconv-lite/encodings/sbcs-data.js +2 -0
  186. package/node_modules/iconv-lite/encodings/utf32.js +10 -3
  187. package/node_modules/iconv-lite/package.json +2 -2
  188. package/node_modules/iconv-lite/types/encodings.d.ts +2 -0
  189. package/node_modules/path-to-regexp/Readme.md +3 -3
  190. package/node_modules/path-to-regexp/dist/index.js +135 -158
  191. package/node_modules/path-to-regexp/dist/index.js.map +1 -1
  192. package/node_modules/path-to-regexp/package.json +2 -2
  193. package/node_modules/qs/CHANGELOG.md +11 -0
  194. package/node_modules/qs/dist/qs.js +17 -17
  195. package/node_modules/qs/lib/parse.js +19 -8
  196. package/node_modules/qs/lib/utils.js +47 -8
  197. package/node_modules/qs/package.json +6 -5
  198. package/node_modules/qs/test/parse.js +230 -0
  199. package/node_modules/qs/test/utils.js +194 -0
  200. package/node_modules/typebox/build/compile/validator.d.mts +1 -4
  201. package/node_modules/typebox/build/guard/guard.d.mts +1 -1
  202. package/node_modules/typebox/build/guard/guard.mjs +5 -2
  203. package/node_modules/typebox/build/guard/string.mjs +16 -4
  204. package/node_modules/typebox/build/schema/engine/_functions.mjs +34 -15
  205. package/node_modules/typebox/build/type/action/readonly_object.d.mts +5 -0
  206. package/node_modules/typebox/build/type/action/readonly_object.mjs +8 -0
  207. package/node_modules/typebox/build/typebox.d.mts +1 -1
  208. package/node_modules/typebox/build/typebox.mjs +1 -1
  209. package/node_modules/typebox/package.json +1 -1
  210. package/node_modules/typebox/readme.md +29 -32
  211. package/node_modules/{es-set-tostringtag → undici}/LICENSE +1 -1
  212. package/node_modules/undici/README.md +741 -0
  213. package/node_modules/undici/docs/docs/api/Agent.md +84 -0
  214. package/node_modules/undici/docs/docs/api/BalancedPool.md +99 -0
  215. package/node_modules/undici/docs/docs/api/CacheStorage.md +30 -0
  216. package/node_modules/undici/docs/docs/api/CacheStore.md +164 -0
  217. package/node_modules/undici/docs/docs/api/Client.md +288 -0
  218. package/node_modules/undici/docs/docs/api/ClientStats.md +27 -0
  219. package/node_modules/undici/docs/docs/api/Connector.md +115 -0
  220. package/node_modules/undici/docs/docs/api/ContentType.md +57 -0
  221. package/node_modules/undici/docs/docs/api/Cookies.md +128 -0
  222. package/node_modules/undici/docs/docs/api/Debug.md +62 -0
  223. package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +315 -0
  224. package/node_modules/undici/docs/docs/api/Dispatcher.md +1392 -0
  225. package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +159 -0
  226. package/node_modules/undici/docs/docs/api/Errors.md +49 -0
  227. package/node_modules/undici/docs/docs/api/EventSource.md +45 -0
  228. package/node_modules/undici/docs/docs/api/Fetch.md +60 -0
  229. package/node_modules/undici/docs/docs/api/GlobalInstallation.md +139 -0
  230. package/node_modules/undici/docs/docs/api/H2CClient.md +263 -0
  231. package/node_modules/undici/docs/docs/api/MockAgent.md +603 -0
  232. package/node_modules/undici/docs/docs/api/MockCallHistory.md +197 -0
  233. package/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +43 -0
  234. package/node_modules/undici/docs/docs/api/MockClient.md +81 -0
  235. package/node_modules/undici/docs/docs/api/MockErrors.md +12 -0
  236. package/node_modules/undici/docs/docs/api/MockPool.md +555 -0
  237. package/node_modules/undici/docs/docs/api/Pool.md +84 -0
  238. package/node_modules/undici/docs/docs/api/PoolStats.md +35 -0
  239. package/node_modules/undici/docs/docs/api/ProxyAgent.md +229 -0
  240. package/node_modules/undici/docs/docs/api/RedirectHandler.md +93 -0
  241. package/node_modules/undici/docs/docs/api/RetryAgent.md +50 -0
  242. package/node_modules/undici/docs/docs/api/RetryHandler.md +118 -0
  243. package/node_modules/undici/docs/docs/api/RoundRobinPool.md +145 -0
  244. package/node_modules/undici/docs/docs/api/SnapshotAgent.md +616 -0
  245. package/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +275 -0
  246. package/node_modules/undici/docs/docs/api/Util.md +25 -0
  247. package/node_modules/undici/docs/docs/api/WebSocket.md +141 -0
  248. package/node_modules/undici/docs/docs/api/api-lifecycle.md +91 -0
  249. package/node_modules/undici/docs/docs/best-practices/client-certificate.md +64 -0
  250. package/node_modules/undici/docs/docs/best-practices/crawling.md +58 -0
  251. package/node_modules/undici/docs/docs/best-practices/mocking-request.md +190 -0
  252. package/node_modules/undici/docs/docs/best-practices/proxy.md +127 -0
  253. package/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +224 -0
  254. package/node_modules/undici/docs/docs/best-practices/writing-tests.md +63 -0
  255. package/node_modules/undici/index-fetch.js +65 -0
  256. package/node_modules/undici/index.d.ts +3 -0
  257. package/node_modules/undici/index.js +234 -0
  258. package/node_modules/undici/lib/api/abort-signal.js +59 -0
  259. package/node_modules/undici/lib/api/api-connect.js +110 -0
  260. package/node_modules/undici/lib/api/api-pipeline.js +252 -0
  261. package/node_modules/undici/lib/api/api-request.js +214 -0
  262. package/node_modules/undici/lib/api/api-stream.js +209 -0
  263. package/node_modules/undici/lib/api/api-upgrade.js +111 -0
  264. package/node_modules/undici/lib/api/index.js +7 -0
  265. package/node_modules/undici/lib/api/readable.js +580 -0
  266. package/node_modules/undici/lib/cache/memory-cache-store.js +234 -0
  267. package/node_modules/undici/lib/cache/sqlite-cache-store.js +461 -0
  268. package/node_modules/undici/lib/core/connect.js +153 -0
  269. package/node_modules/undici/lib/core/constants.js +143 -0
  270. package/node_modules/undici/lib/core/diagnostics.js +227 -0
  271. package/node_modules/undici/lib/core/errors.js +477 -0
  272. package/node_modules/undici/lib/core/request.js +453 -0
  273. package/node_modules/undici/lib/core/socks5-client.js +422 -0
  274. package/node_modules/undici/lib/core/socks5-utils.js +212 -0
  275. package/node_modules/undici/lib/core/symbols.js +75 -0
  276. package/node_modules/undici/lib/core/tree.js +160 -0
  277. package/node_modules/undici/lib/core/util.js +992 -0
  278. package/node_modules/undici/lib/dispatcher/agent.js +158 -0
  279. package/node_modules/undici/lib/dispatcher/balanced-pool.js +219 -0
  280. package/node_modules/undici/lib/dispatcher/client-h1.js +1722 -0
  281. package/node_modules/undici/lib/dispatcher/client-h2.js +995 -0
  282. package/node_modules/undici/lib/dispatcher/client.js +664 -0
  283. package/node_modules/undici/lib/dispatcher/dispatcher-base.js +184 -0
  284. package/node_modules/undici/lib/dispatcher/dispatcher.js +48 -0
  285. package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +146 -0
  286. package/node_modules/undici/lib/dispatcher/fixed-queue.js +135 -0
  287. package/node_modules/undici/lib/dispatcher/h2c-client.js +51 -0
  288. package/node_modules/undici/lib/dispatcher/pool-base.js +214 -0
  289. package/node_modules/undici/lib/dispatcher/pool.js +118 -0
  290. package/node_modules/undici/lib/dispatcher/proxy-agent.js +319 -0
  291. package/node_modules/undici/lib/dispatcher/retry-agent.js +35 -0
  292. package/node_modules/undici/lib/dispatcher/round-robin-pool.js +137 -0
  293. package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +260 -0
  294. package/node_modules/undici/lib/encoding/index.js +33 -0
  295. package/node_modules/undici/lib/global.js +59 -0
  296. package/node_modules/undici/lib/handler/cache-handler.js +578 -0
  297. package/node_modules/undici/lib/handler/cache-revalidation-handler.js +124 -0
  298. package/node_modules/undici/lib/handler/decorator-handler.js +67 -0
  299. package/node_modules/undici/lib/handler/deduplication-handler.js +460 -0
  300. package/node_modules/undici/lib/handler/redirect-handler.js +238 -0
  301. package/node_modules/undici/lib/handler/retry-handler.js +394 -0
  302. package/node_modules/undici/lib/handler/unwrap-handler.js +106 -0
  303. package/node_modules/undici/lib/handler/wrap-handler.js +105 -0
  304. package/node_modules/undici/lib/interceptor/cache.js +495 -0
  305. package/node_modules/undici/lib/interceptor/decompress.js +259 -0
  306. package/node_modules/undici/lib/interceptor/deduplicate.js +117 -0
  307. package/node_modules/undici/lib/interceptor/dns.js +571 -0
  308. package/node_modules/undici/lib/interceptor/dump.js +112 -0
  309. package/node_modules/undici/lib/interceptor/redirect.js +21 -0
  310. package/node_modules/undici/lib/interceptor/response-error.js +95 -0
  311. package/node_modules/undici/lib/interceptor/retry.js +19 -0
  312. package/node_modules/undici/lib/llhttp/.gitkeep +0 -0
  313. package/node_modules/undici/lib/llhttp/constants.d.ts +195 -0
  314. package/node_modules/undici/lib/llhttp/constants.js +531 -0
  315. package/node_modules/undici/lib/llhttp/llhttp-wasm.js +15 -0
  316. package/node_modules/undici/lib/llhttp/llhttp_simd-wasm.js +15 -0
  317. package/node_modules/undici/lib/llhttp/utils.d.ts +2 -0
  318. package/node_modules/undici/lib/llhttp/utils.js +12 -0
  319. package/node_modules/undici/lib/mock/mock-agent.js +232 -0
  320. package/node_modules/undici/lib/mock/mock-call-history.js +248 -0
  321. package/node_modules/undici/lib/mock/mock-client.js +68 -0
  322. package/node_modules/undici/lib/mock/mock-errors.js +29 -0
  323. package/node_modules/undici/lib/mock/mock-interceptor.js +209 -0
  324. package/node_modules/undici/lib/mock/mock-pool.js +68 -0
  325. package/node_modules/undici/lib/mock/mock-symbols.js +32 -0
  326. package/node_modules/undici/lib/mock/mock-utils.js +486 -0
  327. package/node_modules/undici/lib/mock/pending-interceptors-formatter.js +43 -0
  328. package/node_modules/undici/lib/mock/snapshot-agent.js +353 -0
  329. package/node_modules/undici/lib/mock/snapshot-recorder.js +588 -0
  330. package/node_modules/undici/lib/mock/snapshot-utils.js +158 -0
  331. package/node_modules/undici/lib/util/cache.js +414 -0
  332. package/node_modules/undici/lib/util/date.js +653 -0
  333. package/node_modules/undici/lib/util/promise.js +28 -0
  334. package/node_modules/undici/lib/util/runtime-features.js +124 -0
  335. package/node_modules/undici/lib/util/stats.js +32 -0
  336. package/node_modules/undici/lib/util/timers.js +425 -0
  337. package/node_modules/undici/lib/web/cache/cache.js +864 -0
  338. package/node_modules/undici/lib/web/cache/cachestorage.js +152 -0
  339. package/node_modules/undici/lib/web/cache/util.js +45 -0
  340. package/node_modules/undici/lib/web/cookies/constants.js +12 -0
  341. package/node_modules/undici/lib/web/cookies/index.js +199 -0
  342. package/node_modules/undici/lib/web/cookies/parse.js +314 -0
  343. package/node_modules/undici/lib/web/cookies/util.js +282 -0
  344. package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +399 -0
  345. package/node_modules/undici/lib/web/eventsource/eventsource.js +501 -0
  346. package/node_modules/undici/lib/web/eventsource/util.js +29 -0
  347. package/node_modules/{has-tostringtag → undici/lib/web/fetch}/LICENSE +1 -1
  348. package/node_modules/undici/lib/web/fetch/body.js +509 -0
  349. package/node_modules/undici/lib/web/fetch/constants.js +131 -0
  350. package/node_modules/undici/lib/web/fetch/data-url.js +596 -0
  351. package/node_modules/undici/lib/web/fetch/formdata-parser.js +586 -0
  352. package/node_modules/undici/lib/web/fetch/formdata.js +259 -0
  353. package/node_modules/undici/lib/web/fetch/global.js +40 -0
  354. package/node_modules/undici/lib/web/fetch/headers.js +719 -0
  355. package/node_modules/undici/lib/web/fetch/index.js +2413 -0
  356. package/node_modules/undici/lib/web/fetch/request.js +1115 -0
  357. package/node_modules/undici/lib/web/fetch/response.js +641 -0
  358. package/node_modules/undici/lib/web/fetch/util.js +1522 -0
  359. package/node_modules/undici/lib/web/infra/index.js +229 -0
  360. package/node_modules/undici/lib/web/subresource-integrity/Readme.md +9 -0
  361. package/node_modules/undici/lib/web/subresource-integrity/subresource-integrity.js +307 -0
  362. package/node_modules/undici/lib/web/webidl/index.js +1006 -0
  363. package/node_modules/undici/lib/web/websocket/connection.js +329 -0
  364. package/node_modules/undici/lib/web/websocket/constants.js +126 -0
  365. package/node_modules/undici/lib/web/websocket/events.js +331 -0
  366. package/node_modules/undici/lib/web/websocket/frame.js +133 -0
  367. package/node_modules/undici/lib/web/websocket/permessage-deflate.js +100 -0
  368. package/node_modules/undici/lib/web/websocket/receiver.js +507 -0
  369. package/node_modules/undici/lib/web/websocket/sender.js +109 -0
  370. package/node_modules/undici/lib/web/websocket/stream/websocketerror.js +104 -0
  371. package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +498 -0
  372. package/node_modules/undici/lib/web/websocket/util.js +347 -0
  373. package/node_modules/undici/lib/web/websocket/websocket.js +758 -0
  374. package/node_modules/undici/package.json +152 -0
  375. package/node_modules/undici/scripts/strip-comments.js +10 -0
  376. package/node_modules/undici/types/README.md +6 -0
  377. package/node_modules/undici/types/agent.d.ts +32 -0
  378. package/node_modules/undici/types/api.d.ts +43 -0
  379. package/node_modules/undici/types/balanced-pool.d.ts +30 -0
  380. package/node_modules/undici/types/cache-interceptor.d.ts +179 -0
  381. package/node_modules/undici/types/cache.d.ts +36 -0
  382. package/node_modules/undici/types/client-stats.d.ts +15 -0
  383. package/node_modules/undici/types/client.d.ts +139 -0
  384. package/node_modules/undici/types/connector.d.ts +36 -0
  385. package/node_modules/undici/types/content-type.d.ts +21 -0
  386. package/node_modules/undici/types/cookies.d.ts +30 -0
  387. package/node_modules/undici/types/diagnostics-channel.d.ts +74 -0
  388. package/node_modules/undici/types/dispatcher.d.ts +275 -0
  389. package/node_modules/undici/types/env-http-proxy-agent.d.ts +22 -0
  390. package/node_modules/undici/types/errors.d.ts +177 -0
  391. package/node_modules/undici/types/eventsource.d.ts +66 -0
  392. package/node_modules/undici/types/fetch.d.ts +231 -0
  393. package/node_modules/undici/types/formdata.d.ts +114 -0
  394. package/node_modules/undici/types/global-dispatcher.d.ts +9 -0
  395. package/node_modules/undici/types/global-origin.d.ts +7 -0
  396. package/node_modules/undici/types/h2c-client.d.ts +73 -0
  397. package/node_modules/undici/types/handlers.d.ts +14 -0
  398. package/node_modules/undici/types/header.d.ts +160 -0
  399. package/node_modules/undici/types/index.d.ts +91 -0
  400. package/node_modules/undici/types/interceptors.d.ts +80 -0
  401. package/node_modules/undici/types/mock-agent.d.ts +68 -0
  402. package/node_modules/undici/types/mock-call-history.d.ts +111 -0
  403. package/node_modules/undici/types/mock-client.d.ts +27 -0
  404. package/node_modules/undici/types/mock-errors.d.ts +12 -0
  405. package/node_modules/undici/types/mock-interceptor.d.ts +94 -0
  406. package/node_modules/undici/types/mock-pool.d.ts +27 -0
  407. package/node_modules/undici/types/patch.d.ts +29 -0
  408. package/node_modules/undici/types/pool-stats.d.ts +19 -0
  409. package/node_modules/undici/types/pool.d.ts +41 -0
  410. package/node_modules/undici/types/proxy-agent.d.ts +29 -0
  411. package/node_modules/undici/types/readable.d.ts +68 -0
  412. package/node_modules/undici/types/retry-agent.d.ts +8 -0
  413. package/node_modules/undici/types/retry-handler.d.ts +125 -0
  414. package/node_modules/undici/types/round-robin-pool.d.ts +41 -0
  415. package/node_modules/undici/types/snapshot-agent.d.ts +109 -0
  416. package/node_modules/undici/types/socks5-proxy-agent.d.ts +25 -0
  417. package/node_modules/undici/types/util.d.ts +18 -0
  418. package/node_modules/undici/types/utility.d.ts +7 -0
  419. package/node_modules/undici/types/webidl.d.ts +347 -0
  420. package/node_modules/undici/types/websocket.d.ts +188 -0
  421. package/node_modules/ws/lib/receiver.js +15 -32
  422. package/node_modules/ws/lib/websocket-server.js +4 -4
  423. package/node_modules/ws/lib/websocket.js +4 -4
  424. package/node_modules/ws/package.json +5 -1
  425. package/openclaw.plugin.json +1064 -1243
  426. package/package.json +99 -18
  427. package/dist/action-runtime.runtime-DEWOcFM4.js +0 -2
  428. package/dist/config-api-Df9a1B8C.js +0 -3
  429. package/dist/doctor-contract-DKpe5VBr.js +0 -202
  430. package/dist/interactive-replies-Dm_t2R9S.js +0 -197
  431. package/dist/interactive-replies-api.js +0 -2
  432. package/node_modules/@slack/bolt/dist/WorkflowStep.d.ts +0 -159
  433. package/node_modules/@slack/bolt/dist/WorkflowStep.d.ts.map +0 -1
  434. package/node_modules/@slack/bolt/dist/WorkflowStep.js +0 -217
  435. package/node_modules/@slack/bolt/dist/WorkflowStep.js.map +0 -1
  436. package/node_modules/@slack/bolt/dist/types/actions/workflow-step-edit.d.ts +0 -49
  437. package/node_modules/@slack/bolt/dist/types/actions/workflow-step-edit.d.ts.map +0 -1
  438. package/node_modules/@slack/bolt/dist/types/actions/workflow-step-edit.js +0 -3
  439. package/node_modules/@slack/bolt/dist/types/actions/workflow-step-edit.js.map +0 -1
  440. package/node_modules/@types/ws/LICENSE +0 -21
  441. package/node_modules/@types/ws/README.md +0 -15
  442. package/node_modules/@types/ws/index.d.mts +0 -451
  443. package/node_modules/@types/ws/index.d.ts +0 -445
  444. package/node_modules/@types/ws/package.json +0 -72
  445. package/node_modules/asynckit/LICENSE +0 -21
  446. package/node_modules/asynckit/README.md +0 -233
  447. package/node_modules/asynckit/bench.js +0 -76
  448. package/node_modules/asynckit/index.js +0 -6
  449. package/node_modules/asynckit/lib/abort.js +0 -29
  450. package/node_modules/asynckit/lib/async.js +0 -34
  451. package/node_modules/asynckit/lib/defer.js +0 -26
  452. package/node_modules/asynckit/lib/iterate.js +0 -75
  453. package/node_modules/asynckit/lib/readable_asynckit.js +0 -91
  454. package/node_modules/asynckit/lib/readable_parallel.js +0 -25
  455. package/node_modules/asynckit/lib/readable_serial.js +0 -25
  456. package/node_modules/asynckit/lib/readable_serial_ordered.js +0 -29
  457. package/node_modules/asynckit/lib/state.js +0 -37
  458. package/node_modules/asynckit/lib/streamify.js +0 -141
  459. package/node_modules/asynckit/lib/terminator.js +0 -29
  460. package/node_modules/asynckit/package.json +0 -63
  461. package/node_modules/asynckit/parallel.js +0 -43
  462. package/node_modules/asynckit/serial.js +0 -17
  463. package/node_modules/asynckit/serialOrdered.js +0 -75
  464. package/node_modules/asynckit/stream.js +0 -21
  465. package/node_modules/axios/CHANGELOG.md +0 -1676
  466. package/node_modules/axios/LICENSE +0 -7
  467. package/node_modules/axios/MIGRATION_GUIDE.md +0 -877
  468. package/node_modules/axios/README.md +0 -2391
  469. package/node_modules/axios/dist/axios.js +0 -4864
  470. package/node_modules/axios/dist/axios.js.map +0 -1
  471. package/node_modules/axios/dist/axios.min.js +0 -5
  472. package/node_modules/axios/dist/axios.min.js.map +0 -1
  473. package/node_modules/axios/dist/browser/axios.cjs +0 -4701
  474. package/node_modules/axios/dist/browser/axios.cjs.map +0 -1
  475. package/node_modules/axios/dist/esm/axios.js +0 -4722
  476. package/node_modules/axios/dist/esm/axios.js.map +0 -1
  477. package/node_modules/axios/dist/esm/axios.min.js +0 -3
  478. package/node_modules/axios/dist/esm/axios.min.js.map +0 -1
  479. package/node_modules/axios/dist/node/axios.cjs +0 -5348
  480. package/node_modules/axios/dist/node/axios.cjs.map +0 -1
  481. package/node_modules/axios/index.d.cts +0 -715
  482. package/node_modules/axios/index.d.ts +0 -734
  483. package/node_modules/axios/index.js +0 -45
  484. package/node_modules/axios/lib/adapters/README.md +0 -36
  485. package/node_modules/axios/lib/adapters/adapters.js +0 -132
  486. package/node_modules/axios/lib/adapters/fetch.js +0 -469
  487. package/node_modules/axios/lib/adapters/http.js +0 -1202
  488. package/node_modules/axios/lib/adapters/xhr.js +0 -226
  489. package/node_modules/axios/lib/axios.js +0 -89
  490. package/node_modules/axios/lib/cancel/CancelToken.js +0 -135
  491. package/node_modules/axios/lib/cancel/CanceledError.js +0 -22
  492. package/node_modules/axios/lib/cancel/isCancel.js +0 -5
  493. package/node_modules/axios/lib/core/Axios.js +0 -281
  494. package/node_modules/axios/lib/core/AxiosError.js +0 -176
  495. package/node_modules/axios/lib/core/AxiosHeaders.js +0 -380
  496. package/node_modules/axios/lib/core/InterceptorManager.js +0 -72
  497. package/node_modules/axios/lib/core/README.md +0 -8
  498. package/node_modules/axios/lib/core/buildFullPath.js +0 -22
  499. package/node_modules/axios/lib/core/dispatchRequest.js +0 -89
  500. package/node_modules/axios/lib/core/mergeConfig.js +0 -124
  501. package/node_modules/axios/lib/core/settle.js +0 -27
  502. package/node_modules/axios/lib/core/transformData.js +0 -28
  503. package/node_modules/axios/lib/defaults/index.js +0 -177
  504. package/node_modules/axios/lib/defaults/transitional.js +0 -8
  505. package/node_modules/axios/lib/env/README.md +0 -3
  506. package/node_modules/axios/lib/env/classes/FormData.js +0 -2
  507. package/node_modules/axios/lib/env/data.js +0 -1
  508. package/node_modules/axios/lib/helpers/AxiosTransformStream.js +0 -156
  509. package/node_modules/axios/lib/helpers/AxiosURLSearchParams.js +0 -61
  510. package/node_modules/axios/lib/helpers/HttpStatusCode.js +0 -77
  511. package/node_modules/axios/lib/helpers/README.md +0 -7
  512. package/node_modules/axios/lib/helpers/ZlibHeaderTransformStream.js +0 -29
  513. package/node_modules/axios/lib/helpers/bind.js +0 -14
  514. package/node_modules/axios/lib/helpers/buildURL.js +0 -66
  515. package/node_modules/axios/lib/helpers/callbackify.js +0 -18
  516. package/node_modules/axios/lib/helpers/combineURLs.js +0 -15
  517. package/node_modules/axios/lib/helpers/composeSignals.js +0 -56
  518. package/node_modules/axios/lib/helpers/cookies.js +0 -60
  519. package/node_modules/axios/lib/helpers/deprecatedMethod.js +0 -31
  520. package/node_modules/axios/lib/helpers/estimateDataURLDecodedBytes.js +0 -100
  521. package/node_modules/axios/lib/helpers/formDataToJSON.js +0 -97
  522. package/node_modules/axios/lib/helpers/formDataToStream.js +0 -119
  523. package/node_modules/axios/lib/helpers/fromDataURI.js +0 -53
  524. package/node_modules/axios/lib/helpers/isAbsoluteURL.js +0 -19
  525. package/node_modules/axios/lib/helpers/isAxiosError.js +0 -14
  526. package/node_modules/axios/lib/helpers/isURLSameOrigin.js +0 -16
  527. package/node_modules/axios/lib/helpers/null.js +0 -2
  528. package/node_modules/axios/lib/helpers/parseHeaders.js +0 -69
  529. package/node_modules/axios/lib/helpers/parseProtocol.js +0 -6
  530. package/node_modules/axios/lib/helpers/progressEventReducer.js +0 -51
  531. package/node_modules/axios/lib/helpers/readBlob.js +0 -15
  532. package/node_modules/axios/lib/helpers/resolveConfig.js +0 -106
  533. package/node_modules/axios/lib/helpers/shouldBypassProxy.js +0 -178
  534. package/node_modules/axios/lib/helpers/speedometer.js +0 -55
  535. package/node_modules/axios/lib/helpers/spread.js +0 -28
  536. package/node_modules/axios/lib/helpers/throttle.js +0 -44
  537. package/node_modules/axios/lib/helpers/toFormData.js +0 -249
  538. package/node_modules/axios/lib/helpers/toURLEncodedForm.js +0 -19
  539. package/node_modules/axios/lib/helpers/trackStream.js +0 -89
  540. package/node_modules/axios/lib/helpers/validator.js +0 -112
  541. package/node_modules/axios/lib/platform/browser/classes/Blob.js +0 -3
  542. package/node_modules/axios/lib/platform/browser/classes/FormData.js +0 -3
  543. package/node_modules/axios/lib/platform/browser/classes/URLSearchParams.js +0 -4
  544. package/node_modules/axios/lib/platform/browser/index.js +0 -13
  545. package/node_modules/axios/lib/platform/common/utils.js +0 -52
  546. package/node_modules/axios/lib/platform/index.js +0 -7
  547. package/node_modules/axios/lib/platform/node/classes/FormData.js +0 -3
  548. package/node_modules/axios/lib/platform/node/classes/URLSearchParams.js +0 -4
  549. package/node_modules/axios/lib/platform/node/index.js +0 -37
  550. package/node_modules/axios/lib/utils.js +0 -931
  551. package/node_modules/axios/package.json +0 -183
  552. package/node_modules/combined-stream/License +0 -19
  553. package/node_modules/combined-stream/Readme.md +0 -138
  554. package/node_modules/combined-stream/lib/combined_stream.js +0 -208
  555. package/node_modules/combined-stream/package.json +0 -25
  556. package/node_modules/delayed-stream/.npmignore +0 -1
  557. package/node_modules/delayed-stream/License +0 -19
  558. package/node_modules/delayed-stream/Makefile +0 -7
  559. package/node_modules/delayed-stream/Readme.md +0 -141
  560. package/node_modules/delayed-stream/lib/delayed_stream.js +0 -107
  561. package/node_modules/delayed-stream/package.json +0 -27
  562. package/node_modules/es-set-tostringtag/.eslintrc +0 -13
  563. package/node_modules/es-set-tostringtag/.nycrc +0 -9
  564. package/node_modules/es-set-tostringtag/CHANGELOG.md +0 -67
  565. package/node_modules/es-set-tostringtag/README.md +0 -53
  566. package/node_modules/es-set-tostringtag/index.d.ts +0 -10
  567. package/node_modules/es-set-tostringtag/index.js +0 -35
  568. package/node_modules/es-set-tostringtag/package.json +0 -78
  569. package/node_modules/es-set-tostringtag/test/index.js +0 -85
  570. package/node_modules/es-set-tostringtag/tsconfig.json +0 -9
  571. package/node_modules/follow-redirects/LICENSE +0 -18
  572. package/node_modules/follow-redirects/README.md +0 -157
  573. package/node_modules/follow-redirects/debug.js +0 -15
  574. package/node_modules/follow-redirects/http.js +0 -1
  575. package/node_modules/follow-redirects/https.js +0 -1
  576. package/node_modules/follow-redirects/index.js +0 -709
  577. package/node_modules/follow-redirects/package.json +0 -58
  578. package/node_modules/form-data/CHANGELOG.md +0 -669
  579. package/node_modules/form-data/License +0 -19
  580. package/node_modules/form-data/README.md +0 -350
  581. package/node_modules/form-data/index.d.ts +0 -51
  582. package/node_modules/form-data/lib/browser.js +0 -4
  583. package/node_modules/form-data/lib/form_data.js +0 -514
  584. package/node_modules/form-data/lib/populate.js +0 -10
  585. package/node_modules/form-data/node_modules/mime-db/HISTORY.md +0 -507
  586. package/node_modules/form-data/node_modules/mime-db/LICENSE +0 -23
  587. package/node_modules/form-data/node_modules/mime-db/README.md +0 -100
  588. package/node_modules/form-data/node_modules/mime-db/db.json +0 -8519
  589. package/node_modules/form-data/node_modules/mime-db/index.js +0 -12
  590. package/node_modules/form-data/node_modules/mime-db/package.json +0 -60
  591. package/node_modules/form-data/node_modules/mime-types/HISTORY.md +0 -397
  592. package/node_modules/form-data/node_modules/mime-types/LICENSE +0 -23
  593. package/node_modules/form-data/node_modules/mime-types/README.md +0 -113
  594. package/node_modules/form-data/node_modules/mime-types/index.js +0 -188
  595. package/node_modules/form-data/node_modules/mime-types/package.json +0 -44
  596. package/node_modules/form-data/package.json +0 -83
  597. package/node_modules/has-tostringtag/.eslintrc +0 -5
  598. package/node_modules/has-tostringtag/.github/FUNDING.yml +0 -12
  599. package/node_modules/has-tostringtag/.nycrc +0 -13
  600. package/node_modules/has-tostringtag/CHANGELOG.md +0 -42
  601. package/node_modules/has-tostringtag/README.md +0 -46
  602. package/node_modules/has-tostringtag/index.d.ts +0 -3
  603. package/node_modules/has-tostringtag/index.js +0 -8
  604. package/node_modules/has-tostringtag/package.json +0 -108
  605. package/node_modules/has-tostringtag/shams.d.ts +0 -3
  606. package/node_modules/has-tostringtag/shams.js +0 -8
  607. package/node_modules/has-tostringtag/test/index.js +0 -21
  608. package/node_modules/has-tostringtag/test/shams/core-js.js +0 -31
  609. package/node_modules/has-tostringtag/test/shams/get-own-property-symbols.js +0 -30
  610. package/node_modules/has-tostringtag/test/tests.js +0 -15
  611. package/node_modules/has-tostringtag/tsconfig.json +0 -49
  612. package/node_modules/is-electron/LICENSE +0 -21
  613. package/node_modules/is-electron/README.md +0 -25
  614. package/node_modules/is-electron/index.d.ts +0 -2
  615. package/node_modules/is-electron/index.js +0 -21
  616. package/node_modules/is-electron/package.json +0 -34
  617. package/node_modules/is-stream/index.d.ts +0 -79
  618. package/node_modules/is-stream/index.js +0 -28
  619. package/node_modules/is-stream/license +0 -9
  620. package/node_modules/is-stream/package.json +0 -42
  621. package/node_modules/is-stream/readme.md +0 -60
  622. package/node_modules/proxy-from-env/LICENSE +0 -20
  623. package/node_modules/proxy-from-env/README.md +0 -163
  624. package/node_modules/proxy-from-env/index.cjs +0 -105
  625. package/node_modules/proxy-from-env/index.js +0 -103
  626. package/node_modules/proxy-from-env/package.json +0 -43
  627. package/npm-shrinkwrap.json +0 -1440
@@ -1,2082 +1,2235 @@
1
- import { u as resolveSlackReplyToMode } from "./accounts-DQEbYKh9.js";
1
+ import { t as resolveSlackReplyToMode } from "./account-reply-mode-DrUXogvO.js";
2
2
  import { i as parseSlackTarget } from "./target-parsing-N6lHogYZ.js";
3
3
  import "./targets-KmjP2E8s.js";
4
- import { C as hasSlackNativeDataBlock, F as SLACK_TEXT_LIMIT, N as SLACK_EDIT_TEXT_LIMIT, O as normalizeSlackOutboundText, Y as escapeSlackMrkdwn, _ as resolveSlackReplyRenderPlan, c as normalizeSlackAllowOwnerEntry, k as buildSlackBlocksFallbackText, s as normalizeAllowListLower, u as resolveSlackAllowListMatch, z as truncateSlackText } from "./group-policy-Bw-65m3V.js";
5
- import { n as isSlackInteractiveRepliesEnabled, t as compileSlackInteractiveReplies } from "./interactive-replies-Dm_t2R9S.js";
6
- import { r as formatSlackError } from "./probe-Borhn9PQ.js";
4
+ import { A as normalizeSlackOutboundText, C as hasSlackNativeDataBlock, F as SLACK_EDIT_TEXT_LIMIT, L as SLACK_TEXT_LIMIT, V as truncateSlackText, Z as escapeSlackMrkdwn, _ as resolveSlackReplyRenderPlan, c as normalizeSlackAllowOwnerEntry, j as buildSlackBlocksFallbackText, s as normalizeAllowListLower, u as resolveSlackAllowListMatch } from "./group-policy-CthIXE2T.js";
5
+ import { r as formatSlackError } from "./probe-BhQTDVBQ.js";
7
6
  import { n as resolveSlackNativeStreaming, r as resolveSlackStreamingMode, t as mapStreamingModeToSlackLegacyDraftStreamMode } from "./streaming-compat-3_X-21wW.js";
8
- import { S as buildSlackEditTextPayload, _ as resolveSlackThreadHistory, b as resolveSlackBlocksText, f as removeSlackReaction, l as reactSlackMessage, r as editSlackMessage, t as deleteSlackMessage, v as resolveSlackThreadStarter, x as formatSlackFileReference, y as chooseSlackPrimaryText } from "./actions-CF0tUY5q.js";
9
- import { c as hasSlackThreadParticipationWithPersistence, l as recordSlackThreadParticipation, r as sendMessageSlack, u as buildSlackNativeDataDeliveryPlan } from "./send-KuTDky7i.js";
10
- import { C as stripSlackMentionsForCommandDetection, _ as readSessionUpdatedAt, b as resolveStorePath, d as resolveSlackCommandIngress, f as resolveSlackEffectiveAllowFrom, g as resolveSlackChatType, h as normalizeSlackChannelType, l as resolveConversationLabel$1, m as parseSlackAssistantThreadMetadata, o as authorizeSlackDirectMessage, p as buildSlackAssistantThreadMetadata, r as resolveSlackRoomContextHints, u as authorizeSlackBotRoomMessage, v as resolveChannelContextVisibilityMode, x as updateLastRoute, y as resolveChannelResetConfig } from "./provider-B-HVL5ZE.js";
11
- import { n as resolveSlackChannelConfig } from "./policy-b_dOPDCq.js";
12
- import { a as resolveDeliveredSlackReplyThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as resolveSlackThreadTs, s as emitSlackMessageSentHooks, t as createSlackReplyDeliveryPlan } from "./replies-B4bOdrYb.js";
7
+ import { S as buildSlackEditTextPayload, _ as resolveSlackThreadHistory, b as resolveSlackBlocksText, f as removeSlackReaction, l as reactSlackMessage, r as editSlackMessage, t as deleteSlackMessage, v as resolveSlackThreadStarter, x as formatSlackFileReference, y as chooseSlackPrimaryText } from "./actions-BkOaiDSX.js";
8
+ import { c as hasSlackThreadParticipationWithPersistence, l as recordSlackThreadParticipation, r as sendMessageSlack, u as buildSlackNativeDataDeliveryPlan } from "./send-2IoDXPeB.js";
9
+ import { C as stripSlackMentionsForCommandDetection, _ as readSessionUpdatedAt, b as resolveStorePath, d as resolveSlackCommandIngress, f as resolveSlackEffectiveAllowFrom, g as resolveSlackChatType, h as normalizeSlackChannelType, l as resolveConversationLabel$1, m as parseSlackAssistantThreadMetadata, o as authorizeSlackDirectMessage, p as buildSlackAssistantThreadMetadata, r as resolveSlackRoomContextHints, u as authorizeSlackBotRoomMessage, v as resolveChannelContextVisibilityMode, x as updateLastRoute, y as resolveChannelResetConfig } from "./provider-PSppCU_K.js";
10
+ import { n as resolveSlackChannelConfig } from "./policy-DVg6FyMP.js";
11
+ import { a as resolveDeliveredSlackReplyThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as resolveSlackThreadTs, s as emitSlackMessageSentHooks, t as createSlackReplyDeliveryPlan } from "./replies-BkPPM2Ri.js";
13
12
  import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
14
13
  import { resolveAgentRoute, resolveInboundLastRouteSessionKey, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
15
14
  import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelMessageReplyPipeline, createChannelProgressDraftCompositor, createChannelProgressReceiptTracker, createDraftStreamLoop, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, formatChannelProgressDraftText, formatPlanChecklistLines, isChannelProgressDraftWorkToolName, mergeChannelProgressDraftLine, resolveAgentOutboundIdentity, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelProgressDraftConfig, resolveChannelProgressDraftMaxLineChars, resolveChannelProgressDraftMaxLines, resolveChannelProgressDraftRender, resolveChannelStreamingBlockEnabled, resolveChannelStreamingNativeTransport, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingSuppressDefaultToolProgressMessages } from "openclaw/plugin-sdk/channel-outbound";
16
15
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
17
- import { danger, logVerbose, shouldLogVerbose, sleep } from "openclaw/plugin-sdk/runtime-env";
16
+ import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
18
17
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
19
18
  import { createHash } from "node:crypto";
20
19
  import fs from "node:fs/promises";
21
- import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
20
+ import { formatErrorMessage, toErrorObject } from "openclaw/plugin-sdk/error-runtime";
22
21
  import { asDateTimestampMs, asFiniteNumberInRange, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
23
22
  import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
24
23
  import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist, shouldIncludeSupplementalContext } from "openclaw/plugin-sdk/security-runtime";
25
24
  import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
26
25
  import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
27
- import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
26
+ import { resolveDefaultAgentId, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
28
27
  import { mergePairLoopGuardConfig } from "openclaw/plugin-sdk/pair-loop-guard-runtime";
29
28
  import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
30
29
  import { resolveChannelImplicitMentions } from "openclaw/plugin-sdk/channel-ingress-runtime";
31
30
  import { buildChannelInboundEventContext, buildMentionRegexes, classifyChannelInboundEvent, dispatchChannelInboundTurn, formatInboundEnvelope, hasVisibleInboundReplyDispatch, implicitMentionKindWhen, logInboundDrop, matchesMentionWithExplicit, recordDroppedChannelInboundHistory, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, resolveInboundSupplementalSenderAllowed, resolveUnmentionedGroupInboundPolicy, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
32
- import { DEFAULT_TIMING, createStatusReactionController, logAckFailure, logTypingFailure, removeAckReactionAfterReply, resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
31
+ import { DEFAULT_TIMING, createStatusReactionController, logAckFailure, logTypingFailure, resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
33
32
  import { hasControlCommand } from "openclaw/plugin-sdk/command-detection";
34
33
  import { isAbortRequestText } from "openclaw/plugin-sdk/command-primitives-runtime";
35
34
  import { shouldHandleTextCommands } from "openclaw/plugin-sdk/command-surface";
36
35
  import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
37
36
  import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
38
- //#region extensions/slack/src/draft-stream.ts
39
- const DEFAULT_THROTTLE_MS = 1e3;
40
- function createSlackDraftStream(params) {
41
- const maxChars = Math.min(params.maxChars ?? 8e3, SLACK_TEXT_LIMIT);
42
- const throttleMs = Math.max(250, params.throttleMs ?? DEFAULT_THROTTLE_MS);
43
- const send = params.send ?? sendMessageSlack;
44
- const edit = params.edit ?? editSlackMessage;
45
- const remove = params.remove ?? deleteSlackMessage;
46
- let streamMessageId;
47
- let streamChannelId;
48
- let lastSentKey = "";
49
- let pendingUpdate;
50
- let stopped = false;
51
- const normalizeUpdate = (update) => typeof update === "string" ? { text: update } : update;
52
- const sendOrEditStreamMessage = async (text) => {
53
- if (stopped) return;
54
- const trimmed = text.trimEnd();
55
- if (!trimmed) return;
56
- if (trimmed.length > maxChars) {
57
- stopped = true;
58
- params.warn?.(`slack stream preview stopped (text length ${trimmed.length} > ${maxChars})`);
59
- return;
60
- }
61
- const update = normalizeUpdate(pendingUpdate ?? text);
62
- const blocks = update.text === text ? update.blocks : void 0;
63
- const sentKey = `${trimmed}\n${blocks ? JSON.stringify(blocks) : ""}`;
64
- if (sentKey === lastSentKey) return;
65
- lastSentKey = sentKey;
66
- try {
67
- if (streamChannelId && streamMessageId) {
68
- await edit(streamChannelId, streamMessageId, trimmed, {
69
- cfg: params.cfg,
70
- token: params.token,
71
- accountId: params.accountId,
72
- ...params.eventScope ? { client: params.eventScope.client } : {},
73
- ...blocks ? { blocks } : {}
74
- });
75
- return;
76
- }
77
- const sent = await send(params.target, trimmed, {
78
- cfg: params.cfg,
79
- token: params.token,
80
- accountId: params.accountId,
81
- threadTs: params.resolveThreadTs?.(),
82
- identity: params.identity,
83
- ...params.eventScope ? {
84
- client: params.eventScope.client,
85
- enterpriseEventScope: params.eventScope
86
- } : {},
87
- ...params.metadata ? { metadata: params.metadata } : {},
88
- ...blocks ? { blocks } : {}
89
- });
90
- streamChannelId = sent.channelId || streamChannelId;
91
- streamMessageId = sent.messageId || streamMessageId;
92
- if (!streamChannelId || !streamMessageId) {
93
- stopped = true;
94
- params.warn?.("slack stream preview stopped (missing identifiers from sendMessage)");
95
- return;
96
- }
97
- params.onMessageSent?.();
98
- } catch (err) {
99
- stopped = true;
100
- params.warn?.(`slack stream preview failed: ${formatSlackError(err)}`);
101
- }
102
- };
103
- const loop = createDraftStreamLoop({
104
- throttleMs,
105
- isStopped: () => stopped,
106
- sendOrEditStreamMessage
37
+ //#region extensions/slack/src/streaming.ts
38
+ /**
39
+ * Thrown when Slack definitively rejects a stream flush/finalize while text
40
+ * remains buffered locally by the Slack SDK. Carries the pending text so the
41
+ * caller can deliver it via the normal Slack reply path.
42
+ */
43
+ var SlackStreamNotDeliveredError = class extends Error {
44
+ constructor(pendingText, slackCode) {
45
+ super(`slack-stream: finalize failed with ${slackCode} before buffered text reached Slack (${pendingText.length} chars pending)`);
46
+ this.name = "SlackStreamNotDeliveredError";
47
+ this.pendingText = pendingText;
48
+ this.slackCode = slackCode;
49
+ }
50
+ };
51
+ /**
52
+ * Start a new Slack text stream.
53
+ *
54
+ * Returns a {@link SlackStreamSession} that should be passed to
55
+ * {@link appendSlackStream} and {@link stopSlackStream}.
56
+ *
57
+ * The first chunk of text can optionally be included via `text`.
58
+ */
59
+ async function startSlackStream(params) {
60
+ const { client, channel, threadTs, text, chunks, taskDisplayMode, teamId, userId, identity } = params;
61
+ const identityPayload = identity?.iconUrl ? {
62
+ ...identity.username ? { username: identity.username } : {},
63
+ icon_url: identity.iconUrl
64
+ } : identity?.iconEmoji ? {
65
+ ...identity.username ? { username: identity.username } : {},
66
+ icon_emoji: identity.iconEmoji
67
+ } : identity?.username ? { username: identity.username } : {};
68
+ logVerbose(`slack-stream: starting stream in ${channel} thread=${threadTs}${teamId ? ` team=${teamId}` : ""}${userId ? ` user=${userId}` : ""}`);
69
+ const streamer = client.chatStream({
70
+ channel,
71
+ thread_ts: threadTs,
72
+ ...taskDisplayMode ? { task_display_mode: taskDisplayMode } : {},
73
+ ...teamId ? { recipient_team_id: teamId } : {},
74
+ ...userId ? { recipient_user_id: userId } : {},
75
+ ...identityPayload
107
76
  });
108
- const stop = () => {
109
- stopped = true;
110
- loop.stop();
111
- };
112
- const discardPending = async () => {
113
- stop();
114
- await loop.waitForInFlight();
77
+ const session = {
78
+ streamer,
79
+ channel,
80
+ threadTs,
81
+ stopped: false,
82
+ delivered: false,
83
+ pendingText: ""
115
84
  };
116
- const clear = async () => {
117
- await discardPending();
118
- const channelId = streamChannelId;
119
- const messageId = streamMessageId;
120
- streamChannelId = void 0;
121
- streamMessageId = void 0;
122
- lastSentKey = "";
123
- pendingUpdate = void 0;
124
- if (!channelId || !messageId) return;
85
+ if (text || chunks?.length) {
86
+ if (text) session.pendingText += text;
125
87
  try {
126
- await remove(channelId, messageId, {
127
- token: params.token,
128
- accountId: params.accountId,
129
- ...params.eventScope ? { client: params.eventScope.client } : {}
88
+ const result = await streamer.append({
89
+ ...text ? { markdown_text: text } : {},
90
+ ...chunks?.length ? { chunks } : {}
130
91
  });
92
+ if (result) {
93
+ session.delivered = true;
94
+ session.pendingText = "";
95
+ }
96
+ logVerbose(`slack-stream: appended initial payload (${text?.length ?? 0} chars, ${chunks?.length ?? 0} chunks, ${result ? "flushed" : "buffered"})`);
131
97
  } catch (err) {
132
- params.warn?.(`slack stream preview cleanup failed: ${formatSlackError(err)}`);
98
+ if (isBenignSlackFinalizeError(err) && session.pendingText) throw new SlackStreamNotDeliveredError(session.pendingText, extractSlackErrorCode(err) ?? "unknown");
99
+ throw err;
133
100
  }
134
- };
135
- const forceNewMessage = () => {
136
- streamMessageId = void 0;
137
- streamChannelId = void 0;
138
- lastSentKey = "";
139
- pendingUpdate = void 0;
140
- loop.resetPending();
141
- };
142
- params.log?.(`slack stream preview ready (maxChars=${maxChars}, throttleMs=${throttleMs})`);
143
- return {
144
- update: (update) => {
145
- const normalized = normalizeUpdate(update);
146
- pendingUpdate = update;
147
- loop.update(normalized.text);
148
- },
149
- flush: loop.flush,
150
- clear,
151
- discardPending,
152
- seal: discardPending,
153
- stop,
154
- forceNewMessage,
155
- messageId: () => streamMessageId,
156
- channelId: () => streamChannelId
157
- };
158
- }
159
- //#endregion
160
- //#region extensions/slack/src/progress-blocks.ts
161
- const SLACK_PROGRESS_FIELD_MAX = 1800;
162
- const DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS = 120;
163
- const DEFAULT_SLACK_PROGRESS_TASK_DETAIL_MAX_CHARS = 48;
164
- const SLACK_PROGRESS_CHUNK_TEXT_MAX = 256;
165
- const SLACK_PROGRESS_TASK_TITLE_MAX = 120;
166
- const SLACK_PROGRESS_PLAN_FALLBACK_TITLE = "Thinking";
167
- function field(text) {
168
- return {
169
- type: "mrkdwn",
170
- text: truncateSlackText(text, SLACK_PROGRESS_FIELD_MAX)
171
- };
172
- }
173
- function resolveMaxLineChars(value, fallback) {
174
- return value && value > 0 ? Math.floor(value) : fallback;
175
- }
176
- function compactDetail(value, maxChars) {
177
- const normalized = value.replace(/\s+/g, " ").trim();
178
- const chars = Array.from(normalized);
179
- if (chars.length <= maxChars) return normalized;
180
- if (maxChars <= 1) return "…";
181
- const keepStart = Math.max(1, Math.ceil((maxChars - 1) * .45));
182
- const keepEnd = Math.max(1, maxChars - keepStart - 1);
183
- return `${chars.slice(0, keepStart).join("").trimEnd()}…${chars.slice(-keepEnd).join("").trimStart()}`;
184
- }
185
- function compactTitle(value) {
186
- return truncateSlackText(value.replace(/\s+/g, " ").trim(), SLACK_PROGRESS_TASK_TITLE_MAX);
187
- }
188
- function compactChunkText(value) {
189
- return truncateSlackText(value.replace(/\s+/g, " ").trim(), SLACK_PROGRESS_CHUNK_TEXT_MAX);
190
- }
191
- function lineDetailParts(line) {
192
- return [line.detail, line.status && line.status !== "completed" && !line.detail?.includes(line.status) ? line.status : void 0].map((part) => part?.trim()).filter((part) => Boolean(part));
193
- }
194
- function legacyLineTitle(line) {
195
- return `${line.icon ?? "•"} *${escapeSlackMrkdwn(line.label)}*`;
196
- }
197
- function legacyLineDetail(line, maxChars) {
198
- const detail = lineDetailParts(line).join(" · ");
199
- return detail ? escapeSlackMrkdwn(compactDetail(detail, maxChars)) : "—";
200
- }
201
- function lineTaskTitle(line, maxLineChars) {
202
- const label = line.label.replace(/\s+/g, " ").trim() || line.toolName || line.kind || "Update";
203
- const detail = lineDetailParts(line).join(" · ") || line.status?.trim();
204
- const fallback = line.text.replace(/\s+/g, " ").trim();
205
- if (detail) return compactTitle(`${label} — ${compactDetail(detail, maxLineChars)}`);
206
- if (fallback && fallback !== label) return compactTitle(fallback);
207
- return compactTitle(label);
101
+ }
102
+ return session;
208
103
  }
209
- function lineTaskStatus(line) {
210
- const normalized = line.status?.replace(/\s+/g, " ").trim().toLowerCase();
211
- if (!normalized) return "in_progress";
212
- if (normalized === "complete" || normalized === "completed" || normalized === "done" || normalized === "ok" || normalized === "success" || normalized === "succeeded" || normalized === "successful" || normalized === "exit 0") return "complete";
213
- if (normalized === "error" || normalized === "failed" || normalized === "failure" || normalized.startsWith("exit ")) return normalized === "exit 0" ? "complete" : "error";
214
- return "in_progress";
104
+ /**
105
+ * Append markdown text to an active Slack stream.
106
+ */
107
+ async function appendSlackStream(params) {
108
+ const { session, text, chunks } = params;
109
+ if (session.stopped) {
110
+ logVerbose("slack-stream: attempted to append to a stopped stream, ignoring");
111
+ return;
112
+ }
113
+ if (!text && !chunks?.length) return;
114
+ if (text) session.pendingText += text;
115
+ try {
116
+ const result = await session.streamer.append({
117
+ ...text ? { markdown_text: text } : {},
118
+ ...chunks?.length ? { chunks } : {}
119
+ });
120
+ if (result) {
121
+ session.delivered = true;
122
+ session.pendingText = "";
123
+ }
124
+ logVerbose(`slack-stream: appended ${text?.length ?? 0} chars, ${chunks?.length ?? 0} chunks (${result ? "flushed" : "buffered"})`);
125
+ } catch (err) {
126
+ if (isBenignSlackFinalizeError(err) && session.pendingText) throw new SlackStreamNotDeliveredError(session.pendingText, extractSlackErrorCode(err) ?? "unknown");
127
+ throw err;
128
+ }
215
129
  }
216
- function slugTaskIdPart(value) {
217
- return value?.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "task";
218
- }
219
- function stableTaskIdPart(value, slugValue = value) {
220
- const suffix = createHash("sha256").update(value).digest("hex").slice(0, 8);
221
- return `${slugTaskIdPart(slugValue).slice(0, 48)}_${suffix}`;
222
- }
223
- function resolveLineTaskIdentity(line) {
224
- if (line.id?.trim()) return {
225
- id: stableTaskIdPart(line.id),
226
- contentDerived: false
227
- };
228
- return {
229
- id: stableTaskIdPart([
230
- line.kind,
231
- line.toolName,
232
- line.label,
233
- line.text
234
- ].join("\0"), line.toolName ?? line.kind ?? line.label),
235
- contentDerived: true
236
- };
237
- }
238
- function buildPlanTasks(params) {
239
- if (params.plan) return params.plan.slice(-50).map((entry, index) => ({
240
- id: `plan_step_${index + 1}`,
241
- title: compactTitle(entry.step),
242
- status: entry.status === "completed" ? "complete" : entry.status
243
- }));
244
- const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_TASK_DETAIL_MAX_CHARS);
245
- const lines = params.lines.slice(-50);
246
- const identities = lines.map(resolveLineTaskIdentity);
247
- const contentIdOccurrences = /* @__PURE__ */ new Map();
248
- return lines.map((line, index) => {
249
- const identity = identities[index];
250
- let id = identity.id;
251
- if (identity.contentDerived) {
252
- const occurrence = (contentIdOccurrences.get(id) ?? 0) + 1;
253
- contentIdOccurrences.set(id, occurrence);
254
- id = `${id}_${occurrence}`;
130
+ /**
131
+ * Stop (finalize) a Slack stream.
132
+ *
133
+ * After calling this the stream message becomes a normal Slack message.
134
+ * Optionally include final text to append before stopping.
135
+ *
136
+ * If Slack's `chat.stopStream` responds with a definitive recipient/channel
137
+ * rejection while text is still buffered locally, this function throws a
138
+ * {@link SlackStreamNotDeliveredError} carrying that pending text so the caller
139
+ * can deliver it through the normal Slack reply path. Ambiguous failures
140
+ * propagate unchanged because Slack may have committed the request.
141
+ *
142
+ * If Slack responds with a known benign finalize error (see
143
+ * {@link BENIGN_SLACK_FINALIZE_ERROR_CODES}) after prior `append` calls already
144
+ * landed, the error is swallowed and the session is marked stopped - the
145
+ * already-delivered text stays visible.
146
+ *
147
+ * Errors without buffered text propagate unchanged.
148
+ *
149
+ * On success, returns the finalized message's Slack `ts` (when reported) so the
150
+ * caller can emit the `message_sent` hook with a populated `messageId`.
151
+ */
152
+ async function stopSlackStream(params) {
153
+ const { session, text, chunks, metadata } = params;
154
+ if (session.stopped) {
155
+ logVerbose("slack-stream: stream already stopped, ignoring duplicate stop");
156
+ return {};
157
+ }
158
+ session.stopped = true;
159
+ if (text) session.pendingText += text;
160
+ logVerbose(`slack-stream: stopping stream in ${session.channel} thread=${session.threadTs}${text ? ` (final text: ${text.length} chars)` : ""}`);
161
+ try {
162
+ const stopResponse = await session.streamer.stop(text || chunks?.length || metadata ? {
163
+ ...text ? { markdown_text: text } : {},
164
+ ...chunks?.length ? { chunks } : {},
165
+ ...metadata ? { metadata } : {}
166
+ } : void 0);
167
+ session.delivered = true;
168
+ session.pendingText = "";
169
+ logVerbose("slack-stream: stream stopped");
170
+ const messageId = stopResponse?.ts ?? stopResponse?.message?.ts;
171
+ return messageId ? { messageId } : {};
172
+ } catch (err) {
173
+ const code = extractSlackErrorCode(err) ?? "unknown";
174
+ const benignFinalizeError = isBenignSlackFinalizeError(err);
175
+ if (session.pendingText && (benignFinalizeError || code === "missing_scope")) throw new SlackStreamNotDeliveredError(session.pendingText, code);
176
+ if (benignFinalizeError) {
177
+ if (session.delivered) {
178
+ logVerbose(`slack-stream: finalize rejected by Slack (${code}); prior appends delivered, treating stream as stopped`);
179
+ return {};
180
+ }
255
181
  }
256
- return {
257
- id,
258
- title: lineTaskTitle(line, maxLineChars),
259
- status: lineTaskStatus(line)
260
- };
261
- });
262
- }
263
- function resolvePlanTitle(params) {
264
- return compactChunkText(params.title?.trim() || params.label?.trim() || params.tasks.at(-1)?.title || SLACK_PROGRESS_PLAN_FALLBACK_TITLE);
265
- }
266
- function buildSlackProgressStreamChunks(params) {
267
- const tasks = buildPlanTasks({
268
- lines: params.lines,
269
- plan: params.plan,
270
- maxLineChars: params.maxLineChars
271
- });
272
- if (tasks.length === 0) {
273
- const title = params.title?.trim() || params.label?.trim();
274
- return title ? [{
275
- type: "plan_update",
276
- title: compactChunkText(title)
277
- }] : void 0;
182
+ throw err;
278
183
  }
279
- return [{
280
- type: "plan_update",
281
- title: resolvePlanTitle({
282
- label: params.label,
283
- title: params.title,
284
- tasks
285
- })
286
- }, ...tasks.map((task) => ({
287
- type: "task_update",
288
- id: task.id,
289
- title: task.title,
290
- status: task.status === "in_progress" ? params.finalInProgressStatus ?? (params.completeInProgress ? "complete" : task.status) : task.status
291
- }))];
292
- }
293
- function buildSlackProgressDraftBlocks(params) {
294
- const label = params.label?.trim() || params.title?.trim();
295
- const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS);
296
- const planLines = formatPlanChecklistLines(params.plan ?? [], {
297
- maxLines: 50,
298
- maxLineChars
299
- });
300
- const narration = params.narration?.replace(/\s+/g, " ").trim();
301
- const headBlocks = [
302
- ...label ? [{
303
- type: "section",
304
- text: field(`*${escapeSlackMrkdwn(label)}*`)
305
- }] : [],
306
- ...narration ? [{
307
- type: "section",
308
- text: field(`_${escapeSlackMrkdwn(narration)}_`)
309
- }] : [],
310
- ...planLines.length > 0 ? [{
311
- type: "section",
312
- text: field(planLines.map((line) => escapeSlackMrkdwn(line)).join("\n"))
313
- }] : []
314
- ].slice(0, 50);
315
- const lineBudget = Math.max(0, 50 - headBlocks.length);
316
- const renderedBlocks = [...headBlocks, ...params.lines.slice(-lineBudget).map((line) => ({
317
- type: "section",
318
- fields: [field(legacyLineTitle(line)), field(legacyLineDetail(line, maxLineChars))]
319
- }))];
320
- return renderedBlocks.length ? renderedBlocks : void 0;
321
184
  }
322
185
  /**
323
- * Slack native streams key task rows by persistent id with no removal chunk.
324
- * When the task source switches representation (tool lines <-> typed plan) or
325
- * a snapshot drops ids, previously emitted non-terminal rows must receive a
326
- * final update or they linger in_progress forever.
186
+ * Slack API error codes that indicate `chat.stopStream` (or the
187
+ * `chat.startStream` call the SDK issues inside `stop()` when the buffer
188
+ * never flushed) cannot finalize the stream for the current recipient or
189
+ * team. Either the caller falls back to a normal message (see
190
+ * {@link SlackStreamNotDeliveredError}) or, if prior appends already
191
+ * delivered text, the error is logged verbosely and swallowed.
327
192
  */
328
- function reconcileSlackNativeTaskChunks(params) {
329
- const nextTasks = /* @__PURE__ */ new Map();
330
- for (const chunk of params.chunks ?? []) if (chunk.type === "task_update") nextTasks.set(chunk.id, {
331
- title: chunk.title,
332
- status: chunk.status
333
- });
334
- const terminalized = [...params.previousTasks].filter(([id, task]) => !nextTasks.has(id) && task.status !== "complete" && task.status !== "error").map(([id, task]) => {
335
- const entry = {
336
- title: task.title,
337
- status: "complete"
338
- };
339
- nextTasks.set(id, entry);
340
- return {
341
- type: "task_update",
342
- id,
343
- title: task.title,
344
- status: "complete"
345
- };
346
- });
347
- for (const [id, task] of params.previousTasks) if (!nextTasks.has(id)) nextTasks.set(id, task);
348
- return {
349
- chunks: params.chunks?.length ? [...params.chunks, ...terminalized] : terminalized.length ? terminalized : params.chunks,
350
- tasks: nextTasks
351
- };
193
+ const BENIGN_SLACK_FINALIZE_ERROR_CODES = /* @__PURE__ */ new Set([
194
+ "user_not_found",
195
+ "team_not_found",
196
+ "missing_recipient_user_id",
197
+ "method_not_supported_for_channel_type"
198
+ ]);
199
+ function isBenignSlackFinalizeError(err) {
200
+ const code = extractSlackErrorCode(err);
201
+ return code !== void 0 && BENIGN_SLACK_FINALIZE_ERROR_CODES.has(code);
352
202
  }
353
- function buildSlackProgressStreamStartChunks(params) {
354
- return buildSlackProgressStreamChunks(params);
203
+ function extractSlackErrorCode(err) {
204
+ if (!err || typeof err !== "object") return;
205
+ const record = err;
206
+ if (record.data && typeof record.data === "object") {
207
+ const inner = record.data.error;
208
+ if (typeof inner === "string") return inner;
209
+ }
210
+ return (typeof record.message === "string" ? record.message : "").match(/An API error occurred:\s*([a-z_][a-z0-9_]*)/i)?.[1];
355
211
  }
356
- function buildSlackProgressStreamUpdateChunks(params) {
357
- return buildSlackProgressStreamChunks(params);
212
+ function markSlackStreamFallbackDelivered(session) {
213
+ const nativeStreamWasStarted = session.delivered;
214
+ session.pendingText = "";
215
+ session.streamer.buffer = "";
216
+ session.stopped = !nativeStreamWasStarted;
358
217
  }
359
- function buildSlackProgressStreamCompletionChunks(params) {
360
- return buildSlackProgressStreamChunks({
361
- ...params,
362
- completeInProgress: true
218
+ //#endregion
219
+ //#region extensions/slack/src/monitor/message-handler/timestamp.ts
220
+ const SLACK_TIMESTAMP_RE = /^\d+(?:\.\d+)?$/;
221
+ const MAX_SAFE_SLACK_TIMESTAMP_SECONDS = Number.MAX_SAFE_INTEGER / 1e3;
222
+ function resolveSlackTimestampMs(ts) {
223
+ const trimmed = ts?.trim();
224
+ if (!trimmed || !SLACK_TIMESTAMP_RE.test(trimmed)) return;
225
+ const seconds = asFiniteNumberInRange(parseStrictFiniteNumber(trimmed), {
226
+ min: 0,
227
+ max: MAX_SAFE_SLACK_TIMESTAMP_SECONDS
363
228
  });
229
+ return seconds === void 0 ? void 0 : Math.round(seconds * 1e3);
364
230
  }
365
231
  //#endregion
366
- //#region extensions/slack/src/stream-mode.ts
367
- function resolveSlackStreamingConfig(params) {
368
- const mode = resolveSlackStreamingMode(params);
232
+ //#region extensions/slack/src/monitor/message-handler/dispatch-helpers.ts
233
+ function resolveSlackMessageTimestampMs(message) {
234
+ return resolveSlackTimestampMs(message.event_ts ?? message.ts);
235
+ }
236
+ function resolveSlackBotLoopProtection(prepared) {
237
+ const senderBotId = prepared.message.bot_id;
238
+ if (!senderBotId) return;
239
+ const receiverBotId = prepared.ctx.botId || prepared.ctx.botUserId;
240
+ if (!receiverBotId || senderBotId === prepared.ctx.botId || prepared.message.user === prepared.ctx.botUserId) return;
369
241
  return {
370
- mode,
371
- nativeStreaming: resolveSlackNativeStreaming(params),
372
- draftMode: mapStreamingModeToSlackLegacyDraftStreamMode(mode)
242
+ scopeId: prepared.route.accountId,
243
+ conversationId: prepared.message.channel,
244
+ senderId: senderBotId,
245
+ receiverId: receiverBotId,
246
+ config: mergePairLoopGuardConfig(prepared.account.config.botLoopProtection, prepared.channelConfig?.botLoopProtection),
247
+ defaultsConfig: prepared.ctx.cfg.channels?.defaults?.botLoopProtection,
248
+ defaultEnabled: true,
249
+ nowMs: resolveSlackMessageTimestampMs(prepared.message)
373
250
  };
374
251
  }
375
- function applyAppendOnlyStreamUpdate(params) {
376
- const incoming = params.incoming.trimEnd();
377
- if (!incoming) return {
378
- rendered: params.rendered,
379
- source: params.source,
380
- changed: false
381
- };
382
- if (!params.rendered) return {
383
- rendered: incoming,
384
- source: incoming,
385
- changed: true
386
- };
387
- if (incoming === params.source) return {
388
- rendered: params.rendered,
389
- source: params.source,
390
- changed: false
391
- };
392
- if (incoming.startsWith(params.source) || incoming.startsWith(params.rendered)) return {
393
- rendered: incoming,
394
- source: incoming,
395
- changed: incoming !== params.rendered
396
- };
397
- if (params.source.startsWith(incoming)) return {
398
- rendered: params.rendered,
399
- source: params.source,
400
- changed: false
401
- };
402
- const separator = params.rendered.endsWith("\n") ? "" : "\n";
403
- return {
404
- rendered: `${params.rendered}${separator}${incoming}`,
405
- source: incoming,
406
- changed: true
407
- };
252
+ function isSlackStreamingEnabled(params) {
253
+ if (params.mode === "partial") return params.nativeStreaming;
254
+ if (params.mode === "progress") return params.nativeStreaming && params.nativeProgressTaskCards === true;
255
+ return false;
408
256
  }
409
- //#endregion
410
- //#region extensions/slack/src/streaming.ts
411
- /**
412
- * Thrown when Slack definitively rejects a stream flush/finalize while text
413
- * remains buffered locally by the Slack SDK. Carries the pending text so the
414
- * caller can deliver it via the normal Slack reply path.
415
- */
416
- var SlackStreamNotDeliveredError = class extends Error {
417
- constructor(pendingText, slackCode) {
418
- super(`slack-stream: finalize failed with ${slackCode} before buffered text reached Slack (${pendingText.length} chars pending)`);
419
- this.name = "SlackStreamNotDeliveredError";
420
- this.pendingText = pendingText;
421
- this.slackCode = slackCode;
422
- }
423
- };
424
- /**
425
- * Start a new Slack text stream.
426
- *
427
- * Returns a {@link SlackStreamSession} that should be passed to
428
- * {@link appendSlackStream} and {@link stopSlackStream}.
429
- *
430
- * The first chunk of text can optionally be included via `text`.
431
- */
432
- async function startSlackStream(params) {
433
- const { client, channel, threadTs, text, chunks, taskDisplayMode, teamId, userId, identity } = params;
434
- const identityPayload = identity?.iconUrl ? {
435
- ...identity.username ? { username: identity.username } : {},
436
- icon_url: identity.iconUrl
437
- } : identity?.iconEmoji ? {
438
- ...identity.username ? { username: identity.username } : {},
439
- icon_emoji: identity.iconEmoji
440
- } : identity?.username ? { username: identity.username } : {};
441
- logVerbose(`slack-stream: starting stream in ${channel} thread=${threadTs}${teamId ? ` team=${teamId}` : ""}${userId ? ` user=${userId}` : ""}`);
442
- const streamer = client.chatStream({
443
- channel,
444
- thread_ts: threadTs,
445
- ...taskDisplayMode ? { task_display_mode: taskDisplayMode } : {},
446
- ...teamId ? { recipient_team_id: teamId } : {},
447
- ...userId ? { recipient_user_id: userId } : {},
448
- ...identityPayload
257
+ function shouldEnableSlackPreviewStreaming(params) {
258
+ return params.mode !== "off";
259
+ }
260
+ function shouldInitializeSlackDraftStream(params) {
261
+ return params.previewStreamingEnabled && !params.useStreaming;
262
+ }
263
+ function resolveSlackDisableBlockStreaming(params) {
264
+ if (params.useStreaming || params.shouldUseDraftStream) return true;
265
+ return typeof params.blockStreamingEnabled === "boolean" ? !params.blockStreamingEnabled : void 0;
266
+ }
267
+ function resolveExplicitSlackProgressTitle(entry) {
268
+ const label = resolveChannelProgressDraftConfig(entry).label;
269
+ if (typeof label !== "string") return;
270
+ const trimmed = label.trim();
271
+ return trimmed && trimmed.toLowerCase() !== "auto" ? trimmed : void 0;
272
+ }
273
+ function resolveSlackNativeProgressTaskCards(entry) {
274
+ const streaming = entry?.streaming;
275
+ if (!streaming || typeof streaming !== "object" || Array.isArray(streaming)) return false;
276
+ const progressConfig = streaming.progress;
277
+ return Boolean(progressConfig) && typeof progressConfig === "object" && !Array.isArray(progressConfig) && progressConfig.nativeTaskCards === true;
278
+ }
279
+ function resolveSlackStreamingThreadHint(params) {
280
+ return resolveSlackThreadTs({
281
+ replyToMode: params.replyToMode,
282
+ incomingThreadTs: params.incomingThreadTs,
283
+ messageTs: params.messageTs,
284
+ hasReplied: false,
285
+ isThreadReply: params.isThreadReply
449
286
  });
450
- const session = {
451
- streamer,
452
- channel,
453
- threadTs,
454
- stopped: false,
455
- delivered: false,
456
- pendingText: ""
457
- };
458
- if (text || chunks?.length) {
459
- if (text) session.pendingText += text;
460
- try {
461
- const result = await streamer.append({
462
- ...text ? { markdown_text: text } : {},
463
- ...chunks?.length ? { chunks } : {}
464
- });
465
- if (result) {
466
- session.delivered = true;
467
- session.pendingText = "";
468
- }
469
- logVerbose(`slack-stream: appended initial payload (${text?.length ?? 0} chars, ${chunks?.length ?? 0} chunks, ${result ? "flushed" : "buffered"})`);
470
- } catch (err) {
471
- if (isBenignSlackFinalizeError(err) && session.pendingText) throw new SlackStreamNotDeliveredError(session.pendingText, extractSlackErrorCode(err) ?? "unknown");
472
- throw err;
473
- }
287
+ }
288
+ const SLACK_STREAM_RECIPIENT_TEAM_CACHE_MAX = 2e3;
289
+ const slackStreamRecipientTeamCaches = /* @__PURE__ */ new WeakMap();
290
+ function getSlackStreamRecipientTeamCache(client) {
291
+ const existing = slackStreamRecipientTeamCaches.get(client);
292
+ if (existing) return existing;
293
+ const cache = /* @__PURE__ */ new Map();
294
+ slackStreamRecipientTeamCaches.set(client, cache);
295
+ return cache;
296
+ }
297
+ function buildSlackEventDeliveryKey(params) {
298
+ const reply = resolveSendableOutboundReplyParts(params.payload, { text: params.textOverride });
299
+ const renderPlan = resolveSlackReplyRenderPlan(params.payload, params.textOverride ?? params.payload.text);
300
+ const plannedBlocks = renderPlan.mode === "single" ? renderPlan.blocks : renderPlan.blockPart?.blocks;
301
+ const slackBlocks = readSlackReplyBlocks(params.payload) ?? plannedBlocks;
302
+ const renderedText = renderPlan.mode === "single" ? renderPlan.text : renderPlan.fallbackText;
303
+ if (!reply.hasContent && !slackBlocks?.length && !renderedText.trim()) return null;
304
+ return JSON.stringify({
305
+ kind: params.kind,
306
+ threadTs: params.threadTs ?? "",
307
+ replyToId: params.payload.replyToId ?? null,
308
+ text: renderedText || reply.trimmedText,
309
+ mediaUrls: reply.mediaUrls,
310
+ blocks: slackBlocks ?? null
311
+ });
312
+ }
313
+ function readSlackStreamRecipientTeamCache(params) {
314
+ if (!params.fallbackTeamId || !params.userId) return;
315
+ const cacheKey = `${params.fallbackTeamId}:${params.userId}`;
316
+ const cache = getSlackStreamRecipientTeamCache(params.client);
317
+ const cached = cache.get(cacheKey);
318
+ if (!cached) return;
319
+ cache.delete(cacheKey);
320
+ cache.set(cacheKey, cached);
321
+ return cached;
322
+ }
323
+ function rememberSlackStreamRecipientTeam(params) {
324
+ if (!params.fallbackTeamId || !params.userId) return;
325
+ const cacheKey = `${params.fallbackTeamId}:${params.userId}`;
326
+ const cache = getSlackStreamRecipientTeamCache(params.client);
327
+ if (cache.has(cacheKey)) cache.delete(cacheKey);
328
+ cache.set(cacheKey, params.teamId);
329
+ if (cache.size > SLACK_STREAM_RECIPIENT_TEAM_CACHE_MAX) {
330
+ const oldest = cache.keys().next().value;
331
+ if (oldest) cache.delete(oldest);
474
332
  }
475
- return session;
476
333
  }
477
- /**
478
- * Append markdown text to an active Slack stream.
479
- */
480
- async function appendSlackStream(params) {
481
- const { session, text, chunks } = params;
482
- if (session.stopped) {
483
- logVerbose("slack-stream: attempted to append to a stopped stream, ignoring");
484
- return;
334
+ function createSlackEventDeliveryTracker() {
335
+ const deliveredKeys = /* @__PURE__ */ new Set();
336
+ return {
337
+ hasDelivered(params) {
338
+ const key = buildSlackEventDeliveryKey(params);
339
+ return key ? deliveredKeys.has(key) : false;
340
+ },
341
+ markDelivered(params) {
342
+ const key = buildSlackEventDeliveryKey(params);
343
+ if (key) deliveredKeys.add(key);
344
+ }
345
+ };
346
+ }
347
+ function shouldUseStreaming(params) {
348
+ if (!params.streamingEnabled) return false;
349
+ if (!params.threadTs) {
350
+ logVerbose("slack-stream: streaming disabled — no reply thread target available");
351
+ return false;
485
352
  }
486
- if (!text && !chunks?.length) return;
487
- if (text) session.pendingText += text;
488
- try {
489
- const result = await session.streamer.append({
490
- ...text ? { markdown_text: text } : {},
491
- ...chunks?.length ? { chunks } : {}
353
+ return true;
354
+ }
355
+ async function resolveSlackStreamRecipientTeamId(params) {
356
+ const cachedTeamId = readSlackStreamRecipientTeamCache(params);
357
+ if (cachedTeamId) return cachedTeamId;
358
+ if (params.userId) try {
359
+ const info = await params.client.users.info({
360
+ token: params.token,
361
+ user: params.userId
492
362
  });
493
- if (result) {
494
- session.delivered = true;
495
- session.pendingText = "";
363
+ const teamId = info.user?.team_id ?? info.user?.profile?.team;
364
+ if (teamId) {
365
+ rememberSlackStreamRecipientTeam({
366
+ ...params,
367
+ teamId
368
+ });
369
+ return teamId;
496
370
  }
497
- logVerbose(`slack-stream: appended ${text?.length ?? 0} chars, ${chunks?.length ?? 0} chunks (${result ? "flushed" : "buffered"})`);
498
371
  } catch (err) {
499
- if (isBenignSlackFinalizeError(err) && session.pendingText) throw new SlackStreamNotDeliveredError(session.pendingText, extractSlackErrorCode(err) ?? "unknown");
500
- throw err;
372
+ logVerbose(`slack-stream: users.info team lookup failed (${formatErrorMessage(err)})`);
501
373
  }
374
+ return params.fallbackTeamId;
502
375
  }
503
- /**
504
- * Stop (finalize) a Slack stream.
505
- *
506
- * After calling this the stream message becomes a normal Slack message.
507
- * Optionally include final text to append before stopping.
508
- *
509
- * If Slack's `chat.stopStream` responds with a definitive recipient/channel
510
- * rejection while text is still buffered locally, this function throws a
511
- * {@link SlackStreamNotDeliveredError} carrying that pending text so the caller
512
- * can deliver it through the normal Slack reply path. Ambiguous failures
513
- * propagate unchanged because Slack may have committed the request.
514
- *
515
- * If Slack responds with a known benign finalize error (see
516
- * {@link BENIGN_SLACK_FINALIZE_ERROR_CODES}) after prior `append` calls already
517
- * landed, the error is swallowed and the session is marked stopped - the
518
- * already-delivered text stays visible.
519
- *
520
- * Errors without buffered text propagate unchanged.
521
- *
522
- * On success, returns the finalized message's Slack `ts` (when reported) so the
523
- * caller can emit the `message_sent` hook with a populated `messageId`.
524
- */
525
- async function stopSlackStream(params) {
526
- const { session, text, chunks, metadata } = params;
527
- if (session.stopped) {
528
- logVerbose("slack-stream: stream already stopped, ignoring duplicate stop");
529
- return {};
530
- }
531
- session.stopped = true;
532
- if (text) session.pendingText += text;
533
- logVerbose(`slack-stream: stopping stream in ${session.channel} thread=${session.threadTs}${text ? ` (final text: ${text.length} chars)` : ""}`);
534
- try {
535
- const stopResponse = await session.streamer.stop(text || chunks?.length || metadata ? {
536
- ...text ? { markdown_text: text } : {},
537
- ...chunks?.length ? { chunks } : {},
538
- ...metadata ? { metadata } : {}
539
- } : void 0);
540
- session.delivered = true;
541
- session.pendingText = "";
542
- logVerbose("slack-stream: stream stopped");
543
- const messageId = stopResponse?.ts ?? stopResponse?.message?.ts;
544
- return messageId ? { messageId } : {};
545
- } catch (err) {
546
- const code = extractSlackErrorCode(err) ?? "unknown";
547
- const benignFinalizeError = isBenignSlackFinalizeError(err);
548
- if (session.pendingText && (benignFinalizeError || code === "missing_scope")) throw new SlackStreamNotDeliveredError(session.pendingText, code);
549
- if (benignFinalizeError) {
550
- if (session.delivered) {
551
- logVerbose(`slack-stream: finalize rejected by Slack (${code}); prior appends delivered, treating stream as stopped`);
552
- return {};
376
+ //#endregion
377
+ //#region extensions/slack/src/draft-stream.ts
378
+ const DEFAULT_THROTTLE_MS = 1e3;
379
+ function createSlackDraftStream(params) {
380
+ const maxChars = Math.min(params.maxChars ?? 8e3, SLACK_TEXT_LIMIT);
381
+ const throttleMs = Math.max(250, params.throttleMs ?? DEFAULT_THROTTLE_MS);
382
+ const send = params.send ?? sendMessageSlack;
383
+ const edit = params.edit ?? editSlackMessage;
384
+ const remove = params.remove ?? deleteSlackMessage;
385
+ let streamMessageId;
386
+ let streamChannelId;
387
+ let lastSentKey = "";
388
+ let pendingUpdate;
389
+ let stopped = false;
390
+ const normalizeUpdate = (update) => typeof update === "string" ? { text: update } : update;
391
+ const sendOrEditStreamMessage = async (text) => {
392
+ if (stopped) return;
393
+ const trimmed = text.trimEnd();
394
+ if (!trimmed) return;
395
+ if (trimmed.length > maxChars) {
396
+ stopped = true;
397
+ params.warn?.(`slack stream preview stopped (text length ${trimmed.length} > ${maxChars})`);
398
+ return;
399
+ }
400
+ const update = normalizeUpdate(pendingUpdate ?? text);
401
+ const blocks = update.text === text ? update.blocks : void 0;
402
+ const sentKey = `${trimmed}\n${blocks ? JSON.stringify(blocks) : ""}`;
403
+ if (sentKey === lastSentKey) return;
404
+ lastSentKey = sentKey;
405
+ try {
406
+ if (streamChannelId && streamMessageId) {
407
+ await edit(streamChannelId, streamMessageId, trimmed, {
408
+ cfg: params.cfg,
409
+ token: params.token,
410
+ accountId: params.accountId,
411
+ ...params.eventScope ? { client: params.eventScope.client } : {},
412
+ ...blocks ? { blocks } : {}
413
+ });
414
+ return;
415
+ }
416
+ const sent = await send(params.target, trimmed, {
417
+ cfg: params.cfg,
418
+ token: params.token,
419
+ accountId: params.accountId,
420
+ threadTs: params.resolveThreadTs?.(),
421
+ identity: params.identity,
422
+ ...params.eventScope ? {
423
+ client: params.eventScope.client,
424
+ enterpriseEventScope: params.eventScope
425
+ } : {},
426
+ ...params.metadata ? { metadata: params.metadata } : {},
427
+ ...blocks ? { blocks } : {}
428
+ });
429
+ streamChannelId = sent.channelId || streamChannelId;
430
+ streamMessageId = sent.messageId || streamMessageId;
431
+ if (!streamChannelId || !streamMessageId) {
432
+ stopped = true;
433
+ params.warn?.("slack stream preview stopped (missing identifiers from sendMessage)");
434
+ return;
553
435
  }
436
+ params.onMessageSent?.();
437
+ } catch (err) {
438
+ stopped = true;
439
+ params.warn?.(`slack stream preview failed: ${formatSlackError(err)}`);
554
440
  }
555
- throw err;
556
- }
557
- }
558
- /**
559
- * Slack API error codes that indicate `chat.stopStream` (or the
560
- * `chat.startStream` call the SDK issues inside `stop()` when the buffer
561
- * never flushed) cannot finalize the stream for the current recipient or
562
- * team. Either the caller falls back to a normal message (see
563
- * {@link SlackStreamNotDeliveredError}) or, if prior appends already
564
- * delivered text, the error is logged verbosely and swallowed.
565
- */
566
- const BENIGN_SLACK_FINALIZE_ERROR_CODES = /* @__PURE__ */ new Set([
567
- "user_not_found",
568
- "team_not_found",
569
- "missing_recipient_user_id",
570
- "method_not_supported_for_channel_type"
571
- ]);
572
- function isBenignSlackFinalizeError(err) {
573
- const code = extractSlackErrorCode(err);
574
- return code !== void 0 && BENIGN_SLACK_FINALIZE_ERROR_CODES.has(code);
575
- }
576
- function extractSlackErrorCode(err) {
577
- if (!err || typeof err !== "object") return;
578
- const record = err;
579
- if (record.data && typeof record.data === "object") {
580
- const inner = record.data.error;
581
- if (typeof inner === "string") return inner;
582
- }
583
- return (typeof record.message === "string" ? record.message : "").match(/An API error occurred:\s*([a-z_][a-z0-9_]*)/i)?.[1];
584
- }
585
- function markSlackStreamFallbackDelivered(session) {
586
- const nativeStreamWasStarted = session.delivered;
587
- session.pendingText = "";
588
- session.streamer.buffer = "";
589
- session.stopped = !nativeStreamWasStarted;
590
- }
591
- //#endregion
592
- //#region extensions/slack/src/threading.ts
593
- function resolveSlackThreadContext(params) {
594
- const incomingThreadTs = params.message.thread_ts;
595
- const eventTs = params.message.event_ts;
596
- const messageTs = params.message.ts ?? eventTs;
597
- const hasThreadTs = typeof incomingThreadTs === "string" && incomingThreadTs.length > 0;
598
- const isThreadReply = hasThreadTs && (incomingThreadTs !== messageTs || Boolean(params.message.parent_user_id));
599
- const replyToId = isThreadReply ? incomingThreadTs : void 0;
600
- const isAssistantDmThreadRoot = hasThreadTs && !isThreadReply && params.isDirectMessage === true;
441
+ };
442
+ const loop = createDraftStreamLoop({
443
+ throttleMs,
444
+ isStopped: () => stopped,
445
+ sendOrEditStreamMessage
446
+ });
447
+ const stop = () => {
448
+ stopped = true;
449
+ loop.stop();
450
+ };
451
+ const discardPending = async () => {
452
+ stop();
453
+ await loop.waitForInFlight();
454
+ };
455
+ const clear = async () => {
456
+ await discardPending();
457
+ const channelId = streamChannelId;
458
+ const messageId = streamMessageId;
459
+ streamChannelId = void 0;
460
+ streamMessageId = void 0;
461
+ lastSentKey = "";
462
+ pendingUpdate = void 0;
463
+ if (!channelId || !messageId) return;
464
+ try {
465
+ await remove(channelId, messageId, {
466
+ token: params.token,
467
+ accountId: params.accountId,
468
+ ...params.eventScope ? { client: params.eventScope.client } : {}
469
+ });
470
+ } catch (err) {
471
+ params.warn?.(`slack stream preview cleanup failed: ${formatSlackError(err)}`);
472
+ }
473
+ };
474
+ const forceNewMessage = () => {
475
+ streamMessageId = void 0;
476
+ streamChannelId = void 0;
477
+ lastSentKey = "";
478
+ pendingUpdate = void 0;
479
+ loop.resetPending();
480
+ };
481
+ params.log?.(`slack stream preview ready (maxChars=${maxChars}, throttleMs=${throttleMs})`);
601
482
  return {
602
- incomingThreadTs,
603
- messageTs,
604
- isThreadReply,
605
- replyToId,
606
- messageThreadId: isThreadReply || isAssistantDmThreadRoot ? incomingThreadTs : params.replyToMode === "all" ? messageTs : void 0
483
+ update: (update) => {
484
+ const normalized = normalizeUpdate(update);
485
+ pendingUpdate = update;
486
+ loop.update(normalized.text);
487
+ },
488
+ flush: loop.flush,
489
+ clear,
490
+ discardPending,
491
+ seal: discardPending,
492
+ stop,
493
+ forceNewMessage,
494
+ messageId: () => streamMessageId,
495
+ channelId: () => streamChannelId
607
496
  };
608
497
  }
609
- /**
610
- * Resolves Slack thread targeting for replies and status indicators.
611
- *
612
- * @returns replyThreadTs - Thread timestamp for reply messages
613
- * @returns statusThreadTs - Thread timestamp for status indicators (typing, etc.)
614
- * @returns isThreadReply - true if this is a genuine user reply in a thread,
615
- * false if thread_ts comes from a bot status message (e.g. typing indicator)
616
- */
617
- function resolveSlackThreadTargets(params) {
618
- const { incomingThreadTs, messageTs, isThreadReply } = resolveSlackThreadContext(params);
619
- const replyThreadTs = isThreadReply ? incomingThreadTs : params.replyToMode === "all" ? messageTs : void 0;
498
+ //#endregion
499
+ //#region extensions/slack/src/progress-blocks.ts
500
+ const SLACK_PROGRESS_FIELD_MAX = 1800;
501
+ const DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS = 120;
502
+ const DEFAULT_SLACK_PROGRESS_TASK_DETAIL_MAX_CHARS = 48;
503
+ const SLACK_PROGRESS_CHUNK_TEXT_MAX = 256;
504
+ const SLACK_PROGRESS_TASK_TITLE_MAX = 120;
505
+ const SLACK_PROGRESS_PLAN_FALLBACK_TITLE = "Thinking";
506
+ function field(text) {
620
507
  return {
621
- replyThreadTs,
622
- statusThreadTs: replyThreadTs,
623
- isThreadReply
508
+ type: "mrkdwn",
509
+ text: truncateSlackText(text, SLACK_PROGRESS_FIELD_MAX)
624
510
  };
625
511
  }
626
- //#endregion
627
- //#region extensions/slack/src/monitor/message-handler/preview-finalize.ts
628
- function buildExpectedSlackEditText(params) {
629
- const trimmedText = params.text.trim();
630
- if (trimmedText) return normalizeSlackOutboundText(trimmedText);
631
- if (params.blocks?.length) return normalizeSlackOutboundText(buildSlackBlocksFallbackText(params.blocks));
632
- return " ";
512
+ function resolveMaxLineChars(value, fallback) {
513
+ return value && value > 0 ? Math.floor(value) : fallback;
633
514
  }
634
- function buildAcceptedSlackEditTexts(params) {
635
- const expected = buildExpectedSlackEditText(params);
636
- const texts = /* @__PURE__ */ new Set([
637
- expected,
638
- normalizeSlackOutboundText(truncateSlackText(expected, SLACK_EDIT_TEXT_LIMIT)),
639
- normalizeSlackOutboundText(buildSlackEditTextPayload(params.text, params.blocks))
640
- ]);
641
- if (params.blocks?.length && hasSlackNativeDataBlock(params.blocks)) {
642
- const fallbackPlan = buildSlackNativeDataDeliveryPlan({
643
- baseText: params.text,
644
- blocks: params.blocks
645
- });
646
- for (const message of fallbackPlan.fallbackMessages) texts.add(normalizeSlackOutboundText(message.text));
647
- }
648
- return texts;
515
+ function compactDetail(value, maxChars) {
516
+ const normalized = value.replace(/\s+/g, " ").trim();
517
+ const chars = Array.from(normalized);
518
+ if (chars.length <= maxChars) return normalized;
519
+ if (maxChars <= 1) return "…";
520
+ const keepStart = Math.max(1, Math.ceil((maxChars - 1) * .45));
521
+ const keepEnd = Math.max(1, maxChars - keepStart - 1);
522
+ return `${chars.slice(0, keepStart).join("").trimEnd()}…${chars.slice(-keepEnd).join("").trimStart()}`;
649
523
  }
650
- function blocksMatch(expected, actual) {
651
- if (!expected?.length) return !actual?.length;
652
- if (!actual?.length) {
653
- if (!hasSlackNativeDataBlock(expected)) return false;
654
- return buildSlackNativeDataDeliveryPlan({ blocks: expected }).fallbackMessages.every((message) => !message.blocks?.length);
655
- }
656
- if (JSON.stringify(expected) === JSON.stringify(actual)) return true;
657
- if (!hasSlackNativeDataBlock(expected)) return false;
658
- try {
659
- const fallbackPlan = buildSlackNativeDataDeliveryPlan({ blocks: expected });
660
- const fallbackBlocks = fallbackPlan.fallbackMessages.flatMap((message) => message.blocks ?? []);
661
- if (JSON.stringify(fallbackBlocks) === JSON.stringify(actual)) return true;
662
- const fallbackText = fallbackPlan.fallbackMessages.map((message) => message.text).filter(Boolean).join("\n\n");
663
- return normalizeSlackOutboundText(buildSlackBlocksFallbackText(actual)) === normalizeSlackOutboundText(fallbackText);
664
- } catch {
665
- return false;
666
- }
667
- }
668
- async function readSlackMessageAfterEditError(params) {
669
- if (params.threadTs) return ((await params.client.conversations.replies({
670
- token: params.token,
671
- channel: params.channelId,
672
- ts: params.threadTs,
673
- latest: params.messageId,
674
- inclusive: true,
675
- limit: 100
676
- })).messages ?? []).find((message) => message?.ts === params.messageId) ?? null;
677
- const message = (await params.client.conversations.history({
678
- token: params.token,
679
- channel: params.channelId,
680
- latest: params.messageId,
681
- oldest: params.messageId,
682
- inclusive: true,
683
- limit: 1
684
- })).messages?.[0];
685
- if (!message?.ts || message.ts !== params.messageId) return null;
686
- return message;
687
- }
688
- async function didSlackPreviewEditApplyAfterError(params) {
689
- const readback = await readSlackMessageAfterEditError(params);
690
- if (!readback) return false;
691
- const expectedText = buildExpectedSlackEditText({
692
- text: params.text,
693
- blocks: params.blocks
694
- });
695
- const acceptedTexts = buildAcceptedSlackEditTexts({
696
- text: params.text,
697
- blocks: params.blocks
698
- });
699
- const actualText = normalizeSlackOutboundText((readback.text ?? "").trim());
700
- if (params.blocks?.length) return acceptedTexts.has(actualText) && blocksMatch(params.blocks, readback.blocks);
701
- return actualText === expectedText;
702
- }
703
- async function finalizeSlackPreviewEdit(params) {
704
- try {
705
- await editSlackMessage(params.channelId, params.messageId, params.text, {
706
- token: params.token,
707
- accountId: params.accountId,
708
- client: params.client,
709
- ...params.blocks?.length ? { blocks: params.blocks } : {}
710
- });
711
- } catch (err) {
712
- try {
713
- if (await didSlackPreviewEditApplyAfterError({
714
- client: params.client,
715
- token: params.token,
716
- channelId: params.channelId,
717
- messageId: params.messageId,
718
- text: params.text,
719
- blocks: params.blocks,
720
- threadTs: params.threadTs
721
- })) {
722
- logVerbose(`slack: preview final edit response failed but readback matched message ${params.channelId}/${params.messageId}; suppressing duplicate fallback send`);
723
- return;
724
- }
725
- } catch (readbackErr) {
726
- logVerbose(`slack: preview final edit readback failed (${String(readbackErr)})`);
727
- }
728
- throw err;
729
- }
524
+ function compactTitle(value) {
525
+ return truncateSlackText(value.replace(/\s+/g, " ").trim(), SLACK_PROGRESS_TASK_TITLE_MAX);
730
526
  }
731
- //#endregion
732
- //#region extensions/slack/src/monitor/message-handler/timestamp.ts
733
- const SLACK_TIMESTAMP_RE = /^\d+(?:\.\d+)?$/;
734
- const MAX_SAFE_SLACK_TIMESTAMP_SECONDS = Number.MAX_SAFE_INTEGER / 1e3;
735
- function resolveSlackTimestampMs(ts) {
736
- const trimmed = ts?.trim();
737
- if (!trimmed || !SLACK_TIMESTAMP_RE.test(trimmed)) return;
738
- const seconds = asFiniteNumberInRange(parseStrictFiniteNumber(trimmed), {
739
- min: 0,
740
- max: MAX_SAFE_SLACK_TIMESTAMP_SECONDS
741
- });
742
- return seconds === void 0 ? void 0 : Math.round(seconds * 1e3);
527
+ function compactChunkText(value) {
528
+ return truncateSlackText(value.replace(/\s+/g, " ").trim(), SLACK_PROGRESS_CHUNK_TEXT_MAX);
743
529
  }
744
- //#endregion
745
- //#region extensions/slack/src/monitor/message-handler/dispatch.ts
746
- function resolveSlackMessageTimestampMs(message) {
747
- return resolveSlackTimestampMs(message.event_ts ?? message.ts);
530
+ function lineDetailParts(line) {
531
+ return [line.detail, line.status && line.status !== "completed" && !line.detail?.includes(line.status) ? line.status : void 0].map((part) => part?.trim()).filter((part) => Boolean(part));
748
532
  }
749
- function resolveSlackBotLoopProtection(prepared) {
750
- const senderBotId = prepared.message.bot_id;
751
- if (!senderBotId) return;
752
- const receiverBotId = prepared.ctx.botId || prepared.ctx.botUserId;
753
- if (!receiverBotId || senderBotId === prepared.ctx.botId || prepared.message.user === prepared.ctx.botUserId) return;
754
- return {
755
- scopeId: prepared.route.accountId,
756
- conversationId: prepared.message.channel,
757
- senderId: senderBotId,
758
- receiverId: receiverBotId,
759
- config: mergePairLoopGuardConfig(prepared.account.config.botLoopProtection, prepared.channelConfig?.botLoopProtection),
760
- defaultsConfig: prepared.ctx.cfg.channels?.defaults?.botLoopProtection,
761
- defaultEnabled: true,
762
- nowMs: resolveSlackMessageTimestampMs(prepared.message)
763
- };
533
+ function legacyLineTitle(line) {
534
+ return `${line.icon ?? "•"} *${escapeSlackMrkdwn(line.label)}*`;
764
535
  }
765
- function isSlackStreamingEnabled(params) {
766
- if (params.mode === "partial") return params.nativeStreaming;
767
- if (params.mode === "progress") return params.nativeStreaming && params.nativeProgressTaskCards === true;
768
- return false;
536
+ function legacyLineDetail(line, maxChars) {
537
+ const detail = lineDetailParts(line).join(" · ");
538
+ return detail ? escapeSlackMrkdwn(compactDetail(detail, maxChars)) : "";
769
539
  }
770
- function shouldEnableSlackPreviewStreaming(params) {
771
- return params.mode !== "off";
540
+ function lineTaskTitle(line, maxLineChars) {
541
+ const label = line.label.replace(/\s+/g, " ").trim() || line.toolName || line.kind || "Update";
542
+ const detail = lineDetailParts(line).join(" · ") || line.status?.trim();
543
+ const fallback = line.text.replace(/\s+/g, " ").trim();
544
+ if (detail) return compactTitle(`${label} — ${compactDetail(detail, maxLineChars)}`);
545
+ if (fallback && fallback !== label) return compactTitle(fallback);
546
+ return compactTitle(label);
772
547
  }
773
- function shouldInitializeSlackDraftStream(params) {
774
- return params.previewStreamingEnabled && !params.useStreaming;
548
+ function lineTaskStatus(line) {
549
+ const normalized = line.status?.replace(/\s+/g, " ").trim().toLowerCase();
550
+ if (!normalized) return "in_progress";
551
+ if (normalized === "complete" || normalized === "completed" || normalized === "done" || normalized === "ok" || normalized === "success" || normalized === "succeeded" || normalized === "successful" || normalized === "exit 0") return "complete";
552
+ if (normalized === "error" || normalized === "failed" || normalized === "failure" || normalized.startsWith("exit ")) return normalized === "exit 0" ? "complete" : "error";
553
+ return "in_progress";
775
554
  }
776
- function resolveSlackDisableBlockStreaming(params) {
777
- if (params.useStreaming || params.shouldUseDraftStream) return true;
778
- return typeof params.blockStreamingEnabled === "boolean" ? !params.blockStreamingEnabled : void 0;
555
+ function slugTaskIdPart(value) {
556
+ return value?.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "task";
779
557
  }
780
- function resolveExplicitSlackProgressTitle(entry) {
781
- const label = resolveChannelProgressDraftConfig(entry).label;
782
- if (typeof label !== "string") return;
783
- const trimmed = label.trim();
784
- return trimmed && trimmed.toLowerCase() !== "auto" ? trimmed : void 0;
558
+ function stableTaskIdPart(value, slugValue = value) {
559
+ const suffix = createHash("sha256").update(value).digest("hex").slice(0, 8);
560
+ return `${slugTaskIdPart(slugValue).slice(0, 48)}_${suffix}`;
785
561
  }
786
- function resolveSlackNativeProgressTaskCards(entry) {
787
- const streaming = entry?.streaming;
788
- if (!streaming || typeof streaming !== "object" || Array.isArray(streaming)) return false;
789
- const progressConfig = streaming.progress;
790
- return Boolean(progressConfig) && typeof progressConfig === "object" && !Array.isArray(progressConfig) && progressConfig.nativeTaskCards === true;
562
+ function resolveLineTaskIdentity(line) {
563
+ if (line.id?.trim()) return {
564
+ id: stableTaskIdPart(line.id),
565
+ contentDerived: false
566
+ };
567
+ return {
568
+ id: stableTaskIdPart([
569
+ line.kind,
570
+ line.toolName,
571
+ line.label,
572
+ line.text
573
+ ].join("\0"), line.toolName ?? line.kind ?? line.label),
574
+ contentDerived: true
575
+ };
791
576
  }
792
- function resolveSlackStreamingThreadHint(params) {
793
- return resolveSlackThreadTs({
794
- replyToMode: params.replyToMode,
795
- incomingThreadTs: params.incomingThreadTs,
796
- messageTs: params.messageTs,
797
- hasReplied: false,
798
- isThreadReply: params.isThreadReply
577
+ function buildPlanTasks(params) {
578
+ if (params.plan) return params.plan.slice(-50).map((entry, index) => ({
579
+ id: `plan_step_${index + 1}`,
580
+ title: compactTitle(entry.step),
581
+ status: entry.status === "completed" ? "complete" : entry.status
582
+ }));
583
+ const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_TASK_DETAIL_MAX_CHARS);
584
+ const lines = params.lines.slice(-50);
585
+ const identities = lines.map(resolveLineTaskIdentity);
586
+ const contentIdOccurrences = /* @__PURE__ */ new Map();
587
+ return lines.map((line, index) => {
588
+ const identity = identities[index];
589
+ let id = identity.id;
590
+ if (identity.contentDerived) {
591
+ const occurrence = (contentIdOccurrences.get(id) ?? 0) + 1;
592
+ contentIdOccurrences.set(id, occurrence);
593
+ id = `${id}_${occurrence}`;
594
+ }
595
+ return {
596
+ id,
597
+ title: lineTaskTitle(line, maxLineChars),
598
+ status: lineTaskStatus(line)
599
+ };
799
600
  });
800
601
  }
801
- const SLACK_STREAM_RECIPIENT_TEAM_CACHE_MAX = 2e3;
802
- const slackStreamRecipientTeamCaches = /* @__PURE__ */ new WeakMap();
803
- function getSlackStreamRecipientTeamCache(client) {
804
- const existing = slackStreamRecipientTeamCaches.get(client);
805
- if (existing) return existing;
806
- const cache = /* @__PURE__ */ new Map();
807
- slackStreamRecipientTeamCaches.set(client, cache);
808
- return cache;
602
+ function resolvePlanTitle(params) {
603
+ return compactChunkText(params.title?.trim() || params.label?.trim() || params.tasks.at(-1)?.title || SLACK_PROGRESS_PLAN_FALLBACK_TITLE);
809
604
  }
810
- function buildSlackEventDeliveryKey(params) {
811
- const reply = resolveSendableOutboundReplyParts(params.payload, { text: params.textOverride });
812
- const renderPlan = resolveSlackReplyRenderPlan(params.payload, params.textOverride ?? params.payload.text);
813
- const plannedBlocks = renderPlan.mode === "single" ? renderPlan.blocks : renderPlan.blockPart?.blocks;
814
- const slackBlocks = readSlackReplyBlocks(params.payload) ?? plannedBlocks;
815
- const renderedText = renderPlan.mode === "single" ? renderPlan.text : renderPlan.fallbackText;
816
- if (!reply.hasContent && !slackBlocks?.length && !renderedText.trim()) return null;
817
- return JSON.stringify({
818
- kind: params.kind,
819
- threadTs: params.threadTs ?? "",
820
- replyToId: params.payload.replyToId ?? null,
821
- text: renderedText || reply.trimmedText,
822
- mediaUrls: reply.mediaUrls,
823
- blocks: slackBlocks ?? null
605
+ function buildSlackProgressStreamChunks(params) {
606
+ const tasks = buildPlanTasks({
607
+ lines: params.lines,
608
+ plan: params.plan,
609
+ maxLineChars: params.maxLineChars
824
610
  });
825
- }
826
- function readSlackStreamRecipientTeamCache(params) {
827
- if (!params.fallbackTeamId || !params.userId) return;
828
- const cacheKey = `${params.fallbackTeamId}:${params.userId}`;
829
- const cache = getSlackStreamRecipientTeamCache(params.client);
830
- const cached = cache.get(cacheKey);
831
- if (!cached) return;
832
- cache.delete(cacheKey);
833
- cache.set(cacheKey, cached);
834
- return cached;
835
- }
836
- function rememberSlackStreamRecipientTeam(params) {
837
- if (!params.fallbackTeamId || !params.userId) return;
838
- const cacheKey = `${params.fallbackTeamId}:${params.userId}`;
839
- const cache = getSlackStreamRecipientTeamCache(params.client);
840
- if (cache.has(cacheKey)) cache.delete(cacheKey);
841
- cache.set(cacheKey, params.teamId);
842
- if (cache.size > SLACK_STREAM_RECIPIENT_TEAM_CACHE_MAX) {
843
- const oldest = cache.keys().next().value;
844
- if (oldest) cache.delete(oldest);
611
+ if (tasks.length === 0) {
612
+ const title = params.title?.trim() || params.label?.trim();
613
+ return title ? [{
614
+ type: "plan_update",
615
+ title: compactChunkText(title)
616
+ }] : void 0;
845
617
  }
846
- }
847
- function createSlackEventDeliveryTracker() {
848
- const deliveredKeys = /* @__PURE__ */ new Set();
849
- return {
850
- hasDelivered(params) {
851
- const key = buildSlackEventDeliveryKey(params);
852
- return key ? deliveredKeys.has(key) : false;
853
- },
854
- markDelivered(params) {
855
- const key = buildSlackEventDeliveryKey(params);
856
- if (key) deliveredKeys.add(key);
857
- }
618
+ return [{
619
+ type: "plan_update",
620
+ title: resolvePlanTitle({
621
+ label: params.label,
622
+ title: params.title,
623
+ tasks
624
+ })
625
+ }, ...tasks.map((task) => ({
626
+ type: "task_update",
627
+ id: task.id,
628
+ title: task.title,
629
+ status: task.status === "in_progress" ? params.finalInProgressStatus ?? (params.completeInProgress ? "complete" : task.status) : task.status
630
+ }))];
631
+ }
632
+ function buildSlackProgressDraftBlocks(params) {
633
+ const label = params.label?.trim() || params.title?.trim();
634
+ const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS);
635
+ const planLines = formatPlanChecklistLines(params.plan ?? [], {
636
+ maxLines: 50,
637
+ maxLineChars
638
+ });
639
+ const narration = params.narration?.replace(/\s+/g, " ").trim();
640
+ const headBlocks = [
641
+ ...label ? [{
642
+ type: "section",
643
+ text: field(`*${escapeSlackMrkdwn(label)}*`)
644
+ }] : [],
645
+ ...narration ? [{
646
+ type: "section",
647
+ text: field(`_${escapeSlackMrkdwn(narration)}_`)
648
+ }] : [],
649
+ ...planLines.length > 0 ? [{
650
+ type: "section",
651
+ text: field(planLines.map((line) => escapeSlackMrkdwn(line)).join("\n"))
652
+ }] : []
653
+ ].slice(0, 50);
654
+ const lineBudget = Math.max(0, 50 - headBlocks.length);
655
+ const renderedBlocks = [...headBlocks, ...params.lines.slice(-lineBudget).map((line) => ({
656
+ type: "section",
657
+ fields: [field(legacyLineTitle(line)), field(legacyLineDetail(line, maxLineChars))]
658
+ }))];
659
+ return renderedBlocks.length ? renderedBlocks : void 0;
660
+ }
661
+ /**
662
+ * Slack native streams key task rows by persistent id with no removal chunk.
663
+ * When the task source switches representation (tool lines <-> typed plan) or
664
+ * a snapshot drops ids, previously emitted non-terminal rows must receive a
665
+ * final update or they linger in_progress forever.
666
+ */
667
+ function reconcileSlackNativeTaskChunks(params) {
668
+ const nextTasks = /* @__PURE__ */ new Map();
669
+ for (const chunk of params.chunks ?? []) if (chunk.type === "task_update") nextTasks.set(chunk.id, {
670
+ title: chunk.title,
671
+ status: chunk.status
672
+ });
673
+ const terminalized = [...params.previousTasks].filter(([id, task]) => !nextTasks.has(id) && task.status !== "complete" && task.status !== "error").map(([id, task]) => {
674
+ const entry = {
675
+ title: task.title,
676
+ status: "complete"
677
+ };
678
+ nextTasks.set(id, entry);
679
+ return {
680
+ type: "task_update",
681
+ id,
682
+ title: task.title,
683
+ status: "complete"
684
+ };
685
+ });
686
+ for (const [id, task] of params.previousTasks) if (!nextTasks.has(id)) nextTasks.set(id, task);
687
+ return {
688
+ chunks: params.chunks?.length ? [...params.chunks, ...terminalized] : terminalized.length ? terminalized : params.chunks,
689
+ tasks: nextTasks
858
690
  };
859
691
  }
860
- function shouldUseStreaming(params) {
861
- if (!params.streamingEnabled) return false;
862
- if (!params.threadTs) {
863
- logVerbose("slack-stream: streaming disabled — no reply thread target available");
692
+ function buildSlackProgressStreamStartChunks(params) {
693
+ return buildSlackProgressStreamChunks(params);
694
+ }
695
+ function buildSlackProgressStreamUpdateChunks(params) {
696
+ return buildSlackProgressStreamChunks(params);
697
+ }
698
+ function buildSlackProgressStreamCompletionChunks(params) {
699
+ return buildSlackProgressStreamChunks({
700
+ ...params,
701
+ completeInProgress: true
702
+ });
703
+ }
704
+ //#endregion
705
+ //#region extensions/slack/src/stream-mode.ts
706
+ function resolveSlackStreamingConfig(params) {
707
+ const mode = resolveSlackStreamingMode(params);
708
+ return {
709
+ mode,
710
+ nativeStreaming: resolveSlackNativeStreaming(params),
711
+ draftMode: mapStreamingModeToSlackLegacyDraftStreamMode(mode)
712
+ };
713
+ }
714
+ function applyAppendOnlyStreamUpdate(params) {
715
+ const incoming = params.incoming.trimEnd();
716
+ if (!incoming) return {
717
+ rendered: params.rendered,
718
+ source: params.source,
719
+ changed: false
720
+ };
721
+ if (!params.rendered) return {
722
+ rendered: incoming,
723
+ source: incoming,
724
+ changed: true
725
+ };
726
+ if (incoming === params.source) return {
727
+ rendered: params.rendered,
728
+ source: params.source,
729
+ changed: false
730
+ };
731
+ if (incoming.startsWith(params.source) || incoming.startsWith(params.rendered)) return {
732
+ rendered: incoming,
733
+ source: incoming,
734
+ changed: incoming !== params.rendered
735
+ };
736
+ if (params.source.startsWith(incoming)) return {
737
+ rendered: params.rendered,
738
+ source: params.source,
739
+ changed: false
740
+ };
741
+ const separator = params.rendered.endsWith("\n") ? "" : "\n";
742
+ return {
743
+ rendered: `${params.rendered}${separator}${incoming}`,
744
+ source: incoming,
745
+ changed: true
746
+ };
747
+ }
748
+ //#endregion
749
+ //#region extensions/slack/src/monitor/message-handler/preview-finalize.ts
750
+ function buildExpectedSlackEditText(params) {
751
+ const trimmedText = params.text.trim();
752
+ if (trimmedText) return normalizeSlackOutboundText(trimmedText);
753
+ if (params.blocks?.length) return normalizeSlackOutboundText(buildSlackBlocksFallbackText(params.blocks));
754
+ return " ";
755
+ }
756
+ function buildAcceptedSlackEditTexts(params) {
757
+ const expected = buildExpectedSlackEditText(params);
758
+ const texts = /* @__PURE__ */ new Set([
759
+ expected,
760
+ normalizeSlackOutboundText(truncateSlackText(expected, SLACK_EDIT_TEXT_LIMIT)),
761
+ normalizeSlackOutboundText(buildSlackEditTextPayload(params.text, params.blocks))
762
+ ]);
763
+ if (params.blocks?.length && hasSlackNativeDataBlock(params.blocks)) {
764
+ const fallbackPlan = buildSlackNativeDataDeliveryPlan({
765
+ baseText: params.text,
766
+ blocks: params.blocks
767
+ });
768
+ for (const message of fallbackPlan.fallbackMessages) texts.add(normalizeSlackOutboundText(message.text));
769
+ }
770
+ return texts;
771
+ }
772
+ function blocksMatch(expected, actual) {
773
+ if (!expected?.length) return !actual?.length;
774
+ if (!actual?.length) {
775
+ if (!hasSlackNativeDataBlock(expected)) return false;
776
+ return buildSlackNativeDataDeliveryPlan({ blocks: expected }).fallbackMessages.every((message) => !message.blocks?.length);
777
+ }
778
+ if (JSON.stringify(expected) === JSON.stringify(actual)) return true;
779
+ if (!hasSlackNativeDataBlock(expected)) return false;
780
+ try {
781
+ const fallbackPlan = buildSlackNativeDataDeliveryPlan({ blocks: expected });
782
+ const fallbackBlocks = fallbackPlan.fallbackMessages.flatMap((message) => message.blocks ?? []);
783
+ if (JSON.stringify(fallbackBlocks) === JSON.stringify(actual)) return true;
784
+ const fallbackText = fallbackPlan.fallbackMessages.map((message) => message.text).filter(Boolean).join("\n\n");
785
+ return normalizeSlackOutboundText(buildSlackBlocksFallbackText(actual)) === normalizeSlackOutboundText(fallbackText);
786
+ } catch {
864
787
  return false;
865
788
  }
866
- return true;
867
789
  }
868
- async function resolveSlackStreamRecipientTeamId(params) {
869
- const cachedTeamId = readSlackStreamRecipientTeamCache(params);
870
- if (cachedTeamId) return cachedTeamId;
871
- if (params.userId) try {
872
- const info = await params.client.users.info({
790
+ async function readSlackMessageAfterEditError(params) {
791
+ if (params.threadTs) return ((await params.client.conversations.replies({
792
+ token: params.token,
793
+ channel: params.channelId,
794
+ ts: params.threadTs,
795
+ latest: params.messageId,
796
+ inclusive: true,
797
+ limit: 100
798
+ })).messages ?? []).find((message) => message?.ts === params.messageId) ?? null;
799
+ const message = (await params.client.conversations.history({
800
+ token: params.token,
801
+ channel: params.channelId,
802
+ latest: params.messageId,
803
+ oldest: params.messageId,
804
+ inclusive: true,
805
+ limit: 1
806
+ })).messages?.[0];
807
+ if (!message?.ts || message.ts !== params.messageId) return null;
808
+ return message;
809
+ }
810
+ async function didSlackPreviewEditApplyAfterError(params) {
811
+ const readback = await readSlackMessageAfterEditError(params);
812
+ if (!readback) return false;
813
+ const expectedText = buildExpectedSlackEditText({
814
+ text: params.text,
815
+ blocks: params.blocks
816
+ });
817
+ const acceptedTexts = buildAcceptedSlackEditTexts({
818
+ text: params.text,
819
+ blocks: params.blocks
820
+ });
821
+ const actualText = normalizeSlackOutboundText((readback.text ?? "").trim());
822
+ if (params.blocks?.length) return acceptedTexts.has(actualText) && blocksMatch(params.blocks, readback.blocks);
823
+ return actualText === expectedText;
824
+ }
825
+ async function finalizeSlackPreviewEdit(params) {
826
+ try {
827
+ await editSlackMessage(params.channelId, params.messageId, params.text, {
873
828
  token: params.token,
874
- user: params.userId
829
+ accountId: params.accountId,
830
+ client: params.client,
831
+ ...params.blocks?.length ? { blocks: params.blocks } : {}
875
832
  });
876
- const teamId = info.user?.team_id ?? info.user?.profile?.team;
877
- if (teamId) {
878
- rememberSlackStreamRecipientTeam({
879
- ...params,
880
- teamId
881
- });
882
- return teamId;
883
- }
884
833
  } catch (err) {
885
- logVerbose(`slack-stream: users.info team lookup failed (${formatErrorMessage(err)})`);
834
+ try {
835
+ if (await didSlackPreviewEditApplyAfterError({
836
+ client: params.client,
837
+ token: params.token,
838
+ channelId: params.channelId,
839
+ messageId: params.messageId,
840
+ text: params.text,
841
+ blocks: params.blocks,
842
+ threadTs: params.threadTs
843
+ })) {
844
+ logVerbose(`slack: preview final edit response failed but readback matched message ${params.channelId}/${params.messageId}; suppressing duplicate fallback send`);
845
+ return;
846
+ }
847
+ } catch (readbackErr) {
848
+ logVerbose(`slack: preview final edit readback failed (${String(readbackErr)})`);
849
+ }
850
+ throw err;
886
851
  }
887
- return params.fallbackTeamId;
888
852
  }
889
- async function dispatchPreparedSlackMessage(prepared) {
890
- const { ctx, account, message, route } = prepared;
891
- const slackClient = prepared.eventScope?.client ?? ctx.app.client;
892
- const slackStreamFallbackTeamId = prepared.eventScope?.teamId ?? ctx.teamId;
893
- const cfg = ctx.cfg;
894
- const runtime = ctx.runtime;
895
- const outboundIdentity = resolveAgentOutboundIdentity(cfg, route.agentId);
896
- const slackIdentity = outboundIdentity ? {
897
- username: outboundIdentity.name,
898
- iconUrl: outboundIdentity.avatarUrl,
899
- iconEmoji: outboundIdentity.emoji
900
- } : prepared.relayIdentity;
901
- if (prepared.isDirectMessage) {
902
- const sessionCfg = cfg.session;
903
- const storePath = resolveStorePath(sessionCfg?.store, { agentId: route.agentId });
904
- const pinnedMainDmOwner = resolvePinnedMainDmOwnerFromAllowlist({
905
- dmScope: cfg.session?.dmScope,
906
- allowFrom: ctx.allowFrom,
907
- normalizeEntry: normalizeSlackAllowOwnerEntry
908
- });
909
- const senderRecipient = normalizeOptionalLowercaseString(message.user);
910
- const inboundLastRouteSessionKey = resolveInboundLastRouteSessionKey({
911
- route,
912
- sessionKey: prepared.ctxPayload.SessionKey ?? route.sessionKey
913
- });
914
- if (inboundLastRouteSessionKey === route.mainSessionKey && pinnedMainDmOwner && senderRecipient && normalizeOptionalLowercaseString(pinnedMainDmOwner) !== senderRecipient) logVerbose(`slack: skip main-session last route for ${senderRecipient} (pinned owner ${pinnedMainDmOwner})`);
915
- else await updateLastRoute({
916
- storePath,
917
- sessionKey: inboundLastRouteSessionKey,
918
- deliveryContext: {
919
- channel: "slack",
920
- to: `user:${message.user}`,
921
- accountId: route.accountId,
922
- threadId: prepared.ctxPayload.MessageThreadId ?? prepared.ctxPayload.TransportThreadId
923
- },
924
- ctx: prepared.ctxPayload
853
+ //#endregion
854
+ //#region extensions/slack/src/monitor/message-handler/dispatch-progress-io.ts
855
+ async function collapseSlackProgressReceipt(params) {
856
+ const { account, ctx, slackClient } = params.setup;
857
+ try {
858
+ await finalizeSlackPreviewEdit({
859
+ client: slackClient,
860
+ token: ctx.botToken,
861
+ accountId: account.accountId,
862
+ ...params.edit
925
863
  });
864
+ return true;
865
+ } catch (err) {
866
+ logVerbose(`slack: progress receipt edit failed (${formatSlackError(err)})`);
867
+ return false;
926
868
  }
927
- const threadTargets = resolveSlackThreadTargets({
928
- message,
929
- replyToMode: prepared.replyToMode
869
+ }
870
+ //#endregion
871
+ //#region extensions/slack/src/monitor/message-handler/dispatch-progress-render.ts
872
+ function resolveStructuredProgressLines(lines) {
873
+ return lines.map((line) => {
874
+ if (typeof line !== "string") return line;
875
+ const reasoning = line.startsWith("🧠 ");
876
+ const text = line.replace(/^(?:🧠|💬)\s+/u, "").replace(/^_(.*)_$/su, "$1").trim();
877
+ return {
878
+ ...reasoning ? { id: "reasoning" } : {},
879
+ kind: "item",
880
+ text,
881
+ label: reasoning ? "Reasoning" : "Update",
882
+ prefix: false
883
+ };
930
884
  });
931
- const forcedReplyThreadTs = prepared.forcedReplyThreadTs;
932
- const slackMessageMetadata = prepared.slackMessageMetadata;
933
- const statusThreadTs = forcedReplyThreadTs ?? threadTargets.statusThreadTs;
934
- const isThreadReply = threadTargets.isThreadReply;
935
- const replyDeliveryMode = forcedReplyThreadTs ? "off" : prepared.replyToMode;
936
- const sourceReplyDeliveryMode = resolveChannelMessageSourceReplyDeliveryMode({
937
- cfg,
938
- ctx: prepared.ctxPayload
885
+ }
886
+ function resolveNativeProgressPlan(snapshot) {
887
+ return snapshot.plan?.length ? snapshot.plan : void 0;
888
+ }
889
+ function resolveNativeProgressLines(snapshot) {
890
+ const lines = resolveStructuredProgressLines(snapshot.lines);
891
+ if (snapshot.plan?.length || !snapshot.planExplanation) return lines;
892
+ const explanationLine = buildChannelProgressDraftLine({
893
+ event: "plan",
894
+ phase: "update",
895
+ explanation: snapshot.planExplanation
939
896
  });
940
- const sourceRepliesAreToolOnly = sourceReplyDeliveryMode === "message_tool_only";
941
- const suppressRoomEventTyping = prepared.ctxPayload.InboundEventKind === "room_event";
942
- const messageSentHookTarget = prepared.ctxPayload.OriginatingTo ?? prepared.ctxPayload.To ?? prepared.replyTarget;
943
- const messageSentHookContext = {
944
- sessionKeyForInternalHooks: prepared.ctxPayload.SessionKey ?? route.sessionKey,
945
- isGroup: prepared.isRoomish,
946
- groupId: prepared.isRoomish ? message.channel : void 0
947
- };
948
- const messageSentDeliveryHookContext = {
949
- ...messageSentHookContext,
950
- messageSentHookTarget
897
+ return explanationLine ? [...lines, explanationLine] : lines;
898
+ }
899
+ function combineProgressHeadlineAndExplanation(headline, explanation) {
900
+ return headline && explanation && headline !== explanation ? `${headline} — ${explanation}` : headline ?? explanation;
901
+ }
902
+ function buildNativeProgressChunks(params) {
903
+ const input = {
904
+ title: params.title,
905
+ lines: resolveNativeProgressLines(params.snapshot),
906
+ plan: resolveNativeProgressPlan(params.snapshot),
907
+ maxLineChars: params.maxLineChars
951
908
  };
952
- const reactionMessageTs = prepared.ackReactionMessageTs;
953
- const messageTs = message.ts ?? message.event_ts;
954
- const incomingThreadTs = message.thread_ts;
955
- let didSetStatus = false;
956
- const statusReactionsEnabled = prepared.ctxPayload.InboundEventKind !== "room_event" && Boolean(prepared.ackReactionPromise) && Boolean(reactionMessageTs) && cfg.messages?.statusReactions?.enabled === true;
957
- const slackStatusAdapter = {
958
- setReaction: async (emoji) => {
959
- await reactSlackMessage(message.channel, reactionMessageTs ?? "", emoji, {
960
- token: ctx.botToken,
961
- client: slackClient
962
- }).catch((err) => {
963
- if (formatErrorMessage(err).includes("already_reacted")) return;
964
- throw err;
965
- });
909
+ return params.streamStarted ? buildSlackProgressStreamUpdateChunks(input) : buildSlackProgressStreamStartChunks(input);
910
+ }
911
+ //#endregion
912
+ //#region extensions/slack/src/monitor/message-handler/dispatch-progress.ts
913
+ function createSlackProgressRuntime(runtimeParams) {
914
+ const { setup, delivery, resetPreviewDeliveryState } = runtimeParams;
915
+ const { account, cfg, ctx, message, prepared, replyPlan, runtime, slackClient, slackIdentity, slackMessageMetadata, slackStreaming, slackStreamFallbackTeamId, shouldUseDraftStream, useStreaming, previewStreamingEnabled } = setup;
916
+ const draftStream = shouldUseDraftStream ? createSlackDraftStream({
917
+ target: prepared.replyTarget,
918
+ cfg,
919
+ token: ctx.botToken,
920
+ accountId: account.accountId,
921
+ ...prepared.eventScope ? { eventScope: prepared.eventScope } : {},
922
+ identity: slackIdentity,
923
+ ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
924
+ maxChars: Math.min(ctx.textLimit, SLACK_TEXT_LIMIT),
925
+ resolveThreadTs: () => {
926
+ const ts = replyPlan.peekThreadTs();
927
+ if (ts) delivery.usedReplyThreadTs ??= ts;
928
+ return ts;
966
929
  },
967
- removeReaction: async (emoji) => {
968
- await removeSlackReaction(message.channel, reactionMessageTs ?? "", emoji, {
969
- token: ctx.botToken,
970
- client: slackClient
971
- }).catch((err) => {
972
- if (formatErrorMessage(err).includes("no_reaction")) return;
973
- throw err;
974
- });
930
+ log: logVerbose,
931
+ warn: logVerbose
932
+ }) : void 0;
933
+ let hasStreamedMessage = false;
934
+ const streamMode = slackStreaming.draftMode;
935
+ const useNativeProgressStreaming = useStreaming && slackStreaming.mode === "progress";
936
+ const progressDraftActive = Boolean(draftStream) || useNativeProgressStreaming;
937
+ const previewToolProgressEnabled = progressDraftActive && resolveChannelStreamingPreviewToolProgress(account.config);
938
+ let shouldYieldDraftProgress = () => false;
939
+ const suppressDefaultToolProgressMessages = resolveChannelStreamingSuppressDefaultToolProgressMessages(account.config, {
940
+ draftStreamActive: Boolean(draftStream) || useNativeProgressStreaming,
941
+ previewToolProgressEnabled,
942
+ previewStreamingEnabled
943
+ });
944
+ let previewToolProgressSuppressed = false;
945
+ let legacyPreviewToolProgressLines = [];
946
+ let nativeTaskState = /* @__PURE__ */ new Map();
947
+ let appendRenderedText = "";
948
+ let appendSourceText = "";
949
+ let nativeProgressCompletionSent = false;
950
+ let nativeProgressTerminalStatus = "complete";
951
+ let nativeProgressChunkKey;
952
+ const progressReceipt = createChannelProgressReceiptTracker();
953
+ let progressReceiptCollapsed = false;
954
+ let pendingNativeProgressReceipt;
955
+ const progressSeed = `${account.accountId}:${message.channel}`;
956
+ const useRichProgressDraft = streamMode === "status_final" && resolveChannelProgressDraftRender(account.config) === "rich";
957
+ const explicitProgressTitle = resolveExplicitSlackProgressTitle(account.config);
958
+ const progressDraftMaxLineChars = resolveChannelProgressDraftMaxLineChars(account.config);
959
+ const waitForNativeProgressStreamStart = async () => {
960
+ if (delivery.streamSession || !delivery.nativeProgressStreamStartPromise) return true;
961
+ try {
962
+ await delivery.nativeProgressStreamStartPromise;
963
+ } catch {
964
+ delivery.streamFailed = true;
965
+ return false;
975
966
  }
967
+ return !delivery.streamFailed;
976
968
  };
977
- const statusReactionTiming = {
978
- ...DEFAULT_TIMING,
979
- ...cfg.messages?.statusReactions?.timing
980
- };
981
- const statusReactions = createStatusReactionController({
982
- enabled: statusReactionsEnabled,
983
- adapter: slackStatusAdapter,
984
- initialEmoji: prepared.ackReactionValue || "eyes",
985
- emojis: cfg.messages?.statusReactions?.emojis,
986
- timing: cfg.messages?.statusReactions?.timing,
987
- onError: (err) => {
988
- logAckFailure({
989
- log: logVerbose,
990
- channel: "slack",
991
- target: `${message.channel}/${message.ts}`,
992
- error: err
969
+ const appendNativeProgressCompletion = async (isError) => {
970
+ const session = delivery.streamSession;
971
+ if (isError) nativeProgressTerminalStatus = "error";
972
+ if (!session || nativeProgressCompletionSent) return;
973
+ const chunks = buildNativeProgressCompletionChunks(isError ? "error" : "complete");
974
+ if (!chunks?.length) return;
975
+ try {
976
+ await appendSlackStream({
977
+ session,
978
+ chunks
993
979
  });
980
+ nativeProgressCompletionSent = true;
981
+ delivery.observedReplyDelivery ||= session.delivered;
982
+ } catch (err) {
983
+ delivery.streamFailed = true;
984
+ runtime.error?.(danger(`slack-stream: native progress completion failed: ${formatSlackError(err)}`));
994
985
  }
986
+ };
987
+ const buildNativeProgressChunks$1 = (snapshot) => buildNativeProgressChunks({
988
+ snapshot,
989
+ streamStarted: Boolean(delivery.streamSession),
990
+ title: combineProgressHeadlineAndExplanation(explicitProgressTitle ?? snapshot.statusHeadline, snapshot.planExplanation),
991
+ maxLineChars: progressDraftMaxLineChars
995
992
  });
996
- if (statusReactionsEnabled) statusReactions.setQueued();
997
- const hasRepliedRef = { value: false };
998
- const replyPlan = createSlackReplyDeliveryPlan({
999
- replyToMode: replyDeliveryMode,
1000
- incomingThreadTs: forcedReplyThreadTs ?? incomingThreadTs,
1001
- messageTs,
1002
- hasRepliedRef,
1003
- isThreadReply: Boolean(forcedReplyThreadTs) || isThreadReply
1004
- });
1005
- const typingTarget = statusThreadTs ? `${message.channel}/${statusThreadTs}` : message.channel;
1006
- const typingReaction = ctx.typingReaction;
1007
- const { onModelSelected, ...replyPipeline } = createChannelMessageReplyPipeline({
1008
- cfg,
1009
- agentId: route.agentId,
1010
- channel: "slack",
1011
- accountId: route.accountId,
1012
- transformReplyPayload: (payload) => {
1013
- if (payload.isReasoning === true) return null;
1014
- return isSlackInteractiveRepliesEnabled({
1015
- cfg,
1016
- accountId: route.accountId
1017
- }) ? compileSlackInteractiveReplies(payload) : payload;
1018
- },
1019
- typing: {
1020
- start: async () => {
1021
- didSetStatus = true;
1022
- await ctx.setSlackThreadStatus({
1023
- channelId: message.channel,
1024
- threadTs: statusThreadTs,
1025
- status: "is typing...",
1026
- eventScope: prepared.eventScope
1027
- });
1028
- if (typingReaction && message.ts) await reactSlackMessage(message.channel, message.ts, typingReaction, {
1029
- token: ctx.botToken,
1030
- client: slackClient
1031
- }).catch((err) => {
1032
- logVerbose(`slack send: typing reaction failed: ${formatSlackError(err)}`);
1033
- });
1034
- },
1035
- stop: async () => {
1036
- if (!didSetStatus) return;
1037
- didSetStatus = false;
1038
- await ctx.setSlackThreadStatus({
1039
- channelId: message.channel,
1040
- threadTs: statusThreadTs,
1041
- status: "",
1042
- eventScope: prepared.eventScope
1043
- });
1044
- if (typingReaction && message.ts) await removeSlackReaction(message.channel, message.ts, typingReaction, {
1045
- token: ctx.botToken,
1046
- client: slackClient
1047
- }).catch((err) => {
1048
- logVerbose(`slack send: typing reaction removal failed: ${formatSlackError(err)}`);
1049
- });
1050
- },
1051
- onStartError: (err) => {
1052
- logTypingFailure({
1053
- log: (messageValue) => runtime.error?.(danger(messageValue)),
1054
- channel: "slack",
1055
- action: "start",
1056
- target: typingTarget,
1057
- error: err
1058
- });
1059
- },
1060
- onStopError: (err) => {
1061
- logTypingFailure({
1062
- log: (messageLocal) => runtime.error?.(danger(messageLocal)),
1063
- channel: "slack",
1064
- action: "stop",
1065
- target: typingTarget,
1066
- error: err
1067
- });
1068
- }
993
+ const resolveNativeProgressTitle = (snapshot) => combineProgressHeadlineAndExplanation(explicitProgressTitle ?? snapshot.statusHeadline, snapshot.planExplanation);
994
+ const markNativeProgressDelivered = (session, threadTs) => {
995
+ if (session.delivered) delivery.observedReplyDelivery = true;
996
+ if (threadTs) {
997
+ delivery.usedReplyThreadTs ??= threadTs;
998
+ delivery.rememberDeliveredThreadTs("block", threadTs);
1069
999
  }
1070
- });
1071
- const slackStreaming = resolveSlackStreamingConfig({
1072
- streaming: account.config.streaming,
1073
- nativeStreaming: resolveChannelStreamingNativeTransport(account.config)
1074
- });
1075
- const streamThreadHint = forcedReplyThreadTs ?? resolveSlackStreamingThreadHint({
1076
- replyToMode: replyDeliveryMode,
1077
- incomingThreadTs,
1078
- messageTs,
1079
- isThreadReply
1080
- });
1081
- const previewStreamingEnabled = !sourceRepliesAreToolOnly && shouldEnableSlackPreviewStreaming({ mode: slackStreaming.mode });
1082
- const hasSlackCustomIdentity = Boolean(slackIdentity?.username || slackIdentity?.iconUrl || slackIdentity?.iconEmoji);
1083
- const useStreaming = shouldUseStreaming({
1084
- streamingEnabled: !sourceRepliesAreToolOnly && isSlackStreamingEnabled({
1085
- mode: slackStreaming.mode,
1086
- nativeStreaming: slackStreaming.nativeStreaming,
1087
- nativeProgressTaskCards: resolveSlackNativeProgressTaskCards(account.config)
1088
- }),
1089
- threadTs: streamThreadHint
1090
- });
1091
- const shouldUseDraftStream = !hasSlackCustomIdentity && shouldInitializeSlackDraftStream({
1092
- previewStreamingEnabled,
1093
- useStreaming
1094
- });
1095
- const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
1096
- const disableBlockStreaming = sourceRepliesAreToolOnly ? true : resolveSlackDisableBlockStreaming({
1097
- useStreaming,
1098
- shouldUseDraftStream,
1099
- blockStreamingEnabled
1100
- });
1101
- let streamSession = null;
1102
- let nativeProgressStreamStartPromise = null;
1103
- let nativeProgressStreamThreadTs;
1104
- let streamFailed = false;
1105
- let usedReplyThreadTs;
1106
- let usedBlockReplyThreadTs;
1107
- let observedReplyDelivery = false;
1108
- let observedFinalReplyDelivery = false;
1109
- const streamedDeliveries = [];
1110
- const streamedFailuresOwnedByDispatcher = {
1111
- tool: 0,
1112
- block: 0,
1113
- final: 0
1114
- };
1115
- const refreshStreamedAcknowledgements = (session) => {
1116
- if (session.pendingText.length === 0) for (const delivery of streamedDeliveries) delivery.acknowledged = true;
1117
1000
  };
1118
- const recordStreamedDelivery = (kind, content) => {
1119
- const delivery = {
1120
- kind,
1121
- content,
1122
- acknowledged: false
1123
- };
1124
- streamedDeliveries.push(delivery);
1125
- return delivery;
1126
- };
1127
- const rememberStreamedDelivery = (kind, content, session) => {
1128
- recordStreamedDelivery(kind, content);
1129
- refreshStreamedAcknowledgements(session);
1130
- };
1131
- const emitAcknowledgedStreamedDeliveries = (messageId) => {
1132
- for (const delivery of streamedDeliveries) {
1133
- if (!delivery.acknowledged || delivery.outcome) continue;
1134
- emitSlackMessageSentHooks({
1135
- ...messageSentHookContext,
1136
- to: messageSentHookTarget,
1137
- accountId: account.accountId,
1138
- content: delivery.content,
1139
- success: true,
1140
- ...messageId ? { messageId } : {}
1001
+ const startNativeProgressStream = async (chunks, chunkKey) => {
1002
+ const streamThreadTs = replyPlan.nextThreadTs();
1003
+ if (!streamThreadTs) {
1004
+ logVerbose("slack-stream: no reply thread target for native progress stream start, falling back");
1005
+ delivery.streamFailed = true;
1006
+ return;
1007
+ }
1008
+ delivery.nativeProgressStreamThreadTs = streamThreadTs;
1009
+ const startPromise = (async () => {
1010
+ const session = await startSlackStream({
1011
+ client: slackClient,
1012
+ channel: message.channel,
1013
+ threadTs: streamThreadTs,
1014
+ chunks,
1015
+ taskDisplayMode: "plan",
1016
+ ...slackIdentity ? { identity: slackIdentity } : {},
1017
+ teamId: await resolveSlackStreamRecipientTeamId({
1018
+ client: slackClient,
1019
+ token: ctx.botToken,
1020
+ userId: message.user,
1021
+ fallbackTeamId: slackStreamFallbackTeamId
1022
+ }),
1023
+ userId: message.user
1141
1024
  });
1142
- delivery.outcome = "success";
1025
+ delivery.streamSession = session;
1026
+ return session;
1027
+ })();
1028
+ delivery.nativeProgressStreamStartPromise = startPromise;
1029
+ let startedSession;
1030
+ try {
1031
+ startedSession = await startPromise;
1032
+ } finally {
1033
+ if (delivery.nativeProgressStreamStartPromise === startPromise) delivery.nativeProgressStreamStartPromise = null;
1143
1034
  }
1035
+ if (startedSession) markNativeProgressDelivered(startedSession, streamThreadTs);
1036
+ nativeProgressChunkKey = chunkKey;
1037
+ replyPlan.markSent();
1144
1038
  };
1145
- const acknowledgeStoppedStreamedDeliveries = (session, messageId) => {
1146
- refreshStreamedAcknowledgements(session);
1147
- for (const delivery of streamedDeliveries) delivery.acknowledged = true;
1148
- emitAcknowledgedStreamedDeliveries(messageId);
1039
+ const appendNativeProgressStream = async (chunks, chunkKey) => {
1040
+ if (!delivery.streamSession) return;
1041
+ await appendSlackStream({
1042
+ session: delivery.streamSession,
1043
+ chunks
1044
+ });
1045
+ markNativeProgressDelivered(delivery.streamSession);
1046
+ nativeProgressChunkKey = chunkKey;
1149
1047
  };
1150
- const emitFailedPendingStreamedDeliveries = (error) => {
1151
- for (const delivery of streamedDeliveries) {
1152
- if (delivery.acknowledged || delivery.outcome) continue;
1153
- emitSlackMessageSentHooks({
1154
- ...messageSentHookContext,
1155
- to: messageSentHookTarget,
1156
- accountId: account.accountId,
1157
- content: delivery.content,
1158
- success: false,
1159
- error
1160
- });
1161
- delivery.outcome = "failure";
1048
+ const updateNativeProgressStream = async () => {
1049
+ const snapshot = progressDraft.getSnapshot();
1050
+ const progressLines = resolveNativeProgressLines(snapshot);
1051
+ const hasRetirableNativeTasks = [...nativeTaskState.values()].some((task) => task.status !== "complete" && task.status !== "error");
1052
+ if (!useNativeProgressStreaming || delivery.streamFailed || progressLines.length === 0 && !snapshot.plan?.length && !snapshot.statusHeadline && !explicitProgressTitle && !hasRetirableNativeTasks) return;
1053
+ if (!await waitForNativeProgressStreamStart()) return;
1054
+ const reconciled = reconcileSlackNativeTaskChunks({
1055
+ previousTasks: nativeTaskState,
1056
+ chunks: buildNativeProgressChunks$1(snapshot)
1057
+ });
1058
+ const chunks = reconciled.chunks;
1059
+ if (!chunks?.length) return;
1060
+ const chunkKey = JSON.stringify(chunks);
1061
+ if (chunkKey === nativeProgressChunkKey) return;
1062
+ try {
1063
+ if (!delivery.streamSession) await startNativeProgressStream(chunks, chunkKey);
1064
+ else await appendNativeProgressStream(chunks, chunkKey);
1065
+ nativeTaskState = reconciled.tasks;
1066
+ } catch (err) {
1067
+ runtime.error?.(danger(`slack-stream: native progress stream failed: ${formatSlackError(err)}, falling back`));
1068
+ delivery.streamFailed = true;
1162
1069
  }
1163
1070
  };
1164
- const emitSuccessfulPendingStreamedDeliveries = (messageId) => {
1165
- for (const delivery of streamedDeliveries) {
1166
- if (delivery.acknowledged || delivery.outcome) continue;
1167
- emitSlackMessageSentHooks({
1168
- ...messageSentHookContext,
1169
- to: messageSentHookTarget,
1170
- accountId: account.accountId,
1171
- content: delivery.content,
1172
- success: true,
1173
- ...messageId ? { messageId } : {}
1174
- });
1175
- delivery.outcome = "success";
1176
- }
1071
+ const resetProgressTurnState = () => {
1072
+ progressReceipt.reset();
1073
+ progressReceiptCollapsed = false;
1074
+ pendingNativeProgressReceipt = void 0;
1177
1075
  };
1178
- let deliveryTracker = createSlackEventDeliveryTracker();
1179
- const markPreviewPayloadDelivered = (params) => {
1180
- deliveryTracker.markDelivered(params);
1181
- const nextThreadTs = replyPlan.peekThreadTs();
1182
- if (nextThreadTs !== params.threadTs) deliveryTracker.markDelivered({
1183
- ...params,
1184
- threadTs: nextThreadTs
1076
+ const collapseProgressReceipt = async (params) => {
1077
+ progressReceiptCollapsed = await collapseSlackProgressReceipt({
1078
+ setup,
1079
+ edit: params
1185
1080
  });
1186
1081
  };
1187
- const resolveDeliveryThreadTs = (params) => {
1188
- const plannedThreadTs = params.forcedThreadTs ? void 0 : replyPlan.nextThreadTs();
1189
- return params.forcedThreadTs ?? plannedThreadTs ?? (params.kind === "block" ? usedBlockReplyThreadTs : void 0);
1190
- };
1191
- const rememberDeliveredThreadTs = (kind, deliveredThreadTs) => {
1192
- if (!deliveredThreadTs) return;
1193
- usedReplyThreadTs ??= deliveredThreadTs;
1194
- if (kind === "block") usedBlockReplyThreadTs = deliveredThreadTs;
1082
+ const progressDraft = createChannelProgressDraftCompositor({
1083
+ entry: account.config,
1084
+ mode: slackStreaming.mode,
1085
+ active: progressDraftActive && streamMode === "status_final",
1086
+ seed: progressSeed,
1087
+ formatLine: escapeSlackMrkdwn,
1088
+ reasoningLinePrefix: "🧠 ",
1089
+ commentaryLinePrefix: "💬 ",
1090
+ reasoningGate: previewToolProgressEnabled,
1091
+ commentaryItalics: false,
1092
+ updateOnLineChange: useNativeProgressStreaming || useRichProgressDraft,
1093
+ update: async (previewText, options) => {
1094
+ if (useNativeProgressStreaming) {
1095
+ await updateNativeProgressStream();
1096
+ return;
1097
+ }
1098
+ if (!draftStream) return;
1099
+ const snapshot = progressDraft.getSnapshot();
1100
+ const structuredLines = resolveStructuredProgressLines(options?.lines ?? snapshot.lines);
1101
+ const richNarration = combineProgressHeadlineAndExplanation(snapshot.statusHeadline, snapshot.planExplanation);
1102
+ const richProgressBlocks = useRichProgressDraft ? buildSlackProgressDraftBlocks({
1103
+ title: explicitProgressTitle,
1104
+ lines: structuredLines,
1105
+ plan: snapshot.plan,
1106
+ narration: richNarration,
1107
+ maxLineChars: progressDraftMaxLineChars
1108
+ }) : void 0;
1109
+ draftStream.update(useRichProgressDraft && richProgressBlocks ? {
1110
+ text: previewText,
1111
+ blocks: richProgressBlocks
1112
+ } : previewText);
1113
+ hasStreamedMessage = true;
1114
+ if (options?.flush) await draftStream.flush();
1115
+ }
1116
+ });
1117
+ const commentaryProgressEnabled = progressDraft.commentaryProgressEnabled;
1118
+ const buildNativeProgressCompletionChunks = (finalInProgressStatus) => {
1119
+ const snapshot = progressDraft.getSnapshot();
1120
+ const lines = resolveNativeProgressLines(snapshot);
1121
+ const hasRetirableNativeTasks = [...nativeTaskState.values()].some((task) => task.status !== "complete" && task.status !== "error");
1122
+ if (lines.length === 0 && !snapshot.plan?.length && !hasRetirableNativeTasks) return;
1123
+ return reconcileSlackNativeTaskChunks({
1124
+ previousTasks: nativeTaskState,
1125
+ chunks: buildSlackProgressStreamCompletionChunks({
1126
+ title: resolveNativeProgressTitle(snapshot),
1127
+ lines,
1128
+ plan: resolveNativeProgressPlan(snapshot),
1129
+ maxLineChars: progressDraftMaxLineChars,
1130
+ finalInProgressStatus
1131
+ })
1132
+ }).chunks;
1195
1133
  };
1196
- const deliverPendingStreamFallback = async (session, err) => {
1197
- let fallbackError = err;
1198
- if (!session.stopped) try {
1134
+ const finishNativeProgressTurn = async (completionChunks) => {
1135
+ if (delivery.nativeProgressStreamStartPromise) await delivery.nativeProgressStreamStartPromise.catch(() => null);
1136
+ const session = delivery.streamSession;
1137
+ if (session && !session.stopped) try {
1138
+ if (completionChunks?.length) nativeProgressCompletionSent = true;
1199
1139
  const stopResult = await stopSlackStream({
1200
1140
  session,
1141
+ ...completionChunks?.length ? { chunks: completionChunks } : {},
1201
1142
  ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
1202
1143
  });
1203
- acknowledgeStoppedStreamedDeliveries(session, stopResult.messageId);
1204
- observedReplyDelivery = true;
1205
- usedReplyThreadTs ??= session.threadTs;
1206
- return true;
1207
- } catch (stopErr) {
1208
- if (stopErr instanceof SlackStreamNotDeliveredError) fallbackError = stopErr;
1209
- else runtime.error?.(danger(`slack-stream: failed to finalize buffered text before fallback: ${formatSlackError(stopErr)}`));
1210
- }
1211
- emitAcknowledgedStreamedDeliveries();
1212
- const fallbackText = fallbackError.pendingText.trim();
1213
- if (!fallbackText) return false;
1214
- try {
1215
- await deliverReplies({
1216
- cfg: ctx.cfg,
1217
- replies: [{ text: fallbackText }],
1218
- target: prepared.replyTarget,
1219
- token: ctx.botToken,
1220
- accountId: account.accountId,
1221
- runtime,
1222
- textLimit: ctx.textLimit,
1223
- mediaMaxBytes: ctx.mediaMaxBytes,
1224
- replyThreadTs: session.threadTs,
1225
- replyToMode: replyDeliveryMode,
1226
- ...slackIdentity ? { identity: slackIdentity } : {},
1227
- ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
1228
- ...messageSentDeliveryHookContext,
1229
- deferMessageSentHooks: true,
1230
- ...prepared.eventScope ? { eventScope: prepared.eventScope } : {}
1144
+ delivery.acknowledgeStoppedStreamedDeliveries(session, stopResult?.messageId);
1145
+ if (pendingNativeProgressReceipt && stopResult?.messageId) await collapseProgressReceipt({
1146
+ channelId: session.channel,
1147
+ messageId: stopResult.messageId,
1148
+ text: pendingNativeProgressReceipt,
1149
+ threadTs: session.threadTs
1231
1150
  });
1232
- markSlackStreamFallbackDelivered(session);
1233
- if (!session.stopped) try {
1234
- await stopSlackStream({
1235
- session,
1236
- ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
1237
- });
1238
- } catch (finalizeErr) {
1239
- runtime.error?.(danger(`slack-stream: failed to finalize native stream after fallback delivery: ${formatSlackError(finalizeErr)}`));
1240
- }
1241
- emitSuccessfulPendingStreamedDeliveries();
1242
- observedReplyDelivery = true;
1243
- usedReplyThreadTs ??= session.threadTs;
1244
- logVerbose(`slack-stream: streamed delivery failed (${fallbackError.slackCode}); delivered ${fallbackText.length} chars via deliverReplies fallback`);
1245
- return true;
1246
- } catch (postErr) {
1247
- emitFailedPendingStreamedDeliveries(formatErrorMessage(postErr));
1248
- runtime.error?.(danger(`slack-stream: fallback deliverReplies failed after ${fallbackError.slackCode}: ${formatErrorMessage(postErr)}`));
1249
- return false;
1151
+ } catch (err) {
1152
+ const error = formatSlackError(err);
1153
+ delivery.emitAcknowledgedStreamedDeliveries();
1154
+ delivery.emitFailedPendingStreamedDeliveries(error);
1155
+ logVerbose(`slack-stream: failed to rotate native progress stream (${error})`);
1250
1156
  }
1157
+ delivery.streamSession = null;
1158
+ delivery.nativeProgressStreamStartPromise = null;
1159
+ delivery.nativeProgressStreamThreadTs = void 0;
1160
+ delivery.streamFailed = false;
1251
1161
  };
1252
- const deliverNormally = async (params) => {
1253
- if (params.payload.isReasoning === true) return;
1254
- const replyThreadTs = resolveDeliveryThreadTs(params);
1255
- const deliveryReplyThreadTs = replyDeliveryMode === "off" && !forcedReplyThreadTs && !isThreadReply ? void 0 : replyThreadTs;
1256
- if (deliveryTracker.hasDelivered({
1257
- kind: params.kind,
1258
- payload: params.payload,
1259
- threadTs: deliveryReplyThreadTs
1260
- })) {
1261
- logVerbose("slack: suppressed duplicate normal delivery within the same turn");
1262
- return deliveryReplyThreadTs;
1162
+ const pushPlanProgress = async (steps, explanation) => {
1163
+ if (streamMode === "status_final") {
1164
+ await progressDraft.pushPlanProgress(steps, { explanation });
1165
+ return;
1263
1166
  }
1264
- await deliverReplies({
1265
- cfg: ctx.cfg,
1266
- replies: [params.payload],
1267
- target: prepared.replyTarget,
1268
- token: ctx.botToken,
1269
- accountId: account.accountId,
1270
- runtime,
1271
- textLimit: ctx.textLimit,
1272
- mediaMaxBytes: ctx.mediaMaxBytes,
1273
- replyThreadTs: deliveryReplyThreadTs,
1274
- replyToMode: replyDeliveryMode,
1275
- ...slackIdentity ? { identity: slackIdentity } : {},
1276
- ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
1277
- ...messageSentDeliveryHookContext,
1278
- ...prepared.eventScope ? { eventScope: prepared.eventScope } : {}
1279
- });
1280
- observedReplyDelivery = true;
1281
- if (params.kind === "final") observedFinalReplyDelivery = true;
1282
- const deliveredThreadTs = resolveDeliveredSlackReplyThreadTs({
1283
- replyToMode: replyDeliveryMode,
1284
- payloadReplyToId: params.payload.replyToId,
1285
- replyThreadTs: deliveryReplyThreadTs
1286
- });
1287
- rememberDeliveredThreadTs(params.kind, deliveredThreadTs);
1288
- replyPlan.markSent();
1289
- deliveryTracker.markDelivered({
1290
- kind: params.kind,
1291
- payload: params.payload,
1292
- threadTs: deliveryReplyThreadTs
1167
+ if (previewToolProgressSuppressed || !draftStream) return;
1168
+ const text = formatChannelProgressDraftText({
1169
+ entry: account.config,
1170
+ lines: legacyPreviewToolProgressLines,
1171
+ seed: progressSeed,
1172
+ formatLine: escapeSlackMrkdwn,
1173
+ narration: explanation,
1174
+ plan: steps
1293
1175
  });
1294
- return deliveryReplyThreadTs;
1295
- };
1296
- const deliverBufferedStreamFallback = async (params) => {
1297
- if (!await deliverPendingStreamFallback(params.session, params.err)) {
1298
- streamedFailuresOwnedByDispatcher[params.kind] += 1;
1299
- return false;
1176
+ if (text) {
1177
+ draftStream.update(text);
1178
+ hasStreamedMessage = true;
1300
1179
  }
1301
- replyPlan.markSent();
1302
- if (params.kind === "final") observedFinalReplyDelivery = true;
1303
- deliveryTracker.markDelivered({
1304
- kind: params.kind,
1305
- payload: params.payload,
1306
- threadTs: params.session.threadTs,
1307
- textOverride: params.textOverride
1308
- });
1309
- rememberDeliveredThreadTs(params.kind, params.session.threadTs);
1310
- return true;
1311
1180
  };
1312
- const appendNativeProgressCompletion = async (isError) => {
1313
- const session = streamSession;
1314
- if (isError) nativeProgressTerminalStatus = "error";
1315
- if (!session || nativeProgressCompletionSent) return;
1316
- const chunks = buildNativeProgressCompletionChunks(isError ? "error" : "complete");
1317
- if (!chunks?.length) return;
1318
- try {
1319
- await appendSlackStream({
1320
- session,
1321
- chunks
1322
- });
1323
- nativeProgressCompletionSent = true;
1324
- observedReplyDelivery ||= session.delivered;
1325
- } catch (err) {
1326
- streamFailed = true;
1327
- runtime.error?.(danger(`slack-stream: native progress completion failed: ${formatSlackError(err)}`));
1181
+ const pushPreviewProgress = async (line, options) => {
1182
+ if (!draftStream && !useNativeProgressStreaming) return;
1183
+ if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return;
1184
+ const normalized = line?.text.replace(/\s+/g, " ").trim();
1185
+ if (streamMode === "status_final") {
1186
+ if (!line || !normalized) {
1187
+ await progressDraft.noteActivity();
1188
+ return;
1189
+ }
1190
+ await progressDraft.pushToolProgress(line, options);
1191
+ return;
1328
1192
  }
1193
+ if (!line || !normalized || !draftStream || !previewToolProgressEnabled || previewToolProgressSuppressed) return;
1194
+ const nextLines = mergeChannelProgressDraftLine(legacyPreviewToolProgressLines, line, { maxLines: resolveChannelProgressDraftMaxLines(account.config) });
1195
+ if (nextLines === legacyPreviewToolProgressLines) return;
1196
+ legacyPreviewToolProgressLines = nextLines;
1197
+ draftStream.update(formatChannelProgressDraftText({
1198
+ entry: account.config,
1199
+ lines: legacyPreviewToolProgressLines,
1200
+ seed: progressSeed,
1201
+ formatLine: escapeSlackMrkdwn
1202
+ }));
1203
+ hasStreamedMessage = true;
1329
1204
  };
1330
- const deliverWithStreaming = async (params) => {
1331
- if (params.payload.isReasoning === true) return;
1332
- const reply = resolveSendableOutboundReplyParts(params.payload);
1333
- const renderPlan = resolveSlackReplyRenderPlan(params.payload);
1334
- const plannedBlocks = renderPlan.mode === "single" ? renderPlan.blocks : renderPlan.blockPart?.blocks;
1335
- if (streamFailed || reply.hasMedia || renderPlan.mode === "split" || Boolean(plannedBlocks?.length) || readSlackReplyBlocks(params.payload)?.length || !reply.hasText) {
1336
- await deliverNormally({
1337
- payload: params.payload,
1338
- kind: params.kind,
1339
- forcedThreadTs: streamSession?.threadTs ?? nativeProgressStreamThreadTs
1205
+ const updateDraftFromPartial = (text) => {
1206
+ const trimmed = text?.trimEnd();
1207
+ if (!trimmed) return;
1208
+ if (streamMode === "append") {
1209
+ previewToolProgressSuppressed = true;
1210
+ legacyPreviewToolProgressLines = [];
1211
+ const next = applyAppendOnlyStreamUpdate({
1212
+ incoming: trimmed,
1213
+ rendered: appendRenderedText,
1214
+ source: appendSourceText
1340
1215
  });
1216
+ appendRenderedText = next.rendered;
1217
+ appendSourceText = next.source;
1218
+ if (!next.changed) return;
1219
+ draftStream?.update(next.rendered);
1220
+ hasStreamedMessage = true;
1341
1221
  return;
1342
1222
  }
1343
- const text = reply.trimmedText;
1344
- let plannedThreadTs;
1345
- try {
1346
- if (!streamSession && nativeProgressStreamStartPromise) await nativeProgressStreamStartPromise;
1347
- if (streamFailed) {
1348
- await deliverNormally({
1349
- payload: params.payload,
1350
- kind: params.kind,
1351
- forcedThreadTs: streamSession?.threadTs ?? nativeProgressStreamThreadTs
1352
- });
1353
- return;
1354
- }
1355
- if (!streamSession) {
1356
- const streamThreadTs = replyPlan.nextThreadTs();
1357
- plannedThreadTs = streamThreadTs;
1358
- if (!streamThreadTs) {
1359
- logVerbose("slack-stream: no reply thread target for stream start, falling back to normal delivery");
1360
- streamFailed = true;
1361
- await deliverNormally({
1362
- payload: params.payload,
1363
- kind: params.kind
1364
- });
1365
- return;
1366
- }
1367
- if (deliveryTracker.hasDelivered({
1368
- kind: params.kind,
1369
- payload: params.payload,
1370
- threadTs: streamThreadTs,
1371
- textOverride: text
1372
- })) {
1373
- logVerbose("slack-stream: suppressed duplicate stream start payload");
1374
- return;
1375
- }
1376
- streamSession = await startSlackStream({
1377
- client: slackClient,
1378
- channel: message.channel,
1379
- threadTs: streamThreadTs,
1380
- text,
1381
- ...slackIdentity ? { identity: slackIdentity } : {},
1382
- teamId: await resolveSlackStreamRecipientTeamId({
1383
- client: slackClient,
1384
- token: ctx.botToken,
1385
- userId: message.user,
1386
- fallbackTeamId: slackStreamFallbackTeamId
1387
- }),
1388
- userId: message.user
1389
- });
1390
- refreshStreamedAcknowledgements(streamSession);
1391
- if (streamSession.delivered) {
1392
- observedReplyDelivery = true;
1393
- if (params.kind === "final") observedFinalReplyDelivery = true;
1394
- }
1395
- if (text) rememberStreamedDelivery(params.kind, text, streamSession);
1396
- rememberDeliveredThreadTs(params.kind, streamThreadTs);
1397
- replyPlan.markSent();
1398
- deliveryTracker.markDelivered({
1399
- kind: params.kind,
1400
- payload: params.payload,
1401
- threadTs: streamThreadTs,
1402
- textOverride: text
1403
- });
1404
- return;
1405
- }
1406
- if (deliveryTracker.hasDelivered({
1407
- kind: params.kind,
1408
- payload: params.payload,
1409
- threadTs: streamSession.threadTs,
1410
- textOverride: text
1411
- })) {
1412
- logVerbose("slack-stream: suppressed duplicate append payload");
1413
- return;
1414
- }
1415
- if (text) recordStreamedDelivery(params.kind, text);
1416
- await appendSlackStream({
1417
- session: streamSession,
1418
- text: "\n" + text
1419
- });
1420
- refreshStreamedAcknowledgements(streamSession);
1421
- if (streamSession.delivered) {
1422
- observedReplyDelivery = true;
1423
- if (params.kind === "final") observedFinalReplyDelivery = true;
1424
- }
1425
- deliveryTracker.markDelivered({
1426
- kind: params.kind,
1427
- payload: params.payload,
1428
- threadTs: streamSession.threadTs,
1429
- textOverride: text
1430
- });
1431
- } catch (err) {
1432
- if (err instanceof SlackStreamNotDeliveredError) {
1433
- streamFailed = true;
1434
- if (streamSession) {
1435
- if (await deliverBufferedStreamFallback({
1436
- session: streamSession,
1437
- err,
1438
- payload: params.payload,
1439
- kind: params.kind,
1440
- textOverride: text
1441
- })) return;
1442
- throw err;
1443
- }
1444
- await deliverNormally({
1445
- payload: params.payload,
1446
- kind: params.kind,
1447
- forcedThreadTs: plannedThreadTs
1448
- });
1449
- return;
1450
- }
1451
- runtime.error?.(danger(`slack-stream: streaming API call failed: ${formatSlackError(err)}, falling back`));
1452
- streamFailed = true;
1453
- if (streamSession && streamSession.pendingText) {
1454
- const bufferedFallbackErr = new SlackStreamNotDeliveredError(streamSession.pendingText, "unknown");
1455
- if (await deliverBufferedStreamFallback({
1456
- session: streamSession,
1457
- err: bufferedFallbackErr,
1458
- payload: params.payload,
1459
- kind: params.kind,
1460
- textOverride: text
1461
- })) return;
1462
- throw err;
1463
- }
1464
- await deliverNormally({
1465
- payload: params.payload,
1466
- kind: params.kind,
1467
- forcedThreadTs: streamSession?.threadTs ?? nativeProgressStreamThreadTs ?? plannedThreadTs
1468
- });
1469
- }
1223
+ if (streamMode === "status_final") return;
1224
+ previewToolProgressSuppressed = true;
1225
+ legacyPreviewToolProgressLines = [];
1226
+ draftStream?.update(trimmed);
1227
+ hasStreamedMessage = true;
1470
1228
  };
1471
- let draftPreviewCommitted = false;
1472
- const deliverSlackPayload = async (payload, info) => {
1473
- if (payload.isReasoning === true) return { visibleReplySent: false };
1474
- if (info.kind === "final" && slackStreaming.mode === "progress" && streamMode === "status_final") {
1475
- const hadProgressDraft = progressDraft.hasStarted;
1476
- progressDraft.markFinalReplyStarted();
1477
- if (useNativeProgressStreaming) {
1478
- await waitForNativeProgressStreamStart();
1479
- const finalThreadTs = streamSession?.threadTs ?? nativeProgressStreamThreadTs;
1480
- await deliverNormally({
1481
- payload,
1482
- kind: info.kind,
1483
- forcedThreadTs: finalThreadTs
1484
- });
1485
- await appendNativeProgressCompletion(payload.isError === true);
1486
- progressDraft.markFinalReplyDelivered();
1487
- if (!payload.isError && hadProgressDraft && streamSession) pendingNativeProgressReceipt = progressReceipt.buildSummaryLine();
1488
- return;
1489
- }
1490
- if (hadProgressDraft) try {
1491
- await draftStream?.flush();
1492
- } catch (err) {
1493
- logVerbose(`slack: progress draft flush before final failed (${formatSlackError(err)})`);
1494
- }
1495
- const receiptChannelId = hadProgressDraft ? draftStream?.channelId() : void 0;
1496
- const receiptMessageId = hadProgressDraft ? draftStream?.messageId() : void 0;
1497
- const draftThreadTs = hadProgressDraft ? usedReplyThreadTs ?? statusThreadTs : void 0;
1498
- await deliverNormally({
1499
- payload,
1500
- kind: info.kind,
1501
- ...draftThreadTs ? { forcedThreadTs: draftThreadTs } : {}
1502
- });
1503
- progressDraft.markFinalReplyDelivered();
1504
- if (!payload.isError && receiptChannelId && receiptMessageId && !progressReceiptCollapsed) await collapseProgressReceipt({
1505
- channelId: receiptChannelId,
1506
- messageId: receiptMessageId,
1507
- text: progressReceipt.buildSummaryLine(),
1508
- threadTs: usedReplyThreadTs ?? statusThreadTs
1229
+ const pushReasoningProgress = async (payload) => {
1230
+ if (!payload?.text) return;
1231
+ if (streamMode !== "status_final") {
1232
+ const normalized = progressDraft.mergeReasoningProgress(payload.text, { snapshot: payload.isReasoningSnapshot === true }).replace(/^_(.*)_$/su, "$1").trim();
1233
+ if (!normalized) return;
1234
+ await pushPreviewProgress({
1235
+ id: "reasoning",
1236
+ kind: "item",
1237
+ text: normalized,
1238
+ label: "Reasoning"
1509
1239
  });
1510
1240
  return;
1511
1241
  }
1512
- if (useNativeProgressStreaming) {
1513
- await deliverNormally({
1514
- payload,
1515
- kind: info.kind,
1516
- forcedThreadTs: streamSession?.threadTs ?? nativeProgressStreamThreadTs
1517
- });
1518
- return;
1519
- }
1520
- if (useStreaming) {
1521
- await deliverWithStreaming({
1522
- payload,
1523
- kind: info.kind
1524
- });
1525
- return;
1526
- }
1527
- const reply = resolveSendableOutboundReplyParts(payload);
1528
- const ttsSupplement = getReplyPayloadTtsSupplement(payload);
1529
- const replySourceText = payload.text ?? ttsSupplement?.spokenText;
1530
- const replyRenderPlan = resolveSlackReplyRenderPlan(payload, replySourceText);
1531
- const slackBlocks = replyRenderPlan.mode === "single" ? replyRenderPlan.blocks : replyRenderPlan.blockPart?.blocks;
1532
- const requiresSeparateFallbackDelivery = replyRenderPlan.mode === "split";
1533
- const trimmedFinalText = replyRenderPlan.mode === "single" ? replyRenderPlan.text.trim() : replyRenderPlan.fallbackText.trim();
1534
- const previewFinalText = replyRenderPlan.mode === "single" && replyRenderPlan.textIsSlackMrkdwn ? trimmedFinalText : normalizeSlackOutboundText((replySourceText ?? "").trim());
1535
- const shouldRestoreTtsSupplementTextForPreviewFallback = Boolean(ttsSupplement) && ttsSupplement?.visibleTextAlreadyDelivered !== true && Boolean(draftStream) && !draftPreviewCommitted && !observedFinalReplyDelivery && previewStreamingEnabled && !payload.text?.trim();
1536
- if (info.kind === "final" && ttsSupplement && draftStream && !draftPreviewCommitted && !observedFinalReplyDelivery && previewStreamingEnabled && !payload.isError && !requiresSeparateFallbackDelivery && trimmedFinalText.length > 0) {
1537
- const channelId = draftStream.channelId();
1538
- const messageId = draftStream.messageId();
1539
- if (channelId && messageId) {
1540
- const finalThreadTs = usedReplyThreadTs ?? statusThreadTs;
1541
- await draftStream.flush();
1542
- await draftStream.seal();
1543
- try {
1544
- await finalizeSlackPreviewEdit({
1545
- client: slackClient,
1546
- token: ctx.botToken,
1547
- accountId: account.accountId,
1548
- channelId,
1549
- messageId,
1550
- text: previewFinalText,
1551
- ...slackBlocks?.length ? { blocks: slackBlocks } : {},
1552
- threadTs: finalThreadTs
1553
- });
1554
- } catch (err) {
1555
- logVerbose(`slack: preview final edit failed; falling back to standard send (${formatSlackError(err)})`);
1556
- await draftStream.discardPending();
1557
- let delivered = false;
1558
- try {
1559
- await deliverNormally({
1560
- payload: payload.text?.trim() ? payload : {
1561
- ...payload,
1562
- text: ttsSupplement.spokenText
1563
- },
1564
- kind: info.kind,
1565
- forcedThreadTs: finalThreadTs
1566
- });
1567
- delivered = true;
1568
- } finally {
1569
- if (delivered) await draftStream.clear();
1570
- }
1571
- return;
1572
- }
1573
- draftPreviewCommitted = true;
1574
- observedFinalReplyDelivery = true;
1575
- observedReplyDelivery = true;
1576
- replyPlan.markSent();
1577
- await deliverNormally({
1578
- payload: buildTtsSupplementMediaPayload(payload),
1579
- kind: info.kind,
1580
- forcedThreadTs: finalThreadTs
1242
+ progressReceipt.noteReasoning();
1243
+ await progressDraft.pushReasoningProgress(payload.text, { snapshot: payload.isReasoningSnapshot === true });
1244
+ };
1245
+ const resetDraftDeliveryState = () => {
1246
+ hasStreamedMessage = false;
1247
+ appendRenderedText = "";
1248
+ appendSourceText = "";
1249
+ };
1250
+ const resetDraftProgressState = () => {
1251
+ progressDraft.resetReasoningProgress();
1252
+ previewToolProgressSuppressed = false;
1253
+ legacyPreviewToolProgressLines = [];
1254
+ };
1255
+ const beginNewProgressTurn = async (options) => {
1256
+ const completionChunks = useNativeProgressStreaming && !nativeProgressCompletionSent ? buildNativeProgressCompletionChunks(nativeProgressTerminalStatus) : void 0;
1257
+ if (!progressDraft.beginNewTurn(options)) return false;
1258
+ if (useNativeProgressStreaming) await finishNativeProgressTurn(completionChunks);
1259
+ else draftStream?.forceNewMessage();
1260
+ resetProgressTurnState();
1261
+ nativeTaskState = /* @__PURE__ */ new Map();
1262
+ nativeProgressCompletionSent = false;
1263
+ nativeProgressTerminalStatus = "complete";
1264
+ nativeProgressChunkKey = void 0;
1265
+ resetPreviewDeliveryState();
1266
+ delivery.resetDeliveryTracker();
1267
+ progressReceiptCollapsed = false;
1268
+ return true;
1269
+ };
1270
+ return {
1271
+ draftStream,
1272
+ streamMode,
1273
+ useNativeProgressStreaming,
1274
+ progressDraftActive,
1275
+ previewToolProgressEnabled,
1276
+ suppressDefaultToolProgressMessages,
1277
+ progressDraft,
1278
+ commentaryProgressEnabled,
1279
+ progressReceipt,
1280
+ get progressReceiptCollapsed() {
1281
+ return progressReceiptCollapsed;
1282
+ },
1283
+ get pendingNativeProgressReceipt() {
1284
+ return pendingNativeProgressReceipt;
1285
+ },
1286
+ set pendingNativeProgressReceipt(value) {
1287
+ pendingNativeProgressReceipt = value;
1288
+ },
1289
+ get nativeProgressCompletionSent() {
1290
+ return nativeProgressCompletionSent;
1291
+ },
1292
+ set nativeProgressCompletionSent(value) {
1293
+ nativeProgressCompletionSent = value;
1294
+ },
1295
+ get nativeProgressTerminalStatus() {
1296
+ return nativeProgressTerminalStatus;
1297
+ },
1298
+ appendNativeProgressCompletion,
1299
+ beginNewProgressTurn,
1300
+ buildNativeProgressCompletionChunks,
1301
+ collapseProgressReceipt,
1302
+ onDraftBoundary: !shouldUseDraftStream && !useNativeProgressStreaming ? void 0 : async () => {
1303
+ if (streamMode === "status_final") {
1304
+ await beginNewProgressTurn();
1305
+ return;
1306
+ }
1307
+ if (hasStreamedMessage) draftStream?.forceNewMessage();
1308
+ resetDraftDeliveryState();
1309
+ resetDraftProgressState();
1310
+ },
1311
+ onQueuedFollowupAdmitted: !shouldUseDraftStream && !useNativeProgressStreaming ? void 0 : async () => {
1312
+ await draftStream?.flush();
1313
+ resetPreviewDeliveryState();
1314
+ if (streamMode === "status_final") await beginNewProgressTurn({ force: true });
1315
+ else draftStream?.forceNewMessage();
1316
+ delivery.resetDeliveryTracker();
1317
+ resetDraftDeliveryState();
1318
+ resetDraftProgressState();
1319
+ },
1320
+ pushPlanProgress,
1321
+ pushPreviewProgress,
1322
+ pushReasoningProgress,
1323
+ updateDraftFromPartial,
1324
+ waitForNativeProgressStreamStart,
1325
+ setShouldYieldDraftProgress: (value) => {
1326
+ shouldYieldDraftProgress = value;
1327
+ },
1328
+ shouldYieldDraftProgress: () => shouldYieldDraftProgress()
1329
+ };
1330
+ }
1331
+ //#endregion
1332
+ //#region extensions/slack/src/threading.ts
1333
+ function resolveSlackThreadContext(params) {
1334
+ const incomingThreadTs = params.message.thread_ts;
1335
+ const eventTs = params.message.event_ts;
1336
+ const messageTs = params.message.ts ?? eventTs;
1337
+ const hasThreadTs = typeof incomingThreadTs === "string" && incomingThreadTs.length > 0;
1338
+ const isThreadReply = hasThreadTs && (incomingThreadTs !== messageTs || Boolean(params.message.parent_user_id));
1339
+ const replyToId = isThreadReply ? incomingThreadTs : void 0;
1340
+ const isAssistantDmThreadRoot = hasThreadTs && !isThreadReply && params.isDirectMessage === true;
1341
+ return {
1342
+ incomingThreadTs,
1343
+ messageTs,
1344
+ isThreadReply,
1345
+ replyToId,
1346
+ messageThreadId: isThreadReply || isAssistantDmThreadRoot ? incomingThreadTs : params.replyToMode === "all" ? messageTs : void 0
1347
+ };
1348
+ }
1349
+ /**
1350
+ * Resolves Slack thread targeting for replies and status indicators.
1351
+ *
1352
+ * @returns replyThreadTs - Thread timestamp for reply messages
1353
+ * @returns statusThreadTs - Thread timestamp for status indicators (typing, etc.)
1354
+ * @returns isThreadReply - true if this is a genuine user reply in a thread,
1355
+ * false if thread_ts comes from a bot status message (e.g. typing indicator)
1356
+ */
1357
+ function resolveSlackThreadTargets(params) {
1358
+ const { incomingThreadTs, messageTs, isThreadReply } = resolveSlackThreadContext(params);
1359
+ const replyThreadTs = isThreadReply ? incomingThreadTs : params.replyToMode === "all" ? messageTs : void 0;
1360
+ return {
1361
+ replyThreadTs,
1362
+ statusThreadTs: replyThreadTs,
1363
+ isThreadReply
1364
+ };
1365
+ }
1366
+ //#endregion
1367
+ //#region extensions/slack/src/monitor/message-handler/dispatch-setup.ts
1368
+ async function createSlackDispatchSetup(prepared) {
1369
+ const { ctx, account, message, route } = prepared;
1370
+ const slackClient = prepared.eventScope?.client ?? ctx.app.client;
1371
+ const slackStreamFallbackTeamId = prepared.eventScope?.teamId ?? ctx.teamId;
1372
+ const cfg = ctx.cfg;
1373
+ const runtime = ctx.runtime;
1374
+ const outboundIdentity = resolveAgentOutboundIdentity(cfg, route.agentId);
1375
+ const slackIdentity = outboundIdentity ? {
1376
+ username: outboundIdentity.name,
1377
+ iconUrl: outboundIdentity.avatarUrl,
1378
+ iconEmoji: outboundIdentity.emoji
1379
+ } : prepared.relayIdentity;
1380
+ if (prepared.isDirectMessage) {
1381
+ const sessionCfg = cfg.session;
1382
+ const storePath = resolveStorePath(sessionCfg?.store, { agentId: route.agentId });
1383
+ const pinnedMainDmOwner = resolvePinnedMainDmOwnerFromAllowlist({
1384
+ dmScope: cfg.session?.dmScope,
1385
+ allowFrom: ctx.allowFrom,
1386
+ normalizeEntry: normalizeSlackAllowOwnerEntry
1387
+ });
1388
+ const senderRecipient = normalizeOptionalLowercaseString(message.user);
1389
+ const inboundLastRouteSessionKey = resolveInboundLastRouteSessionKey({
1390
+ route,
1391
+ sessionKey: prepared.ctxPayload.SessionKey ?? route.sessionKey
1392
+ });
1393
+ if (inboundLastRouteSessionKey === route.mainSessionKey && pinnedMainDmOwner && senderRecipient && normalizeOptionalLowercaseString(pinnedMainDmOwner) !== senderRecipient) logVerbose(`slack: skip main-session last route for ${senderRecipient} (pinned owner ${pinnedMainDmOwner})`);
1394
+ else await updateLastRoute({
1395
+ storePath,
1396
+ sessionKey: inboundLastRouteSessionKey,
1397
+ deliveryContext: {
1398
+ channel: "slack",
1399
+ to: `user:${message.user}`,
1400
+ accountId: route.accountId,
1401
+ threadId: prepared.ctxPayload.MessageThreadId ?? prepared.ctxPayload.TransportThreadId
1402
+ },
1403
+ ctx: prepared.ctxPayload
1404
+ });
1405
+ }
1406
+ const threadTargets = resolveSlackThreadTargets({
1407
+ message,
1408
+ replyToMode: prepared.replyToMode
1409
+ });
1410
+ const forcedReplyThreadTs = prepared.forcedReplyThreadTs;
1411
+ const slackMessageMetadata = prepared.slackMessageMetadata;
1412
+ const statusThreadTs = forcedReplyThreadTs ?? threadTargets.statusThreadTs;
1413
+ const isThreadReply = threadTargets.isThreadReply;
1414
+ const replyDeliveryMode = forcedReplyThreadTs ? "off" : prepared.replyToMode;
1415
+ const sourceReplyDeliveryMode = resolveChannelMessageSourceReplyDeliveryMode({
1416
+ cfg,
1417
+ ctx: prepared.ctxPayload
1418
+ });
1419
+ const sourceRepliesAreToolOnly = sourceReplyDeliveryMode === "message_tool_only";
1420
+ const suppressRoomEventTyping = prepared.ctxPayload.InboundEventKind === "room_event";
1421
+ const messageSentHookTarget = prepared.ctxPayload.OriginatingTo ?? prepared.ctxPayload.To ?? prepared.replyTarget;
1422
+ const messageSentHookContext = {
1423
+ sessionKeyForInternalHooks: prepared.ctxPayload.SessionKey ?? route.sessionKey,
1424
+ isGroup: prepared.isRoomish,
1425
+ groupId: prepared.isRoomish ? message.channel : void 0
1426
+ };
1427
+ const messageSentDeliveryHookContext = {
1428
+ ...messageSentHookContext,
1429
+ messageSentHookTarget
1430
+ };
1431
+ const reactionMessageTs = prepared.ackReactionMessageTs;
1432
+ const messageTs = message.ts ?? message.event_ts;
1433
+ const incomingThreadTs = message.thread_ts;
1434
+ let didSetStatus = false;
1435
+ const statusReactionsEnabled = prepared.ctxPayload.InboundEventKind !== "room_event" && Boolean(prepared.ackReactionPromise) && Boolean(reactionMessageTs) && cfg.messages?.statusReactions?.enabled === true;
1436
+ const statusReactions = createStatusReactionController({
1437
+ enabled: statusReactionsEnabled,
1438
+ adapter: {
1439
+ setReaction: async (emoji) => {
1440
+ await reactSlackMessage(message.channel, reactionMessageTs ?? "", emoji, {
1441
+ token: ctx.botToken,
1442
+ client: slackClient
1443
+ }).catch((err) => {
1444
+ if (formatErrorMessage(err).includes("already_reacted")) return;
1445
+ throw err;
1581
1446
  });
1582
- markPreviewPayloadDelivered({
1583
- kind: info.kind,
1584
- payload,
1585
- threadTs: finalThreadTs
1447
+ },
1448
+ removeReaction: async (emoji) => {
1449
+ await removeSlackReaction(message.channel, reactionMessageTs ?? "", emoji, {
1450
+ token: ctx.botToken,
1451
+ client: slackClient
1452
+ }).catch((err) => {
1453
+ if (formatErrorMessage(err).includes("no_reaction")) return;
1454
+ throw err;
1586
1455
  });
1587
- return;
1588
1456
  }
1457
+ },
1458
+ initialEmoji: prepared.ackReactionValue || "eyes",
1459
+ emojis: void 0,
1460
+ timing: DEFAULT_TIMING,
1461
+ onError: (err) => {
1462
+ logAckFailure({
1463
+ log: logVerbose,
1464
+ channel: "slack",
1465
+ target: `${message.channel}/${message.ts}`,
1466
+ error: err
1467
+ });
1589
1468
  }
1590
- await deliverWithFinalizableLivePreviewAdapter({
1591
- kind: info.kind,
1592
- payload,
1593
- adapter: defineFinalizableLivePreviewAdapter({
1594
- draft: draftStream && !draftPreviewCommitted && !observedFinalReplyDelivery ? {
1595
- flush: draftStream.flush,
1596
- clear: draftStream.clear,
1597
- discardPending: draftStream.discardPending,
1598
- seal: draftStream.seal,
1599
- id: () => {
1600
- const channelId = draftStream.channelId();
1601
- const messageId = draftStream.messageId();
1602
- return channelId && messageId ? {
1603
- channelId,
1604
- messageId
1605
- } : void 0;
1606
- }
1607
- } : void 0,
1608
- buildFinalEdit: () => {
1609
- if (!previewStreamingEnabled || reply.hasMedia && !ttsSupplement || payload.isError || requiresSeparateFallbackDelivery || trimmedFinalText.length === 0 && !slackBlocks?.length) return;
1610
- return {
1611
- text: previewFinalText,
1612
- blocks: slackBlocks,
1613
- threadTs: usedReplyThreadTs ?? statusThreadTs
1614
- };
1615
- },
1616
- editFinal: async (preview, edit) => {
1617
- if (deliveryTracker.hasDelivered({
1618
- kind: info.kind,
1619
- payload,
1620
- threadTs: edit.threadTs
1621
- })) return;
1622
- await finalizeSlackPreviewEdit({
1623
- client: slackClient,
1624
- token: ctx.botToken,
1625
- accountId: account.accountId,
1626
- channelId: preview.channelId,
1627
- messageId: preview.messageId,
1628
- text: edit.text,
1629
- ...edit.blocks?.length ? { blocks: edit.blocks } : {},
1630
- threadTs: edit.threadTs
1631
- });
1632
- if (!ttsSupplement) emitSlackMessageSentHooks({
1633
- ...messageSentHookContext,
1634
- to: messageSentHookTarget,
1635
- accountId: account.accountId,
1636
- content: trimmedFinalText,
1637
- success: true,
1638
- messageId: preview.messageId
1639
- });
1640
- draftPreviewCommitted = true;
1641
- observedFinalReplyDelivery = true;
1642
- },
1643
- onPreviewFinalized: (_preview) => {
1644
- draftPreviewCommitted = true;
1645
- observedFinalReplyDelivery = true;
1646
- const finalThreadTs = usedReplyThreadTs ?? statusThreadTs;
1647
- observedReplyDelivery = true;
1648
- replyPlan.markSent();
1649
- if (!ttsSupplement) markPreviewPayloadDelivered({
1650
- kind: info.kind,
1651
- payload,
1652
- threadTs: finalThreadTs
1653
- });
1654
- },
1655
- buildSupplementalPayload: () => ttsSupplement ? buildTtsSupplementMediaPayload(payload) : void 0,
1656
- deliverSupplemental: async (supplementalPayload) => {
1657
- const previewThreadTs = usedReplyThreadTs ?? statusThreadTs;
1658
- const supplementalThreadTs = await deliverNormally({
1659
- payload: supplementalPayload,
1660
- kind: info.kind,
1661
- forcedThreadTs: previewThreadTs
1662
- });
1663
- markPreviewPayloadDelivered({
1664
- kind: info.kind,
1665
- payload,
1666
- threadTs: supplementalThreadTs
1667
- });
1668
- },
1669
- logPreviewEditFailure: (err) => {
1670
- logVerbose(`slack: preview final edit failed; falling back to standard send (${formatSlackError(err)})`);
1671
- }
1672
- }),
1673
- deliverNormally: async () => {
1674
- await deliverNormally({
1675
- payload: shouldRestoreTtsSupplementTextForPreviewFallback ? {
1676
- ...payload,
1677
- text: ttsSupplement?.spokenText
1678
- } : payload,
1679
- kind: info.kind
1469
+ });
1470
+ if (statusReactionsEnabled) statusReactions.setQueued();
1471
+ const hasRepliedRef = { value: false };
1472
+ const replyPlan = createSlackReplyDeliveryPlan({
1473
+ replyToMode: replyDeliveryMode,
1474
+ incomingThreadTs: forcedReplyThreadTs ?? incomingThreadTs,
1475
+ messageTs,
1476
+ hasRepliedRef,
1477
+ isThreadReply: Boolean(forcedReplyThreadTs) || isThreadReply
1478
+ });
1479
+ const typingTarget = statusThreadTs ? `${message.channel}/${statusThreadTs}` : message.channel;
1480
+ const typingReaction = ctx.typingReaction;
1481
+ const { onModelSelected, ...replyPipeline } = createChannelMessageReplyPipeline({
1482
+ cfg,
1483
+ agentId: route.agentId,
1484
+ channel: "slack",
1485
+ accountId: route.accountId,
1486
+ transformReplyPayload: (payload) => {
1487
+ if (payload.isReasoning === true) return null;
1488
+ return payload;
1489
+ },
1490
+ typing: {
1491
+ start: async () => {
1492
+ didSetStatus = true;
1493
+ await ctx.setSlackThreadStatus({
1494
+ channelId: message.channel,
1495
+ threadTs: statusThreadTs,
1496
+ status: "is typing...",
1497
+ eventScope: prepared.eventScope
1498
+ });
1499
+ if (typingReaction && message.ts) await reactSlackMessage(message.channel, message.ts, typingReaction, {
1500
+ token: ctx.botToken,
1501
+ client: slackClient
1502
+ }).catch((err) => {
1503
+ logVerbose(`slack send: typing reaction failed: ${formatSlackError(err)}`);
1504
+ });
1505
+ },
1506
+ stop: async () => {
1507
+ if (!didSetStatus) return;
1508
+ didSetStatus = false;
1509
+ await ctx.setSlackThreadStatus({
1510
+ channelId: message.channel,
1511
+ threadTs: statusThreadTs,
1512
+ status: "",
1513
+ eventScope: prepared.eventScope
1514
+ });
1515
+ if (typingReaction && message.ts) await removeSlackReaction(message.channel, message.ts, typingReaction, {
1516
+ token: ctx.botToken,
1517
+ client: slackClient
1518
+ }).catch((err) => {
1519
+ logVerbose(`slack send: typing reaction removal failed: ${formatSlackError(err)}`);
1520
+ });
1521
+ },
1522
+ onStartError: (err) => {
1523
+ logTypingFailure({
1524
+ log: (messageValue) => runtime.error?.(danger(messageValue)),
1525
+ channel: "slack",
1526
+ action: "start",
1527
+ target: typingTarget,
1528
+ error: err
1529
+ });
1530
+ },
1531
+ onStopError: (err) => {
1532
+ logTypingFailure({
1533
+ log: (messageLocal) => runtime.error?.(danger(messageLocal)),
1534
+ channel: "slack",
1535
+ action: "stop",
1536
+ target: typingTarget,
1537
+ error: err
1680
1538
  });
1681
1539
  }
1682
- });
1540
+ }
1541
+ });
1542
+ const slackStreaming = resolveSlackStreamingConfig({
1543
+ streaming: account.config.streaming,
1544
+ nativeStreaming: resolveChannelStreamingNativeTransport(account.config)
1545
+ });
1546
+ const streamThreadHint = forcedReplyThreadTs ?? resolveSlackStreamingThreadHint({
1547
+ replyToMode: replyDeliveryMode,
1548
+ incomingThreadTs,
1549
+ messageTs,
1550
+ isThreadReply
1551
+ });
1552
+ const previewStreamingEnabled = !sourceRepliesAreToolOnly && shouldEnableSlackPreviewStreaming({ mode: slackStreaming.mode });
1553
+ const hasSlackCustomIdentity = Boolean(slackIdentity?.username || slackIdentity?.iconUrl || slackIdentity?.iconEmoji);
1554
+ const useStreaming = shouldUseStreaming({
1555
+ streamingEnabled: !sourceRepliesAreToolOnly && isSlackStreamingEnabled({
1556
+ mode: slackStreaming.mode,
1557
+ nativeStreaming: slackStreaming.nativeStreaming,
1558
+ nativeProgressTaskCards: resolveSlackNativeProgressTaskCards(account.config)
1559
+ }),
1560
+ threadTs: streamThreadHint
1561
+ });
1562
+ const shouldUseDraftStream = !hasSlackCustomIdentity && shouldInitializeSlackDraftStream({
1563
+ previewStreamingEnabled,
1564
+ useStreaming
1565
+ });
1566
+ const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
1567
+ const disableBlockStreaming = sourceRepliesAreToolOnly ? true : resolveSlackDisableBlockStreaming({
1568
+ useStreaming,
1569
+ shouldUseDraftStream,
1570
+ blockStreamingEnabled
1571
+ });
1572
+ const onSlackDeliveryError = (err, info) => {
1573
+ runtime.error?.(danger(`slack ${info.kind} reply failed: ${formatSlackError(err)}`));
1574
+ replyPipeline.typingCallbacks?.onIdle?.();
1575
+ };
1576
+ return {
1577
+ prepared,
1578
+ ctx,
1579
+ account,
1580
+ message,
1581
+ route,
1582
+ slackClient,
1583
+ slackStreamFallbackTeamId,
1584
+ cfg,
1585
+ runtime,
1586
+ slackIdentity,
1587
+ forcedReplyThreadTs,
1588
+ slackMessageMetadata,
1589
+ statusThreadTs,
1590
+ isThreadReply,
1591
+ replyDeliveryMode,
1592
+ sourceReplyDeliveryMode,
1593
+ sourceRepliesAreToolOnly,
1594
+ suppressRoomEventTyping,
1595
+ messageSentHookTarget,
1596
+ messageSentHookContext,
1597
+ messageSentDeliveryHookContext,
1598
+ incomingThreadTs,
1599
+ messageTs,
1600
+ statusReactionsEnabled,
1601
+ statusReactions,
1602
+ hasRepliedRef,
1603
+ replyPlan,
1604
+ onModelSelected,
1605
+ replyPipeline,
1606
+ slackStreaming,
1607
+ streamThreadHint,
1608
+ previewStreamingEnabled,
1609
+ shouldUseDraftStream,
1610
+ disableBlockStreaming,
1611
+ useStreaming,
1612
+ onSlackDeliveryError
1683
1613
  };
1684
- const onSlackDeliveryError = (err, info) => {
1685
- runtime.error?.(danger(`slack ${info.kind} reply failed: ${formatSlackError(err)}`));
1686
- replyPipeline.typingCallbacks?.onIdle?.();
1614
+ }
1615
+ //#endregion
1616
+ //#region extensions/slack/src/monitor/message-handler/dispatch-streaming.ts
1617
+ function createSlackStreamingDeliveryRuntime(setup) {
1618
+ const { account, ctx, forcedReplyThreadTs, isThreadReply, message, messageSentDeliveryHookContext, messageSentHookContext, messageSentHookTarget, prepared, replyDeliveryMode, replyPlan, runtime, slackClient, slackIdentity, slackMessageMetadata, slackStreamFallbackTeamId } = setup;
1619
+ const state = {
1620
+ streamSession: null,
1621
+ nativeProgressStreamStartPromise: null,
1622
+ nativeProgressStreamThreadTs: void 0,
1623
+ streamFailed: false,
1624
+ usedReplyThreadTs: void 0,
1625
+ usedBlockReplyThreadTs: void 0,
1626
+ observedReplyDelivery: false,
1627
+ observedFinalReplyDelivery: false
1687
1628
  };
1688
- const draftStream = shouldUseDraftStream ? createSlackDraftStream({
1689
- target: prepared.replyTarget,
1690
- cfg,
1691
- token: ctx.botToken,
1692
- accountId: account.accountId,
1693
- ...prepared.eventScope ? { eventScope: prepared.eventScope } : {},
1694
- identity: slackIdentity,
1695
- ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
1696
- maxChars: Math.min(ctx.textLimit, SLACK_TEXT_LIMIT),
1697
- resolveThreadTs: () => {
1698
- const ts = replyPlan.peekThreadTs();
1699
- if (ts) usedReplyThreadTs ??= ts;
1700
- return ts;
1701
- },
1702
- log: logVerbose,
1703
- warn: logVerbose
1704
- }) : void 0;
1705
- let hasStreamedMessage = false;
1706
- const streamMode = slackStreaming.draftMode;
1707
- const useNativeProgressStreaming = useStreaming && slackStreaming.mode === "progress";
1708
- const progressDraftActive = Boolean(draftStream) || useNativeProgressStreaming;
1709
- const previewToolProgressEnabled = progressDraftActive && resolveChannelStreamingPreviewToolProgress(account.config);
1710
- let shouldYieldDraftProgress = () => false;
1711
- const suppressDefaultToolProgressMessages = resolveChannelStreamingSuppressDefaultToolProgressMessages(account.config, {
1712
- draftStreamActive: Boolean(draftStream) || useNativeProgressStreaming,
1713
- previewToolProgressEnabled,
1714
- previewStreamingEnabled
1715
- });
1716
- let previewToolProgressSuppressed = false;
1717
- let legacyPreviewToolProgressLines = [];
1718
- let nativeTaskState = /* @__PURE__ */ new Map();
1719
- let appendRenderedText = "";
1720
- let appendSourceText = "";
1721
- let nativeProgressCompletionSent = false;
1722
- let nativeProgressTerminalStatus = "complete";
1723
- let nativeProgressChunkKey;
1724
- const progressReceipt = createChannelProgressReceiptTracker();
1725
- let progressReceiptCollapsed = false;
1726
- let pendingNativeProgressReceipt;
1727
- const progressSeed = `${account.accountId}:${message.channel}`;
1728
- const useRichProgressDraft = streamMode === "status_final" && resolveChannelProgressDraftRender(account.config) === "rich";
1729
- const explicitProgressTitle = resolveExplicitSlackProgressTitle(account.config);
1730
- const progressDraftMaxLineChars = resolveChannelProgressDraftMaxLineChars(account.config);
1731
- const waitForNativeProgressStreamStart = async () => {
1732
- if (streamSession || !nativeProgressStreamStartPromise) return true;
1733
- try {
1734
- await nativeProgressStreamStartPromise;
1735
- } catch {
1736
- streamFailed = true;
1737
- return false;
1738
- }
1739
- return !streamFailed;
1629
+ const streamedDeliveries = [];
1630
+ const streamedFailuresOwnedByDispatcher = {
1631
+ tool: 0,
1632
+ block: 0,
1633
+ final: 0
1740
1634
  };
1741
- const resolveStructuredProgressLines = (lines) => lines.map((line) => {
1742
- if (typeof line !== "string") return line;
1743
- const reasoning = line.startsWith("🧠 ");
1744
- const text = line.replace(/^(?:🧠|💬)\s+/u, "").replace(/^_(.*)_$/su, "$1").trim();
1745
- return {
1746
- ...reasoning ? { id: "reasoning" } : {},
1747
- kind: "item",
1748
- text,
1749
- label: reasoning ? "Reasoning" : "Update",
1750
- prefix: false
1635
+ const refreshStreamedAcknowledgements = (session) => {
1636
+ if (session.pendingText.length === 0) for (const delivery of streamedDeliveries) delivery.acknowledged = true;
1637
+ };
1638
+ const recordStreamedDelivery = (kind, content) => {
1639
+ const delivery = {
1640
+ kind,
1641
+ content,
1642
+ acknowledged: false
1751
1643
  };
1752
- });
1753
- const resolveNativeProgressPlan = (snapshot) => snapshot.plan?.length ? snapshot.plan : void 0;
1754
- const resolveNativeProgressLines = (snapshot) => {
1755
- const lines = resolveStructuredProgressLines(snapshot.lines);
1756
- if (snapshot.plan?.length || !snapshot.planExplanation) return lines;
1757
- const explanationLine = buildChannelProgressDraftLine({
1758
- event: "plan",
1759
- phase: "update",
1760
- explanation: snapshot.planExplanation
1761
- });
1762
- return explanationLine ? [...lines, explanationLine] : lines;
1644
+ streamedDeliveries.push(delivery);
1645
+ return delivery;
1763
1646
  };
1764
- const combineProgressHeadlineAndExplanation = (headline, explanation) => headline && explanation && headline !== explanation ? `${headline} — ${explanation}` : headline ?? explanation;
1765
- const resolveNativeProgressTitle = (snapshot) => combineProgressHeadlineAndExplanation(explicitProgressTitle ?? snapshot.statusHeadline, snapshot.planExplanation);
1766
- const buildNativeProgressChunks = (snapshot) => streamSession ? buildSlackProgressStreamUpdateChunks({
1767
- title: resolveNativeProgressTitle(snapshot),
1768
- lines: resolveNativeProgressLines(snapshot),
1769
- plan: resolveNativeProgressPlan(snapshot),
1770
- maxLineChars: progressDraftMaxLineChars
1771
- }) : buildSlackProgressStreamStartChunks({
1772
- title: resolveNativeProgressTitle(snapshot),
1773
- lines: resolveNativeProgressLines(snapshot),
1774
- plan: resolveNativeProgressPlan(snapshot),
1775
- maxLineChars: progressDraftMaxLineChars
1776
- });
1777
- const markNativeProgressDelivered = (session, threadTs) => {
1778
- if (session.delivered) observedReplyDelivery = true;
1779
- if (threadTs) {
1780
- usedReplyThreadTs ??= threadTs;
1781
- rememberDeliveredThreadTs("block", threadTs);
1647
+ const rememberStreamedDelivery = (kind, content, session) => {
1648
+ recordStreamedDelivery(kind, content);
1649
+ refreshStreamedAcknowledgements(session);
1650
+ };
1651
+ const emitAcknowledgedStreamedDeliveries = (messageId) => {
1652
+ for (const delivery of streamedDeliveries) {
1653
+ if (!delivery.acknowledged || delivery.outcome) continue;
1654
+ emitSlackMessageSentHooks({
1655
+ ...messageSentHookContext,
1656
+ to: messageSentHookTarget,
1657
+ accountId: account.accountId,
1658
+ content: delivery.content,
1659
+ success: true,
1660
+ ...messageId ? { messageId } : {}
1661
+ });
1662
+ delivery.outcome = "success";
1782
1663
  }
1783
1664
  };
1784
- const startNativeProgressStream = async (chunks, chunkKey) => {
1785
- const streamThreadTs = replyPlan.nextThreadTs();
1786
- if (!streamThreadTs) {
1787
- logVerbose("slack-stream: no reply thread target for native progress stream start, falling back");
1788
- streamFailed = true;
1789
- return;
1665
+ const acknowledgeStoppedStreamedDeliveries = (session, messageId) => {
1666
+ refreshStreamedAcknowledgements(session);
1667
+ for (const delivery of streamedDeliveries) delivery.acknowledged = true;
1668
+ emitAcknowledgedStreamedDeliveries(messageId);
1669
+ };
1670
+ const emitFailedPendingStreamedDeliveries = (error) => {
1671
+ for (const delivery of streamedDeliveries) {
1672
+ if (delivery.acknowledged || delivery.outcome) continue;
1673
+ emitSlackMessageSentHooks({
1674
+ ...messageSentHookContext,
1675
+ to: messageSentHookTarget,
1676
+ accountId: account.accountId,
1677
+ content: delivery.content,
1678
+ success: false,
1679
+ error
1680
+ });
1681
+ delivery.outcome = "failure";
1790
1682
  }
1791
- nativeProgressStreamThreadTs = streamThreadTs;
1792
- const startPromise = (async () => {
1793
- const session = await startSlackStream({
1794
- client: slackClient,
1795
- channel: message.channel,
1796
- threadTs: streamThreadTs,
1797
- chunks,
1798
- taskDisplayMode: "plan",
1799
- ...slackIdentity ? { identity: slackIdentity } : {},
1800
- teamId: await resolveSlackStreamRecipientTeamId({
1801
- client: slackClient,
1802
- token: ctx.botToken,
1803
- userId: message.user,
1804
- fallbackTeamId: slackStreamFallbackTeamId
1805
- }),
1806
- userId: message.user
1683
+ };
1684
+ const emitSuccessfulPendingStreamedDeliveries = (messageId) => {
1685
+ for (const delivery of streamedDeliveries) {
1686
+ if (delivery.acknowledged || delivery.outcome) continue;
1687
+ emitSlackMessageSentHooks({
1688
+ ...messageSentHookContext,
1689
+ to: messageSentHookTarget,
1690
+ accountId: account.accountId,
1691
+ content: delivery.content,
1692
+ success: true,
1693
+ ...messageId ? { messageId } : {}
1807
1694
  });
1808
- streamSession = session;
1809
- return session;
1810
- })();
1811
- nativeProgressStreamStartPromise = startPromise;
1812
- let startedSession;
1695
+ delivery.outcome = "success";
1696
+ }
1697
+ };
1698
+ let deliveryTracker = createSlackEventDeliveryTracker();
1699
+ const markPreviewPayloadDelivered = (params) => {
1700
+ deliveryTracker.markDelivered(params);
1701
+ const nextThreadTs = replyPlan.peekThreadTs();
1702
+ if (nextThreadTs !== params.threadTs) deliveryTracker.markDelivered({
1703
+ ...params,
1704
+ threadTs: nextThreadTs
1705
+ });
1706
+ };
1707
+ const resolveDeliveryThreadTs = (params) => {
1708
+ const plannedThreadTs = params.forcedThreadTs ? void 0 : replyPlan.nextThreadTs();
1709
+ return params.forcedThreadTs ?? plannedThreadTs ?? (params.kind === "block" ? state.usedBlockReplyThreadTs : void 0);
1710
+ };
1711
+ const rememberDeliveredThreadTs = (kind, deliveredThreadTs) => {
1712
+ if (!deliveredThreadTs) return;
1713
+ state.usedReplyThreadTs ??= deliveredThreadTs;
1714
+ if (kind === "block") state.usedBlockReplyThreadTs = deliveredThreadTs;
1715
+ };
1716
+ const deliverPendingStreamFallback = async (session, err) => {
1717
+ let fallbackError = err;
1718
+ if (!session.stopped) try {
1719
+ const stopResult = await stopSlackStream({
1720
+ session,
1721
+ ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
1722
+ });
1723
+ acknowledgeStoppedStreamedDeliveries(session, stopResult.messageId);
1724
+ state.observedReplyDelivery = true;
1725
+ state.usedReplyThreadTs ??= session.threadTs;
1726
+ return true;
1727
+ } catch (stopErr) {
1728
+ if (stopErr instanceof SlackStreamNotDeliveredError) fallbackError = stopErr;
1729
+ else runtime.error?.(danger(`slack-stream: failed to finalize buffered text before fallback: ${formatSlackError(stopErr)}`));
1730
+ }
1731
+ emitAcknowledgedStreamedDeliveries();
1732
+ const fallbackText = fallbackError.pendingText.trim();
1733
+ if (!fallbackText) return false;
1813
1734
  try {
1814
- startedSession = await startPromise;
1815
- } finally {
1816
- if (nativeProgressStreamStartPromise === startPromise) nativeProgressStreamStartPromise = null;
1735
+ await deliverReplies({
1736
+ cfg: ctx.cfg,
1737
+ replies: [{ text: fallbackText }],
1738
+ target: prepared.replyTarget,
1739
+ token: ctx.botToken,
1740
+ accountId: account.accountId,
1741
+ runtime,
1742
+ textLimit: ctx.textLimit,
1743
+ mediaMaxBytes: ctx.mediaMaxBytes,
1744
+ replyThreadTs: session.threadTs,
1745
+ replyToMode: replyDeliveryMode,
1746
+ ...slackIdentity ? { identity: slackIdentity } : {},
1747
+ ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
1748
+ ...messageSentDeliveryHookContext,
1749
+ deferMessageSentHooks: true,
1750
+ ...prepared.eventScope ? { eventScope: prepared.eventScope } : {}
1751
+ });
1752
+ markSlackStreamFallbackDelivered(session);
1753
+ if (!session.stopped) try {
1754
+ await stopSlackStream({
1755
+ session,
1756
+ ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
1757
+ });
1758
+ } catch (finalizeErr) {
1759
+ runtime.error?.(danger(`slack-stream: failed to finalize native stream after fallback delivery: ${formatSlackError(finalizeErr)}`));
1760
+ }
1761
+ emitSuccessfulPendingStreamedDeliveries();
1762
+ state.observedReplyDelivery = true;
1763
+ state.usedReplyThreadTs ??= session.threadTs;
1764
+ logVerbose(`slack-stream: streamed delivery failed (${fallbackError.slackCode}); delivered ${fallbackText.length} chars via deliverReplies fallback`);
1765
+ return true;
1766
+ } catch (postErr) {
1767
+ emitFailedPendingStreamedDeliveries(formatErrorMessage(postErr));
1768
+ runtime.error?.(danger(`slack-stream: fallback deliverReplies failed after ${fallbackError.slackCode}: ${formatErrorMessage(postErr)}`));
1769
+ return false;
1817
1770
  }
1818
- if (startedSession) markNativeProgressDelivered(startedSession, streamThreadTs);
1819
- nativeProgressChunkKey = chunkKey;
1771
+ };
1772
+ const deliverNormally = async (params) => {
1773
+ if (params.payload.isReasoning === true) return;
1774
+ const replyThreadTs = resolveDeliveryThreadTs(params);
1775
+ const deliveryReplyThreadTs = replyDeliveryMode === "off" && !forcedReplyThreadTs && !isThreadReply ? void 0 : replyThreadTs;
1776
+ if (deliveryTracker.hasDelivered({
1777
+ kind: params.kind,
1778
+ payload: params.payload,
1779
+ threadTs: deliveryReplyThreadTs
1780
+ })) {
1781
+ logVerbose("slack: suppressed duplicate normal delivery within the same turn");
1782
+ return deliveryReplyThreadTs;
1783
+ }
1784
+ await deliverReplies({
1785
+ cfg: ctx.cfg,
1786
+ replies: [params.payload],
1787
+ target: prepared.replyTarget,
1788
+ token: ctx.botToken,
1789
+ accountId: account.accountId,
1790
+ runtime,
1791
+ textLimit: ctx.textLimit,
1792
+ mediaMaxBytes: ctx.mediaMaxBytes,
1793
+ replyThreadTs: deliveryReplyThreadTs,
1794
+ replyToMode: replyDeliveryMode,
1795
+ ...slackIdentity ? { identity: slackIdentity } : {},
1796
+ ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
1797
+ ...messageSentDeliveryHookContext,
1798
+ ...prepared.eventScope ? { eventScope: prepared.eventScope } : {}
1799
+ });
1800
+ state.observedReplyDelivery = true;
1801
+ if (params.kind === "final") state.observedFinalReplyDelivery = true;
1802
+ const deliveredThreadTs = resolveDeliveredSlackReplyThreadTs({
1803
+ replyToMode: replyDeliveryMode,
1804
+ payloadReplyToId: params.payload.replyToId,
1805
+ replyThreadTs: deliveryReplyThreadTs
1806
+ });
1807
+ rememberDeliveredThreadTs(params.kind, deliveredThreadTs);
1820
1808
  replyPlan.markSent();
1821
- };
1822
- const appendNativeProgressStream = async (chunks, chunkKey) => {
1823
- if (!streamSession) return;
1824
- await appendSlackStream({
1825
- session: streamSession,
1826
- chunks
1809
+ deliveryTracker.markDelivered({
1810
+ kind: params.kind,
1811
+ payload: params.payload,
1812
+ threadTs: deliveryReplyThreadTs
1827
1813
  });
1828
- markNativeProgressDelivered(streamSession);
1829
- nativeProgressChunkKey = chunkKey;
1814
+ return deliveryReplyThreadTs;
1830
1815
  };
1831
- const updateNativeProgressStream = async () => {
1832
- const snapshot = progressDraft.getSnapshot();
1833
- const progressLines = resolveNativeProgressLines(snapshot);
1834
- const hasRetirableNativeTasks = [...nativeTaskState.values()].some((task) => task.status !== "complete" && task.status !== "error");
1835
- if (!useNativeProgressStreaming || streamFailed || progressLines.length === 0 && !snapshot.plan?.length && !snapshot.statusHeadline && !explicitProgressTitle && !hasRetirableNativeTasks) return;
1836
- if (!await waitForNativeProgressStreamStart()) return;
1837
- const reconciled = reconcileSlackNativeTaskChunks({
1838
- previousTasks: nativeTaskState,
1839
- chunks: buildNativeProgressChunks(snapshot)
1840
- });
1841
- const chunks = reconciled.chunks;
1842
- if (!chunks?.length) return;
1843
- const chunkKey = JSON.stringify(chunks);
1844
- if (chunkKey === nativeProgressChunkKey) return;
1845
- try {
1846
- if (!streamSession) await startNativeProgressStream(chunks, chunkKey);
1847
- else await appendNativeProgressStream(chunks, chunkKey);
1848
- nativeTaskState = reconciled.tasks;
1849
- } catch (err) {
1850
- runtime.error?.(danger(`slack-stream: native progress stream failed: ${formatSlackError(err)}, falling back`));
1851
- streamFailed = true;
1816
+ const deliverBufferedStreamFallback = async (params) => {
1817
+ if (!await deliverPendingStreamFallback(params.session, params.err)) {
1818
+ streamedFailuresOwnedByDispatcher[params.kind] += 1;
1819
+ return false;
1852
1820
  }
1821
+ replyPlan.markSent();
1822
+ if (params.kind === "final") state.observedFinalReplyDelivery = true;
1823
+ deliveryTracker.markDelivered({
1824
+ kind: params.kind,
1825
+ payload: params.payload,
1826
+ threadTs: params.session.threadTs,
1827
+ textOverride: params.textOverride
1828
+ });
1829
+ rememberDeliveredThreadTs(params.kind, params.session.threadTs);
1830
+ return true;
1853
1831
  };
1854
- const resetProgressTurnState = () => {
1855
- progressReceipt.reset();
1856
- progressReceiptCollapsed = false;
1857
- pendingNativeProgressReceipt = void 0;
1858
- };
1859
- const collapseProgressReceipt = async (params) => {
1860
- const { botToken: token } = ctx;
1861
- try {
1862
- await finalizeSlackPreviewEdit({
1863
- client: slackClient,
1864
- token,
1865
- accountId: account.accountId,
1866
- ...params
1832
+ const deliverWithStreaming = async (params) => {
1833
+ if (params.payload.isReasoning === true) return;
1834
+ const reply = resolveSendableOutboundReplyParts(params.payload);
1835
+ const renderPlan = resolveSlackReplyRenderPlan(params.payload);
1836
+ const plannedBlocks = renderPlan.mode === "single" ? renderPlan.blocks : renderPlan.blockPart?.blocks;
1837
+ if (state.streamFailed || reply.hasMedia || renderPlan.mode === "split" || Boolean(plannedBlocks?.length) || readSlackReplyBlocks(params.payload)?.length || !reply.hasText) {
1838
+ await deliverNormally({
1839
+ payload: params.payload,
1840
+ kind: params.kind,
1841
+ forcedThreadTs: state.streamSession?.threadTs ?? state.nativeProgressStreamThreadTs
1867
1842
  });
1868
- progressReceiptCollapsed = true;
1869
- } catch (err) {
1870
- logVerbose(`slack: progress receipt edit failed (${formatSlackError(err)})`);
1843
+ return;
1871
1844
  }
1872
- };
1873
- const progressDraft = createChannelProgressDraftCompositor({
1874
- entry: account.config,
1875
- mode: slackStreaming.mode,
1876
- active: progressDraftActive && streamMode === "status_final",
1877
- seed: progressSeed,
1878
- formatLine: escapeSlackMrkdwn,
1879
- reasoningLinePrefix: "🧠 ",
1880
- commentaryLinePrefix: "💬 ",
1881
- reasoningGate: previewToolProgressEnabled,
1882
- commentaryItalics: false,
1883
- updateOnLineChange: useNativeProgressStreaming || useRichProgressDraft,
1884
- update: async (previewText, options) => {
1885
- if (useNativeProgressStreaming) {
1886
- await updateNativeProgressStream();
1845
+ const text = reply.trimmedText;
1846
+ let plannedThreadTs;
1847
+ try {
1848
+ if (!state.streamSession && state.nativeProgressStreamStartPromise) await state.nativeProgressStreamStartPromise;
1849
+ if (state.streamFailed) {
1850
+ await deliverNormally({
1851
+ payload: params.payload,
1852
+ kind: params.kind,
1853
+ forcedThreadTs: state.streamSession?.threadTs ?? state.nativeProgressStreamThreadTs
1854
+ });
1887
1855
  return;
1888
1856
  }
1889
- if (!draftStream) return;
1890
- const snapshot = progressDraft.getSnapshot();
1891
- const structuredLines = resolveStructuredProgressLines(options?.lines ?? snapshot.lines);
1892
- const richNarration = combineProgressHeadlineAndExplanation(snapshot.statusHeadline, snapshot.planExplanation);
1893
- const richProgressBlocks = useRichProgressDraft ? buildSlackProgressDraftBlocks({
1894
- title: explicitProgressTitle,
1895
- lines: structuredLines,
1896
- plan: snapshot.plan,
1897
- narration: richNarration,
1898
- maxLineChars: progressDraftMaxLineChars
1899
- }) : void 0;
1900
- draftStream.update(useRichProgressDraft && richProgressBlocks ? {
1901
- text: previewText,
1902
- blocks: richProgressBlocks
1903
- } : previewText);
1904
- hasStreamedMessage = true;
1905
- if (options?.flush) await draftStream.flush();
1906
- }
1907
- });
1908
- const commentaryProgressEnabled = progressDraft.commentaryProgressEnabled;
1909
- const buildNativeProgressCompletionChunks = (finalInProgressStatus) => {
1910
- const snapshot = progressDraft.getSnapshot();
1911
- const lines = resolveNativeProgressLines(snapshot);
1912
- const hasRetirableNativeTasks = [...nativeTaskState.values()].some((task) => task.status !== "complete" && task.status !== "error");
1913
- if (lines.length === 0 && !snapshot.plan?.length && !hasRetirableNativeTasks) return;
1914
- return reconcileSlackNativeTaskChunks({
1915
- previousTasks: nativeTaskState,
1916
- chunks: buildSlackProgressStreamCompletionChunks({
1917
- title: resolveNativeProgressTitle(snapshot),
1918
- lines,
1919
- plan: resolveNativeProgressPlan(snapshot),
1920
- maxLineChars: progressDraftMaxLineChars,
1921
- finalInProgressStatus
1922
- })
1923
- }).chunks;
1924
- };
1925
- const finishNativeProgressTurn = async (completionChunks) => {
1926
- if (nativeProgressStreamStartPromise) await nativeProgressStreamStartPromise.catch(() => null);
1927
- const session = streamSession;
1928
- if (session && !session.stopped) try {
1929
- if (completionChunks?.length) nativeProgressCompletionSent = true;
1930
- const stopResult = await stopSlackStream({
1931
- session,
1932
- ...completionChunks?.length ? { chunks: completionChunks } : {},
1933
- ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
1857
+ if (!state.streamSession) {
1858
+ const streamThreadTs = replyPlan.nextThreadTs();
1859
+ plannedThreadTs = streamThreadTs;
1860
+ if (!streamThreadTs) {
1861
+ logVerbose("slack-stream: no reply thread target for stream start, falling back to normal delivery");
1862
+ state.streamFailed = true;
1863
+ await deliverNormally({
1864
+ payload: params.payload,
1865
+ kind: params.kind
1866
+ });
1867
+ return;
1868
+ }
1869
+ if (deliveryTracker.hasDelivered({
1870
+ kind: params.kind,
1871
+ payload: params.payload,
1872
+ threadTs: streamThreadTs,
1873
+ textOverride: text
1874
+ })) {
1875
+ logVerbose("slack-stream: suppressed duplicate stream start payload");
1876
+ return;
1877
+ }
1878
+ state.streamSession = await startSlackStream({
1879
+ client: slackClient,
1880
+ channel: message.channel,
1881
+ threadTs: streamThreadTs,
1882
+ text,
1883
+ ...slackIdentity ? { identity: slackIdentity } : {},
1884
+ teamId: await resolveSlackStreamRecipientTeamId({
1885
+ client: slackClient,
1886
+ token: ctx.botToken,
1887
+ userId: message.user,
1888
+ fallbackTeamId: slackStreamFallbackTeamId
1889
+ }),
1890
+ userId: message.user
1891
+ });
1892
+ refreshStreamedAcknowledgements(state.streamSession);
1893
+ if (state.streamSession.delivered) {
1894
+ state.observedReplyDelivery = true;
1895
+ if (params.kind === "final") state.observedFinalReplyDelivery = true;
1896
+ }
1897
+ if (text) rememberStreamedDelivery(params.kind, text, state.streamSession);
1898
+ rememberDeliveredThreadTs(params.kind, streamThreadTs);
1899
+ replyPlan.markSent();
1900
+ deliveryTracker.markDelivered({
1901
+ kind: params.kind,
1902
+ payload: params.payload,
1903
+ threadTs: streamThreadTs,
1904
+ textOverride: text
1905
+ });
1906
+ return;
1907
+ }
1908
+ if (deliveryTracker.hasDelivered({
1909
+ kind: params.kind,
1910
+ payload: params.payload,
1911
+ threadTs: state.streamSession.threadTs,
1912
+ textOverride: text
1913
+ })) {
1914
+ logVerbose("slack-stream: suppressed duplicate append payload");
1915
+ return;
1916
+ }
1917
+ if (text) recordStreamedDelivery(params.kind, text);
1918
+ await appendSlackStream({
1919
+ session: state.streamSession,
1920
+ text: "\n" + text
1934
1921
  });
1935
- acknowledgeStoppedStreamedDeliveries(session, stopResult?.messageId);
1936
- if (pendingNativeProgressReceipt && stopResult?.messageId) await collapseProgressReceipt({
1937
- channelId: session.channel,
1938
- messageId: stopResult.messageId,
1939
- text: pendingNativeProgressReceipt,
1940
- threadTs: session.threadTs
1922
+ refreshStreamedAcknowledgements(state.streamSession);
1923
+ if (state.streamSession.delivered) {
1924
+ state.observedReplyDelivery = true;
1925
+ if (params.kind === "final") state.observedFinalReplyDelivery = true;
1926
+ }
1927
+ deliveryTracker.markDelivered({
1928
+ kind: params.kind,
1929
+ payload: params.payload,
1930
+ threadTs: state.streamSession.threadTs,
1931
+ textOverride: text
1941
1932
  });
1942
1933
  } catch (err) {
1943
- const error = formatSlackError(err);
1944
- emitAcknowledgedStreamedDeliveries();
1945
- emitFailedPendingStreamedDeliveries(error);
1946
- logVerbose(`slack-stream: failed to rotate native progress stream (${error})`);
1934
+ if (err instanceof SlackStreamNotDeliveredError) {
1935
+ state.streamFailed = true;
1936
+ if (state.streamSession) {
1937
+ if (await deliverBufferedStreamFallback({
1938
+ session: state.streamSession,
1939
+ err,
1940
+ payload: params.payload,
1941
+ kind: params.kind,
1942
+ textOverride: text
1943
+ })) return;
1944
+ throw err;
1945
+ }
1946
+ await deliverNormally({
1947
+ payload: params.payload,
1948
+ kind: params.kind,
1949
+ forcedThreadTs: plannedThreadTs
1950
+ });
1951
+ return;
1952
+ }
1953
+ runtime.error?.(danger(`slack-stream: streaming API call failed: ${formatSlackError(err)}, falling back`));
1954
+ state.streamFailed = true;
1955
+ if (state.streamSession && state.streamSession.pendingText) {
1956
+ const bufferedFallbackErr = new SlackStreamNotDeliveredError(state.streamSession.pendingText, "unknown");
1957
+ if (await deliverBufferedStreamFallback({
1958
+ session: state.streamSession,
1959
+ err: bufferedFallbackErr,
1960
+ payload: params.payload,
1961
+ kind: params.kind,
1962
+ textOverride: text
1963
+ })) return;
1964
+ throw err;
1965
+ }
1966
+ await deliverNormally({
1967
+ payload: params.payload,
1968
+ kind: params.kind,
1969
+ forcedThreadTs: state.streamSession?.threadTs ?? state.nativeProgressStreamThreadTs ?? plannedThreadTs
1970
+ });
1947
1971
  }
1948
- streamSession = null;
1949
- nativeProgressStreamStartPromise = null;
1950
- nativeProgressStreamThreadTs = void 0;
1951
- streamFailed = false;
1952
1972
  };
1953
- const pushPlanProgress = async (steps, explanation) => {
1954
- if (streamMode === "status_final") {
1955
- await progressDraft.pushPlanProgress(steps, { explanation });
1956
- return;
1957
- }
1958
- if (previewToolProgressSuppressed || !draftStream) return;
1959
- const text = formatChannelProgressDraftText({
1960
- entry: account.config,
1961
- lines: legacyPreviewToolProgressLines,
1962
- seed: progressSeed,
1963
- formatLine: escapeSlackMrkdwn,
1964
- narration: explanation,
1965
- plan: steps
1966
- });
1967
- if (text) {
1968
- draftStream.update(text);
1969
- hasStreamedMessage = true;
1973
+ const reconcileCounts = (counts) => {
1974
+ const next = { ...counts };
1975
+ let changed = false;
1976
+ for (const kind of [
1977
+ "tool",
1978
+ "block",
1979
+ "final"
1980
+ ]) {
1981
+ const failedStreamedCount = streamedDeliveries.filter((delivery) => delivery.kind === kind && delivery.outcome === "failure").length;
1982
+ const additionalFailedStreamed = Math.max(0, failedStreamedCount - streamedFailuresOwnedByDispatcher[kind]);
1983
+ if (additionalFailedStreamed > 0) {
1984
+ next[kind] = Math.max(0, (next[kind] ?? 0) - additionalFailedStreamed);
1985
+ changed = true;
1986
+ }
1970
1987
  }
1988
+ return changed ? next : counts;
1971
1989
  };
1972
- const pushPreviewProgress = async (line, options) => {
1973
- if (!draftStream && !useNativeProgressStreaming) return;
1974
- if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return;
1975
- const normalized = line?.text.replace(/\s+/g, " ").trim();
1976
- if (streamMode === "status_final") {
1977
- if (!line || !normalized) {
1978
- await progressDraft.noteActivity();
1990
+ return Object.assign(state, {
1991
+ acknowledgeStoppedStreamedDeliveries,
1992
+ deliverNormally,
1993
+ deliverPendingStreamFallback,
1994
+ deliverWithStreaming,
1995
+ emitAcknowledgedStreamedDeliveries,
1996
+ emitFailedPendingStreamedDeliveries,
1997
+ hasDelivered: (params) => deliveryTracker.hasDelivered(params),
1998
+ markPreviewPayloadDelivered,
1999
+ rememberDeliveredThreadTs,
2000
+ reconcileCounts,
2001
+ resetDeliveryTracker: () => {
2002
+ deliveryTracker = createSlackEventDeliveryTracker();
2003
+ }
2004
+ });
2005
+ }
2006
+ //#endregion
2007
+ //#region extensions/slack/src/monitor/message-handler/dispatch.ts
2008
+ async function dispatchPreparedSlackMessage(prepared) {
2009
+ const setup = await createSlackDispatchSetup(prepared);
2010
+ const { account, cfg, ctx, disableBlockStreaming, hasRepliedRef, message, messageSentHookContext, messageSentHookTarget, onModelSelected, onSlackDeliveryError, previewStreamingEnabled, replyPipeline, replyPlan, route, runtime, slackClient, slackMessageMetadata, slackStreaming, sourceReplyDeliveryMode, statusReactions, statusReactionsEnabled, statusThreadTs, suppressRoomEventTyping, useStreaming } = setup;
2011
+ const delivery = createSlackStreamingDeliveryRuntime(setup);
2012
+ const draftPreviewCommitted = { value: false };
2013
+ const progress = createSlackProgressRuntime({
2014
+ setup,
2015
+ delivery,
2016
+ resetPreviewDeliveryState: () => {
2017
+ draftPreviewCommitted.value = false;
2018
+ delivery.observedFinalReplyDelivery = false;
2019
+ }
2020
+ });
2021
+ const draftStream = progress.draftStream;
2022
+ const deliverSlackPayload = async (payload, info) => {
2023
+ if (payload.isReasoning === true) return { visibleReplySent: false };
2024
+ if (info.kind === "final" && slackStreaming.mode === "progress" && progress.streamMode === "status_final") {
2025
+ const hadProgressDraft = progress.progressDraft.hasStarted;
2026
+ progress.progressDraft.markFinalReplyStarted();
2027
+ if (progress.useNativeProgressStreaming) {
2028
+ await progress.waitForNativeProgressStreamStart();
2029
+ const finalThreadTs = delivery.streamSession?.threadTs ?? delivery.nativeProgressStreamThreadTs;
2030
+ await delivery.deliverNormally({
2031
+ payload,
2032
+ kind: info.kind,
2033
+ forcedThreadTs: finalThreadTs
2034
+ });
2035
+ await progress.appendNativeProgressCompletion(payload.isError === true);
2036
+ progress.progressDraft.markFinalReplyDelivered();
2037
+ if (!payload.isError && hadProgressDraft && delivery.streamSession) progress.pendingNativeProgressReceipt = progress.progressReceipt.buildSummaryLine();
1979
2038
  return;
1980
2039
  }
1981
- await progressDraft.pushToolProgress(line, options);
2040
+ if (hadProgressDraft) try {
2041
+ await draftStream?.flush();
2042
+ } catch (err) {
2043
+ logVerbose(`slack: progress draft flush before final failed (${formatSlackError(err)})`);
2044
+ }
2045
+ const receiptChannelId = hadProgressDraft ? draftStream?.channelId() : void 0;
2046
+ const receiptMessageId = hadProgressDraft ? draftStream?.messageId() : void 0;
2047
+ const draftThreadTs = hadProgressDraft ? delivery.usedReplyThreadTs ?? statusThreadTs : void 0;
2048
+ await delivery.deliverNormally({
2049
+ payload,
2050
+ kind: info.kind,
2051
+ ...draftThreadTs ? { forcedThreadTs: draftThreadTs } : {}
2052
+ });
2053
+ progress.progressDraft.markFinalReplyDelivered();
2054
+ if (!payload.isError && receiptChannelId && receiptMessageId && !progress.progressReceiptCollapsed) await progress.collapseProgressReceipt({
2055
+ channelId: receiptChannelId,
2056
+ messageId: receiptMessageId,
2057
+ text: progress.progressReceipt.buildSummaryLine(),
2058
+ threadTs: delivery.usedReplyThreadTs ?? statusThreadTs
2059
+ });
1982
2060
  return;
1983
2061
  }
1984
- if (!line || !normalized || !draftStream || !previewToolProgressEnabled || previewToolProgressSuppressed) return;
1985
- const nextLines = mergeChannelProgressDraftLine(legacyPreviewToolProgressLines, line, { maxLines: resolveChannelProgressDraftMaxLines(account.config) });
1986
- if (nextLines === legacyPreviewToolProgressLines) return;
1987
- legacyPreviewToolProgressLines = nextLines;
1988
- draftStream.update(formatChannelProgressDraftText({
1989
- entry: account.config,
1990
- lines: legacyPreviewToolProgressLines,
1991
- seed: progressSeed,
1992
- formatLine: escapeSlackMrkdwn
1993
- }));
1994
- hasStreamedMessage = true;
1995
- };
1996
- const updateDraftFromPartial = (text) => {
1997
- const trimmed = text?.trimEnd();
1998
- if (!trimmed) return;
1999
- if (streamMode === "append") {
2000
- previewToolProgressSuppressed = true;
2001
- legacyPreviewToolProgressLines = [];
2002
- const next = applyAppendOnlyStreamUpdate({
2003
- incoming: trimmed,
2004
- rendered: appendRenderedText,
2005
- source: appendSourceText
2062
+ if (progress.useNativeProgressStreaming) {
2063
+ await delivery.deliverNormally({
2064
+ payload,
2065
+ kind: info.kind,
2066
+ forcedThreadTs: delivery.streamSession?.threadTs ?? delivery.nativeProgressStreamThreadTs
2006
2067
  });
2007
- appendRenderedText = next.rendered;
2008
- appendSourceText = next.source;
2009
- if (!next.changed) return;
2010
- draftStream?.update(next.rendered);
2011
- hasStreamedMessage = true;
2012
2068
  return;
2013
2069
  }
2014
- if (streamMode === "status_final") return;
2015
- previewToolProgressSuppressed = true;
2016
- legacyPreviewToolProgressLines = [];
2017
- draftStream?.update(trimmed);
2018
- hasStreamedMessage = true;
2019
- };
2020
- const pushReasoningProgress = async (payload) => {
2021
- if (!payload?.text) return;
2022
- if (streamMode !== "status_final") {
2023
- const normalized = progressDraft.mergeReasoningProgress(payload.text, { snapshot: payload.isReasoningSnapshot === true }).replace(/^_(.*)_$/su, "$1").trim();
2024
- if (!normalized) return;
2025
- await pushPreviewProgress({
2026
- id: "reasoning",
2027
- kind: "item",
2028
- text: normalized,
2029
- label: "Reasoning"
2070
+ if (useStreaming) {
2071
+ await delivery.deliverWithStreaming({
2072
+ payload,
2073
+ kind: info.kind
2030
2074
  });
2031
2075
  return;
2032
2076
  }
2033
- progressReceipt.noteReasoning();
2034
- await progressDraft.pushReasoningProgress(payload.text, { snapshot: payload.isReasoningSnapshot === true });
2035
- };
2036
- const resetDraftDeliveryState = () => {
2037
- hasStreamedMessage = false;
2038
- appendRenderedText = "";
2039
- appendSourceText = "";
2040
- };
2041
- const resetDraftProgressState = () => {
2042
- progressDraft.resetReasoningProgress();
2043
- previewToolProgressSuppressed = false;
2044
- legacyPreviewToolProgressLines = [];
2045
- };
2046
- const beginNewProgressTurn = async (options) => {
2047
- const completionChunks = useNativeProgressStreaming && !nativeProgressCompletionSent ? buildNativeProgressCompletionChunks(nativeProgressTerminalStatus) : void 0;
2048
- if (!progressDraft.beginNewTurn(options)) return false;
2049
- if (useNativeProgressStreaming) await finishNativeProgressTurn(completionChunks);
2050
- else draftStream?.forceNewMessage();
2051
- resetProgressTurnState();
2052
- nativeTaskState = /* @__PURE__ */ new Map();
2053
- nativeProgressCompletionSent = false;
2054
- nativeProgressTerminalStatus = "complete";
2055
- nativeProgressChunkKey = void 0;
2056
- draftPreviewCommitted = false;
2057
- observedFinalReplyDelivery = false;
2058
- deliveryTracker = createSlackEventDeliveryTracker();
2059
- progressReceiptCollapsed = false;
2060
- return true;
2061
- };
2062
- const onDraftBoundary = !shouldUseDraftStream && !useNativeProgressStreaming ? void 0 : async () => {
2063
- if (streamMode === "status_final") {
2064
- await beginNewProgressTurn();
2065
- return;
2077
+ const reply = resolveSendableOutboundReplyParts(payload);
2078
+ const ttsSupplement = getReplyPayloadTtsSupplement(payload);
2079
+ const replySourceText = payload.text ?? ttsSupplement?.spokenText;
2080
+ const replyRenderPlan = resolveSlackReplyRenderPlan(payload, replySourceText);
2081
+ const slackBlocks = replyRenderPlan.mode === "single" ? replyRenderPlan.blocks : replyRenderPlan.blockPart?.blocks;
2082
+ const requiresSeparateFallbackDelivery = replyRenderPlan.mode === "split";
2083
+ const trimmedFinalText = replyRenderPlan.mode === "single" ? replyRenderPlan.text.trim() : replyRenderPlan.fallbackText.trim();
2084
+ const previewFinalText = replyRenderPlan.mode === "single" && replyRenderPlan.textIsSlackMrkdwn ? trimmedFinalText : normalizeSlackOutboundText((replySourceText ?? "").trim());
2085
+ const shouldRestoreTtsSupplementTextForPreviewFallback = Boolean(ttsSupplement) && ttsSupplement?.visibleTextAlreadyDelivered !== true && Boolean(draftStream) && !draftPreviewCommitted.value && !delivery.observedFinalReplyDelivery && previewStreamingEnabled && !payload.text?.trim();
2086
+ if (info.kind === "final" && ttsSupplement && draftStream && !draftPreviewCommitted.value && !delivery.observedFinalReplyDelivery && previewStreamingEnabled && !payload.isError && !requiresSeparateFallbackDelivery && trimmedFinalText.length > 0) {
2087
+ const channelId = draftStream.channelId();
2088
+ const messageId = draftStream.messageId();
2089
+ if (channelId && messageId) {
2090
+ const finalThreadTs = delivery.usedReplyThreadTs ?? statusThreadTs;
2091
+ await draftStream.flush();
2092
+ await draftStream.seal();
2093
+ try {
2094
+ await finalizeSlackPreviewEdit({
2095
+ client: slackClient,
2096
+ token: ctx.botToken,
2097
+ accountId: account.accountId,
2098
+ channelId,
2099
+ messageId,
2100
+ text: previewFinalText,
2101
+ ...slackBlocks?.length ? { blocks: slackBlocks } : {},
2102
+ threadTs: finalThreadTs
2103
+ });
2104
+ } catch (err) {
2105
+ logVerbose(`slack: preview final edit failed; falling back to standard send (${formatSlackError(err)})`);
2106
+ await draftStream.discardPending();
2107
+ let delivered = false;
2108
+ try {
2109
+ await delivery.deliverNormally({
2110
+ payload: payload.text?.trim() ? payload : {
2111
+ ...payload,
2112
+ text: ttsSupplement.spokenText
2113
+ },
2114
+ kind: info.kind,
2115
+ forcedThreadTs: finalThreadTs
2116
+ });
2117
+ delivered = true;
2118
+ } finally {
2119
+ if (delivered) await draftStream.clear();
2120
+ }
2121
+ return;
2122
+ }
2123
+ draftPreviewCommitted.value = true;
2124
+ delivery.observedFinalReplyDelivery = true;
2125
+ delivery.observedReplyDelivery = true;
2126
+ replyPlan.markSent();
2127
+ await delivery.deliverNormally({
2128
+ payload: buildTtsSupplementMediaPayload(payload),
2129
+ kind: info.kind,
2130
+ forcedThreadTs: finalThreadTs
2131
+ });
2132
+ delivery.markPreviewPayloadDelivered({
2133
+ kind: info.kind,
2134
+ payload,
2135
+ threadTs: finalThreadTs
2136
+ });
2137
+ return;
2138
+ }
2066
2139
  }
2067
- if (hasStreamedMessage) draftStream?.forceNewMessage();
2068
- resetDraftDeliveryState();
2069
- resetDraftProgressState();
2070
- };
2071
- const onQueuedFollowupAdmitted = !shouldUseDraftStream && !useNativeProgressStreaming ? void 0 : async () => {
2072
- await draftStream?.flush();
2073
- draftPreviewCommitted = false;
2074
- observedFinalReplyDelivery = false;
2075
- if (streamMode === "status_final") await beginNewProgressTurn({ force: true });
2076
- else draftStream?.forceNewMessage();
2077
- deliveryTracker = createSlackEventDeliveryTracker();
2078
- resetDraftDeliveryState();
2079
- resetDraftProgressState();
2140
+ await deliverWithFinalizableLivePreviewAdapter({
2141
+ kind: info.kind,
2142
+ payload,
2143
+ adapter: defineFinalizableLivePreviewAdapter({
2144
+ draft: draftStream && !draftPreviewCommitted.value && !delivery.observedFinalReplyDelivery ? {
2145
+ flush: draftStream.flush,
2146
+ clear: draftStream.clear,
2147
+ discardPending: draftStream.discardPending,
2148
+ seal: draftStream.seal,
2149
+ id: () => {
2150
+ const channelId = draftStream.channelId();
2151
+ const messageId = draftStream.messageId();
2152
+ return channelId && messageId ? {
2153
+ channelId,
2154
+ messageId
2155
+ } : void 0;
2156
+ }
2157
+ } : void 0,
2158
+ buildFinalEdit: () => {
2159
+ if (!previewStreamingEnabled || reply.hasMedia && !ttsSupplement || payload.isError || requiresSeparateFallbackDelivery || trimmedFinalText.length === 0 && !slackBlocks?.length) return;
2160
+ return {
2161
+ text: previewFinalText,
2162
+ blocks: slackBlocks,
2163
+ threadTs: delivery.usedReplyThreadTs ?? statusThreadTs
2164
+ };
2165
+ },
2166
+ editFinal: async (preview, edit) => {
2167
+ if (delivery.hasDelivered({
2168
+ kind: info.kind,
2169
+ payload,
2170
+ threadTs: edit.threadTs
2171
+ })) return;
2172
+ await finalizeSlackPreviewEdit({
2173
+ client: slackClient,
2174
+ token: ctx.botToken,
2175
+ accountId: account.accountId,
2176
+ channelId: preview.channelId,
2177
+ messageId: preview.messageId,
2178
+ text: edit.text,
2179
+ ...edit.blocks?.length ? { blocks: edit.blocks } : {},
2180
+ threadTs: edit.threadTs
2181
+ });
2182
+ if (!ttsSupplement) emitSlackMessageSentHooks({
2183
+ ...messageSentHookContext,
2184
+ to: messageSentHookTarget,
2185
+ accountId: account.accountId,
2186
+ content: trimmedFinalText,
2187
+ success: true,
2188
+ messageId: preview.messageId
2189
+ });
2190
+ draftPreviewCommitted.value = true;
2191
+ delivery.observedFinalReplyDelivery = true;
2192
+ },
2193
+ onPreviewFinalized: (_preview) => {
2194
+ draftPreviewCommitted.value = true;
2195
+ delivery.observedFinalReplyDelivery = true;
2196
+ const finalThreadTs = delivery.usedReplyThreadTs ?? statusThreadTs;
2197
+ delivery.observedReplyDelivery = true;
2198
+ replyPlan.markSent();
2199
+ if (!ttsSupplement) delivery.markPreviewPayloadDelivered({
2200
+ kind: info.kind,
2201
+ payload,
2202
+ threadTs: finalThreadTs
2203
+ });
2204
+ },
2205
+ buildSupplementalPayload: () => ttsSupplement ? buildTtsSupplementMediaPayload(payload) : void 0,
2206
+ deliverSupplemental: async (supplementalPayload) => {
2207
+ const previewThreadTs = delivery.usedReplyThreadTs ?? statusThreadTs;
2208
+ const supplementalThreadTs = await delivery.deliverNormally({
2209
+ payload: supplementalPayload,
2210
+ kind: info.kind,
2211
+ forcedThreadTs: previewThreadTs
2212
+ });
2213
+ delivery.markPreviewPayloadDelivered({
2214
+ kind: info.kind,
2215
+ payload,
2216
+ threadTs: supplementalThreadTs
2217
+ });
2218
+ },
2219
+ logPreviewEditFailure: (err) => {
2220
+ logVerbose(`slack: preview final edit failed; falling back to standard send (${formatSlackError(err)})`);
2221
+ }
2222
+ }),
2223
+ deliverNormally: async () => {
2224
+ await delivery.deliverNormally({
2225
+ payload: shouldRestoreTtsSupplementTextForPreviewFallback ? {
2226
+ ...payload,
2227
+ text: ttsSupplement?.spokenText
2228
+ } : payload,
2229
+ kind: info.kind
2230
+ });
2231
+ }
2232
+ });
2080
2233
  };
2081
2234
  let dispatchError;
2082
2235
  let queuedFinal = false;
@@ -2110,33 +2263,33 @@ async function dispatchPreparedSlackMessage(prepared) {
2110
2263
  hasRepliedRef,
2111
2264
  disableBlockStreaming,
2112
2265
  onModelSelected,
2113
- suppressDefaultToolProgressMessages: suppressDefaultToolProgressMessages ? true : void 0,
2114
- commentaryProgressEnabled: commentaryProgressEnabled ? true : void 0,
2115
- progressPreambleEnabled: progressDraftActive && slackStreaming.mode === "progress" ? true : void 0,
2116
- commentaryPayloadsEnabled: commentaryProgressEnabled ? true : void 0,
2117
- onVerboseProgressVisibility: commentaryProgressEnabled ? (isActive) => {
2118
- shouldYieldDraftProgress = isActive;
2266
+ suppressDefaultToolProgressMessages: progress.suppressDefaultToolProgressMessages ? true : void 0,
2267
+ commentaryProgressEnabled: progress.commentaryProgressEnabled ? true : void 0,
2268
+ progressPreambleEnabled: progress.progressDraftActive && slackStreaming.mode === "progress" ? true : void 0,
2269
+ commentaryPayloadsEnabled: progress.commentaryProgressEnabled ? true : void 0,
2270
+ onVerboseProgressVisibility: progress.commentaryProgressEnabled ? (isActive) => {
2271
+ progress.setShouldYieldDraftProgress(isActive);
2119
2272
  } : void 0,
2120
2273
  allowProgressCallbacksWhenSourceDeliverySuppressed: sourceReplyDeliveryMode === "message_tool_only" && statusReactionsEnabled ? true : void 0,
2121
2274
  allowToolLifecycleWhenProgressHidden: statusReactionsEnabled ? true : void 0,
2122
2275
  onPartialReply: useStreaming ? void 0 : !previewStreamingEnabled ? void 0 : async (payload) => {
2123
- updateDraftFromPartial(payload.text);
2276
+ progress.updateDraftFromPartial(payload.text);
2124
2277
  },
2125
- onAssistantMessageStart: onDraftBoundary,
2278
+ onAssistantMessageStart: progress.onDraftBoundary,
2126
2279
  onReasoningEnd: async () => {
2127
- progressReceipt.closeReasoning();
2128
- await onDraftBoundary?.();
2280
+ progress.progressReceipt.closeReasoning();
2281
+ await progress.onDraftBoundary?.();
2129
2282
  },
2130
- onQueuedFollowupAdmitted,
2131
- onReasoningStream: statusReactionsEnabled || previewToolProgressEnabled ? async (payload) => {
2132
- await pushReasoningProgress(payload);
2283
+ onQueuedFollowupAdmitted: progress.onQueuedFollowupAdmitted,
2284
+ onReasoningStream: statusReactionsEnabled || progress.previewToolProgressEnabled ? async (payload) => {
2285
+ await progress.pushReasoningProgress(payload);
2133
2286
  if (!statusReactionsEnabled) return;
2134
2287
  await statusReactions.setThinking();
2135
2288
  } : void 0,
2136
2289
  onToolStart: async (payload) => {
2137
2290
  if (statusReactionsEnabled) await statusReactions.setTool(payload.name);
2138
- if (payload.phase === "start") progressReceipt.noteToolCall(payload.name);
2139
- await pushPreviewProgress(buildChannelProgressDraftLineForEntry(account.config, {
2291
+ if (payload.phase === "start") progress.progressReceipt.noteToolCall(payload.name);
2292
+ await progress.pushPreviewProgress(buildChannelProgressDraftLineForEntry(account.config, {
2140
2293
  event: "tool",
2141
2294
  itemId: payload.itemId,
2142
2295
  toolCallId: payload.toolCallId,
@@ -2146,15 +2299,15 @@ async function dispatchPreparedSlackMessage(prepared) {
2146
2299
  }, payload.detailMode ? { detailMode: payload.detailMode } : void 0), { toolName: payload.name });
2147
2300
  },
2148
2301
  onItemEvent: async (payload) => {
2149
- if (streamMode === "status_final" && payload.kind === "preamble") {
2150
- if (shouldYieldDraftProgress()) return;
2151
- await progressDraft.pushPreambleHeadline(payload.progressText, { itemId: payload.itemId });
2152
- if (commentaryProgressEnabled) {
2153
- if (await progressDraft.pushCommentaryProgress(payload.progressText, { itemId: payload.itemId })) progressReceipt.noteCommentary(payload.itemId, payload.progressText);
2302
+ if (progress.streamMode === "status_final" && payload.kind === "preamble") {
2303
+ if (progress.shouldYieldDraftProgress()) return;
2304
+ await progress.progressDraft.pushPreambleHeadline(payload.progressText, { itemId: payload.itemId });
2305
+ if (progress.commentaryProgressEnabled) {
2306
+ if (await progress.progressDraft.pushCommentaryProgress(payload.progressText, { itemId: payload.itemId })) progress.progressReceipt.noteCommentary(payload.itemId, payload.progressText);
2154
2307
  }
2155
2308
  return;
2156
2309
  }
2157
- await pushPreviewProgress(buildChannelProgressDraftLineForEntry(account.config, {
2310
+ await progress.pushPreviewProgress(buildChannelProgressDraftLineForEntry(account.config, {
2158
2311
  event: "item",
2159
2312
  itemId: payload.itemId,
2160
2313
  toolCallId: payload.toolCallId,
@@ -2170,11 +2323,11 @@ async function dispatchPreparedSlackMessage(prepared) {
2170
2323
  },
2171
2324
  onPlanUpdate: async (payload) => {
2172
2325
  if (payload.phase !== "update") return;
2173
- await pushPlanProgress(payload.steps, payload.explanation);
2326
+ await progress.pushPlanProgress(payload.steps, payload.explanation);
2174
2327
  },
2175
2328
  onApprovalEvent: async (payload) => {
2176
2329
  if (payload.phase !== "requested") return;
2177
- await pushPreviewProgress(buildChannelProgressDraftLine({
2330
+ await progress.pushPreviewProgress(buildChannelProgressDraftLine({
2178
2331
  event: "approval",
2179
2332
  phase: payload.phase,
2180
2333
  title: payload.title,
@@ -2185,7 +2338,7 @@ async function dispatchPreparedSlackMessage(prepared) {
2185
2338
  },
2186
2339
  onCommandOutput: async (payload) => {
2187
2340
  if (payload.phase !== "end") return;
2188
- await pushPreviewProgress(buildChannelProgressDraftLine({
2341
+ await progress.pushPreviewProgress(buildChannelProgressDraftLine({
2189
2342
  event: "command-output",
2190
2343
  itemId: payload.itemId,
2191
2344
  toolCallId: payload.toolCallId,
@@ -2198,7 +2351,7 @@ async function dispatchPreparedSlackMessage(prepared) {
2198
2351
  },
2199
2352
  onPatchSummary: async (payload) => {
2200
2353
  if (payload.phase !== "end") return;
2201
- await pushPreviewProgress(buildChannelProgressDraftLine({
2354
+ await progress.pushPreviewProgress(buildChannelProgressDraftLine({
2202
2355
  event: "patch",
2203
2356
  itemId: payload.itemId,
2204
2357
  toolCallId: payload.toolCallId,
@@ -2221,79 +2374,59 @@ async function dispatchPreparedSlackMessage(prepared) {
2221
2374
  } catch (err) {
2222
2375
  dispatchError = err;
2223
2376
  } finally {
2224
- progressDraft.cancel();
2377
+ progress.progressDraft.cancel();
2225
2378
  await draftStream?.discardPending();
2226
2379
  }
2227
2380
  let streamFallbackDelivered = false;
2228
- const finalStream = streamSession;
2381
+ const finalStream = delivery.streamSession;
2229
2382
  if (finalStream && !finalStream.stopped) try {
2230
- const completionChunks = useNativeProgressStreaming && !nativeProgressCompletionSent ? buildNativeProgressCompletionChunks(dispatchError ? "error" : nativeProgressTerminalStatus) : void 0;
2231
- if (completionChunks?.length) nativeProgressCompletionSent = true;
2383
+ const completionChunks = progress.useNativeProgressStreaming && !progress.nativeProgressCompletionSent ? progress.buildNativeProgressCompletionChunks(dispatchError ? "error" : progress.nativeProgressTerminalStatus) : void 0;
2384
+ if (completionChunks?.length) progress.nativeProgressCompletionSent = true;
2232
2385
  const stopResult = await stopSlackStream({
2233
2386
  session: finalStream,
2234
2387
  ...completionChunks?.length ? { chunks: completionChunks } : {},
2235
2388
  ...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
2236
2389
  });
2237
- acknowledgeStoppedStreamedDeliveries(finalStream, stopResult?.messageId);
2238
- if (pendingNativeProgressReceipt && stopResult?.messageId && !progressReceiptCollapsed) await collapseProgressReceipt({
2390
+ delivery.acknowledgeStoppedStreamedDeliveries(finalStream, stopResult?.messageId);
2391
+ if (progress.pendingNativeProgressReceipt && stopResult?.messageId && !progress.progressReceiptCollapsed) await progress.collapseProgressReceipt({
2239
2392
  channelId: finalStream.channel,
2240
2393
  messageId: stopResult.messageId,
2241
- text: pendingNativeProgressReceipt,
2394
+ text: progress.pendingNativeProgressReceipt,
2242
2395
  threadTs: finalStream.threadTs
2243
2396
  });
2244
2397
  } catch (err) {
2245
2398
  if (err instanceof SlackStreamNotDeliveredError) {
2246
- streamFallbackDelivered = await deliverPendingStreamFallback(finalStream, err);
2399
+ streamFallbackDelivered = await delivery.deliverPendingStreamFallback(finalStream, err);
2247
2400
  if (!streamFallbackDelivered) dispatchError ??= err;
2248
2401
  } else {
2249
2402
  const error = formatSlackError(err);
2250
- emitAcknowledgedStreamedDeliveries();
2251
- emitFailedPendingStreamedDeliveries(error);
2403
+ delivery.emitAcknowledgedStreamedDeliveries();
2404
+ delivery.emitFailedPendingStreamedDeliveries(error);
2252
2405
  runtime.error?.(danger(`slack-stream: failed to stop stream: ${error}`));
2253
2406
  if (!finalStream.delivered) dispatchError ??= err;
2254
2407
  }
2255
2408
  }
2256
- for (const kind of [
2257
- "tool",
2258
- "block",
2259
- "final"
2260
- ]) {
2261
- const failedStreamedCount = streamedDeliveries.filter((delivery) => delivery.kind === kind && delivery.outcome === "failure").length;
2262
- const additionalFailedStreamed = Math.max(0, failedStreamedCount - streamedFailuresOwnedByDispatcher[kind]);
2263
- if (additionalFailedStreamed > 0) counts = {
2264
- ...counts,
2265
- [kind]: Math.max(0, (counts[kind] ?? 0) - additionalFailedStreamed)
2266
- };
2267
- }
2409
+ counts = delivery.reconcileCounts(counts);
2268
2410
  queuedFinal = queuedFinal && (counts.final ?? 0) > 0;
2269
2411
  const anyReplyDelivered = hasVisibleInboundReplyDispatch({
2270
2412
  queuedFinal,
2271
2413
  counts
2272
2414
  }, {
2273
- observedReplyDelivery,
2415
+ observedReplyDelivery: delivery.observedReplyDelivery,
2274
2416
  fallbackDelivered: streamFallbackDelivered
2275
2417
  });
2276
- if (statusReactionsEnabled) if (dispatchError) {
2277
- await statusReactions.setError();
2278
- if (ctx.removeAckAfterReply) (async () => {
2279
- await sleep(statusReactionTiming.errorHoldMs);
2280
- if (anyReplyDelivered) await statusReactions.clear();
2281
- })();
2282
- } else if (anyReplyDelivered) {
2418
+ if (statusReactionsEnabled) if (dispatchError) await statusReactions.setError();
2419
+ else if (anyReplyDelivered) {
2283
2420
  await statusReactions.setDone();
2284
- if (ctx.removeAckAfterReply) (async () => {
2285
- await sleep(statusReactionTiming.doneHoldMs);
2286
- await statusReactions.clear();
2287
- })();
2288
- else statusReactions.restoreInitial();
2421
+ statusReactions.restoreInitial();
2289
2422
  } else await statusReactions.restoreInitial();
2290
- const participationThreadTs = usedReplyThreadTs ?? statusThreadTs;
2423
+ const participationThreadTs = delivery.usedReplyThreadTs ?? statusThreadTs;
2291
2424
  if (anyReplyDelivered && participationThreadTs) recordSlackThreadParticipation(account.accountId, message.channel, participationThreadTs, {
2292
2425
  agentId: route.agentId,
2293
2426
  ...prepared.eventScope ? { teamId: prepared.eventScope.teamId } : {}
2294
2427
  });
2295
- if (dispatchError) throw toLintErrorObject(dispatchError, "Slack dispatch failed");
2296
- if (!anyReplyDelivered && !draftPreviewCommitted) {
2428
+ if (dispatchError) throw toErrorObject(dispatchError, "Slack dispatch failed");
2429
+ if (!anyReplyDelivered && !draftPreviewCommitted.value) {
2297
2430
  await draftStream?.clear();
2298
2431
  return;
2299
2432
  }
@@ -2301,30 +2434,51 @@ async function dispatchPreparedSlackMessage(prepared) {
2301
2434
  const finalCount = counts.final;
2302
2435
  logVerbose(`slack: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${prepared.replyTarget}`);
2303
2436
  }
2304
- if (!statusReactionsEnabled) removeAckReactionAfterReply({
2305
- removeAfterReply: ctx.removeAckAfterReply && anyReplyDelivered,
2306
- ackReactionPromise: prepared.ackReactionPromise,
2307
- ackReactionValue: prepared.ackReactionValue,
2308
- remove: () => removeSlackReaction(message.channel, prepared.ackReactionMessageTs ?? "", prepared.ackReactionValue, {
2309
- token: ctx.botToken,
2310
- client: slackClient
2311
- }),
2312
- onError: (err) => {
2313
- logAckFailure({
2314
- log: logVerbose,
2315
- channel: "slack",
2316
- target: `${message.channel}/${message.ts}`,
2317
- error: err
2318
- });
2319
- }
2320
- });
2321
2437
  }
2322
- function toLintErrorObject(value, fallbackMessage) {
2323
- if (value instanceof Error) return value;
2324
- if (typeof value === "string") return new Error(value);
2325
- const error = new Error(fallbackMessage, { cause: value });
2326
- if (typeof value === "object" && value !== null || typeof value === "function") Object.assign(error, value);
2327
- return error;
2438
+ //#endregion
2439
+ //#region extensions/slack/src/agent-context.ts
2440
+ function asRecord(value) {
2441
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2442
+ }
2443
+ function trimmedString(value) {
2444
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
2445
+ }
2446
+ function normalizeEntity(value) {
2447
+ const entity = asRecord(value);
2448
+ const type = trimmedString(entity?.type);
2449
+ if (!entity || !type) return;
2450
+ const teamId = trimmedString(entity.team_id);
2451
+ if (type === "slack#/types/channel_id" || type === "slack#/types/canvas_id" || type === "slack#/types/list_id") {
2452
+ const entityValue = trimmedString(entity.value);
2453
+ return entityValue ? {
2454
+ type,
2455
+ value: entityValue,
2456
+ ...teamId ? { team_id: teamId } : {}
2457
+ } : void 0;
2458
+ }
2459
+ if (type !== "slack#/types/message_context") return;
2460
+ const message = asRecord(entity.value);
2461
+ const channelId = trimmedString(message?.channel_id);
2462
+ const messageTs = trimmedString(message?.message_ts);
2463
+ return channelId && messageTs ? {
2464
+ type,
2465
+ value: {
2466
+ channel_id: channelId,
2467
+ message_ts: messageTs
2468
+ },
2469
+ ...teamId ? { team_id: teamId } : {}
2470
+ } : void 0;
2471
+ }
2472
+ function isSlackAppContext(value) {
2473
+ return Boolean(asRecord(value));
2474
+ }
2475
+ function normalizeSlackAppContextEntities(value) {
2476
+ const context = asRecord(value);
2477
+ if (!Array.isArray(context?.entities)) return [];
2478
+ return context.entities.flatMap((entity) => {
2479
+ const normalized = normalizeEntity(entity);
2480
+ return normalized ? [normalized] : [];
2481
+ });
2328
2482
  }
2329
2483
  //#endregion
2330
2484
  //#region extensions/slack/src/monitor/message-handler/preflight-audio.ts
@@ -2366,8 +2520,7 @@ async function resolveSlackPreflightAudioTranscript(params) {
2366
2520
  const { transcribeFirstAudio } = await loadSlackPreflightAudioRuntime();
2367
2521
  const transcript = await transcribeFirstAudio({
2368
2522
  ctx: {
2369
- MediaPaths: params.media.map((entry) => entry.path),
2370
- MediaTypes: params.media.map((entry) => entry.contentType ?? ""),
2523
+ media: [...params.media],
2371
2524
  Provider: "slack",
2372
2525
  Surface: "slack",
2373
2526
  OriginatingChannel: "slack",
@@ -2420,7 +2573,7 @@ async function discardSlackPreflightMedia(media) {
2420
2573
  const SLACK_MENTION_RESOLUTION_CONCURRENCY = 4;
2421
2574
  const SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE = 20;
2422
2575
  const SLACK_USER_MENTION_RE$1 = /<@([A-Z0-9]+)(?:\|[^>]+)?>/gi;
2423
- const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-CF0tUY5q.js").then((n) => n.g));
2576
+ const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-BkOaiDSX.js").then((n) => n.g));
2424
2577
  function collectUniqueSlackMentionIds$1(texts) {
2425
2578
  const seen = /* @__PURE__ */ new Set();
2426
2579
  const mentionIds = [];
@@ -2699,7 +2852,7 @@ function resolveSlackInitialAgentRoute(params) {
2699
2852
  };
2700
2853
  }
2701
2854
  function resolveSlackRoutingContext(params) {
2702
- const { ctx, account, message, isDirectMessage, isGroupDm, isRoom, isRoomish, channelConfig, seedTopLevelRoomThread, assistantThreadTs, eventScope } = params;
2855
+ const { ctx, account, message, isDirectMessage, isGroupDm, isRoom, isRoomish, channelConfig, seedTopLevelRoomThread, assistantThreadTs, agentViewThreadTs, eventScope } = params;
2703
2856
  let route = resolveSlackInitialAgentRoute({
2704
2857
  ctx,
2705
2858
  account,
@@ -2719,7 +2872,7 @@ function resolveSlackRoutingContext(params) {
2719
2872
  const isThreadReply = threadContext.isThreadReply;
2720
2873
  const autoThreadId = !isThreadReply && replyToMode === "all" && threadContext.messageTs ? threadContext.messageTs : void 0;
2721
2874
  const seedCandidateThreadId = threadContext.incomingThreadTs ?? threadContext.messageTs;
2722
- const routedThreadId = (isDirectMessage ? assistantThreadTs : isRoomish ? isThreadReply && threadTs ? threadTs : void 0 : isThreadReply ? threadTs : autoThreadId) ?? (isRoomish ? !isThreadReply && isRoom && seedTopLevelRoomThread && replyToMode !== "off" && seedCandidateThreadId ? seedCandidateThreadId : void 0 : void 0);
2875
+ const routedThreadId = (isDirectMessage ? assistantThreadTs ?? agentViewThreadTs : isRoomish ? isThreadReply && threadTs ? threadTs : void 0 : isThreadReply ? threadTs : autoThreadId) ?? (isRoomish ? !isThreadReply && isRoom && seedTopLevelRoomThread && replyToMode !== "off" && seedCandidateThreadId ? seedCandidateThreadId : void 0 : void 0);
2723
2876
  const baseConversationId = resolveSlackBaseConversationId({
2724
2877
  message,
2725
2878
  isDirectMessage,
@@ -2842,10 +2995,11 @@ function formatSlackThreadLabelSnippet(text) {
2842
2995
  }
2843
2996
  //#endregion
2844
2997
  //#region extensions/slack/src/monitor/message-handler/prepare-thread-context.ts
2845
- const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-CF0tUY5q.js").then((n) => n.g));
2998
+ const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-BkOaiDSX.js").then((n) => n.g));
2846
2999
  const SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY = 4;
2847
3000
  function resolveSlackThreadSessionFreshness(params) {
2848
3001
  return params.ctx.channelRuntime?.session?.resolveEntryResetFreshness?.({
3002
+ defaultAgentId: resolveDefaultAgentId(params.ctx.cfg),
2849
3003
  storePath: params.storePath,
2850
3004
  sessionKey: params.sessionKey,
2851
3005
  sessionCfg: params.ctx.cfg.session,
@@ -3495,6 +3649,14 @@ async function prepareSlackMessage(params) {
3495
3649
  const assistantThreadContext = mergeSlackAssistantThreadContext(messageAssistantThreadContext, cachedAssistantThreadContext ?? restoredAssistantThreadContext);
3496
3650
  const assistantThreadContextToCache = messageAssistantThreadContext || restoredAssistantThreadContext ? assistantThreadContext : void 0;
3497
3651
  if (assistantThreadContextToCache) ctx.saveSlackAssistantThreadContext(assistantThreadContextToCache, opts.eventScope);
3652
+ const hasAgentViewMessageSignal = isDirectMessage && !opts.eventScope && !assistantThreadContext && isSlackAppContext(message.app_context);
3653
+ if (hasAgentViewMessageSignal) await ctx.recordSlackAgentView();
3654
+ const managedViewThreadTs = message.thread_ts;
3655
+ const isManagedViewRoot = isDirectMessage && !opts.eventScope && !assistantThreadContext && !message.parent_user_id && Boolean(managedViewThreadTs && message.ts && managedViewThreadTs === message.ts);
3656
+ if (isManagedViewRoot && managedViewThreadTs) await ctx.recordSlackManagedViewThread(message.channel, managedViewThreadTs);
3657
+ const hasManagedViewThread = isDirectMessage && !opts.eventScope && !assistantThreadContext && Boolean(managedViewThreadTs && await ctx.isSlackManagedViewThread(message.channel, managedViewThreadTs));
3658
+ const isAgentViewMessage = isDirectMessage && !opts.eventScope && !assistantThreadContext && (hasAgentViewMessageSignal || isManagedViewRoot || hasManagedViewThread || await ctx.isSlackAgentView());
3659
+ const agentViewThreadTs = isAgentViewMessage ? message.thread_ts ?? message.ts : void 0;
3498
3660
  const channelReplyToMode = channelConfig?.replyToMode ?? resolveSlackReplyToMode(account, channelChatType);
3499
3661
  const willImplicitlyThreadReply = isRoom && !channelRequireMention && channelReplyToMode !== "off";
3500
3662
  const seedTopLevelRoomThreadBySource = opts.source === "app_mention" || opts.wasMentioned === true || explicitlyMentioned || willImplicitlyThreadReply;
@@ -3509,6 +3671,7 @@ async function prepareSlackMessage(params) {
3509
3671
  channelConfig,
3510
3672
  seedTopLevelRoomThread: seedTopLevelRoomThreadBySource,
3511
3673
  assistantThreadTs: assistantThreadContext?.threadTs,
3674
+ agentViewThreadTs,
3512
3675
  eventScope: opts.eventScope
3513
3676
  });
3514
3677
  let mentionCheckTranscript;
@@ -3534,6 +3697,7 @@ async function prepareSlackMessage(params) {
3534
3697
  const { configuredBinding, configuredBindingSessionKey } = routing;
3535
3698
  const isAssistantThreadMessage = Boolean(isDirectMessage && messageAssistantThreadContext);
3536
3699
  const forcedAssistantReplyThreadTs = Boolean(assistantThreadContext?.threadTs && (isThreadReply || isAssistantThreadMessage || replyToMode !== "off")) ? assistantThreadContext?.threadTs : void 0;
3700
+ const forcedReplyThreadTs = agentViewThreadTs ?? forcedAssistantReplyThreadTs;
3537
3701
  if (runtimeBinding && shouldLogVerbose()) logVerbose(`slack: routed via bound conversation ${runtimeBinding.conversation.conversationId} -> ${runtimeBinding.targetSessionKey}`);
3538
3702
  if (configuredBinding) {
3539
3703
  const ensured = await ensureConfiguredBindingRouteReady({
@@ -3744,6 +3908,7 @@ async function prepareSlackMessage(params) {
3744
3908
  channelConfig,
3745
3909
  seedTopLevelRoomThread: true,
3746
3910
  assistantThreadTs: assistantThreadContext?.threadTs,
3911
+ agentViewThreadTs,
3747
3912
  eventScope: opts.eventScope
3748
3913
  });
3749
3914
  return seededMentionRouting;
@@ -3795,7 +3960,7 @@ async function prepareSlackMessage(params) {
3795
3960
  preflightAudioTranscript = void 0;
3796
3961
  preflightAudioMedia = void 0;
3797
3962
  }
3798
- const directThreadRoutedToDmSession = !assistantThreadContext && isDirectMessage && isThreadReply && threadTs && runtimeBinding?.conversation.conversationId !== threadTs;
3963
+ const directThreadRoutedToDmSession = !assistantThreadContext && !agentViewThreadTs && isDirectMessage && isThreadReply && threadTs && runtimeBinding?.conversation.conversationId !== threadTs;
3799
3964
  const mentionDecision = resolveInboundMentionDecision({
3800
3965
  facts: {
3801
3966
  canDetectMention,
@@ -3956,7 +4121,7 @@ async function prepareSlackMessage(params) {
3956
4121
  previousTimestamp,
3957
4122
  envelope: envelopeOptions
3958
4123
  });
3959
- const dmHistoryContext = isDirectMessage && !isThreadReply && dmHistoryLimit > 0 && !previousTimestamp ? await resolveSlackDmHistoryContext({
4124
+ const dmHistoryContext = isDirectMessage && !assistantThreadContext && !agentViewThreadTs && !isThreadReply && dmHistoryLimit > 0 && !previousTimestamp ? await resolveSlackDmHistoryContext({
3960
4125
  ctx,
3961
4126
  channelId: message.channel,
3962
4127
  currentMessageTs: message.ts,
@@ -3983,7 +4148,7 @@ async function prepareSlackMessage(params) {
3983
4148
  })
3984
4149
  });
3985
4150
  const slackTo = isDirectMessage ? `user:${message.user}` : `channel:${message.channel}`;
3986
- const { untrustedChannelMetadata, groupSystemPrompt } = resolveSlackRoomContextHints({
4151
+ const { channelMetadata, groupSystemPrompt } = resolveSlackRoomContextHints({
3987
4152
  isRoomish,
3988
4153
  channelInfo,
3989
4154
  channelConfig
@@ -4014,7 +4179,8 @@ async function prepareSlackMessage(params) {
4014
4179
  }) : dmHistoryContext.inboundHistory;
4015
4180
  const commandBody = textForCommandDetection.trim();
4016
4181
  const supplementalThreadHistoryBody = directThreadRoutedToDmSession && !threadHistoryBody ? threadStarterBody : threadHistoryBody;
4017
- const effectiveMessageThreadId = assistantThreadContext?.threadTs ?? threadContext.messageThreadId;
4182
+ const effectiveMessageThreadId = assistantThreadContext?.threadTs ?? agentViewThreadTs ?? threadContext.messageThreadId;
4183
+ const agentContextEntities = isAgentViewMessage ? normalizeSlackAppContextEntities(message.app_context) : [];
4018
4184
  const ctxPayload = buildChannelInboundEventContext({
4019
4185
  channel: "slack",
4020
4186
  accountId: route.accountId,
@@ -4056,6 +4222,7 @@ async function prepareSlackMessage(params) {
4056
4222
  commandBody,
4057
4223
  inboundHistory
4058
4224
  },
4225
+ sessionTranscript: { historyLimit: isRoomish ? ctx.historyLimit : dmHistoryLimit },
4059
4226
  access: {
4060
4227
  mentions: {
4061
4228
  canDetectMention: isRoomish,
@@ -4078,9 +4245,16 @@ async function prepareSlackMessage(params) {
4078
4245
  },
4079
4246
  extra: {
4080
4247
  GroupSubject: isRoomish ? roomLabel : void 0,
4081
- UntrustedContext: untrustedChannelMetadata ? [untrustedChannelMetadata] : void 0,
4248
+ ChannelPromptContext: channelMetadata ? [channelMetadata] : void 0,
4249
+ ChannelStructuredContext: agentContextEntities.length > 0 ? [{
4250
+ label: "Slack active context",
4251
+ source: "slack",
4252
+ type: "active_view",
4253
+ payload: { entities: agentContextEntities }
4254
+ }] : void 0,
4082
4255
  TransportThreadId: directThreadRoutedToDmSession ? threadContext.messageThreadId : void 0,
4083
4256
  SlackAssistantThread: assistantThreadContext ? true : void 0,
4257
+ SlackAgentThread: agentViewThreadTs ? true : void 0,
4084
4258
  SlackAssistantThreadContextChannelId: assistantThreadContext?.channelId,
4085
4259
  SlackAssistantThreadContextTeamId: assistantThreadContext?.teamId,
4086
4260
  SlackAssistantThreadContextEnterpriseId: assistantThreadContext?.enterpriseId ?? void 0,
@@ -4170,7 +4344,7 @@ async function prepareSlackMessage(params) {
4170
4344
  } : void 0
4171
4345
  },
4172
4346
  replyToMode,
4173
- ...forcedAssistantReplyThreadTs ? { forcedReplyThreadTs: forcedAssistantReplyThreadTs } : {},
4347
+ ...forcedReplyThreadTs ? { forcedReplyThreadTs } : {},
4174
4348
  ...assistantThreadContext ? { slackMessageMetadata: buildSlackAssistantThreadMetadata(assistantThreadContext) } : {},
4175
4349
  requireMention: shouldRequireMention,
4176
4350
  isDirectMessage,