@openclaw/discord 2026.3.13 → 2026.5.2-beta.1

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 (502) hide show
  1. package/account-inspect-api.ts +6 -0
  2. package/action-runtime-api.ts +1 -0
  3. package/api.ts +132 -0
  4. package/channel-config-api.ts +1 -0
  5. package/channel-plugin-api.ts +3 -0
  6. package/config-api.ts +4 -0
  7. package/configured-state.ts +6 -0
  8. package/contract-api.ts +21 -0
  9. package/directory-contract-api.ts +4 -0
  10. package/doctor-contract-api.ts +1 -0
  11. package/index.test.ts +13 -0
  12. package/index.ts +18 -13
  13. package/openclaw.plugin.json +3326 -1
  14. package/package.json +68 -2
  15. package/runtime-api.actions.ts +15 -0
  16. package/runtime-api.lookup.ts +22 -0
  17. package/runtime-api.monitor.ts +50 -0
  18. package/runtime-api.send.ts +79 -0
  19. package/runtime-api.threads.ts +30 -0
  20. package/runtime-api.ts +180 -0
  21. package/runtime-setter-api.ts +3 -0
  22. package/secret-contract-api.ts +4 -0
  23. package/security-audit-contract-api.ts +1 -0
  24. package/security-contract-api.ts +4 -0
  25. package/session-key-api.ts +1 -0
  26. package/setup-entry.ts +9 -0
  27. package/setup-plugin-api.ts +3 -0
  28. package/src/account-inspect.test.ts +126 -0
  29. package/src/account-inspect.ts +132 -0
  30. package/src/accounts.test.ts +247 -0
  31. package/src/accounts.ts +196 -0
  32. package/src/actions/handle-action.guild-admin.ts +411 -0
  33. package/src/actions/handle-action.test.ts +306 -0
  34. package/src/actions/handle-action.ts +372 -0
  35. package/src/actions/runtime.guild.ts +446 -0
  36. package/src/actions/runtime.messaging.messages.ts +205 -0
  37. package/src/actions/runtime.messaging.reactions.ts +67 -0
  38. package/src/actions/runtime.messaging.runtime.ts +69 -0
  39. package/src/actions/runtime.messaging.send.ts +248 -0
  40. package/src/actions/runtime.messaging.shared.ts +97 -0
  41. package/src/actions/runtime.messaging.ts +37 -0
  42. package/src/actions/runtime.moderation-shared.ts +48 -0
  43. package/src/actions/runtime.moderation.authz.test.ts +151 -0
  44. package/src/actions/runtime.moderation.ts +116 -0
  45. package/src/actions/runtime.presence.test.ts +160 -0
  46. package/src/actions/runtime.presence.ts +117 -0
  47. package/src/actions/runtime.shared.ts +83 -0
  48. package/src/actions/runtime.test.ts +1087 -0
  49. package/src/actions/runtime.ts +87 -0
  50. package/src/api-barrel.test.ts +80 -0
  51. package/src/api.test.ts +130 -0
  52. package/src/api.ts +169 -0
  53. package/src/approval-handler.runtime.test.ts +41 -0
  54. package/src/approval-handler.runtime.ts +632 -0
  55. package/src/approval-native.test.ts +330 -0
  56. package/src/approval-native.ts +219 -0
  57. package/src/approval-runtime.ts +14 -0
  58. package/src/approval-shared.ts +53 -0
  59. package/src/audit-core.ts +141 -0
  60. package/src/audit.test.ts +145 -0
  61. package/src/audit.ts +32 -0
  62. package/src/channel-actions.contract.test.ts +45 -0
  63. package/src/channel-actions.runtime.ts +1 -0
  64. package/src/channel-actions.test.ts +275 -0
  65. package/src/channel-actions.ts +203 -0
  66. package/src/channel-api.ts +29 -0
  67. package/src/channel.conversation.ts +159 -0
  68. package/src/channel.loaders.ts +47 -0
  69. package/src/channel.runtime.ts +1 -0
  70. package/src/channel.setup.ts +12 -0
  71. package/src/channel.test.ts +547 -12
  72. package/src/channel.ts +597 -430
  73. package/src/chunk.test.ts +157 -0
  74. package/src/chunk.ts +321 -0
  75. package/src/client.proxy.test.ts +176 -0
  76. package/src/client.test.ts +76 -0
  77. package/src/client.ts +132 -0
  78. package/src/component-custom-id.ts +72 -0
  79. package/src/components-registry.ts +356 -0
  80. package/src/components.builders.ts +409 -0
  81. package/src/components.modal.ts +124 -0
  82. package/src/components.parse.ts +407 -0
  83. package/src/components.test.ts +312 -0
  84. package/src/components.ts +54 -0
  85. package/src/components.types.ts +187 -0
  86. package/src/config-schema.test.ts +325 -0
  87. package/src/config-schema.ts +6 -0
  88. package/src/config-ui-hints.ts +249 -0
  89. package/src/conversation-identity.ts +58 -0
  90. package/src/delivery-retry.ts +56 -0
  91. package/src/directory-cache.ts +116 -0
  92. package/src/directory-config.ts +58 -0
  93. package/src/directory-contract.test.ts +129 -0
  94. package/src/directory-live.test.ts +126 -0
  95. package/src/directory-live.ts +135 -0
  96. package/src/doctor-contract.ts +477 -0
  97. package/src/doctor-shared.ts +5 -0
  98. package/src/doctor.test.ts +405 -0
  99. package/src/doctor.ts +340 -0
  100. package/src/draft-chunking.test.ts +64 -0
  101. package/src/draft-chunking.ts +43 -0
  102. package/src/draft-stream.test.ts +159 -0
  103. package/src/draft-stream.ts +154 -0
  104. package/src/error-body.ts +38 -0
  105. package/src/exec-approvals.test.ts +88 -0
  106. package/src/exec-approvals.ts +110 -0
  107. package/src/gateway-logging.test.ts +98 -0
  108. package/src/gateway-logging.ts +67 -0
  109. package/src/group-policy.ts +113 -0
  110. package/src/guilds.ts +29 -0
  111. package/src/inbound-context.contract.test.ts +11 -0
  112. package/src/interactive-dispatch.ts +104 -0
  113. package/src/internal/api.commands.ts +51 -0
  114. package/src/internal/api.guild.ts +164 -0
  115. package/src/internal/api.interactions.ts +53 -0
  116. package/src/internal/api.messages.ts +113 -0
  117. package/src/internal/api.reactions.ts +38 -0
  118. package/src/internal/api.test.ts +262 -0
  119. package/src/internal/api.ts +61 -0
  120. package/src/internal/api.users.ts +19 -0
  121. package/src/internal/api.webhooks.ts +13 -0
  122. package/src/internal/client.test.ts +408 -0
  123. package/src/internal/client.ts +308 -0
  124. package/src/internal/command-deploy.ts +237 -0
  125. package/src/internal/commands.ts +188 -0
  126. package/src/internal/components.base.ts +65 -0
  127. package/src/internal/components.message.ts +279 -0
  128. package/src/internal/components.modal.ts +95 -0
  129. package/src/internal/components.ts +31 -0
  130. package/src/internal/discord.ts +11 -0
  131. package/src/internal/embeds.ts +35 -0
  132. package/src/internal/entity-cache.ts +98 -0
  133. package/src/internal/event-queue.ts +162 -0
  134. package/src/internal/gateway-close-codes.ts +25 -0
  135. package/src/internal/gateway-dispatch.ts +96 -0
  136. package/src/internal/gateway-identify-limiter.ts +26 -0
  137. package/src/internal/gateway-lifecycle.ts +61 -0
  138. package/src/internal/gateway-rate-limit.ts +104 -0
  139. package/src/internal/gateway.test.ts +603 -0
  140. package/src/internal/gateway.ts +476 -0
  141. package/src/internal/interaction-dispatch.test.ts +148 -0
  142. package/src/internal/interaction-dispatch.ts +162 -0
  143. package/src/internal/interaction-options.ts +98 -0
  144. package/src/internal/interaction-response.ts +53 -0
  145. package/src/internal/interactions.test.ts +325 -0
  146. package/src/internal/interactions.ts +378 -0
  147. package/src/internal/listeners.ts +85 -0
  148. package/src/internal/live-smoke.live.test.ts +26 -0
  149. package/src/internal/modal-fields.ts +95 -0
  150. package/src/internal/payload.ts +69 -0
  151. package/src/internal/rest-body.ts +115 -0
  152. package/src/internal/rest-errors.ts +88 -0
  153. package/src/internal/rest-routes.ts +50 -0
  154. package/src/internal/rest-scheduler.ts +557 -0
  155. package/src/internal/rest.test.ts +673 -0
  156. package/src/internal/rest.ts +322 -0
  157. package/src/internal/schemas.ts +36 -0
  158. package/src/internal/structures.test.ts +43 -0
  159. package/src/internal/structures.ts +280 -0
  160. package/src/internal/test-builders.test-support.ts +163 -0
  161. package/src/internal/voice.ts +49 -0
  162. package/src/media-detection.ts +28 -0
  163. package/src/mentions.test.ts +111 -0
  164. package/src/mentions.ts +147 -0
  165. package/src/monitor/access-groups.ts +55 -0
  166. package/src/monitor/ack-reactions.ts +70 -0
  167. package/src/monitor/acp-bind-here.integration.test.ts +211 -0
  168. package/src/monitor/agent-components-auth.ts +7 -0
  169. package/src/monitor/agent-components-context.ts +154 -0
  170. package/src/monitor/agent-components-data.ts +224 -0
  171. package/src/monitor/agent-components-dm-auth.ts +221 -0
  172. package/src/monitor/agent-components-guild-auth.ts +322 -0
  173. package/src/monitor/agent-components-helpers.runtime.ts +5 -0
  174. package/src/monitor/agent-components-helpers.ts +34 -0
  175. package/src/monitor/agent-components-reply.ts +10 -0
  176. package/src/monitor/agent-components.deps.runtime.ts +2 -0
  177. package/src/monitor/agent-components.dispatch.ts +366 -0
  178. package/src/monitor/agent-components.handlers.ts +303 -0
  179. package/src/monitor/agent-components.modal.ts +160 -0
  180. package/src/monitor/agent-components.plugin-interactive.ts +187 -0
  181. package/src/monitor/agent-components.runtime.ts +14 -0
  182. package/src/monitor/agent-components.system-controls.ts +211 -0
  183. package/src/monitor/agent-components.ts +70 -0
  184. package/src/monitor/agent-components.types.ts +58 -0
  185. package/src/monitor/agent-components.wildcard-controls.ts +168 -0
  186. package/src/monitor/agent-components.wildcard.test.ts +71 -0
  187. package/src/monitor/allow-list.test.ts +14 -0
  188. package/src/monitor/allow-list.ts +633 -0
  189. package/src/monitor/auto-presence.test.ts +156 -0
  190. package/src/monitor/auto-presence.ts +356 -0
  191. package/src/monitor/channel-access.test.ts +99 -0
  192. package/src/monitor/channel-access.ts +102 -0
  193. package/src/monitor/commands.test.ts +24 -0
  194. package/src/monitor/commands.ts +9 -0
  195. package/src/monitor/dm-command-auth.test.ts +197 -0
  196. package/src/monitor/dm-command-auth.ts +158 -0
  197. package/src/monitor/dm-command-decision.test.ts +113 -0
  198. package/src/monitor/dm-command-decision.ts +49 -0
  199. package/src/monitor/exec-approvals.test.ts +226 -0
  200. package/src/monitor/exec-approvals.ts +158 -0
  201. package/src/monitor/format.ts +45 -0
  202. package/src/monitor/gateway-handle.ts +34 -0
  203. package/src/monitor/gateway-metadata.test.ts +29 -0
  204. package/src/monitor/gateway-metadata.ts +298 -0
  205. package/src/monitor/gateway-plugin.test.ts +297 -0
  206. package/src/monitor/gateway-plugin.ts +294 -0
  207. package/src/monitor/gateway-registry.ts +37 -0
  208. package/src/monitor/gateway-supervisor.test.ts +150 -0
  209. package/src/monitor/gateway-supervisor.ts +206 -0
  210. package/src/monitor/inbound-context.test-helpers.ts +37 -0
  211. package/src/monitor/inbound-context.test.ts +106 -0
  212. package/src/monitor/inbound-context.ts +103 -0
  213. package/src/monitor/inbound-dedupe.ts +79 -0
  214. package/src/monitor/inbound-job.test.ts +203 -0
  215. package/src/monitor/inbound-job.ts +118 -0
  216. package/src/monitor/listeners.queue.ts +91 -0
  217. package/src/monitor/listeners.reactions.ts +610 -0
  218. package/src/monitor/listeners.test.ts +200 -0
  219. package/src/monitor/listeners.ts +150 -0
  220. package/src/monitor/message-channel-info.ts +96 -0
  221. package/src/monitor/message-forwarded.ts +107 -0
  222. package/src/monitor/message-handler.batch-gate.test.ts +22 -0
  223. package/src/monitor/message-handler.batch-gate.ts +19 -0
  224. package/src/monitor/message-handler.bot-self-filter.test.ts +68 -0
  225. package/src/monitor/message-handler.context.ts +406 -0
  226. package/src/monitor/message-handler.dm-preflight.ts +123 -0
  227. package/src/monitor/message-handler.draft-preview.ts +246 -0
  228. package/src/monitor/message-handler.hydration.test.ts +80 -0
  229. package/src/monitor/message-handler.hydration.ts +198 -0
  230. package/src/monitor/message-handler.inbound-context.test.ts +59 -0
  231. package/src/monitor/message-handler.module-test-helpers.ts +31 -0
  232. package/src/monitor/message-handler.preflight-channel-access.ts +86 -0
  233. package/src/monitor/message-handler.preflight-channel-context.test.ts +18 -0
  234. package/src/monitor/message-handler.preflight-channel-context.ts +58 -0
  235. package/src/monitor/message-handler.preflight-context.ts +54 -0
  236. package/src/monitor/message-handler.preflight-helpers.ts +164 -0
  237. package/src/monitor/message-handler.preflight-history.ts +23 -0
  238. package/src/monitor/message-handler.preflight-logging.ts +36 -0
  239. package/src/monitor/message-handler.preflight-pluralkit.ts +26 -0
  240. package/src/monitor/message-handler.preflight-runtime.ts +28 -0
  241. package/src/monitor/message-handler.preflight-thread.ts +49 -0
  242. package/src/monitor/message-handler.preflight.acp-bindings.test.ts +369 -0
  243. package/src/monitor/message-handler.preflight.test-helpers.ts +111 -0
  244. package/src/monitor/message-handler.preflight.test.ts +1623 -0
  245. package/src/monitor/message-handler.preflight.ts +679 -0
  246. package/src/monitor/message-handler.preflight.types.ts +110 -0
  247. package/src/monitor/message-handler.process.test.ts +1369 -0
  248. package/src/monitor/message-handler.process.ts +686 -0
  249. package/src/monitor/message-handler.queue.test.ts +496 -0
  250. package/src/monitor/message-handler.routing-preflight.ts +112 -0
  251. package/src/monitor/message-handler.test-harness.ts +99 -0
  252. package/src/monitor/message-handler.test-helpers.ts +75 -0
  253. package/src/monitor/message-handler.ts +274 -0
  254. package/src/monitor/message-media.ts +509 -0
  255. package/src/monitor/message-run-queue.ts +101 -0
  256. package/src/monitor/message-text.ts +171 -0
  257. package/src/monitor/message-utils.test.ts +1157 -0
  258. package/src/monitor/message-utils.ts +32 -0
  259. package/src/monitor/model-picker-preferences.test.ts +67 -0
  260. package/src/monitor/model-picker-preferences.ts +184 -0
  261. package/src/monitor/model-picker.state.ts +364 -0
  262. package/src/monitor/model-picker.test-utils.ts +26 -0
  263. package/src/monitor/model-picker.test.ts +794 -0
  264. package/src/monitor/model-picker.ts +38 -0
  265. package/src/monitor/model-picker.view.ts +695 -0
  266. package/src/monitor/monitor.agent-components.test.ts +375 -0
  267. package/src/monitor/monitor.test.ts +849 -0
  268. package/src/monitor/monitor.threading-utils.test.ts +598 -0
  269. package/src/monitor/native-command-agent-reply.ts +125 -0
  270. package/src/monitor/native-command-arg-ui.ts +233 -0
  271. package/src/monitor/native-command-auth.ts +308 -0
  272. package/src/monitor/native-command-bypass.ts +13 -0
  273. package/src/monitor/native-command-context.test.ts +98 -0
  274. package/src/monitor/native-command-context.ts +103 -0
  275. package/src/monitor/native-command-dispatch.ts +35 -0
  276. package/src/monitor/native-command-model-picker-apply.ts +177 -0
  277. package/src/monitor/native-command-model-picker-interaction.ts +461 -0
  278. package/src/monitor/native-command-model-picker-ui.ts +368 -0
  279. package/src/monitor/native-command-reply.test.ts +68 -0
  280. package/src/monitor/native-command-reply.ts +185 -0
  281. package/src/monitor/native-command-route.ts +91 -0
  282. package/src/monitor/native-command-status.ts +76 -0
  283. package/src/monitor/native-command-ui.ts +26 -0
  284. package/src/monitor/native-command-ui.types.ts +20 -0
  285. package/src/monitor/native-command.args.ts +45 -0
  286. package/src/monitor/native-command.command-arg.test.ts +99 -0
  287. package/src/monitor/native-command.commands-allowfrom.test.ts +490 -0
  288. package/src/monitor/native-command.model-picker.test.ts +767 -0
  289. package/src/monitor/native-command.options.test.ts +369 -0
  290. package/src/monitor/native-command.options.ts +153 -0
  291. package/src/monitor/native-command.plugin-dispatch.test.ts +961 -0
  292. package/src/monitor/native-command.runtime.ts +50 -0
  293. package/src/monitor/native-command.status-direct.test.ts +272 -0
  294. package/src/monitor/native-command.test-helpers.ts +64 -0
  295. package/src/monitor/native-command.think-autocomplete.test.ts +416 -0
  296. package/src/monitor/native-command.ts +700 -0
  297. package/src/monitor/native-command.types.ts +9 -0
  298. package/src/monitor/native-interaction-channel-context.ts +50 -0
  299. package/src/monitor/preflight-audio.runtime.ts +9 -0
  300. package/src/monitor/preflight-audio.test.ts +157 -0
  301. package/src/monitor/preflight-audio.ts +130 -0
  302. package/src/monitor/presence-cache.ts +61 -0
  303. package/src/monitor/presence.test.ts +44 -0
  304. package/src/monitor/presence.ts +50 -0
  305. package/src/monitor/provider-session.runtime.ts +12 -0
  306. package/src/monitor/provider.acp.ts +89 -0
  307. package/src/monitor/provider.allowlist.test.ts +149 -0
  308. package/src/monitor/provider.allowlist.ts +394 -0
  309. package/src/monitor/provider.cleanup.ts +41 -0
  310. package/src/monitor/provider.commands.ts +129 -0
  311. package/src/monitor/provider.config-log.ts +45 -0
  312. package/src/monitor/provider.deploy-errors.ts +362 -0
  313. package/src/monitor/provider.deploy.ts +221 -0
  314. package/src/monitor/provider.interactions.ts +160 -0
  315. package/src/monitor/provider.lifecycle.test.ts +713 -0
  316. package/src/monitor/provider.lifecycle.ts +552 -0
  317. package/src/monitor/provider.proxy.test.ts +745 -0
  318. package/src/monitor/provider.rest-proxy.test.ts +121 -0
  319. package/src/monitor/provider.runtime.ts +1 -0
  320. package/src/monitor/provider.skill-dedupe.test.ts +42 -0
  321. package/src/monitor/provider.startup-log.ts +32 -0
  322. package/src/monitor/provider.startup.test.ts +426 -0
  323. package/src/monitor/provider.startup.ts +323 -0
  324. package/src/monitor/provider.test.ts +1111 -0
  325. package/src/monitor/provider.ts +713 -0
  326. package/src/monitor/reply-context.ts +64 -0
  327. package/src/monitor/reply-delivery.test.ts +244 -0
  328. package/src/monitor/reply-delivery.ts +203 -0
  329. package/src/monitor/rest-fetch.ts +43 -0
  330. package/src/monitor/route-resolution.test.ts +204 -0
  331. package/src/monitor/route-resolution.ts +140 -0
  332. package/src/monitor/sender-identity.ts +81 -0
  333. package/src/monitor/startup-status.test.ts +30 -0
  334. package/src/monitor/startup-status.ts +10 -0
  335. package/src/monitor/status.ts +22 -0
  336. package/src/monitor/system-events.ts +55 -0
  337. package/src/monitor/thread-bindings.config.ts +35 -0
  338. package/src/monitor/thread-bindings.discord-api.test.ts +229 -0
  339. package/src/monitor/thread-bindings.discord-api.ts +310 -0
  340. package/src/monitor/thread-bindings.lifecycle.test.ts +1871 -0
  341. package/src/monitor/thread-bindings.lifecycle.ts +354 -0
  342. package/src/monitor/thread-bindings.manager.ts +553 -0
  343. package/src/monitor/thread-bindings.messages.ts +6 -0
  344. package/src/monitor/thread-bindings.persona.test.ts +34 -0
  345. package/src/monitor/thread-bindings.persona.ts +25 -0
  346. package/src/monitor/thread-bindings.session-adapter.ts +229 -0
  347. package/src/monitor/thread-bindings.session-shared.ts +59 -0
  348. package/src/monitor/thread-bindings.session-updates.ts +35 -0
  349. package/src/monitor/thread-bindings.shared-state.test.ts +36 -0
  350. package/src/monitor/thread-bindings.state.ts +540 -0
  351. package/src/monitor/thread-bindings.ts +48 -0
  352. package/src/monitor/thread-bindings.types.ts +83 -0
  353. package/src/monitor/thread-channel-context.ts +112 -0
  354. package/src/monitor/thread-session-close.test.ts +180 -0
  355. package/src/monitor/thread-session-close.ts +63 -0
  356. package/src/monitor/thread-title.generate.test.ts +197 -0
  357. package/src/monitor/thread-title.test.ts +31 -0
  358. package/src/monitor/thread-title.ts +181 -0
  359. package/src/monitor/threading.auto-thread.test.ts +327 -0
  360. package/src/monitor/threading.auto-thread.ts +287 -0
  361. package/src/monitor/threading.cache.ts +45 -0
  362. package/src/monitor/threading.parent-info.test.ts +156 -0
  363. package/src/monitor/threading.starter.test.ts +260 -0
  364. package/src/monitor/threading.starter.ts +287 -0
  365. package/src/monitor/threading.ts +20 -0
  366. package/src/monitor/threading.types.ts +102 -0
  367. package/src/monitor/timeouts.ts +84 -0
  368. package/src/monitor/typing.test.ts +42 -0
  369. package/src/monitor/typing.ts +17 -0
  370. package/src/monitor.gateway.test.ts +187 -0
  371. package/src/monitor.gateway.ts +75 -0
  372. package/src/monitor.test.ts +1397 -0
  373. package/src/monitor.ts +28 -0
  374. package/src/normalize.test.ts +56 -0
  375. package/src/normalize.ts +86 -0
  376. package/src/outbound-adapter.interactive-order.test.ts +64 -0
  377. package/src/outbound-adapter.test-harness.ts +207 -0
  378. package/src/outbound-adapter.test.ts +696 -0
  379. package/src/outbound-adapter.ts +291 -0
  380. package/src/outbound-approval.ts +29 -0
  381. package/src/outbound-components.ts +81 -0
  382. package/src/outbound-payload.contract.test.ts +38 -0
  383. package/src/outbound-payload.ts +134 -0
  384. package/src/outbound-send-context.ts +92 -0
  385. package/src/outbound-session-route.test.ts +34 -0
  386. package/src/outbound-session-route.ts +72 -0
  387. package/src/pluralkit.test.ts +67 -0
  388. package/src/pluralkit.ts +58 -0
  389. package/src/preview-streaming.ts +32 -0
  390. package/src/probe.intents.test.ts +94 -0
  391. package/src/probe.parse-token.test.ts +43 -0
  392. package/src/probe.runtime.ts +1 -0
  393. package/src/probe.ts +237 -0
  394. package/src/proxy-fetch.ts +92 -0
  395. package/src/proxy-request-client.test.ts +78 -0
  396. package/src/proxy-request-client.ts +21 -0
  397. package/src/recipient-resolution.ts +39 -0
  398. package/src/resolve-allowlist-common.test.ts +36 -0
  399. package/src/resolve-allowlist-common.ts +39 -0
  400. package/src/resolve-channels.test.ts +340 -0
  401. package/src/resolve-channels.ts +369 -0
  402. package/src/resolve-users.test.ts +222 -0
  403. package/src/resolve-users.ts +184 -0
  404. package/src/retry.test.ts +83 -0
  405. package/src/retry.ts +98 -0
  406. package/src/runtime-api.ts +64 -0
  407. package/src/runtime.ts +22 -5
  408. package/src/secret-config-contract.ts +140 -0
  409. package/src/security-audit.runtime.ts +1 -0
  410. package/src/security-audit.test.ts +246 -0
  411. package/src/security-audit.ts +208 -0
  412. package/src/security-contract.ts +47 -0
  413. package/src/security-doctor.test.ts +25 -0
  414. package/src/security-doctor.ts +20 -0
  415. package/src/security.ts +60 -0
  416. package/src/send-target-parsing.ts +14 -0
  417. package/src/send.channels.ts +139 -0
  418. package/src/send.components.test.ts +275 -0
  419. package/src/send.components.ts +381 -0
  420. package/src/send.creates-thread.test.ts +643 -0
  421. package/src/send.emojis-stickers.ts +57 -0
  422. package/src/send.guild.ts +170 -0
  423. package/src/send.message-request.ts +97 -0
  424. package/src/send.messages.test.ts +53 -0
  425. package/src/send.messages.ts +225 -0
  426. package/src/send.outbound.ts +413 -0
  427. package/src/send.permissions.authz.test.ts +188 -0
  428. package/src/send.permissions.ts +283 -0
  429. package/src/send.reactions.ts +155 -0
  430. package/src/send.sends-basic-channel-messages.test.ts +941 -0
  431. package/src/send.shared.ts +447 -0
  432. package/src/send.test-harness.ts +56 -0
  433. package/src/send.ts +82 -0
  434. package/src/send.types.ts +188 -0
  435. package/src/send.typing.test.ts +41 -0
  436. package/src/send.typing.ts +9 -0
  437. package/src/send.voice.ts +134 -0
  438. package/src/send.webhook-activity.test.ts +105 -0
  439. package/src/send.webhook.proxy.test.ts +191 -0
  440. package/src/send.webhook.ts +133 -0
  441. package/src/session-contract.ts +3 -0
  442. package/src/session-key-normalization.test.ts +44 -0
  443. package/src/session-key-normalization.ts +47 -0
  444. package/src/setup-account-state.test.ts +91 -0
  445. package/src/setup-account-state.ts +144 -0
  446. package/src/setup-adapter.ts +12 -0
  447. package/src/setup-core.ts +212 -0
  448. package/src/setup-runtime-helpers.ts +10 -0
  449. package/src/setup-surface.test.ts +137 -0
  450. package/src/setup-surface.ts +129 -0
  451. package/src/shared-interactive.test.ts +153 -0
  452. package/src/shared-interactive.ts +124 -0
  453. package/src/shared.test.ts +165 -0
  454. package/src/shared.ts +190 -0
  455. package/src/status-issues.test.ts +70 -0
  456. package/src/status-issues.ts +169 -0
  457. package/src/subagent-hooks.test.ts +130 -81
  458. package/src/subagent-hooks.ts +184 -122
  459. package/src/target-parsing.ts +53 -0
  460. package/src/target-resolver.ts +129 -0
  461. package/src/targets.test.ts +367 -0
  462. package/src/targets.ts +12 -0
  463. package/src/test-http-helpers.ts +10 -0
  464. package/src/test-support/component-runtime.ts +190 -0
  465. package/src/test-support/config.ts +7 -0
  466. package/src/test-support/configured-binding-runtime.ts +29 -0
  467. package/src/test-support/partial-channel.ts +26 -0
  468. package/src/test-support/provider.test-support.ts +545 -0
  469. package/src/token.test.ts +107 -0
  470. package/src/token.ts +60 -0
  471. package/src/ui-colors.ts +27 -0
  472. package/src/ui.ts +20 -0
  473. package/src/voice/access.test.ts +217 -0
  474. package/src/voice/access.ts +124 -0
  475. package/src/voice/audio.ts +173 -0
  476. package/src/voice/capture-state.test.ts +48 -0
  477. package/src/voice/capture-state.ts +120 -0
  478. package/src/voice/command.test.ts +164 -0
  479. package/src/voice/command.ts +283 -0
  480. package/src/voice/config.ts +8 -0
  481. package/src/voice/manager.e2e.test.ts +928 -0
  482. package/src/voice/manager.ready-listener.test.ts +37 -0
  483. package/src/voice/manager.runtime.ts +11 -0
  484. package/src/voice/manager.ts +691 -0
  485. package/src/voice/prompt.test.ts +16 -0
  486. package/src/voice/prompt.ts +17 -0
  487. package/src/voice/receive-recovery.test.ts +79 -0
  488. package/src/voice/receive-recovery.ts +159 -0
  489. package/src/voice/sanitize.test.ts +34 -0
  490. package/src/voice/sanitize.ts +32 -0
  491. package/src/voice/sdk-runtime.ts +14 -0
  492. package/src/voice/segment.ts +156 -0
  493. package/src/voice/session.ts +50 -0
  494. package/src/voice/speaker-context.ts +127 -0
  495. package/src/voice/tts.ts +125 -0
  496. package/src/voice-message.test.ts +234 -0
  497. package/src/voice-message.ts +444 -0
  498. package/subagent-hooks-api.ts +27 -0
  499. package/test-api.ts +4 -0
  500. package/thread-binding-api.ts +1 -0
  501. package/timeouts.ts +6 -0
  502. package/tsconfig.json +16 -0
