@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,72 @@
1
+ import { buildThreadAwareOutboundSessionRoute } from "openclaw/plugin-sdk/channel-core";
2
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
3
+ import { buildOutboundBaseSessionKey, type RoutePeer } from "openclaw/plugin-sdk/routing";
4
+ import { parseDiscordTarget } from "./target-parsing.js";
5
+
6
+ export type ResolveDiscordOutboundSessionRouteParams = {
7
+ cfg: OpenClawConfig;
8
+ agentId: string;
9
+ accountId?: string | null;
10
+ target: string;
11
+ resolvedTarget?: { kind: string };
12
+ replyToId?: string | null;
13
+ threadId?: string | number | null;
14
+ };
15
+
16
+ export function resolveDiscordOutboundSessionRoute(
17
+ params: ResolveDiscordOutboundSessionRouteParams,
18
+ ) {
19
+ const parsed = parseDiscordTarget(params.target, {
20
+ defaultKind: resolveDiscordOutboundTargetKindHint(params),
21
+ });
22
+ if (!parsed) {
23
+ return null;
24
+ }
25
+ const isDm = parsed.kind === "user";
26
+ const peer: RoutePeer = {
27
+ kind: isDm ? "direct" : "channel",
28
+ id: parsed.id,
29
+ };
30
+ const baseSessionKey = buildOutboundBaseSessionKey({
31
+ cfg: params.cfg,
32
+ agentId: params.agentId,
33
+ channel: "discord",
34
+ accountId: params.accountId,
35
+ peer,
36
+ });
37
+ return buildThreadAwareOutboundSessionRoute({
38
+ route: {
39
+ sessionKey: baseSessionKey,
40
+ baseSessionKey,
41
+ peer,
42
+ chatType: isDm ? ("direct" as const) : ("channel" as const),
43
+ from: isDm ? `discord:${parsed.id}` : `discord:channel:${parsed.id}`,
44
+ to: isDm ? `user:${parsed.id}` : `channel:${parsed.id}`,
45
+ },
46
+ threadId: params.threadId,
47
+ precedence: ["threadId"],
48
+ useSuffix: false,
49
+ });
50
+ }
51
+
52
+ function resolveDiscordOutboundTargetKindHint(params: {
53
+ target: string;
54
+ resolvedTarget?: { kind: string };
55
+ }): "user" | "channel" | undefined {
56
+ const resolvedKind = params.resolvedTarget?.kind;
57
+ if (resolvedKind === "user") {
58
+ return "user";
59
+ }
60
+ if (resolvedKind === "group" || resolvedKind === "channel") {
61
+ return "channel";
62
+ }
63
+
64
+ const target = params.target.trim();
65
+ if (/^channel:/i.test(target)) {
66
+ return "channel";
67
+ }
68
+ if (/^(user:|discord:|@|<@!?)/i.test(target)) {
69
+ return "user";
70
+ }
71
+ return undefined;
72
+ }
@@ -0,0 +1,67 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { fetchPluralKitMessageInfo } from "./pluralkit.js";
3
+
4
+ type MockResponse = {
5
+ status: number;
6
+ ok: boolean;
7
+ text: () => Promise<string>;
8
+ json: () => Promise<unknown>;
9
+ };
10
+
11
+ const buildResponse = (params: { status: number; body?: unknown }): MockResponse => {
12
+ const body = params.body;
13
+ const textPayload = typeof body === "string" ? body : body == null ? "" : JSON.stringify(body);
14
+ return {
15
+ status: params.status,
16
+ ok: params.status >= 200 && params.status < 300,
17
+ text: async () => textPayload,
18
+ json: async () => body ?? {},
19
+ };
20
+ };
21
+
22
+ describe("fetchPluralKitMessageInfo", () => {
23
+ it("returns null when disabled", async () => {
24
+ const fetcher = vi.fn();
25
+ const result = await fetchPluralKitMessageInfo({
26
+ messageId: "123",
27
+ config: { enabled: false },
28
+ fetcher: fetcher as unknown as typeof fetch,
29
+ });
30
+ expect(result).toBeNull();
31
+ expect(fetcher).not.toHaveBeenCalled();
32
+ });
33
+
34
+ it("returns null on 404", async () => {
35
+ const fetcher = vi.fn(async () => buildResponse({ status: 404 }));
36
+ const result = await fetchPluralKitMessageInfo({
37
+ messageId: "missing",
38
+ config: { enabled: true },
39
+ fetcher: fetcher as unknown as typeof fetch,
40
+ });
41
+ expect(result).toBeNull();
42
+ });
43
+
44
+ it("returns payload and sends token when configured", async () => {
45
+ let receivedHeaders: Record<string, string> | undefined;
46
+ const fetcher = vi.fn(async (_url: string, init?: RequestInit) => {
47
+ receivedHeaders = init?.headers as Record<string, string> | undefined;
48
+ return buildResponse({
49
+ status: 200,
50
+ body: {
51
+ id: "123",
52
+ member: { id: "mem_1", name: "Alex" },
53
+ system: { id: "sys_1", name: "System" },
54
+ },
55
+ });
56
+ });
57
+
58
+ const result = await fetchPluralKitMessageInfo({
59
+ messageId: "123",
60
+ config: { enabled: true, token: "pk_test" },
61
+ fetcher: fetcher as unknown as typeof fetch,
62
+ });
63
+
64
+ expect(result?.member?.id).toBe("mem_1");
65
+ expect(receivedHeaders?.Authorization).toBe("pk_test");
66
+ });
67
+ });
@@ -0,0 +1,58 @@
1
+ import { resolveFetch } from "openclaw/plugin-sdk/fetch-runtime";
2
+
3
+ const PLURALKIT_API_BASE = "https://api.pluralkit.me/v2";
4
+
5
+ export type DiscordPluralKitConfig = {
6
+ enabled?: boolean;
7
+ token?: string;
8
+ };
9
+
10
+ export type PluralKitSystemInfo = {
11
+ id: string;
12
+ name?: string | null;
13
+ tag?: string | null;
14
+ };
15
+
16
+ export type PluralKitMemberInfo = {
17
+ id: string;
18
+ name?: string | null;
19
+ display_name?: string | null;
20
+ };
21
+
22
+ export type PluralKitMessageInfo = {
23
+ id: string;
24
+ original?: string | null;
25
+ sender?: string | null;
26
+ system?: PluralKitSystemInfo | null;
27
+ member?: PluralKitMemberInfo | null;
28
+ };
29
+
30
+ export async function fetchPluralKitMessageInfo(params: {
31
+ messageId: string;
32
+ config?: DiscordPluralKitConfig;
33
+ fetcher?: typeof fetch;
34
+ }): Promise<PluralKitMessageInfo | null> {
35
+ if (!params.config?.enabled) {
36
+ return null;
37
+ }
38
+ const fetchImpl = resolveFetch(params.fetcher);
39
+ if (!fetchImpl) {
40
+ return null;
41
+ }
42
+ const headers: Record<string, string> = {};
43
+ if (params.config.token?.trim()) {
44
+ headers.Authorization = params.config.token.trim();
45
+ }
46
+ const res = await fetchImpl(`${PLURALKIT_API_BASE}/messages/${params.messageId}`, {
47
+ headers,
48
+ });
49
+ if (res.status === 404) {
50
+ return null;
51
+ }
52
+ if (!res.ok) {
53
+ const text = await res.text().catch(() => "");
54
+ const detail = text.trim() ? `: ${text.trim()}` : "";
55
+ throw new Error(`PluralKit API failed (${res.status})${detail}`);
56
+ }
57
+ return (await res.json()) as PluralKitMessageInfo;
58
+ }
@@ -0,0 +1,32 @@
1
+ type DiscordPreviewStreamMode = "off" | "partial" | "block";
2
+
3
+ function parsePreviewStreamingMode(value: unknown): DiscordPreviewStreamMode | undefined {
4
+ return value === "off" || value === "partial" || value === "block" ? value : undefined;
5
+ }
6
+
7
+ export function resolveDiscordPreviewStreamMode(
8
+ params: {
9
+ streamMode?: unknown;
10
+ streaming?: unknown;
11
+ } = {},
12
+ ): DiscordPreviewStreamMode {
13
+ const parsedStreaming =
14
+ params.streaming && typeof params.streaming === "object" && !Array.isArray(params.streaming)
15
+ ? parsePreviewStreamingMode(
16
+ (params.streaming as Record<string, unknown>).mode ??
17
+ (params.streaming as Record<string, unknown>).streaming,
18
+ )
19
+ : parsePreviewStreamingMode(params.streaming);
20
+ if (parsedStreaming) {
21
+ return parsedStreaming;
22
+ }
23
+
24
+ const legacy = parsePreviewStreamingMode(params.streamMode);
25
+ if (legacy) {
26
+ return legacy;
27
+ }
28
+ if (typeof params.streaming === "boolean") {
29
+ return params.streaming ? "partial" : "off";
30
+ }
31
+ return "off";
32
+ }
@@ -0,0 +1,94 @@
1
+ import { withFetchPreconnect } from "openclaw/plugin-sdk/test-env";
2
+ import { describe, expect, it } from "vitest";
3
+ import {
4
+ fetchDiscordApplicationId,
5
+ fetchDiscordApplicationSummary,
6
+ resolveDiscordPrivilegedIntentsFromFlags,
7
+ } from "./probe.js";
8
+ import { jsonResponse } from "./test-http-helpers.js";
9
+
10
+ describe("resolveDiscordPrivilegedIntentsFromFlags", () => {
11
+ it("reports disabled when no bits set", () => {
12
+ expect(resolveDiscordPrivilegedIntentsFromFlags(0)).toEqual({
13
+ presence: "disabled",
14
+ guildMembers: "disabled",
15
+ messageContent: "disabled",
16
+ });
17
+ });
18
+
19
+ it("reports enabled when full intent bits set", () => {
20
+ const flags = (1 << 12) | (1 << 14) | (1 << 18);
21
+ expect(resolveDiscordPrivilegedIntentsFromFlags(flags)).toEqual({
22
+ presence: "enabled",
23
+ guildMembers: "enabled",
24
+ messageContent: "enabled",
25
+ });
26
+ });
27
+
28
+ it("reports limited when limited intent bits set", () => {
29
+ const flags = (1 << 13) | (1 << 15) | (1 << 19);
30
+ expect(resolveDiscordPrivilegedIntentsFromFlags(flags)).toEqual({
31
+ presence: "limited",
32
+ guildMembers: "limited",
33
+ messageContent: "limited",
34
+ });
35
+ });
36
+
37
+ it("prefers enabled over limited when both set", () => {
38
+ const flags = (1 << 12) | (1 << 13) | (1 << 14) | (1 << 15) | (1 << 18) | (1 << 19);
39
+ expect(resolveDiscordPrivilegedIntentsFromFlags(flags)).toEqual({
40
+ presence: "enabled",
41
+ guildMembers: "enabled",
42
+ messageContent: "enabled",
43
+ });
44
+ });
45
+
46
+ it("retries Cloudflare HTML rate limits during application id lookup", async () => {
47
+ let calls = 0;
48
+ const fetcher = withFetchPreconnect(async () => {
49
+ calls += 1;
50
+ if (calls === 1) {
51
+ return new Response("<html><title>Error 1015</title></html>", {
52
+ status: 429,
53
+ headers: { "content-type": "text/html", "retry-after": "0" },
54
+ });
55
+ }
56
+ return jsonResponse({ id: "app-1" });
57
+ });
58
+
59
+ await expect(fetchDiscordApplicationId("unparseable.token", 1_000, fetcher)).resolves.toBe(
60
+ "app-1",
61
+ );
62
+ expect(calls).toBe(2);
63
+ });
64
+
65
+ it("does not retry Cloudflare HTML rate limits during application summary probes", async () => {
66
+ let calls = 0;
67
+ const fetcher = withFetchPreconnect(async () => {
68
+ calls += 1;
69
+ return new Response("<html><title>Error 1015</title></html>", {
70
+ status: 429,
71
+ headers: { "content-type": "text/html" },
72
+ });
73
+ });
74
+
75
+ await expect(
76
+ fetchDiscordApplicationSummary("unparseable.token", 1_000, fetcher),
77
+ ).resolves.toBeUndefined();
78
+ expect(calls).toBe(1);
79
+ });
80
+
81
+ it("derives application id from parseable tokens before probing REST", async () => {
82
+ let calls = 0;
83
+ const fetcher = withFetchPreconnect(async () => {
84
+ calls += 1;
85
+ return new Response("<html><title>Error 1015</title></html>", {
86
+ status: 429,
87
+ headers: { "content-type": "text/html" },
88
+ });
89
+ });
90
+
91
+ await expect(fetchDiscordApplicationId("MTIz.abc.def", 1_000, fetcher)).resolves.toBe("123");
92
+ expect(calls).toBe(0);
93
+ });
94
+ });
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { parseApplicationIdFromToken } from "./probe.js";
3
+
4
+ describe("parseApplicationIdFromToken", () => {
5
+ it("extracts application ID from a valid token", () => {
6
+ // "1234567890" base64-encoded is "MTIzNDU2Nzg5MA=="
7
+ const token = `${Buffer.from("1234567890").toString("base64")}.timestamp.hmac`;
8
+ expect(parseApplicationIdFromToken(token)).toBe("1234567890");
9
+ });
10
+
11
+ it("extracts large snowflake IDs without precision loss", () => {
12
+ // ID that exceeds Number.MAX_SAFE_INTEGER (2^53 - 1 = 9007199254740991)
13
+ const largeId = "1477179610322964541";
14
+ const token = `${Buffer.from(largeId).toString("base64")}.GhIiP9.vU1xEpJ6NjFm`;
15
+ expect(parseApplicationIdFromToken(token)).toBe(largeId);
16
+ });
17
+
18
+ it("handles tokens with Bot prefix", () => {
19
+ const token = `Bot ${Buffer.from("9876543210").toString("base64")}.ts.hmac`;
20
+ expect(parseApplicationIdFromToken(token)).toBe("9876543210");
21
+ });
22
+
23
+ it("returns undefined for empty string", () => {
24
+ expect(parseApplicationIdFromToken("")).toBeUndefined();
25
+ });
26
+
27
+ it("returns undefined for token without dots", () => {
28
+ expect(parseApplicationIdFromToken("nodots")).toBeUndefined();
29
+ });
30
+
31
+ it("returns undefined when decoded segment is not numeric", () => {
32
+ const token = `${Buffer.from("not-a-number").toString("base64")}.ts.hmac`;
33
+ expect(parseApplicationIdFromToken(token)).toBeUndefined();
34
+ });
35
+
36
+ it("returns undefined for whitespace-only input", () => {
37
+ expect(parseApplicationIdFromToken(" ")).toBeUndefined();
38
+ });
39
+
40
+ it("returns undefined when first segment is empty (starts with dot)", () => {
41
+ expect(parseApplicationIdFromToken(".ts.hmac")).toBeUndefined();
42
+ });
43
+ });
@@ -0,0 +1 @@
1
+ export * from "./probe.js";
package/src/probe.ts ADDED
@@ -0,0 +1,237 @@
1
+ import type { BaseProbeResult } from "openclaw/plugin-sdk/channel-contract";
2
+ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
3
+ import { resolveFetch } from "openclaw/plugin-sdk/fetch-runtime";
4
+ import { fetchWithTimeout } from "openclaw/plugin-sdk/text-runtime";
5
+ import { DiscordApiError, fetchDiscord } from "./api.js";
6
+ import { normalizeDiscordToken } from "./token.js";
7
+
8
+ const DISCORD_API_BASE = "https://discord.com/api/v10";
9
+
10
+ export type DiscordProbe = BaseProbeResult & {
11
+ status?: number | null;
12
+ elapsedMs: number;
13
+ bot?: { id?: string | null; username?: string | null };
14
+ application?: DiscordApplicationSummary;
15
+ };
16
+
17
+ export type DiscordPrivilegedIntentStatus = "enabled" | "limited" | "disabled";
18
+
19
+ export type DiscordPrivilegedIntentsSummary = {
20
+ messageContent: DiscordPrivilegedIntentStatus;
21
+ guildMembers: DiscordPrivilegedIntentStatus;
22
+ presence: DiscordPrivilegedIntentStatus;
23
+ };
24
+
25
+ export type DiscordApplicationSummary = {
26
+ id?: string | null;
27
+ flags?: number | null;
28
+ intents?: DiscordPrivilegedIntentsSummary;
29
+ };
30
+
31
+ const DISCORD_APP_FLAG_GATEWAY_PRESENCE = 1 << 12;
32
+ const DISCORD_APP_FLAG_GATEWAY_PRESENCE_LIMITED = 1 << 13;
33
+ const DISCORD_APP_FLAG_GATEWAY_GUILD_MEMBERS = 1 << 14;
34
+ const DISCORD_APP_FLAG_GATEWAY_GUILD_MEMBERS_LIMITED = 1 << 15;
35
+ const DISCORD_APP_FLAG_GATEWAY_MESSAGE_CONTENT = 1 << 18;
36
+ const DISCORD_APP_FLAG_GATEWAY_MESSAGE_CONTENT_LIMITED = 1 << 19;
37
+
38
+ async function fetchDiscordApplicationMe(
39
+ token: string,
40
+ timeoutMs: number,
41
+ fetcher: typeof fetch,
42
+ ): Promise<{ id?: string; flags?: number } | undefined> {
43
+ try {
44
+ const normalized = normalizeDiscordToken(token, "channels.discord.token");
45
+ if (!normalized) {
46
+ return undefined;
47
+ }
48
+ return await fetchDiscord<{ id?: string; flags?: number }>(
49
+ "/oauth2/applications/@me",
50
+ normalized,
51
+ createDiscordTimeoutFetch(fetcher, timeoutMs),
52
+ { retry: { attempts: 1 } },
53
+ );
54
+ } catch {
55
+ return undefined;
56
+ }
57
+ }
58
+
59
+ function createDiscordTimeoutFetch(fetcher: typeof fetch, timeoutMs: number): typeof fetch {
60
+ const fetchImpl = getResolvedFetch(fetcher);
61
+ return ((input: RequestInfo | URL, init?: RequestInit) =>
62
+ fetchWithTimeout(
63
+ typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url,
64
+ init ?? {},
65
+ timeoutMs,
66
+ fetchImpl,
67
+ )) as typeof fetch;
68
+ }
69
+
70
+ export function resolveDiscordPrivilegedIntentsFromFlags(
71
+ flags: number,
72
+ ): DiscordPrivilegedIntentsSummary {
73
+ const resolve = (enabledBit: number, limitedBit: number) => {
74
+ if ((flags & enabledBit) !== 0) {
75
+ return "enabled";
76
+ }
77
+ if ((flags & limitedBit) !== 0) {
78
+ return "limited";
79
+ }
80
+ return "disabled";
81
+ };
82
+ return {
83
+ presence: resolve(DISCORD_APP_FLAG_GATEWAY_PRESENCE, DISCORD_APP_FLAG_GATEWAY_PRESENCE_LIMITED),
84
+ guildMembers: resolve(
85
+ DISCORD_APP_FLAG_GATEWAY_GUILD_MEMBERS,
86
+ DISCORD_APP_FLAG_GATEWAY_GUILD_MEMBERS_LIMITED,
87
+ ),
88
+ messageContent: resolve(
89
+ DISCORD_APP_FLAG_GATEWAY_MESSAGE_CONTENT,
90
+ DISCORD_APP_FLAG_GATEWAY_MESSAGE_CONTENT_LIMITED,
91
+ ),
92
+ };
93
+ }
94
+
95
+ export async function fetchDiscordApplicationSummary(
96
+ token: string,
97
+ timeoutMs: number,
98
+ fetcher: typeof fetch = fetch,
99
+ ): Promise<DiscordApplicationSummary | undefined> {
100
+ const json = await fetchDiscordApplicationMe(token, timeoutMs, fetcher);
101
+ if (!json) {
102
+ return undefined;
103
+ }
104
+ const flags =
105
+ typeof json.flags === "number" && Number.isFinite(json.flags) ? json.flags : undefined;
106
+ return {
107
+ id: json.id ?? null,
108
+ flags: flags ?? null,
109
+ intents:
110
+ typeof flags === "number" ? resolveDiscordPrivilegedIntentsFromFlags(flags) : undefined,
111
+ };
112
+ }
113
+
114
+ function getResolvedFetch(fetcher: typeof fetch): typeof fetch {
115
+ const fetchImpl = resolveFetch(fetcher);
116
+ if (!fetchImpl) {
117
+ throw new Error("fetch is not available");
118
+ }
119
+ return fetchImpl;
120
+ }
121
+
122
+ export async function probeDiscord(
123
+ token: string,
124
+ timeoutMs: number,
125
+ opts?: { fetcher?: typeof fetch; includeApplication?: boolean },
126
+ ): Promise<DiscordProbe> {
127
+ const started = Date.now();
128
+ const fetcher = opts?.fetcher ?? fetch;
129
+ const includeApplication = opts?.includeApplication === true;
130
+ const normalized = normalizeDiscordToken(token, "channels.discord.token");
131
+ const result: DiscordProbe = {
132
+ ok: false,
133
+ status: null,
134
+ error: null,
135
+ elapsedMs: 0,
136
+ };
137
+ if (!normalized) {
138
+ return {
139
+ ...result,
140
+ error: "missing token",
141
+ elapsedMs: Date.now() - started,
142
+ };
143
+ }
144
+ try {
145
+ const res = await fetchWithTimeout(
146
+ `${DISCORD_API_BASE}/users/@me`,
147
+ { headers: { Authorization: `Bot ${normalized}` } },
148
+ timeoutMs,
149
+ getResolvedFetch(fetcher),
150
+ );
151
+ if (!res.ok) {
152
+ result.status = res.status;
153
+ result.error = `getMe failed (${res.status})`;
154
+ return { ...result, elapsedMs: Date.now() - started };
155
+ }
156
+ const json = (await res.json()) as { id?: string; username?: string };
157
+ result.ok = true;
158
+ result.bot = {
159
+ id: json.id ?? null,
160
+ username: json.username ?? null,
161
+ };
162
+ if (includeApplication) {
163
+ result.application =
164
+ (await fetchDiscordApplicationSummary(normalized, timeoutMs, fetcher)) ?? undefined;
165
+ }
166
+ return { ...result, elapsedMs: Date.now() - started };
167
+ } catch (err) {
168
+ return {
169
+ ...result,
170
+ status: err instanceof Response ? err.status : result.status,
171
+ error: formatErrorMessage(err),
172
+ elapsedMs: Date.now() - started,
173
+ };
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Extract the application (bot user) ID from a Discord bot token by
179
+ * base64-decoding the first segment. Discord tokens have the format:
180
+ * base64(user_id) . timestamp . hmac
181
+ * The decoded first segment is the numeric snowflake ID as a plain string,
182
+ * so we keep it as a string to avoid precision loss for IDs that exceed
183
+ * Number.MAX_SAFE_INTEGER.
184
+ */
185
+ export function parseApplicationIdFromToken(token: string): string | undefined {
186
+ const normalized = normalizeDiscordToken(token, "channels.discord.token");
187
+ if (!normalized) {
188
+ return undefined;
189
+ }
190
+ const firstDot = normalized.indexOf(".");
191
+ if (firstDot <= 0) {
192
+ return undefined;
193
+ }
194
+ try {
195
+ const decoded = Buffer.from(normalized.slice(0, firstDot), "base64").toString("utf-8");
196
+ if (/^\d+$/.test(decoded)) {
197
+ return decoded;
198
+ }
199
+ return undefined;
200
+ } catch {
201
+ return undefined;
202
+ }
203
+ }
204
+
205
+ export async function fetchDiscordApplicationId(
206
+ token: string,
207
+ timeoutMs: number,
208
+ fetcher: typeof fetch = fetch,
209
+ ): Promise<string | undefined> {
210
+ const normalized = normalizeDiscordToken(token, "channels.discord.token");
211
+ if (!normalized) {
212
+ return undefined;
213
+ }
214
+ const parsedApplicationId = parseApplicationIdFromToken(token);
215
+ if (parsedApplicationId) {
216
+ return parsedApplicationId;
217
+ }
218
+ try {
219
+ const json = await fetchDiscord<{ id?: string }>(
220
+ "/oauth2/applications/@me",
221
+ normalized,
222
+ createDiscordTimeoutFetch(fetcher, timeoutMs),
223
+ );
224
+ if (json?.id) {
225
+ return json.id;
226
+ }
227
+ return undefined;
228
+ } catch (error) {
229
+ if (error instanceof DiscordApiError) {
230
+ if (error.status === 429) {
231
+ throw error;
232
+ }
233
+ return undefined;
234
+ }
235
+ return undefined;
236
+ }
237
+ }
@@ -0,0 +1,92 @@
1
+ import { isIP } from "node:net";
2
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
3
+ import { makeProxyFetch } from "openclaw/plugin-sdk/fetch-runtime";
4
+ import { danger } from "openclaw/plugin-sdk/runtime-env";
5
+ import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
6
+ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
7
+ import type { ResolvedDiscordAccount } from "./accounts.js";
8
+
9
+ function resolveDiscordProxyUrl(
10
+ account: Pick<ResolvedDiscordAccount, "config">,
11
+ cfg: OpenClawConfig,
12
+ ): string | undefined {
13
+ const accountProxy = account.config.proxy?.trim();
14
+ if (accountProxy) {
15
+ return accountProxy;
16
+ }
17
+ const channelProxy = cfg?.channels?.discord?.proxy;
18
+ if (typeof channelProxy !== "string") {
19
+ return undefined;
20
+ }
21
+ const trimmed = channelProxy.trim();
22
+ return trimmed || undefined;
23
+ }
24
+
25
+ function resolveDiscordProxyFetchByUrl(
26
+ proxyUrl: string | undefined,
27
+ runtime?: Pick<RuntimeEnv, "error">,
28
+ ): typeof fetch | undefined {
29
+ return withValidatedDiscordProxy(proxyUrl, runtime, (proxy) => makeProxyFetch(proxy));
30
+ }
31
+
32
+ export function resolveDiscordProxyFetchForAccount(
33
+ account: Pick<ResolvedDiscordAccount, "config">,
34
+ cfg: OpenClawConfig,
35
+ runtime?: Pick<RuntimeEnv, "error">,
36
+ ): typeof fetch | undefined {
37
+ return resolveDiscordProxyFetchByUrl(resolveDiscordProxyUrl(account, cfg), runtime);
38
+ }
39
+
40
+ export function withValidatedDiscordProxy<T>(
41
+ proxyUrl: string | undefined,
42
+ runtime: Pick<RuntimeEnv, "error"> | undefined,
43
+ createValue: (proxyUrl: string) => T,
44
+ ): T | undefined {
45
+ const proxy = proxyUrl?.trim();
46
+ if (!proxy) {
47
+ return undefined;
48
+ }
49
+ try {
50
+ validateDiscordProxyUrl(proxy);
51
+ return createValue(proxy);
52
+ } catch (err) {
53
+ runtime?.error?.(danger(`discord: invalid rest proxy: ${String(err)}`));
54
+ return undefined;
55
+ }
56
+ }
57
+
58
+ export function validateDiscordProxyUrl(proxyUrl: string): string {
59
+ let parsed: URL;
60
+ try {
61
+ parsed = new URL(proxyUrl);
62
+ } catch {
63
+ throw new Error("Proxy URL must be a valid http or https URL");
64
+ }
65
+ if (!["http:", "https:"].includes(parsed.protocol)) {
66
+ throw new Error("Proxy URL must use http or https");
67
+ }
68
+ if (!isLoopbackProxyHostname(parsed.hostname)) {
69
+ throw new Error("Proxy URL must target a loopback host");
70
+ }
71
+ return proxyUrl;
72
+ }
73
+
74
+ function isLoopbackProxyHostname(hostname: string): boolean {
75
+ const normalized = normalizeLowercaseStringOrEmpty(hostname);
76
+ if (!normalized) {
77
+ return false;
78
+ }
79
+ const bracketless =
80
+ normalized.startsWith("[") && normalized.endsWith("]") ? normalized.slice(1, -1) : normalized;
81
+ if (bracketless === "localhost") {
82
+ return true;
83
+ }
84
+ const ipFamily = isIP(bracketless);
85
+ if (ipFamily === 4) {
86
+ return bracketless.startsWith("127.");
87
+ }
88
+ if (ipFamily === 6) {
89
+ return bracketless === "::1" || bracketless === "0:0:0:0:0:0:0:1";
90
+ }
91
+ return false;
92
+ }