@openclaw/discord 2026.3.13 → 2026.5.1-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 (498) 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 +3282 -1
  14. package/package.json +67 -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 +185 -0
  34. package/src/actions/handle-action.ts +332 -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 +244 -0
  40. package/src/actions/runtime.messaging.shared.ts +92 -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 +1056 -0
  49. package/src/actions/runtime.ts +81 -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 +236 -0
  65. package/src/channel-actions.ts +198 -0
  66. package/src/channel-api.ts +28 -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 +539 -12
  72. package/src/channel.ts +596 -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 +139 -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 +297 -0
  123. package/src/internal/client.ts +246 -0
  124. package/src/internal/command-deploy.ts +202 -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 +475 -0
  140. package/src/internal/gateway.ts +437 -0
  141. package/src/internal/interaction-dispatch.test.ts +148 -0
  142. package/src/internal/interaction-dispatch.ts +130 -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 +253 -0
  146. package/src/internal/interactions.ts +337 -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 +412 -0
  155. package/src/internal/rest.test.ts +437 -0
  156. package/src/internal/rest.ts +213 -0
  157. package/src/internal/schemas.ts +36 -0
  158. package/src/internal/structures.ts +278 -0
  159. package/src/internal/test-builders.test-support.ts +163 -0
  160. package/src/internal/voice.ts +49 -0
  161. package/src/media-detection.ts +28 -0
  162. package/src/mentions.test.ts +111 -0
  163. package/src/mentions.ts +147 -0
  164. package/src/monitor/access-groups.ts +55 -0
  165. package/src/monitor/ack-reactions.ts +70 -0
  166. package/src/monitor/acp-bind-here.integration.test.ts +211 -0
  167. package/src/monitor/agent-components-auth.ts +7 -0
  168. package/src/monitor/agent-components-context.ts +144 -0
  169. package/src/monitor/agent-components-data.ts +224 -0
  170. package/src/monitor/agent-components-dm-auth.ts +221 -0
  171. package/src/monitor/agent-components-guild-auth.ts +322 -0
  172. package/src/monitor/agent-components-helpers.runtime.ts +5 -0
  173. package/src/monitor/agent-components-helpers.ts +34 -0
  174. package/src/monitor/agent-components-reply.ts +10 -0
  175. package/src/monitor/agent-components.deps.runtime.ts +2 -0
  176. package/src/monitor/agent-components.dispatch.ts +366 -0
  177. package/src/monitor/agent-components.handlers.ts +303 -0
  178. package/src/monitor/agent-components.modal.ts +160 -0
  179. package/src/monitor/agent-components.plugin-interactive.ts +187 -0
  180. package/src/monitor/agent-components.runtime.ts +14 -0
  181. package/src/monitor/agent-components.system-controls.ts +211 -0
  182. package/src/monitor/agent-components.ts +70 -0
  183. package/src/monitor/agent-components.types.ts +57 -0
  184. package/src/monitor/agent-components.wildcard-controls.ts +168 -0
  185. package/src/monitor/agent-components.wildcard.test.ts +71 -0
  186. package/src/monitor/allow-list.ts +623 -0
  187. package/src/monitor/auto-presence.test.ts +156 -0
  188. package/src/monitor/auto-presence.ts +356 -0
  189. package/src/monitor/channel-access.ts +70 -0
  190. package/src/monitor/commands.test.ts +24 -0
  191. package/src/monitor/commands.ts +9 -0
  192. package/src/monitor/dm-command-auth.test.ts +197 -0
  193. package/src/monitor/dm-command-auth.ts +158 -0
  194. package/src/monitor/dm-command-decision.test.ts +113 -0
  195. package/src/monitor/dm-command-decision.ts +49 -0
  196. package/src/monitor/exec-approvals.test.ts +226 -0
  197. package/src/monitor/exec-approvals.ts +158 -0
  198. package/src/monitor/format.ts +45 -0
  199. package/src/monitor/gateway-handle.ts +34 -0
  200. package/src/monitor/gateway-metadata.test.ts +29 -0
  201. package/src/monitor/gateway-metadata.ts +298 -0
  202. package/src/monitor/gateway-plugin.test.ts +297 -0
  203. package/src/monitor/gateway-plugin.ts +294 -0
  204. package/src/monitor/gateway-registry.ts +37 -0
  205. package/src/monitor/gateway-supervisor.test.ts +150 -0
  206. package/src/monitor/gateway-supervisor.ts +206 -0
  207. package/src/monitor/inbound-context.test-helpers.ts +37 -0
  208. package/src/monitor/inbound-context.test.ts +106 -0
  209. package/src/monitor/inbound-context.ts +103 -0
  210. package/src/monitor/inbound-dedupe.ts +79 -0
  211. package/src/monitor/inbound-job.test.ts +203 -0
  212. package/src/monitor/inbound-job.ts +118 -0
  213. package/src/monitor/listeners.queue.ts +91 -0
  214. package/src/monitor/listeners.reactions.ts +610 -0
  215. package/src/monitor/listeners.test.ts +200 -0
  216. package/src/monitor/listeners.ts +150 -0
  217. package/src/monitor/message-channel-info.ts +96 -0
  218. package/src/monitor/message-forwarded.ts +107 -0
  219. package/src/monitor/message-handler.batch-gate.test.ts +22 -0
  220. package/src/monitor/message-handler.batch-gate.ts +19 -0
  221. package/src/monitor/message-handler.bot-self-filter.test.ts +68 -0
  222. package/src/monitor/message-handler.context.ts +393 -0
  223. package/src/monitor/message-handler.dm-preflight.ts +123 -0
  224. package/src/monitor/message-handler.draft-preview.ts +246 -0
  225. package/src/monitor/message-handler.hydration.test.ts +80 -0
  226. package/src/monitor/message-handler.hydration.ts +198 -0
  227. package/src/monitor/message-handler.inbound-context.test.ts +59 -0
  228. package/src/monitor/message-handler.module-test-helpers.ts +31 -0
  229. package/src/monitor/message-handler.preflight-channel-access.ts +86 -0
  230. package/src/monitor/message-handler.preflight-channel-context.ts +55 -0
  231. package/src/monitor/message-handler.preflight-context.ts +54 -0
  232. package/src/monitor/message-handler.preflight-helpers.ts +164 -0
  233. package/src/monitor/message-handler.preflight-history.ts +23 -0
  234. package/src/monitor/message-handler.preflight-logging.ts +36 -0
  235. package/src/monitor/message-handler.preflight-pluralkit.ts +27 -0
  236. package/src/monitor/message-handler.preflight-runtime.ts +28 -0
  237. package/src/monitor/message-handler.preflight-thread.ts +49 -0
  238. package/src/monitor/message-handler.preflight.acp-bindings.test.ts +369 -0
  239. package/src/monitor/message-handler.preflight.test-helpers.ts +111 -0
  240. package/src/monitor/message-handler.preflight.test.ts +1544 -0
  241. package/src/monitor/message-handler.preflight.ts +680 -0
  242. package/src/monitor/message-handler.preflight.types.ts +109 -0
  243. package/src/monitor/message-handler.process.test.ts +1301 -0
  244. package/src/monitor/message-handler.process.ts +684 -0
  245. package/src/monitor/message-handler.queue.test.ts +496 -0
  246. package/src/monitor/message-handler.routing-preflight.ts +112 -0
  247. package/src/monitor/message-handler.test-harness.ts +99 -0
  248. package/src/monitor/message-handler.test-helpers.ts +75 -0
  249. package/src/monitor/message-handler.ts +274 -0
  250. package/src/monitor/message-media.ts +507 -0
  251. package/src/monitor/message-run-queue.ts +101 -0
  252. package/src/monitor/message-text.ts +171 -0
  253. package/src/monitor/message-utils.test.ts +1151 -0
  254. package/src/monitor/message-utils.ts +32 -0
  255. package/src/monitor/model-picker-preferences.test.ts +67 -0
  256. package/src/monitor/model-picker-preferences.ts +184 -0
  257. package/src/monitor/model-picker.state.ts +364 -0
  258. package/src/monitor/model-picker.test-utils.ts +26 -0
  259. package/src/monitor/model-picker.test.ts +794 -0
  260. package/src/monitor/model-picker.ts +38 -0
  261. package/src/monitor/model-picker.view.ts +695 -0
  262. package/src/monitor/monitor.agent-components.test.ts +375 -0
  263. package/src/monitor/monitor.test.ts +849 -0
  264. package/src/monitor/monitor.threading-utils.test.ts +598 -0
  265. package/src/monitor/native-command-agent-reply.ts +123 -0
  266. package/src/monitor/native-command-arg-ui.ts +233 -0
  267. package/src/monitor/native-command-auth.ts +308 -0
  268. package/src/monitor/native-command-bypass.ts +13 -0
  269. package/src/monitor/native-command-context.test.ts +98 -0
  270. package/src/monitor/native-command-context.ts +103 -0
  271. package/src/monitor/native-command-dispatch.ts +35 -0
  272. package/src/monitor/native-command-model-picker-apply.ts +177 -0
  273. package/src/monitor/native-command-model-picker-interaction.ts +461 -0
  274. package/src/monitor/native-command-model-picker-ui.ts +368 -0
  275. package/src/monitor/native-command-reply.test.ts +68 -0
  276. package/src/monitor/native-command-reply.ts +183 -0
  277. package/src/monitor/native-command-route.ts +91 -0
  278. package/src/monitor/native-command-status.ts +76 -0
  279. package/src/monitor/native-command-ui.ts +26 -0
  280. package/src/monitor/native-command-ui.types.ts +20 -0
  281. package/src/monitor/native-command.args.ts +45 -0
  282. package/src/monitor/native-command.command-arg.test.ts +99 -0
  283. package/src/monitor/native-command.commands-allowfrom.test.ts +490 -0
  284. package/src/monitor/native-command.model-picker.test.ts +767 -0
  285. package/src/monitor/native-command.options.test.ts +369 -0
  286. package/src/monitor/native-command.options.ts +153 -0
  287. package/src/monitor/native-command.plugin-dispatch.test.ts +879 -0
  288. package/src/monitor/native-command.runtime.ts +50 -0
  289. package/src/monitor/native-command.status-direct.test.ts +272 -0
  290. package/src/monitor/native-command.test-helpers.ts +64 -0
  291. package/src/monitor/native-command.think-autocomplete.test.ts +416 -0
  292. package/src/monitor/native-command.ts +699 -0
  293. package/src/monitor/native-command.types.ts +9 -0
  294. package/src/monitor/native-interaction-channel-context.ts +50 -0
  295. package/src/monitor/preflight-audio.runtime.ts +9 -0
  296. package/src/monitor/preflight-audio.test.ts +157 -0
  297. package/src/monitor/preflight-audio.ts +130 -0
  298. package/src/monitor/presence-cache.ts +61 -0
  299. package/src/monitor/presence.test.ts +44 -0
  300. package/src/monitor/presence.ts +50 -0
  301. package/src/monitor/provider-session.runtime.ts +12 -0
  302. package/src/monitor/provider.acp.ts +89 -0
  303. package/src/monitor/provider.allowlist.test.ts +149 -0
  304. package/src/monitor/provider.allowlist.ts +394 -0
  305. package/src/monitor/provider.cleanup.ts +41 -0
  306. package/src/monitor/provider.commands.ts +129 -0
  307. package/src/monitor/provider.config-log.ts +45 -0
  308. package/src/monitor/provider.deploy-errors.ts +362 -0
  309. package/src/monitor/provider.deploy.ts +221 -0
  310. package/src/monitor/provider.interactions.ts +160 -0
  311. package/src/monitor/provider.lifecycle.test.ts +658 -0
  312. package/src/monitor/provider.lifecycle.ts +545 -0
  313. package/src/monitor/provider.proxy.test.ts +745 -0
  314. package/src/monitor/provider.rest-proxy.test.ts +121 -0
  315. package/src/monitor/provider.runtime.ts +1 -0
  316. package/src/monitor/provider.skill-dedupe.test.ts +42 -0
  317. package/src/monitor/provider.startup-log.ts +32 -0
  318. package/src/monitor/provider.startup.test.ts +426 -0
  319. package/src/monitor/provider.startup.ts +323 -0
  320. package/src/monitor/provider.test.ts +1111 -0
  321. package/src/monitor/provider.ts +713 -0
  322. package/src/monitor/reply-context.ts +64 -0
  323. package/src/monitor/reply-delivery.test.ts +244 -0
  324. package/src/monitor/reply-delivery.ts +203 -0
  325. package/src/monitor/rest-fetch.ts +43 -0
  326. package/src/monitor/route-resolution.test.ts +204 -0
  327. package/src/monitor/route-resolution.ts +140 -0
  328. package/src/monitor/sender-identity.ts +81 -0
  329. package/src/monitor/startup-status.test.ts +30 -0
  330. package/src/monitor/startup-status.ts +10 -0
  331. package/src/monitor/status.ts +22 -0
  332. package/src/monitor/system-events.ts +55 -0
  333. package/src/monitor/thread-bindings.config.ts +35 -0
  334. package/src/monitor/thread-bindings.discord-api.test.ts +229 -0
  335. package/src/monitor/thread-bindings.discord-api.ts +318 -0
  336. package/src/monitor/thread-bindings.lifecycle.test.ts +1871 -0
  337. package/src/monitor/thread-bindings.lifecycle.ts +354 -0
  338. package/src/monitor/thread-bindings.manager.ts +553 -0
  339. package/src/monitor/thread-bindings.messages.ts +6 -0
  340. package/src/monitor/thread-bindings.persona.test.ts +34 -0
  341. package/src/monitor/thread-bindings.persona.ts +25 -0
  342. package/src/monitor/thread-bindings.session-adapter.ts +229 -0
  343. package/src/monitor/thread-bindings.session-shared.ts +59 -0
  344. package/src/monitor/thread-bindings.session-updates.ts +35 -0
  345. package/src/monitor/thread-bindings.shared-state.test.ts +36 -0
  346. package/src/monitor/thread-bindings.state.ts +540 -0
  347. package/src/monitor/thread-bindings.ts +48 -0
  348. package/src/monitor/thread-bindings.types.ts +83 -0
  349. package/src/monitor/thread-channel-context.ts +112 -0
  350. package/src/monitor/thread-session-close.test.ts +180 -0
  351. package/src/monitor/thread-session-close.ts +63 -0
  352. package/src/monitor/thread-title.generate.test.ts +197 -0
  353. package/src/monitor/thread-title.test.ts +31 -0
  354. package/src/monitor/thread-title.ts +181 -0
  355. package/src/monitor/threading.auto-thread.test.ts +327 -0
  356. package/src/monitor/threading.auto-thread.ts +287 -0
  357. package/src/monitor/threading.cache.ts +45 -0
  358. package/src/monitor/threading.parent-info.test.ts +156 -0
  359. package/src/monitor/threading.starter.test.ts +260 -0
  360. package/src/monitor/threading.starter.ts +287 -0
  361. package/src/monitor/threading.ts +20 -0
  362. package/src/monitor/threading.types.ts +102 -0
  363. package/src/monitor/timeouts.ts +84 -0
  364. package/src/monitor/typing.test.ts +42 -0
  365. package/src/monitor/typing.ts +17 -0
  366. package/src/monitor.gateway.test.ts +187 -0
  367. package/src/monitor.gateway.ts +75 -0
  368. package/src/monitor.test.ts +1397 -0
  369. package/src/monitor.ts +28 -0
  370. package/src/normalize.test.ts +56 -0
  371. package/src/normalize.ts +86 -0
  372. package/src/outbound-adapter.interactive-order.test.ts +64 -0
  373. package/src/outbound-adapter.test-harness.ts +207 -0
  374. package/src/outbound-adapter.test.ts +696 -0
  375. package/src/outbound-adapter.ts +291 -0
  376. package/src/outbound-approval.ts +29 -0
  377. package/src/outbound-components.ts +81 -0
  378. package/src/outbound-payload.contract.test.ts +38 -0
  379. package/src/outbound-payload.ts +134 -0
  380. package/src/outbound-send-context.ts +92 -0
  381. package/src/outbound-session-route.test.ts +34 -0
  382. package/src/outbound-session-route.ts +72 -0
  383. package/src/pluralkit.test.ts +67 -0
  384. package/src/pluralkit.ts +58 -0
  385. package/src/preview-streaming.ts +32 -0
  386. package/src/probe.intents.test.ts +94 -0
  387. package/src/probe.parse-token.test.ts +43 -0
  388. package/src/probe.runtime.ts +1 -0
  389. package/src/probe.ts +237 -0
  390. package/src/proxy-fetch.ts +92 -0
  391. package/src/proxy-request-client.test.ts +78 -0
  392. package/src/proxy-request-client.ts +54 -0
  393. package/src/recipient-resolution.ts +39 -0
  394. package/src/resolve-allowlist-common.test.ts +36 -0
  395. package/src/resolve-allowlist-common.ts +39 -0
  396. package/src/resolve-channels.test.ts +340 -0
  397. package/src/resolve-channels.ts +369 -0
  398. package/src/resolve-users.test.ts +222 -0
  399. package/src/resolve-users.ts +184 -0
  400. package/src/retry.test.ts +83 -0
  401. package/src/retry.ts +98 -0
  402. package/src/runtime-api.ts +64 -0
  403. package/src/runtime.ts +22 -5
  404. package/src/secret-config-contract.ts +140 -0
  405. package/src/security-audit.runtime.ts +1 -0
  406. package/src/security-audit.test.ts +246 -0
  407. package/src/security-audit.ts +208 -0
  408. package/src/security-contract.ts +47 -0
  409. package/src/security-doctor.test.ts +25 -0
  410. package/src/security-doctor.ts +20 -0
  411. package/src/security.ts +60 -0
  412. package/src/send-target-parsing.ts +14 -0
  413. package/src/send.channels.ts +139 -0
  414. package/src/send.components.test.ts +275 -0
  415. package/src/send.components.ts +383 -0
  416. package/src/send.creates-thread.test.ts +643 -0
  417. package/src/send.emojis-stickers.ts +57 -0
  418. package/src/send.guild.ts +170 -0
  419. package/src/send.message-request.ts +97 -0
  420. package/src/send.messages.test.ts +53 -0
  421. package/src/send.messages.ts +225 -0
  422. package/src/send.outbound.ts +414 -0
  423. package/src/send.permissions.authz.test.ts +188 -0
  424. package/src/send.permissions.ts +283 -0
  425. package/src/send.reactions.ts +155 -0
  426. package/src/send.sends-basic-channel-messages.test.ts +919 -0
  427. package/src/send.shared.ts +445 -0
  428. package/src/send.test-harness.ts +56 -0
  429. package/src/send.ts +82 -0
  430. package/src/send.types.ts +188 -0
  431. package/src/send.typing.test.ts +41 -0
  432. package/src/send.typing.ts +9 -0
  433. package/src/send.voice.ts +134 -0
  434. package/src/send.webhook-activity.test.ts +105 -0
  435. package/src/send.webhook.proxy.test.ts +191 -0
  436. package/src/send.webhook.ts +133 -0
  437. package/src/session-contract.ts +3 -0
  438. package/src/session-key-normalization.test.ts +44 -0
  439. package/src/session-key-normalization.ts +47 -0
  440. package/src/setup-account-state.test.ts +91 -0
  441. package/src/setup-account-state.ts +144 -0
  442. package/src/setup-adapter.ts +12 -0
  443. package/src/setup-core.ts +180 -0
  444. package/src/setup-runtime-helpers.ts +10 -0
  445. package/src/setup-surface.test.ts +96 -0
  446. package/src/setup-surface.ts +129 -0
  447. package/src/shared-interactive.test.ts +153 -0
  448. package/src/shared-interactive.ts +124 -0
  449. package/src/shared.test.ts +159 -0
  450. package/src/shared.ts +190 -0
  451. package/src/status-issues.test.ts +70 -0
  452. package/src/status-issues.ts +169 -0
  453. package/src/subagent-hooks.test.ts +40 -44
  454. package/src/subagent-hooks.ts +185 -122
  455. package/src/target-parsing.ts +53 -0
  456. package/src/target-resolver.ts +129 -0
  457. package/src/targets.test.ts +367 -0
  458. package/src/targets.ts +12 -0
  459. package/src/test-http-helpers.ts +10 -0
  460. package/src/test-support/component-runtime.ts +190 -0
  461. package/src/test-support/config.ts +7 -0
  462. package/src/test-support/configured-binding-runtime.ts +29 -0
  463. package/src/test-support/partial-channel.ts +26 -0
  464. package/src/test-support/provider.test-support.ts +545 -0
  465. package/src/token.test.ts +107 -0
  466. package/src/token.ts +60 -0
  467. package/src/ui-colors.ts +27 -0
  468. package/src/ui.ts +20 -0
  469. package/src/voice/access.test.ts +217 -0
  470. package/src/voice/access.ts +124 -0
  471. package/src/voice/audio.ts +173 -0
  472. package/src/voice/capture-state.test.ts +48 -0
  473. package/src/voice/capture-state.ts +120 -0
  474. package/src/voice/command.test.ts +164 -0
  475. package/src/voice/command.ts +283 -0
  476. package/src/voice/config.ts +8 -0
  477. package/src/voice/manager.e2e.test.ts +928 -0
  478. package/src/voice/manager.ready-listener.test.ts +37 -0
  479. package/src/voice/manager.runtime.ts +11 -0
  480. package/src/voice/manager.ts +691 -0
  481. package/src/voice/prompt.test.ts +16 -0
  482. package/src/voice/prompt.ts +17 -0
  483. package/src/voice/receive-recovery.test.ts +79 -0
  484. package/src/voice/receive-recovery.ts +159 -0
  485. package/src/voice/sanitize.test.ts +34 -0
  486. package/src/voice/sanitize.ts +32 -0
  487. package/src/voice/sdk-runtime.ts +14 -0
  488. package/src/voice/segment.ts +156 -0
  489. package/src/voice/session.ts +50 -0
  490. package/src/voice/speaker-context.ts +127 -0
  491. package/src/voice/tts.ts +125 -0
  492. package/src/voice-message.test.ts +234 -0
  493. package/src/voice-message.ts +444 -0
  494. package/subagent-hooks-api.ts +27 -0
  495. package/test-api.ts +4 -0
  496. package/thread-binding-api.ts +1 -0
  497. package/timeouts.ts +6 -0
  498. package/tsconfig.json +16 -0
