@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,278 @@
1
+ import {
2
+ type APIChannel,
3
+ type APIEmbed,
4
+ type APIGuild,
5
+ type APIGuildMember,
6
+ type APIMessage,
7
+ type APIRole,
8
+ type APIUser,
9
+ type MessageType,
10
+ } from "discord-api-types/v10";
11
+ import {
12
+ createChannelMessage,
13
+ createUserDmChannel,
14
+ deleteChannelMessage,
15
+ editChannelMessage,
16
+ getChannelMessage,
17
+ pinChannelMessage,
18
+ unpinChannelMessage,
19
+ } from "./api.js";
20
+ import { serializePayload, type MessagePayload } from "./payload.js";
21
+ import type { RequestClient } from "./rest.js";
22
+
23
+ type RawOrId<T> = T | string | { id: string; channelId?: string };
24
+ export type StructureClient = {
25
+ rest: RequestClient;
26
+ fetchUser(id: string): Promise<User>;
27
+ };
28
+
29
+ export class Base {
30
+ constructor(protected client: StructureClient) {}
31
+ }
32
+
33
+ export class User<IsPartial extends boolean = false> extends Base {
34
+ protected _rawData: APIUser | null;
35
+ readonly id: string;
36
+
37
+ constructor(client: StructureClient, rawDataOrId: IsPartial extends true ? string : APIUser) {
38
+ super(client);
39
+ this._rawData = typeof rawDataOrId === "string" ? null : rawDataOrId;
40
+ this.id = typeof rawDataOrId === "string" ? rawDataOrId : rawDataOrId.id;
41
+ }
42
+
43
+ get rawData(): Readonly<APIUser> {
44
+ if (!this._rawData) {
45
+ throw new Error("Partial Discord user has no raw data");
46
+ }
47
+ return this._rawData;
48
+ }
49
+ get partial(): IsPartial {
50
+ return (this._rawData === null) as IsPartial;
51
+ }
52
+ get username() {
53
+ return this._rawData?.username ?? "";
54
+ }
55
+ get globalName() {
56
+ return this._rawData?.global_name;
57
+ }
58
+ get discriminator() {
59
+ return this._rawData?.discriminator;
60
+ }
61
+ get bot() {
62
+ return this._rawData?.bot;
63
+ }
64
+ get avatar() {
65
+ return this._rawData?.avatar;
66
+ }
67
+ get avatarUrl() {
68
+ return this.avatar ? `https://cdn.discordapp.com/avatars/${this.id}/${this.avatar}.png` : null;
69
+ }
70
+ toString(): string {
71
+ return `<@${this.id}>`;
72
+ }
73
+ async fetch(): Promise<User> {
74
+ return this.client.fetchUser(this.id);
75
+ }
76
+ async createDm() {
77
+ return await createUserDmChannel(this.client.rest, this.id);
78
+ }
79
+ async send(data: MessagePayload): Promise<Message> {
80
+ const dm = await this.createDm();
81
+ const message = await createChannelMessage(this.client.rest, dm.id, {
82
+ body: serializePayload(data),
83
+ });
84
+ return new Message(this.client, message);
85
+ }
86
+ }
87
+
88
+ export class Role<IsPartial extends boolean = false> extends Base {
89
+ protected _rawData: APIRole | null;
90
+ readonly id: string;
91
+ constructor(client: StructureClient, rawDataOrId: IsPartial extends true ? string : APIRole) {
92
+ super(client);
93
+ this._rawData = typeof rawDataOrId === "string" ? null : rawDataOrId;
94
+ this.id = typeof rawDataOrId === "string" ? rawDataOrId : rawDataOrId.id;
95
+ }
96
+ get name() {
97
+ return this._rawData?.name ?? "";
98
+ }
99
+ }
100
+
101
+ export class Guild<IsPartial extends boolean = false> extends Base {
102
+ protected _rawData: APIGuild | null;
103
+ readonly id: string;
104
+ constructor(client: StructureClient, rawDataOrId: IsPartial extends true ? string : APIGuild) {
105
+ super(client);
106
+ this._rawData = typeof rawDataOrId === "string" ? null : rawDataOrId;
107
+ this.id = typeof rawDataOrId === "string" ? rawDataOrId : rawDataOrId.id;
108
+ }
109
+ get name() {
110
+ return this._rawData?.name ?? "";
111
+ }
112
+ }
113
+
114
+ export class GuildMember extends Base {
115
+ constructor(
116
+ client: StructureClient,
117
+ public rawData: APIGuildMember,
118
+ ) {
119
+ super(client);
120
+ }
121
+ get user() {
122
+ return this.rawData.user ? new User(this.client, this.rawData.user) : null;
123
+ }
124
+ get roles() {
125
+ return (this.rawData.roles ?? []) as Array<string | Role>;
126
+ }
127
+ get nickname() {
128
+ return this.rawData.nick ?? undefined;
129
+ }
130
+ }
131
+
132
+ export class Message<IsPartial extends boolean = false> extends Base {
133
+ protected _rawData: APIMessage | null;
134
+ readonly id: string;
135
+ readonly channelId: string;
136
+
137
+ constructor(client: StructureClient, rawDataOrIds: RawOrId<APIMessage>) {
138
+ super(client);
139
+ this._rawData =
140
+ typeof rawDataOrIds === "string" || !("author" in rawDataOrIds) ? null : rawDataOrIds;
141
+ this.id = typeof rawDataOrIds === "string" ? rawDataOrIds : rawDataOrIds.id;
142
+ this.channelId =
143
+ typeof rawDataOrIds === "string"
144
+ ? ""
145
+ : "channel_id" in rawDataOrIds
146
+ ? rawDataOrIds.channel_id
147
+ : (rawDataOrIds.channelId ?? "");
148
+ }
149
+
150
+ get rawData(): Readonly<APIMessage> {
151
+ if (!this._rawData) {
152
+ throw new Error("Partial Discord message has no raw data");
153
+ }
154
+ return this._rawData;
155
+ }
156
+ get partial(): IsPartial {
157
+ return (this._rawData === null) as IsPartial;
158
+ }
159
+ get message(): Message<IsPartial> {
160
+ return this;
161
+ }
162
+ get channel_id() {
163
+ return this.channelId;
164
+ }
165
+ get guild_id() {
166
+ return (this._rawData as { guild_id?: string } | null)?.guild_id;
167
+ }
168
+ get guild() {
169
+ return this.guild_id ? new Guild<true>(this.client, this.guild_id) : null;
170
+ }
171
+ get webhookId() {
172
+ return this.webhook_id;
173
+ }
174
+ get webhook_id() {
175
+ return (this._rawData as { webhook_id?: string | null } | null)?.webhook_id ?? null;
176
+ }
177
+ get member() {
178
+ const member = (this._rawData as { member?: APIGuildMember } | null)?.member;
179
+ return member ? new GuildMember(this.client, member) : null;
180
+ }
181
+ get rawMember() {
182
+ return (this._rawData as { member?: APIGuildMember } | null)?.member;
183
+ }
184
+ get content() {
185
+ return this._rawData?.content ?? "";
186
+ }
187
+ get author() {
188
+ return this._rawData?.author ? new User(this.client, this._rawData.author) : null;
189
+ }
190
+ get embeds(): APIEmbed[] {
191
+ return this._rawData?.embeds ?? [];
192
+ }
193
+ get attachments() {
194
+ return this._rawData?.attachments ?? [];
195
+ }
196
+ get stickers() {
197
+ return this._rawData?.sticker_items ?? [];
198
+ }
199
+ get mentionedUsers() {
200
+ return (this._rawData?.mentions ?? []).map((user) => new User(this.client, user));
201
+ }
202
+ get mentionedRoles() {
203
+ return this._rawData?.mention_roles ?? [];
204
+ }
205
+ get mentionedEveryone() {
206
+ return this._rawData?.mention_everyone ?? false;
207
+ }
208
+ get timestamp() {
209
+ return this._rawData?.timestamp;
210
+ }
211
+ get type(): MessageType | undefined {
212
+ return this._rawData?.type;
213
+ }
214
+ get messageReference() {
215
+ return this._rawData?.message_reference;
216
+ }
217
+ get referencedMessage() {
218
+ return this._rawData?.referenced_message
219
+ ? new Message(this.client, this._rawData.referenced_message)
220
+ : null;
221
+ }
222
+ get thread() {
223
+ return this._rawData?.thread ? channelFactory(this.client, this._rawData.thread) : null;
224
+ }
225
+ async fetch(): Promise<Message> {
226
+ const raw = await getChannelMessage(this.client.rest, this.channelId, this.id);
227
+ return new Message(this.client, raw);
228
+ }
229
+ async delete(): Promise<void> {
230
+ await deleteChannelMessage(this.client.rest, this.channelId, this.id);
231
+ }
232
+ async edit(data: MessagePayload): Promise<Message> {
233
+ const raw = await editChannelMessage(this.client.rest, this.channelId, this.id, {
234
+ body: serializePayload(data),
235
+ });
236
+ return new Message(this.client, raw);
237
+ }
238
+ async reply(data: MessagePayload): Promise<Message> {
239
+ const raw = await createChannelMessage(this.client.rest, this.channelId, {
240
+ body: {
241
+ ...serializePayload(data),
242
+ message_reference: { message_id: this.id, fail_if_not_exists: false },
243
+ },
244
+ });
245
+ return new Message(this.client, raw);
246
+ }
247
+ async pin(): Promise<void> {
248
+ await pinChannelMessage(this.client.rest, this.channelId, this.id);
249
+ }
250
+ async unpin(): Promise<void> {
251
+ await unpinChannelMessage(this.client.rest, this.channelId, this.id);
252
+ }
253
+ }
254
+
255
+ export type DiscordChannel = APIChannel & {
256
+ rawData?: APIChannel;
257
+ guildId?: string;
258
+ guild?: Guild;
259
+ name?: string;
260
+ parentId?: string | null;
261
+ };
262
+
263
+ export function channelFactory(
264
+ _client: StructureClient,
265
+ channelData: APIChannel,
266
+ _partial?: boolean,
267
+ ): DiscordChannel {
268
+ return {
269
+ ...channelData,
270
+ rawData: channelData,
271
+ guildId: "guild_id" in channelData ? channelData.guild_id : undefined,
272
+ guild:
273
+ "guild_id" in channelData && typeof channelData.guild_id === "string"
274
+ ? new Guild<true>(_client, channelData.guild_id)
275
+ : undefined,
276
+ parentId: "parent_id" in channelData ? channelData.parent_id : undefined,
277
+ } as DiscordChannel;
278
+ }
@@ -0,0 +1,163 @@
1
+ import { ComponentType, InteractionType } from "discord-api-types/v10";
2
+ import { vi, type Mock } from "vitest";
3
+ import { Client } from "./client.js";
4
+ import type { BaseCommand } from "./commands.js";
5
+ import type { RawInteraction } from "./interactions.js";
6
+ import type { QueuedRequest, RequestClient, RequestData } from "./rest.js";
7
+
8
+ type RestMock = Partial<Record<"get" | "post" | "patch" | "put" | "delete", Mock>>;
9
+ type RestMethod = "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
10
+ type RawInteractionOverrides = Omit<Partial<RawInteraction>, "data" | "type"> &
11
+ Pick<RawInteraction, "id" | "token"> & {
12
+ data?: Record<string, unknown>;
13
+ };
14
+
15
+ type FakeRestCall = {
16
+ method: RestMethod;
17
+ path: string;
18
+ data?: RequestData;
19
+ query?: QueuedRequest["query"];
20
+ };
21
+
22
+ type FakeRestClient = RequestClient & {
23
+ calls: FakeRestCall[];
24
+ enqueueResponse: (value: unknown) => void;
25
+ };
26
+
27
+ export function createDeferred<T>() {
28
+ let resolve: ((value: T) => void) | undefined;
29
+ const promise = new Promise<T>((res) => {
30
+ resolve = res;
31
+ });
32
+ return { promise, resolve: resolve! };
33
+ }
34
+
35
+ export function createJsonResponse(body: unknown, init?: ResponseInit): Response {
36
+ return new Response(JSON.stringify(body), {
37
+ status: 200,
38
+ ...init,
39
+ });
40
+ }
41
+
42
+ export function createAbortableFetchMock() {
43
+ let receivedSignal: AbortSignal | undefined;
44
+ const fetch = vi.fn(
45
+ (_input: string | URL | Request, init?: RequestInit) =>
46
+ new Promise<Response>((_resolve, reject) => {
47
+ receivedSignal = init?.signal ?? undefined;
48
+ init?.signal?.addEventListener("abort", () => {
49
+ reject(new DOMException("The operation was aborted.", "AbortError"));
50
+ });
51
+ }),
52
+ );
53
+ return {
54
+ fetch,
55
+ get receivedSignal() {
56
+ return receivedSignal;
57
+ },
58
+ };
59
+ }
60
+
61
+ export function createInternalTestClient(commands: BaseCommand[] = []): Client {
62
+ return new Client(
63
+ {
64
+ baseUrl: "http://localhost",
65
+ clientId: "app1",
66
+ publicKey: "public",
67
+ token: "token",
68
+ },
69
+ { commands },
70
+ );
71
+ }
72
+
73
+ function createRestMock(overrides: RestMock = {}): RestMock & RequestClient {
74
+ return {
75
+ get: vi.fn(async () => undefined),
76
+ post: vi.fn(async () => undefined),
77
+ patch: vi.fn(async () => undefined),
78
+ put: vi.fn(async () => undefined),
79
+ delete: vi.fn(async () => undefined),
80
+ ...overrides,
81
+ } as RestMock & RequestClient;
82
+ }
83
+
84
+ export function attachRestMock(client: Client, rest: RestMock): RestMock & RequestClient {
85
+ const mock = createRestMock(rest);
86
+ client.rest = mock;
87
+ return mock;
88
+ }
89
+
90
+ export function createFakeRestClient(responses: unknown[] = []): FakeRestClient {
91
+ const calls: FakeRestCall[] = [];
92
+ const queued = [...responses];
93
+ const request = async (
94
+ method: RestMethod,
95
+ path: string,
96
+ data?: RequestData,
97
+ query?: QueuedRequest["query"],
98
+ ) => {
99
+ calls.push({ method, path, data, query });
100
+ return queued.shift();
101
+ };
102
+ return {
103
+ calls,
104
+ enqueueResponse: (value: unknown) => {
105
+ queued.push(value);
106
+ },
107
+ get: async (path, query) => await request("GET", path, undefined, query),
108
+ post: async (path, data, query) => await request("POST", path, data, query),
109
+ patch: async (path, data, query) => await request("PATCH", path, data, query),
110
+ put: async (path, data, query) => await request("PUT", path, data, query),
111
+ delete: async (path, data, query) => await request("DELETE", path, data, query),
112
+ } as FakeRestClient;
113
+ }
114
+
115
+ export function createInternalInteractionPayload(
116
+ overrides: Partial<RawInteraction> & Pick<RawInteraction, "id" | "token">,
117
+ ): RawInteraction {
118
+ return {
119
+ application_id: "app1",
120
+ type: InteractionType.ApplicationCommand,
121
+ version: 1,
122
+ data: {
123
+ id: "command1",
124
+ name: "test",
125
+ type: 1,
126
+ },
127
+ ...overrides,
128
+ } as unknown as RawInteraction;
129
+ }
130
+
131
+ export function createInternalComponentInteractionPayload(
132
+ overrides: RawInteractionOverrides,
133
+ ): RawInteraction {
134
+ const { data, ...rest } = overrides;
135
+ return {
136
+ application_id: "app1",
137
+ version: 1,
138
+ data: {
139
+ component_type: ComponentType.Button,
140
+ custom_id: "component1",
141
+ ...data,
142
+ },
143
+ ...rest,
144
+ type: InteractionType.MessageComponent,
145
+ } as unknown as RawInteraction;
146
+ }
147
+
148
+ export function createInternalModalInteractionPayload(
149
+ overrides: RawInteractionOverrides,
150
+ ): RawInteraction {
151
+ const { data, ...rest } = overrides;
152
+ return {
153
+ application_id: "app1",
154
+ version: 1,
155
+ data: {
156
+ custom_id: "modal1",
157
+ components: [],
158
+ ...data,
159
+ },
160
+ ...rest,
161
+ type: InteractionType.ModalSubmit,
162
+ } as unknown as RawInteraction;
163
+ }
@@ -0,0 +1,49 @@
1
+ import type {
2
+ DiscordGatewayAdapterCreator,
3
+ DiscordGatewayAdapterLibraryMethods,
4
+ } from "@discordjs/voice";
5
+ import type { GatewaySendPayload } from "discord-api-types/v10";
6
+ import { Plugin, type Client } from "./client.js";
7
+ import type { GatewayPlugin } from "./gateway.js";
8
+
9
+ export class VoicePlugin extends Plugin {
10
+ readonly id = "voice";
11
+ protected client?: Client;
12
+ readonly adapters = new Map<string, DiscordGatewayAdapterLibraryMethods>();
13
+ private gatewayPlugin?: GatewayPlugin;
14
+
15
+ registerClient(client: Client): void {
16
+ this.client = client;
17
+ this.gatewayPlugin = client.getPlugin<GatewayPlugin>("gateway");
18
+ if (!this.gatewayPlugin) {
19
+ throw new Error("Discord voice cannot be used without a gateway connection.");
20
+ }
21
+ }
22
+
23
+ getGateway(_guildId: string): GatewayPlugin | undefined {
24
+ return this.gatewayPlugin;
25
+ }
26
+
27
+ getGatewayAdapterCreator(guildId: string): DiscordGatewayAdapterCreator {
28
+ const gateway = this.getGateway(guildId);
29
+ if (!gateway) {
30
+ throw new Error("Discord voice cannot be used without a gateway connection.");
31
+ }
32
+ return (methods) => {
33
+ this.adapters.set(guildId, methods);
34
+ return {
35
+ sendPayload(payload) {
36
+ try {
37
+ gateway.send(payload as GatewaySendPayload, true);
38
+ return true;
39
+ } catch {
40
+ return false;
41
+ }
42
+ },
43
+ destroy: () => {
44
+ this.adapters.delete(guildId);
45
+ },
46
+ };
47
+ };
48
+ }
49
+ }
@@ -0,0 +1,28 @@
1
+ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
2
+
3
+ const DISCORD_VIDEO_MEDIA_EXTENSIONS = new Set([".avi", ".m4v", ".mkv", ".mov", ".mp4", ".webm"]);
4
+
5
+ function normalizeMediaPathForExtension(mediaUrl: string): string {
6
+ const trimmed = mediaUrl.trim();
7
+ if (!trimmed) {
8
+ return "";
9
+ }
10
+ try {
11
+ const parsed = new URL(trimmed);
12
+ return normalizeLowercaseStringOrEmpty(parsed.pathname);
13
+ } catch {
14
+ const withoutHash = trimmed.split("#", 1)[0] ?? trimmed;
15
+ const withoutQuery = withoutHash.split("?", 1)[0] ?? withoutHash;
16
+ return normalizeLowercaseStringOrEmpty(withoutQuery);
17
+ }
18
+ }
19
+
20
+ export function isLikelyDiscordVideoMedia(mediaUrl: string): boolean {
21
+ const normalized = normalizeMediaPathForExtension(mediaUrl);
22
+ for (const ext of DISCORD_VIDEO_MEDIA_EXTENSIONS) {
23
+ if (normalized.endsWith(ext)) {
24
+ return true;
25
+ }
26
+ }
27
+ return false;
28
+ }
@@ -0,0 +1,111 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import {
3
+ __resetDiscordDirectoryCacheForTest,
4
+ rememberDiscordDirectoryUser,
5
+ } from "./directory-cache.js";
6
+ import { formatMention, rewriteDiscordKnownMentions } from "./mentions.js";
7
+
8
+ describe("formatMention", () => {
9
+ it("formats user mentions from ids", () => {
10
+ expect(formatMention({ userId: "123456789" })).toBe("<@123456789>");
11
+ });
12
+
13
+ it("formats role mentions from ids", () => {
14
+ expect(formatMention({ roleId: "987654321" })).toBe("<@&987654321>");
15
+ });
16
+
17
+ it("formats channel mentions from ids", () => {
18
+ expect(formatMention({ channelId: "777555333" })).toBe("<#777555333>");
19
+ });
20
+
21
+ it("throws when no mention id is provided", () => {
22
+ expect(() => formatMention({})).toThrow(/exactly one/i);
23
+ });
24
+
25
+ it("throws when more than one mention id is provided", () => {
26
+ expect(() => formatMention({ userId: "1", roleId: "2" })).toThrow(/exactly one/i);
27
+ });
28
+ });
29
+
30
+ describe("rewriteDiscordKnownMentions", () => {
31
+ beforeEach(() => {
32
+ __resetDiscordDirectoryCacheForTest();
33
+ });
34
+
35
+ it("rewrites @name mentions when a cached user id exists", () => {
36
+ rememberDiscordDirectoryUser({
37
+ accountId: "default",
38
+ userId: "123456789",
39
+ handles: ["Alice", "@alice_user", "alice#1234"],
40
+ });
41
+ const rewritten = rewriteDiscordKnownMentions("ping @Alice and @alice_user", {
42
+ accountId: "default",
43
+ });
44
+ expect(rewritten).toBe("ping <@123456789> and <@123456789>");
45
+ });
46
+
47
+ it("rewrites configured mention aliases before the cache", () => {
48
+ rememberDiscordDirectoryUser({
49
+ accountId: "default",
50
+ userId: "111111111",
51
+ handles: ["vladislava"],
52
+ });
53
+ const rewritten = rewriteDiscordKnownMentions("ping @Vladislava and @BuildBot#1234", {
54
+ accountId: "default",
55
+ mentionAliases: {
56
+ BuildBot: "222222222",
57
+ Vladislava: "333333333",
58
+ },
59
+ });
60
+ expect(rewritten).toBe("ping <@333333333> and <@222222222>");
61
+ });
62
+
63
+ it("supports configured aliases with a leading @ key", () => {
64
+ const rewritten = rewriteDiscordKnownMentions("ping @OpsLead", {
65
+ accountId: "default",
66
+ mentionAliases: {
67
+ "@opslead": "444444444",
68
+ },
69
+ });
70
+ expect(rewritten).toBe("ping <@444444444>");
71
+ });
72
+
73
+ it("preserves unknown mentions and reserved mentions", () => {
74
+ rememberDiscordDirectoryUser({
75
+ accountId: "default",
76
+ userId: "123456789",
77
+ handles: ["alice"],
78
+ });
79
+ const rewritten = rewriteDiscordKnownMentions("hello @unknown @everyone @here", {
80
+ accountId: "default",
81
+ });
82
+ expect(rewritten).toBe("hello @unknown @everyone @here");
83
+ });
84
+
85
+ it("does not rewrite mentions inside markdown code spans", () => {
86
+ rememberDiscordDirectoryUser({
87
+ accountId: "default",
88
+ userId: "123456789",
89
+ handles: ["alice"],
90
+ });
91
+ const rewritten = rewriteDiscordKnownMentions(
92
+ "inline `@alice` fence ```\n@alice\n``` text @alice",
93
+ {
94
+ accountId: "default",
95
+ },
96
+ );
97
+ expect(rewritten).toBe("inline `@alice` fence ```\n@alice\n``` text <@123456789>");
98
+ });
99
+
100
+ it("is account-scoped", () => {
101
+ rememberDiscordDirectoryUser({
102
+ accountId: "ops",
103
+ userId: "999888777",
104
+ handles: ["alice"],
105
+ });
106
+ const defaultRewrite = rewriteDiscordKnownMentions("@alice", { accountId: "default" });
107
+ const opsRewrite = rewriteDiscordKnownMentions("@alice", { accountId: "ops" });
108
+ expect(defaultRewrite).toBe("@alice");
109
+ expect(opsRewrite).toBe("<@999888777>");
110
+ });
111
+ });