@@ -0,0 +1,368 @@
1
+ import { resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
2
+ import {
3
+ resolveStoredModelOverride,
4
+ serializeCommandArgs,
5
+ type ChatCommandDefinition,
6
+ type CommandArgs,
7
+ } from "openclaw/plugin-sdk/command-auth";
8
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
9
+ import type { ResolvedAgentRoute } from "openclaw/plugin-sdk/routing";
10
+ import { loadSessionStore, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
11
+ import {
12
+ normalizeLowercaseStringOrEmpty,
13
+ normalizeOptionalString,
14
+ } from "openclaw/plugin-sdk/text-runtime";
15
+ import {
16
+ Container,
17
+ TextDisplay,
18
+ type AutocompleteInteraction,
19
+ type ButtonInteraction,
20
+ type CommandInteraction,
21
+ type StringSelectMenuInteraction,
22
+ } from "../internal/discord.js";
23
+ import {
24
+ readDiscordModelPickerRecentModels,
25
+ type DiscordModelPickerPreferenceScope,
26
+ } from "./model-picker-preferences.js";
27
+ import {
28
+ loadDiscordModelPickerData,
29
+ renderDiscordModelPickerModelsView,
30
+ toDiscordModelPickerMessagePayload,
31
+ type DiscordModelPickerCommandContext,
32
+ } from "./model-picker.js";
33
+ import { resolveDiscordNativeInteractionRouteState } from "./native-command-route.js";
34
+ import type { SafeDiscordInteractionCall } from "./native-command-ui.types.js";
35
+ import { resolveDiscordNativeInteractionChannelContext } from "./native-interaction-channel-context.js";
36
+ import type { ThreadBindingManager } from "./thread-bindings.js";
37
+
38
+ type DiscordNativeChoiceInteraction =
39
+ | AutocompleteInteraction
40
+ | CommandInteraction
41
+ | ButtonInteraction
42
+ | StringSelectMenuInteraction;
43
+
44
+ function resolveDiscordModelPickerCommandContext(
45
+ command: ChatCommandDefinition,
46
+ ): DiscordModelPickerCommandContext | null {
47
+ const normalized = normalizeLowercaseStringOrEmpty(command.nativeName ?? command.key);
48
+ if (normalized === "model" || normalized === "models") {
49
+ return normalized;
50
+ }
51
+ return null;
52
+ }
53
+
54
+ function resolveCommandArgStringValue(args: CommandArgs | undefined, key: string): string {
55
+ const value = args?.values?.[key];
56
+ if (typeof value !== "string") {
57
+ return "";
58
+ }
59
+ return value.trim();
60
+ }
61
+
62
+ export function shouldOpenDiscordModelPickerFromCommand(params: {
63
+ command: ChatCommandDefinition;
64
+ commandArgs?: CommandArgs;
65
+ }): DiscordModelPickerCommandContext | null {
66
+ const context = resolveDiscordModelPickerCommandContext(params.command);
67
+ if (!context) {
68
+ return null;
69
+ }
70
+
71
+ const serializedArgs =
72
+ normalizeOptionalString(serializeCommandArgs(params.command, params.commandArgs)) ?? "";
73
+ if (context === "model") {
74
+ const modelValue = resolveCommandArgStringValue(params.commandArgs, "model");
75
+ return !modelValue && !serializedArgs ? context : null;
76
+ }
77
+
78
+ return serializedArgs ? null : context;
79
+ }
80
+
81
+ function buildDiscordModelPickerCurrentModel(
82
+ defaultProvider: string,
83
+ defaultModel: string,
84
+ ): string {
85
+ return `${defaultProvider}/${defaultModel}`;
86
+ }
87
+
88
+ function resolveConfiguredAgentRuntimeId(value: {
89
+ agentRuntime?: { id?: unknown };
90
+ }): string | undefined {
91
+ return normalizeOptionalString(value.agentRuntime?.id);
92
+ }
93
+
94
+ export function buildDiscordModelPickerAllowedModelRefs(
95
+ data: Awaited<ReturnType<typeof loadDiscordModelPickerData>>,
96
+ ): Set<string> {
97
+ const out = new Set<string>();
98
+ for (const provider of data.providers) {
99
+ const models = data.byProvider.get(provider);
100
+ if (!models) {
101
+ continue;
102
+ }
103
+ for (const model of models) {
104
+ out.add(`${provider}/${model}`);
105
+ }
106
+ }
107
+ return out;
108
+ }
109
+
110
+ export function resolveDiscordModelPickerPreferenceScope(params: {
111
+ interaction: CommandInteraction | ButtonInteraction | StringSelectMenuInteraction;
112
+ accountId: string;
113
+ userId: string;
114
+ }): DiscordModelPickerPreferenceScope {
115
+ return {
116
+ accountId: params.accountId,
117
+ guildId: params.interaction.guild?.id ?? undefined,
118
+ userId: params.userId,
119
+ };
120
+ }
121
+
122
+ export function buildDiscordModelPickerNoticePayload(message: string): { components: Container[] } {
123
+ return {
124
+ components: [new Container([new TextDisplay(message)])],
125
+ };
126
+ }
127
+
128
+ async function resolveDiscordModelPickerRouteState(params: {
129
+ interaction:
130
+ | CommandInteraction
131
+ | ButtonInteraction
132
+ | StringSelectMenuInteraction
133
+ | AutocompleteInteraction;
134
+ cfg: OpenClawConfig;
135
+ accountId: string;
136
+ threadBindings: ThreadBindingManager;
137
+ enforceConfiguredBindingReadiness?: boolean;
138
+ }) {
139
+ const { interaction, cfg, accountId } = params;
140
+ const { isDirectMessage, isGroupDm, isThreadChannel, rawChannelId, threadParentId } =
141
+ await resolveDiscordNativeInteractionChannelContext({
142
+ channel: interaction.channel,
143
+ client: interaction.client,
144
+ hasGuild: Boolean(interaction.guild),
145
+ channelIdFallback: "unknown",
146
+ });
147
+ const memberRoleIds = Array.isArray(interaction.rawData.member?.roles)
148
+ ? interaction.rawData.member.roles.map((roleId: string) => roleId)
149
+ : [];
150
+
151
+ const threadBinding = isThreadChannel
152
+ ? params.threadBindings.getByThreadId(rawChannelId)
153
+ : undefined;
154
+ return await resolveDiscordNativeInteractionRouteState({
155
+ cfg,
156
+ accountId,
157
+ guildId: interaction.guild?.id ?? undefined,
158
+ memberRoleIds,
159
+ isDirectMessage,
160
+ isGroupDm,
161
+ directUserId: interaction.user?.id ?? rawChannelId,
162
+ conversationId: rawChannelId,
163
+ parentConversationId: threadParentId,
164
+ threadBinding,
165
+ enforceConfiguredBindingReadiness: params.enforceConfiguredBindingReadiness,
166
+ });
167
+ }
168
+
169
+ export async function resolveDiscordModelPickerRoute(params: {
170
+ interaction:
171
+ | CommandInteraction
172
+ | ButtonInteraction
173
+ | StringSelectMenuInteraction
174
+ | AutocompleteInteraction;
175
+ cfg: OpenClawConfig;
176
+ accountId: string;
177
+ threadBindings: ThreadBindingManager;
178
+ }) {
179
+ const resolved = await resolveDiscordModelPickerRouteState(params);
180
+ return resolved.effectiveRoute;
181
+ }
182
+
183
+ export async function resolveDiscordNativeChoiceContext(params: {
184
+ interaction: DiscordNativeChoiceInteraction;
185
+ cfg: OpenClawConfig;
186
+ accountId: string;
187
+ threadBindings: ThreadBindingManager;
188
+ }): Promise<{ provider?: string; model?: string } | null> {
189
+ try {
190
+ const resolved = await resolveDiscordModelPickerRouteState({
191
+ interaction: params.interaction,
192
+ cfg: params.cfg,
193
+ accountId: params.accountId,
194
+ threadBindings: params.threadBindings,
195
+ enforceConfiguredBindingReadiness: true,
196
+ });
197
+ if (resolved.bindingReadiness && !resolved.bindingReadiness.ok) {
198
+ return null;
199
+ }
200
+ const route = resolved.effectiveRoute;
201
+ const fallback = resolveDefaultModelForAgent({
202
+ cfg: params.cfg,
203
+ agentId: route.agentId,
204
+ });
205
+ const storePath = resolveStorePath(params.cfg.session?.store, {
206
+ agentId: route.agentId,
207
+ });
208
+ const sessionStore = loadSessionStore(storePath);
209
+ const sessionEntry = sessionStore[route.sessionKey];
210
+ const override = resolveStoredModelOverride({
211
+ sessionEntry,
212
+ sessionStore,
213
+ sessionKey: route.sessionKey,
214
+ defaultProvider: fallback.provider,
215
+ });
216
+ if (!override?.model) {
217
+ return {
218
+ provider: fallback.provider,
219
+ model: fallback.model,
220
+ };
221
+ }
222
+ return {
223
+ provider: override.provider || fallback.provider,
224
+ model: override.model,
225
+ };
226
+ } catch {
227
+ return null;
228
+ }
229
+ }
230
+
231
+ export function resolveDiscordModelPickerCurrentModel(params: {
232
+ cfg: OpenClawConfig;
233
+ route: ResolvedAgentRoute;
234
+ data: Awaited<ReturnType<typeof loadDiscordModelPickerData>>;
235
+ }): string {
236
+ const fallback = buildDiscordModelPickerCurrentModel(
237
+ params.data.resolvedDefault.provider,
238
+ params.data.resolvedDefault.model,
239
+ );
240
+ try {
241
+ const storePath = resolveStorePath(params.cfg.session?.store, {
242
+ agentId: params.route.agentId,
243
+ });
244
+ const sessionStore = loadSessionStore(storePath, { skipCache: true });
245
+ const sessionEntry = sessionStore[params.route.sessionKey];
246
+ const override = resolveStoredModelOverride({
247
+ sessionEntry,
248
+ sessionStore,
249
+ sessionKey: params.route.sessionKey,
250
+ defaultProvider: params.data.resolvedDefault.provider,
251
+ });
252
+ if (!override?.model) {
253
+ return fallback;
254
+ }
255
+ const provider = (override.provider || params.data.resolvedDefault.provider).trim();
256
+ if (!provider) {
257
+ return fallback;
258
+ }
259
+ return `${provider}/${override.model}`;
260
+ } catch {
261
+ return fallback;
262
+ }
263
+ }
264
+
265
+ export function resolveDiscordModelPickerCurrentRuntime(params: {
266
+ cfg: OpenClawConfig;
267
+ route: ResolvedAgentRoute;
268
+ }): string {
269
+ try {
270
+ const storePath = resolveStorePath(params.cfg.session?.store, {
271
+ agentId: params.route.agentId,
272
+ });
273
+ const sessionStore = loadSessionStore(storePath, { skipCache: true });
274
+ const sessionRuntime = normalizeOptionalString(
275
+ sessionStore[params.route.sessionKey]?.agentRuntimeOverride,
276
+ );
277
+ if (sessionRuntime) {
278
+ return sessionRuntime;
279
+ }
280
+ } catch {
281
+ // Fall through to configured defaults when the session store is unavailable.
282
+ }
283
+
284
+ const agentRuntime = resolveConfiguredAgentRuntimeId(
285
+ params.cfg.agents?.list?.find(
286
+ (entry) => normalizeOptionalString(entry.id) === params.route.agentId,
287
+ ) ?? {},
288
+ );
289
+ if (agentRuntime) {
290
+ return agentRuntime;
291
+ }
292
+ return resolveConfiguredAgentRuntimeId(params.cfg.agents?.defaults ?? {}) ?? "auto";
293
+ }
294
+
295
+ export async function replyWithDiscordModelPickerProviders(params: {
296
+ interaction: CommandInteraction | ButtonInteraction | StringSelectMenuInteraction;
297
+ cfg: OpenClawConfig;
298
+ command: DiscordModelPickerCommandContext;
299
+ userId: string;
300
+ accountId: string;
301
+ threadBindings: ThreadBindingManager;
302
+ preferFollowUp: boolean;
303
+ safeInteractionCall: SafeDiscordInteractionCall;
304
+ }) {
305
+ const route = await resolveDiscordModelPickerRoute({
306
+ interaction: params.interaction,
307
+ cfg: params.cfg,
308
+ accountId: params.accountId,
309
+ threadBindings: params.threadBindings,
310
+ });
311
+ const data = await loadDiscordModelPickerData(params.cfg, route.agentId);
312
+ const currentModel = resolveDiscordModelPickerCurrentModel({
313
+ cfg: params.cfg,
314
+ route,
315
+ data,
316
+ });
317
+ const currentRuntime = resolveDiscordModelPickerCurrentRuntime({
318
+ cfg: params.cfg,
319
+ route,
320
+ });
321
+ const quickModels = await readDiscordModelPickerRecentModels({
322
+ scope: resolveDiscordModelPickerPreferenceScope({
323
+ interaction: params.interaction,
324
+ accountId: params.accountId,
325
+ userId: params.userId,
326
+ }),
327
+ allowedModelRefs: buildDiscordModelPickerAllowedModelRefs(data),
328
+ limit: 5,
329
+ });
330
+
331
+ const rendered = renderDiscordModelPickerModelsView({
332
+ command: params.command,
333
+ userId: params.userId,
334
+ data,
335
+ provider: splitDiscordModelRef(currentModel ?? "")?.provider ?? data.resolvedDefault.provider,
336
+ page: 1,
337
+ providerPage: 1,
338
+ currentModel,
339
+ currentRuntime,
340
+ quickModels,
341
+ });
342
+ const payload = {
343
+ ...toDiscordModelPickerMessagePayload(rendered),
344
+ ephemeral: true,
345
+ };
346
+
347
+ await params.safeInteractionCall("model picker reply", async () => {
348
+ if (params.preferFollowUp) {
349
+ await params.interaction.followUp(payload);
350
+ return;
351
+ }
352
+ await params.interaction.reply(payload);
353
+ });
354
+ }
355
+
356
+ export function splitDiscordModelRef(modelRef: string): { provider: string; model: string } | null {
357
+ const trimmed = modelRef.trim();
358
+ const slashIndex = trimmed.indexOf("/");
359
+ if (slashIndex <= 0 || slashIndex >= trimmed.length - 1) {
360
+ return null;
361
+ }
362
+ const provider = trimmed.slice(0, slashIndex).trim();
363
+ const model = trimmed.slice(slashIndex + 1).trim();
364
+ if (!provider || !model) {
365
+ return null;
366
+ }
367
+ return { provider, model };
368
+ }
@@ -0,0 +1,68 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { Container, TextDisplay } from "../internal/discord.js";
3
+ import {
4
+ deliverDiscordInteractionReply,
5
+ hasRenderableReplyPayload,
6
+ } from "./native-command-reply.js";
7
+
8
+ function createInteraction() {
9
+ return {
10
+ reply: vi.fn().mockResolvedValue({ ok: true }),
11
+ followUp: vi.fn().mockResolvedValue({ ok: true }),
12
+ };
13
+ }
14
+
15
+ describe("deliverDiscordInteractionReply", () => {
16
+ it("sends component-only native command replies as follow-ups", async () => {
17
+ const interaction = createInteraction();
18
+ const components = [new Container([new TextDisplay("Pick a model")])];
19
+ const payload = {
20
+ channelData: {
21
+ discord: {
22
+ components,
23
+ },
24
+ },
25
+ };
26
+
27
+ expect(hasRenderableReplyPayload(payload)).toBe(true);
28
+
29
+ await deliverDiscordInteractionReply({
30
+ interaction: interaction as never,
31
+ payload,
32
+ textLimit: 2000,
33
+ preferFollowUp: true,
34
+ responseEphemeral: true,
35
+ chunkMode: "length",
36
+ });
37
+
38
+ expect(interaction.followUp).toHaveBeenCalledWith({
39
+ components,
40
+ ephemeral: true,
41
+ });
42
+ expect(interaction.reply).not.toHaveBeenCalled();
43
+ });
44
+
45
+ it("sends component-only native command replies through the initial reply when not deferred", async () => {
46
+ const interaction = createInteraction();
47
+ const components = [new Container([new TextDisplay("Choose an action")])];
48
+
49
+ await deliverDiscordInteractionReply({
50
+ interaction: interaction as never,
51
+ payload: {
52
+ channelData: {
53
+ discord: {
54
+ components,
55
+ },
56
+ },
57
+ },
58
+ textLimit: 2000,
59
+ preferFollowUp: false,
60
+ chunkMode: "length",
61
+ });
62
+
63
+ expect(interaction.reply).toHaveBeenCalledWith({
64
+ components,
65
+ });
66
+ expect(interaction.followUp).not.toHaveBeenCalled();
67
+ });
68
+ });
@@ -0,0 +1,185 @@
1
+ import type { ReplyPayload } from "openclaw/plugin-sdk/reply-dispatch-runtime";
2
+ import {
3
+ resolveSendableOutboundReplyParts,
4
+ resolveTextChunksWithFallback,
5
+ } from "openclaw/plugin-sdk/reply-payload";
6
+ import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
7
+ import { loadWebMedia } from "openclaw/plugin-sdk/web-media";
8
+ import { chunkDiscordTextWithMode } from "../chunk.js";
9
+ import type {
10
+ ButtonInteraction,
11
+ CommandInteraction,
12
+ StringSelectMenuInteraction,
13
+ TopLevelComponents,
14
+ } from "../internal/discord.js";
15
+
16
+ export const DISCORD_EMPTY_VISIBLE_REPLY_WARNING = "⚠️ Command produced no visible reply.";
17
+
18
+ export function isDiscordUnknownInteraction(error: unknown): boolean {
19
+ if (!error || typeof error !== "object") {
20
+ return false;
21
+ }
22
+ const err = error as {
23
+ discordCode?: number;
24
+ status?: number;
25
+ message?: string;
26
+ rawBody?: { code?: number; message?: string };
27
+ };
28
+ if (err.discordCode === 10062 || err.rawBody?.code === 10062) {
29
+ return true;
30
+ }
31
+ if (err.status === 404 && /Unknown interaction/i.test(err.message ?? "")) {
32
+ return true;
33
+ }
34
+ if (/Unknown interaction/i.test(err.rawBody?.message ?? "")) {
35
+ return true;
36
+ }
37
+ return false;
38
+ }
39
+
40
+ export function hasRenderableReplyPayload(payload: ReplyPayload): boolean {
41
+ if (resolveSendableOutboundReplyParts(payload).hasContent) {
42
+ return true;
43
+ }
44
+ const discordData = payload.channelData?.discord as
45
+ | { components?: TopLevelComponents[] }
46
+ | undefined;
47
+ if (Array.isArray(discordData?.components) && discordData.components.length > 0) {
48
+ return true;
49
+ }
50
+ return false;
51
+ }
52
+
53
+ export async function safeDiscordInteractionCall<T>(
54
+ label: string,
55
+ fn: () => Promise<T>,
56
+ ): Promise<T | null> {
57
+ try {
58
+ return await fn();
59
+ } catch (error) {
60
+ if (isDiscordUnknownInteraction(error)) {
61
+ logVerbose(`discord: ${label} skipped (interaction expired)`);
62
+ return null;
63
+ }
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ export async function deliverDiscordInteractionReply(params: {
69
+ interaction: CommandInteraction | ButtonInteraction | StringSelectMenuInteraction;
70
+ payload: ReplyPayload;
71
+ mediaLocalRoots?: readonly string[];
72
+ textLimit: number;
73
+ maxLinesPerMessage?: number;
74
+ preferFollowUp: boolean;
75
+ responseEphemeral?: boolean;
76
+ chunkMode: "length" | "newline";
77
+ }) {
78
+ const { interaction, payload, textLimit, maxLinesPerMessage, preferFollowUp, chunkMode } = params;
79
+ const reply = resolveSendableOutboundReplyParts(payload);
80
+ const discordData = payload.channelData?.discord as
81
+ | { components?: TopLevelComponents[] }
82
+ | undefined;
83
+ let firstMessageComponents =
84
+ Array.isArray(discordData?.components) && discordData.components.length > 0
85
+ ? discordData.components
86
+ : undefined;
87
+
88
+ let hasReplied = false;
89
+ const sendMessage = async (
90
+ content: string,
91
+ files?: { name: string; data: Buffer }[],
92
+ components?: TopLevelComponents[],
93
+ ) => {
94
+ const contentPayload = content ? { content } : {};
95
+ const payload =
96
+ files && files.length > 0
97
+ ? {
98
+ ...contentPayload,
99
+ ...(components ? { components } : {}),
100
+ ...(params.responseEphemeral !== undefined
101
+ ? { ephemeral: params.responseEphemeral }
102
+ : {}),
103
+ files: files.map((file) => {
104
+ if (file.data instanceof Blob) {
105
+ return { name: file.name, data: file.data };
106
+ }
107
+ const arrayBuffer = Uint8Array.from(file.data).buffer;
108
+ return { name: file.name, data: new Blob([arrayBuffer]) };
109
+ }),
110
+ }
111
+ : {
112
+ ...contentPayload,
113
+ ...(components ? { components } : {}),
114
+ ...(params.responseEphemeral !== undefined
115
+ ? { ephemeral: params.responseEphemeral }
116
+ : {}),
117
+ };
118
+ await safeDiscordInteractionCall("interaction send", async () => {
119
+ if (!preferFollowUp && !hasReplied) {
120
+ await interaction.reply(payload);
121
+ hasReplied = true;
122
+ firstMessageComponents = undefined;
123
+ return;
124
+ }
125
+ await interaction.followUp(payload);
126
+ hasReplied = true;
127
+ firstMessageComponents = undefined;
128
+ });
129
+ };
130
+
131
+ if (reply.hasMedia) {
132
+ const media = await Promise.all(
133
+ reply.mediaUrls.map(async (url) => {
134
+ const loaded = await loadWebMedia(url, {
135
+ localRoots: params.mediaLocalRoots,
136
+ });
137
+ return {
138
+ name: loaded.fileName ?? "upload",
139
+ data: loaded.buffer,
140
+ };
141
+ }),
142
+ );
143
+ const chunks = resolveTextChunksWithFallback(
144
+ reply.text,
145
+ chunkDiscordTextWithMode(reply.text, {
146
+ maxChars: textLimit,
147
+ maxLines: maxLinesPerMessage,
148
+ chunkMode,
149
+ }),
150
+ );
151
+ const caption = chunks[0] ?? "";
152
+ await sendMessage(caption, media, firstMessageComponents);
153
+ for (const chunk of chunks.slice(1)) {
154
+ if (!chunk.trim()) {
155
+ continue;
156
+ }
157
+ await sendMessage(chunk);
158
+ }
159
+ return;
160
+ }
161
+
162
+ if (!reply.hasText && !firstMessageComponents) {
163
+ return;
164
+ }
165
+ let chunks =
166
+ reply.text || firstMessageComponents
167
+ ? resolveTextChunksWithFallback(
168
+ reply.text,
169
+ chunkDiscordTextWithMode(reply.text, {
170
+ maxChars: textLimit,
171
+ maxLines: maxLinesPerMessage,
172
+ chunkMode,
173
+ }),
174
+ )
175
+ : [];
176
+ if (chunks.length === 0 && firstMessageComponents) {
177
+ chunks = [""];
178
+ }
179
+ for (const chunk of chunks) {
180
+ if (!chunk.trim() && !firstMessageComponents) {
181
+ continue;
182
+ }
183
+ await sendMessage(chunk, undefined, firstMessageComponents);
184
+ }
185
+ }
@@ -0,0 +1,91 @@
1
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
2
+ import * as conversationRuntime from "openclaw/plugin-sdk/conversation-binding-runtime";
3
+ import type { ResolvedAgentRoute } from "openclaw/plugin-sdk/routing";
4
+ import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
5
+ import {
6
+ resolveDiscordBoundConversationRoute,
7
+ resolveDiscordEffectiveRoute,
8
+ } from "./route-resolution.js";
9
+ import type { ThreadBindingRecord } from "./thread-bindings.js";
10
+
11
+ type ResolvedConfiguredBindingRoute = ReturnType<
12
+ typeof conversationRuntime.resolveConfiguredBindingRoute
13
+ >;
14
+ type ConfiguredBindingResolution = NonNullable<
15
+ NonNullable<ResolvedConfiguredBindingRoute>["bindingResolution"]
16
+ >;
17
+
18
+ type DiscordNativeInteractionRouteState = {
19
+ route: ResolvedAgentRoute;
20
+ effectiveRoute: ResolvedAgentRoute;
21
+ boundSessionKey?: string;
22
+ configuredRoute: ResolvedConfiguredBindingRoute | null;
23
+ configuredBinding: ConfiguredBindingResolution | null;
24
+ bindingReadiness: Awaited<
25
+ ReturnType<typeof conversationRuntime.ensureConfiguredBindingRouteReady>
26
+ > | null;
27
+ };
28
+
29
+ export async function resolveDiscordNativeInteractionRouteState(params: {
30
+ cfg: OpenClawConfig;
31
+ accountId: string;
32
+ guildId?: string;
33
+ memberRoleIds?: string[];
34
+ isDirectMessage: boolean;
35
+ isGroupDm: boolean;
36
+ directUserId?: string;
37
+ conversationId: string;
38
+ parentConversationId?: string;
39
+ threadBinding?: ThreadBindingRecord;
40
+ enforceConfiguredBindingReadiness?: boolean;
41
+ }): Promise<DiscordNativeInteractionRouteState> {
42
+ const route = resolveDiscordBoundConversationRoute({
43
+ cfg: params.cfg,
44
+ accountId: params.accountId,
45
+ guildId: params.guildId,
46
+ memberRoleIds: params.memberRoleIds,
47
+ isDirectMessage: params.isDirectMessage,
48
+ isGroupDm: params.isGroupDm,
49
+ directUserId: params.directUserId,
50
+ conversationId: params.conversationId,
51
+ parentConversationId: params.parentConversationId,
52
+ });
53
+ const configuredRoute =
54
+ params.threadBinding == null
55
+ ? conversationRuntime.resolveConfiguredBindingRoute({
56
+ cfg: params.cfg,
57
+ route,
58
+ conversation: {
59
+ channel: "discord",
60
+ accountId: params.accountId,
61
+ conversationId: params.conversationId,
62
+ parentConversationId: params.parentConversationId,
63
+ },
64
+ })
65
+ : null;
66
+ const configuredBinding = configuredRoute?.bindingResolution ?? null;
67
+ const configuredBoundSessionKey = normalizeOptionalString(configuredRoute?.boundSessionKey);
68
+ const boundSessionKey =
69
+ normalizeOptionalString(params.threadBinding?.targetSessionKey) ?? configuredBoundSessionKey;
70
+ const effectiveRoute = resolveDiscordEffectiveRoute({
71
+ route,
72
+ boundSessionKey,
73
+ configuredRoute,
74
+ matchedBy: configuredBinding ? "binding.channel" : undefined,
75
+ });
76
+ const bindingReadiness =
77
+ params.enforceConfiguredBindingReadiness && configuredBinding
78
+ ? await conversationRuntime.ensureConfiguredBindingRouteReady({
79
+ cfg: params.cfg,
80
+ bindingResolution: configuredBinding,
81
+ })
82
+ : null;
83
+ return {
84
+ route,
85
+ effectiveRoute,
86
+ boundSessionKey,
87
+ configuredRoute,
88
+ configuredBinding,
89
+ bindingReadiness,
90
+ };
91
+ }