@@ -0,0 +1,246 @@
1
+ import type { APIApplicationCommand, APIInteraction } from "discord-api-types/v10";
2
+ import { DiscordCommandDeployer, type DeployCommandOptions } from "./command-deploy.js";
3
+ import type { BaseCommand } from "./commands.js";
4
+ import { BaseMessageInteractiveComponent, parseCustomId, type Modal } from "./components.js";
5
+ import { DiscordEntityCache } from "./entity-cache.js";
6
+ import { DiscordEventQueue, type DiscordEventQueueOptions } from "./event-queue.js";
7
+ import { dispatchInteraction } from "./interaction-dispatch.js";
8
+ import { RequestClient, type RequestClientOptions } from "./rest.js";
9
+ import type { Guild, GuildMember, User } from "./structures.js";
10
+
11
+ export interface Route {
12
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
13
+ path: `/${string}`;
14
+ handler(req: Request, ctx?: Context): Response | Promise<Response>;
15
+ protected?: boolean;
16
+ disabled?: boolean;
17
+ }
18
+
19
+ export interface Context {
20
+ waitUntil?(promise: Promise<unknown>): void;
21
+ env?: unknown;
22
+ }
23
+
24
+ export abstract class Plugin {
25
+ abstract readonly id: string;
26
+ registerClient?(client: Client): Promise<void> | void;
27
+ registerRoutes?(client: Client): Promise<void> | void;
28
+ onRequest?(req: Request, ctx: Context): Promise<Response | undefined> | Response | undefined;
29
+ }
30
+
31
+ export type AnyListener = {
32
+ type: string;
33
+ handle(data: unknown, client: Client): Promise<void> | void;
34
+ };
35
+
36
+ export interface ClientOptions {
37
+ baseUrl: string;
38
+ clientId: string;
39
+ deploySecret?: string;
40
+ publicKey: string | string[];
41
+ token: string;
42
+ requestOptions?: RequestClientOptions;
43
+ autoDeploy?: boolean;
44
+ disableDeployRoute?: boolean;
45
+ disableInteractionsRoute?: boolean;
46
+ disableEventsRoute?: boolean;
47
+ devGuilds?: string[];
48
+ eventQueue?: DiscordEventQueueOptions;
49
+ restCacheTtlMs?: number;
50
+ }
51
+
52
+ export class ComponentRegistry<
53
+ T extends { customId: string; customIdParser?: typeof parseCustomId; type?: number },
54
+ > {
55
+ private entries = new Map<string, T[]>();
56
+ private wildcardEntries: T[] = [];
57
+
58
+ register(entry: T): void {
59
+ const key = parseRegistryKey(entry.customId, entry.customIdParser);
60
+ if (key === "*") {
61
+ if (!this.wildcardEntries.includes(entry)) {
62
+ this.wildcardEntries.push(entry);
63
+ }
64
+ return;
65
+ }
66
+ const entries = this.entries.get(key) ?? [];
67
+ if (!entries.includes(entry)) {
68
+ entries.push(entry);
69
+ this.entries.set(key, entries);
70
+ }
71
+ }
72
+
73
+ resolve(customId: string, options?: { componentType?: number }): T | undefined {
74
+ for (const entries of this.entries.values()) {
75
+ const match = entries.find((entry) => {
76
+ if (options?.componentType !== undefined && entry.type !== options.componentType) {
77
+ return false;
78
+ }
79
+ const parser = entry.customIdParser ?? parseCustomId;
80
+ return parseRegistryKey(entry.customId, parser) === parseRegistryKey(customId, parser);
81
+ });
82
+ if (match) {
83
+ return match;
84
+ }
85
+ }
86
+ return this.wildcardEntries.find((entry) => {
87
+ if (options?.componentType !== undefined && entry.type !== options.componentType) {
88
+ return false;
89
+ }
90
+ return true;
91
+ });
92
+ }
93
+ }
94
+
95
+ function parseRegistryKey(customId: string, parser: typeof parseCustomId = parseCustomId): string {
96
+ return parser(customId).key;
97
+ }
98
+
99
+ export class Client {
100
+ routes: Route[] = [];
101
+ plugins: Array<{ id: string; plugin: Plugin }> = [];
102
+ options: ClientOptions;
103
+ commands: BaseCommand[];
104
+ listeners: AnyListener[];
105
+ rest: RequestClient;
106
+ componentHandler = new ComponentRegistry<BaseMessageInteractiveComponent>();
107
+ private commandDeployer: DiscordCommandDeployer;
108
+ private entityCache: DiscordEntityCache;
109
+ private eventQueue?: DiscordEventQueue;
110
+ modalHandler = new ComponentRegistry<Modal>();
111
+ shardId?: number;
112
+ totalShards?: number;
113
+
114
+ constructor(
115
+ options: ClientOptions,
116
+ handlers: {
117
+ commands?: BaseCommand[];
118
+ listeners?: AnyListener[];
119
+ components?: BaseMessageInteractiveComponent[];
120
+ modals?: Modal[];
121
+ },
122
+ plugins: Plugin[] = [],
123
+ ) {
124
+ if (!options.clientId) {
125
+ throw new Error("Missing Discord application ID");
126
+ }
127
+ if (!options.token) {
128
+ throw new Error("Missing Discord bot token");
129
+ }
130
+ this.options = { ...options, baseUrl: options.baseUrl.replace(/\/+$/, "") };
131
+ this.commands = handlers.commands ?? [];
132
+ this.listeners = handlers.listeners ?? [];
133
+ this.rest = new RequestClient(options.token, options.requestOptions);
134
+ this.eventQueue = this.options.eventQueue
135
+ ? new DiscordEventQueue(this.options.eventQueue)
136
+ : undefined;
137
+ this.entityCache = new DiscordEntityCache({
138
+ client: this,
139
+ rest: () => this.rest,
140
+ ttlMs: this.options.restCacheTtlMs,
141
+ });
142
+ this.commandDeployer = new DiscordCommandDeployer({
143
+ clientId: this.options.clientId,
144
+ commands: this.commands,
145
+ devGuilds: this.options.devGuilds,
146
+ rest: () => this.rest,
147
+ });
148
+ for (const component of handlers.components ?? []) {
149
+ this.componentHandler.register(component);
150
+ }
151
+ for (const command of this.commands) {
152
+ for (const component of command.components ?? []) {
153
+ this.componentHandler.register(component);
154
+ }
155
+ }
156
+ for (const modal of handlers.modals ?? []) {
157
+ this.modalHandler.register(modal);
158
+ }
159
+ for (const plugin of plugins) {
160
+ void plugin.registerClient?.(this);
161
+ void plugin.registerRoutes?.(this);
162
+ this.plugins.push({ id: plugin.id, plugin });
163
+ }
164
+ }
165
+
166
+ getPlugin<T = Plugin>(id: string): T | undefined {
167
+ return this.plugins.find((entry) => entry.id === id)?.plugin as T | undefined;
168
+ }
169
+
170
+ registerListener(listener: AnyListener): AnyListener {
171
+ if (!this.listeners.includes(listener)) {
172
+ this.listeners.push(listener);
173
+ }
174
+ return listener;
175
+ }
176
+
177
+ unregisterListener(listener: AnyListener): boolean {
178
+ const index = this.listeners.indexOf(listener);
179
+ if (index < 0) {
180
+ return false;
181
+ }
182
+ this.listeners.splice(index, 1);
183
+ return true;
184
+ }
185
+
186
+ getRuntimeMetrics() {
187
+ return {
188
+ request: this.rest.getSchedulerMetrics(),
189
+ eventQueue: this.eventQueue?.getMetrics(),
190
+ };
191
+ }
192
+
193
+ async fetchUser(id: string): Promise<User> {
194
+ return await this.entityCache.fetchUser(id);
195
+ }
196
+
197
+ async fetchChannel(id: string) {
198
+ return await this.entityCache.fetchChannel(id);
199
+ }
200
+
201
+ async fetchGuild(id: string): Promise<Guild> {
202
+ return await this.entityCache.fetchGuild(id);
203
+ }
204
+
205
+ async fetchMember(guildId: string, userId: string): Promise<GuildMember> {
206
+ return await this.entityCache.fetchMember(guildId, userId);
207
+ }
208
+
209
+ async getDiscordCommands(): Promise<APIApplicationCommand[]> {
210
+ return await this.commandDeployer.getCommands();
211
+ }
212
+
213
+ async deployCommands(options: DeployCommandOptions = {}) {
214
+ return await this.commandDeployer.deploy(options);
215
+ }
216
+
217
+ async reconcileCommands() {
218
+ return await this.deployCommands({ mode: "reconcile" });
219
+ }
220
+
221
+ async handleInteraction(rawData: APIInteraction, _ctx?: Context): Promise<void> {
222
+ await dispatchInteraction(this, rawData);
223
+ }
224
+
225
+ async dispatchGatewayEvent(type: string, data: unknown): Promise<void> {
226
+ this.entityCache.invalidateForGatewayEvent(type, data);
227
+ const listeners = this.listeners.filter((entry) => entry.type === type);
228
+ if (!this.eventQueue) {
229
+ for (const listener of listeners) {
230
+ await listener.handle(data, this);
231
+ }
232
+ return;
233
+ }
234
+ await Promise.all(
235
+ listeners.map((listener) =>
236
+ this.eventQueue!.enqueue({
237
+ eventType: type,
238
+ listenerName: listener.constructor.name || "AnonymousListener",
239
+ run: async () => {
240
+ await listener.handle(data, this);
241
+ },
242
+ }),
243
+ ),
244
+ );
245
+ }
246
+ }
@@ -0,0 +1,202 @@
1
+ import { createHash } from "node:crypto";
2
+ import { ApplicationCommandType, type APIApplicationCommand } from "discord-api-types/v10";
3
+ import {
4
+ createApplicationCommand,
5
+ deleteApplicationCommand,
6
+ editApplicationCommand,
7
+ listApplicationCommands,
8
+ overwriteApplicationCommands,
9
+ overwriteGuildApplicationCommands,
10
+ } from "./api.js";
11
+ import type { BaseCommand } from "./commands.js";
12
+ import type { RequestClient } from "./rest.js";
13
+
14
+ export type DeployCommandOptions = {
15
+ mode?: "overwrite" | "reconcile";
16
+ force?: boolean;
17
+ };
18
+
19
+ type SerializedCommand = ReturnType<BaseCommand["serialize"]>;
20
+
21
+ export class DiscordCommandDeployer {
22
+ private readonly hashes = new Map<string, string>();
23
+
24
+ constructor(
25
+ private readonly params: {
26
+ clientId: string;
27
+ commands: BaseCommand[];
28
+ devGuilds?: string[];
29
+ rest: () => RequestClient;
30
+ },
31
+ ) {}
32
+
33
+ async getCommands(): Promise<APIApplicationCommand[]> {
34
+ return await listApplicationCommands(this.rest, this.params.clientId);
35
+ }
36
+
37
+ async deploy(options: DeployCommandOptions = {}) {
38
+ const commands = this.params.commands.filter((command) => command.name !== "*");
39
+ const globalCommands = commands.filter((command) => !command.guildIds);
40
+ const serializedGlobal = globalCommands.map((command) => command.serialize());
41
+ for (const [guildId, entries] of groupGuildCommands(commands)) {
42
+ await this.putCommandSetIfChanged(
43
+ `guild:${guildId}`,
44
+ entries,
45
+ async () => {
46
+ await overwriteGuildApplicationCommands(
47
+ this.rest,
48
+ this.params.clientId,
49
+ guildId,
50
+ entries,
51
+ );
52
+ },
53
+ options,
54
+ );
55
+ }
56
+ if (this.params.devGuilds?.length) {
57
+ for (const guildId of this.params.devGuilds) {
58
+ const entries = commands.map((command) => command.serialize());
59
+ await this.putCommandSetIfChanged(
60
+ `dev-guild:${guildId}`,
61
+ entries,
62
+ async () => {
63
+ await overwriteGuildApplicationCommands(
64
+ this.rest,
65
+ this.params.clientId,
66
+ guildId,
67
+ entries,
68
+ );
69
+ },
70
+ options,
71
+ );
72
+ }
73
+ return { mode: options.mode ?? "reconcile", usedDevGuilds: true };
74
+ }
75
+ if (options.mode !== "overwrite") {
76
+ await this.putCommandSetIfChanged(
77
+ "global:reconcile",
78
+ serializedGlobal,
79
+ async () => {
80
+ await this.reconcileGlobalCommands(serializedGlobal);
81
+ },
82
+ options,
83
+ );
84
+ return { mode: "reconcile" as const, usedDevGuilds: false };
85
+ }
86
+ await this.putCommandSetIfChanged(
87
+ "global:overwrite",
88
+ serializedGlobal,
89
+ async () => {
90
+ await overwriteApplicationCommands(this.rest, this.params.clientId, serializedGlobal);
91
+ },
92
+ options,
93
+ );
94
+ return { mode: "overwrite" as const, usedDevGuilds: false };
95
+ }
96
+
97
+ private async reconcileGlobalCommands(desired: SerializedCommand[]) {
98
+ const existing = await this.getCommands();
99
+ const existingByKey = new Map(existing.map((command) => [stableCommandKey(command), command]));
100
+ const desiredKeys = new Set<string>();
101
+ for (const command of desired) {
102
+ const key = stableCommandKey(command as APIApplicationCommand);
103
+ desiredKeys.add(key);
104
+ const current = existingByKey.get(key);
105
+ if (!current) {
106
+ await createApplicationCommand(this.rest, this.params.clientId, command);
107
+ continue;
108
+ }
109
+ if (!commandsEqual(current, command)) {
110
+ await editApplicationCommand(this.rest, this.params.clientId, current.id, command);
111
+ }
112
+ }
113
+ for (const command of existing) {
114
+ if (!desiredKeys.has(stableCommandKey(command))) {
115
+ await deleteApplicationCommand(this.rest, this.params.clientId, command.id);
116
+ }
117
+ }
118
+ }
119
+
120
+ private async putCommandSetIfChanged(
121
+ key: string,
122
+ commands: SerializedCommand[],
123
+ deploy: () => Promise<void>,
124
+ options: { force?: boolean },
125
+ ): Promise<void> {
126
+ const hash = stableCommandSetHash(commands);
127
+ if (!options.force && this.hashes.get(key) === hash) {
128
+ return;
129
+ }
130
+ await deploy();
131
+ this.hashes.set(key, hash);
132
+ }
133
+
134
+ private get rest(): RequestClient {
135
+ return this.params.rest();
136
+ }
137
+ }
138
+
139
+ function groupGuildCommands(commands: BaseCommand[]): Map<string, SerializedCommand[]> {
140
+ const guildCommands = new Map<string, SerializedCommand[]>();
141
+ for (const command of commands.filter((entry) => entry.guildIds)) {
142
+ for (const guildId of command.guildIds ?? []) {
143
+ const entries = guildCommands.get(guildId) ?? [];
144
+ entries.push(command.serialize());
145
+ guildCommands.set(guildId, entries);
146
+ }
147
+ }
148
+ return guildCommands;
149
+ }
150
+
151
+ function stableCommandKey(command: Pick<APIApplicationCommand, "name" | "type">) {
152
+ return `${command.type ?? ApplicationCommandType.ChatInput}:${command.name}`;
153
+ }
154
+
155
+ function comparableCommand(value: unknown): unknown {
156
+ if (!value || typeof value !== "object") {
157
+ return value;
158
+ }
159
+ const omit = new Set([
160
+ "id",
161
+ "application_id",
162
+ "guild_id",
163
+ "version",
164
+ "default_permission",
165
+ "nsfw",
166
+ ]);
167
+ return stableComparableObject(
168
+ Object.fromEntries(
169
+ Object.entries(value).filter(([key, entry]) => !omit.has(key) && entry !== undefined),
170
+ ),
171
+ );
172
+ }
173
+
174
+ function stableComparableObject(value: unknown): unknown {
175
+ if (Array.isArray(value)) {
176
+ return value.map((entry) => stableComparableObject(entry));
177
+ }
178
+ if (!value || typeof value !== "object") {
179
+ return value;
180
+ }
181
+ return Object.fromEntries(
182
+ Object.entries(value as Record<string, unknown>)
183
+ .filter(([, entry]) => entry !== undefined)
184
+ .toSorted(([a], [b]) => a.localeCompare(b))
185
+ .map(([key, entry]) => [key, stableComparableObject(entry)]),
186
+ );
187
+ }
188
+
189
+ function commandsEqual(a: unknown, b: unknown) {
190
+ return JSON.stringify(comparableCommand(a)) === JSON.stringify(comparableCommand(b));
191
+ }
192
+
193
+ function stableCommandSetHash(commands: SerializedCommand[]): string {
194
+ const stable = commands
195
+ .map((command) => stableComparableObject(command))
196
+ .toSorted((a, b) =>
197
+ stableCommandKey(a as APIApplicationCommand).localeCompare(
198
+ stableCommandKey(b as APIApplicationCommand),
199
+ ),
200
+ );
201
+ return createHash("sha256").update(JSON.stringify(stable)).digest("hex");
202
+ }
@@ -0,0 +1,188 @@
1
+ import {
2
+ ApplicationCommandOptionType,
3
+ ApplicationCommandType,
4
+ InteractionContextType,
5
+ type RESTPostAPIApplicationCommandsJSONBody,
6
+ } from "discord-api-types/v10";
7
+ import type { BaseMessageInteractiveComponent } from "./components.js";
8
+ import type { AutocompleteInteraction, CommandInteraction } from "./interactions.js";
9
+
10
+ export type ConditionalCommandOption = (interaction: unknown) => boolean;
11
+ export type CommandOption = Record<string, unknown> & {
12
+ name: string;
13
+ description?: string;
14
+ type: ApplicationCommandOptionType;
15
+ required?: boolean;
16
+ choices?: Array<{ name: string; value: string | number | boolean }>;
17
+ autocomplete?: boolean | ((interaction: AutocompleteInteraction) => Promise<void>);
18
+ };
19
+ export type CommandOptions = CommandOption[];
20
+
21
+ type RawSubcommandOption = {
22
+ name?: unknown;
23
+ type?: unknown;
24
+ options?: RawSubcommandOption[];
25
+ };
26
+
27
+ function clean<T extends Record<string, unknown>>(value: T): T {
28
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)) as T;
29
+ }
30
+
31
+ function resolveConditionalCommandOption(
32
+ value: boolean | ConditionalCommandOption,
33
+ interaction: unknown,
34
+ ): boolean {
35
+ return typeof value === "function" ? value(interaction) : value;
36
+ }
37
+
38
+ export async function deferCommandInteractionIfNeeded(
39
+ command: BaseCommand,
40
+ interaction: CommandInteraction,
41
+ ): Promise<void> {
42
+ if (!resolveConditionalCommandOption(command.defer, interaction)) {
43
+ return;
44
+ }
45
+ await interaction.defer({
46
+ ephemeral: resolveConditionalCommandOption(command.ephemeral, interaction),
47
+ });
48
+ }
49
+
50
+ function readRawCommandOptions(interaction: CommandInteraction): RawSubcommandOption[] {
51
+ const options = (interaction.rawData as { data?: { options?: unknown } }).data?.options;
52
+ return Array.isArray(options) ? (options as RawSubcommandOption[]) : [];
53
+ }
54
+
55
+ function findSelectedSubcommand(
56
+ subcommands: Command[],
57
+ interaction: CommandInteraction,
58
+ ): Command | undefined {
59
+ const subcommandName = readRawCommandOptions(interaction).find(
60
+ (option) => option.type === ApplicationCommandOptionType.Subcommand,
61
+ )?.name;
62
+ return typeof subcommandName === "string"
63
+ ? subcommands.find((command) => command.name === subcommandName)
64
+ : undefined;
65
+ }
66
+
67
+ function findCommandOption(
68
+ options: CommandOptions | undefined,
69
+ name: string | undefined,
70
+ ): CommandOption | undefined {
71
+ if (!name) {
72
+ return undefined;
73
+ }
74
+ return options?.find((option) => option.name === name);
75
+ }
76
+
77
+ function hasCommandOptions(
78
+ command: BaseCommand,
79
+ ): command is BaseCommand & { options?: CommandOptions } {
80
+ return "options" in command;
81
+ }
82
+
83
+ export function resolveFocusedCommandOptionAutocompleteHandler(
84
+ command: BaseCommand,
85
+ interaction: AutocompleteInteraction,
86
+ ): ((interaction: AutocompleteInteraction) => Promise<void>) | undefined {
87
+ const focusedName = interaction.options.getFocused()?.name;
88
+ const options =
89
+ "subcommands" in command && Array.isArray(command.subcommands)
90
+ ? findSelectedSubcommand(command.subcommands, interaction)?.options
91
+ : hasCommandOptions(command)
92
+ ? command.options
93
+ : undefined;
94
+ const autocomplete = findCommandOption(options, focusedName)?.autocomplete;
95
+ return typeof autocomplete === "function" ? autocomplete : undefined;
96
+ }
97
+
98
+ export abstract class BaseCommand {
99
+ id?: string;
100
+ abstract name: string;
101
+ description?: string;
102
+ nameLocalizations?: Record<string, string>;
103
+ descriptionLocalizations?: Record<string, string>;
104
+ defer: boolean | ConditionalCommandOption = false;
105
+ ephemeral: boolean | ConditionalCommandOption = false;
106
+ abstract type: ApplicationCommandType;
107
+ integrationTypes = [0, 1];
108
+ contexts = [
109
+ InteractionContextType.Guild,
110
+ InteractionContextType.BotDM,
111
+ InteractionContextType.PrivateChannel,
112
+ ];
113
+ permission?: bigint | bigint[];
114
+ components?: BaseMessageInteractiveComponent[];
115
+ guildIds?: string[];
116
+ abstract serializeOptions(): unknown[] | undefined;
117
+ serialize(): RESTPostAPIApplicationCommandsJSONBody {
118
+ return clean({
119
+ name: this.name,
120
+ name_localizations: this.nameLocalizations,
121
+ description:
122
+ this.type === ApplicationCommandType.ChatInput ? (this.description ?? "") : undefined,
123
+ description_localizations: this.descriptionLocalizations,
124
+ type: this.type,
125
+ options: this.serializeOptions() as RESTPostAPIApplicationCommandsJSONBody["options"],
126
+ integration_types: this.integrationTypes,
127
+ contexts: this.contexts,
128
+ default_member_permissions: Array.isArray(this.permission)
129
+ ? this.permission.reduce((sum, entry) => sum | entry, 0n).toString()
130
+ : this.permission
131
+ ? this.permission.toString()
132
+ : null,
133
+ }) as RESTPostAPIApplicationCommandsJSONBody;
134
+ }
135
+ }
136
+
137
+ export abstract class Command extends BaseCommand {
138
+ options?: CommandOptions;
139
+ type = ApplicationCommandType.ChatInput;
140
+ abstract run(interaction: unknown): unknown;
141
+ async autocomplete(interaction: unknown): Promise<void> {
142
+ throw new Error(
143
+ `The ${(interaction as { rawData?: { data?: { name?: string } } }).rawData?.data?.name ?? this.name} command does not support autocomplete`,
144
+ );
145
+ }
146
+ async preCheck(interaction: unknown): Promise<unknown> {
147
+ return Boolean(interaction) || true;
148
+ }
149
+ serializeOptions() {
150
+ return this.options?.map((option) => {
151
+ if (typeof option.autocomplete === "function") {
152
+ const { autocomplete: _autocomplete, ...rest } = option;
153
+ return { ...rest, autocomplete: true };
154
+ }
155
+ return option;
156
+ }) as unknown[];
157
+ }
158
+ }
159
+
160
+ export abstract class CommandWithSubcommands extends BaseCommand {
161
+ type = ApplicationCommandType.ChatInput;
162
+ abstract subcommands: Command[];
163
+ async run(interaction: CommandInteraction): Promise<unknown> {
164
+ const subcommand = findSelectedSubcommand(this.subcommands, interaction);
165
+ if (!subcommand) {
166
+ const subcommandName = readRawCommandOptions(interaction).find(
167
+ (option) => option.type === ApplicationCommandOptionType.Subcommand,
168
+ )?.name;
169
+ throw new Error(
170
+ `Unknown Discord subcommand: ${typeof subcommandName === "string" ? subcommandName : "<missing>"}`,
171
+ );
172
+ }
173
+ await deferCommandInteractionIfNeeded(subcommand, interaction);
174
+ return await subcommand.run(interaction);
175
+ }
176
+ serializeOptions() {
177
+ return this.subcommands.map((command) =>
178
+ clean({
179
+ name: command.name,
180
+ name_localizations: command.nameLocalizations,
181
+ description: command.description ?? "",
182
+ description_localizations: command.descriptionLocalizations,
183
+ type: ApplicationCommandOptionType.Subcommand,
184
+ options: command.serializeOptions(),
185
+ }),
186
+ );
187
+ }
188
+ }
@@ -0,0 +1,65 @@
1
+ import type { BaseComponentInteraction } from "./interactions.js";
2
+
3
+ export type ComponentParserResult = {
4
+ key: string;
5
+ data: Record<string, string | boolean>;
6
+ };
7
+ export type ComponentData<
8
+ T extends keyof ComponentParserResult["data"] = keyof ComponentParserResult["data"],
9
+ > = {
10
+ [K in T]: ComponentParserResult["data"][K];
11
+ };
12
+ export type ConditionalComponentOption = (interaction: BaseComponentInteraction) => boolean;
13
+
14
+ export function parseCustomId(id: string): ComponentParserResult {
15
+ const [rawKey, ...parts] = id.split(";");
16
+ const [keyPart, firstValue] = rawKey.split("=");
17
+ const key = keyPart.includes(":") ? keyPart.split(":")[0] : keyPart;
18
+ const data: ComponentParserResult["data"] = {};
19
+ const entries = firstValue === undefined ? parts : [rawKey.slice(key.length + 1), ...parts];
20
+ for (const entry of entries) {
21
+ const index = entry.indexOf("=");
22
+ if (index < 0) {
23
+ continue;
24
+ }
25
+ const name = entry.slice(0, index).replace(/^[^:]+:/, "");
26
+ const raw = entry.slice(index + 1);
27
+ data[name] = raw === "true" ? true : raw === "false" ? false : raw;
28
+ }
29
+ return { key, data };
30
+ }
31
+
32
+ export function clean<T extends Record<string, unknown>>(value: T): T {
33
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)) as T;
34
+ }
35
+
36
+ export function colorToNumber(value: string | number | undefined): number | undefined {
37
+ if (typeof value === "number") {
38
+ return value;
39
+ }
40
+ if (typeof value === "string" && /^#?[0-9a-f]{6}$/i.test(value)) {
41
+ return Number.parseInt(value.replace(/^#/, ""), 16);
42
+ }
43
+ return undefined;
44
+ }
45
+
46
+ export abstract class BaseComponent {
47
+ abstract readonly type: number;
48
+ readonly isV2: boolean = false;
49
+ abstract serialize(): unknown;
50
+ }
51
+
52
+ export abstract class BaseMessageInteractiveComponent extends BaseComponent {
53
+ readonly isV2 = false;
54
+ defer: boolean | ConditionalComponentOption = false;
55
+ ephemeral: boolean | ConditionalComponentOption = false;
56
+ abstract customId: string;
57
+ customIdParser = parseCustomId;
58
+ run(_interaction: BaseComponentInteraction, _data: ComponentData): unknown {
59
+ return undefined;
60
+ }
61
+ }
62
+
63
+ export abstract class BaseModalComponent extends BaseComponent {
64
+ abstract customId: string;
65
+ }