@openclaw/discord 2026.3.13 → 2026.5.2-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (502) hide show
  1. package/account-inspect-api.ts +6 -0
  2. package/action-runtime-api.ts +1 -0
  3. package/api.ts +132 -0
  4. package/channel-config-api.ts +1 -0
  5. package/channel-plugin-api.ts +3 -0
  6. package/config-api.ts +4 -0
  7. package/configured-state.ts +6 -0
  8. package/contract-api.ts +21 -0
  9. package/directory-contract-api.ts +4 -0
  10. package/doctor-contract-api.ts +1 -0
  11. package/index.test.ts +13 -0
  12. package/index.ts +18 -13
  13. package/openclaw.plugin.json +3326 -1
  14. package/package.json +68 -2
  15. package/runtime-api.actions.ts +15 -0
  16. package/runtime-api.lookup.ts +22 -0
  17. package/runtime-api.monitor.ts +50 -0
  18. package/runtime-api.send.ts +79 -0
  19. package/runtime-api.threads.ts +30 -0
  20. package/runtime-api.ts +180 -0
  21. package/runtime-setter-api.ts +3 -0
  22. package/secret-contract-api.ts +4 -0
  23. package/security-audit-contract-api.ts +1 -0
  24. package/security-contract-api.ts +4 -0
  25. package/session-key-api.ts +1 -0
  26. package/setup-entry.ts +9 -0
  27. package/setup-plugin-api.ts +3 -0
  28. package/src/account-inspect.test.ts +126 -0
  29. package/src/account-inspect.ts +132 -0
  30. package/src/accounts.test.ts +247 -0
  31. package/src/accounts.ts +196 -0
  32. package/src/actions/handle-action.guild-admin.ts +411 -0
  33. package/src/actions/handle-action.test.ts +306 -0
  34. package/src/actions/handle-action.ts +372 -0
  35. package/src/actions/runtime.guild.ts +446 -0
  36. package/src/actions/runtime.messaging.messages.ts +205 -0
  37. package/src/actions/runtime.messaging.reactions.ts +67 -0
  38. package/src/actions/runtime.messaging.runtime.ts +69 -0
  39. package/src/actions/runtime.messaging.send.ts +248 -0
  40. package/src/actions/runtime.messaging.shared.ts +97 -0
  41. package/src/actions/runtime.messaging.ts +37 -0
  42. package/src/actions/runtime.moderation-shared.ts +48 -0
  43. package/src/actions/runtime.moderation.authz.test.ts +151 -0
  44. package/src/actions/runtime.moderation.ts +116 -0
  45. package/src/actions/runtime.presence.test.ts +160 -0
  46. package/src/actions/runtime.presence.ts +117 -0
  47. package/src/actions/runtime.shared.ts +83 -0
  48. package/src/actions/runtime.test.ts +1087 -0
  49. package/src/actions/runtime.ts +87 -0
  50. package/src/api-barrel.test.ts +80 -0
  51. package/src/api.test.ts +130 -0
  52. package/src/api.ts +169 -0
  53. package/src/approval-handler.runtime.test.ts +41 -0
  54. package/src/approval-handler.runtime.ts +632 -0
  55. package/src/approval-native.test.ts +330 -0
  56. package/src/approval-native.ts +219 -0
  57. package/src/approval-runtime.ts +14 -0
  58. package/src/approval-shared.ts +53 -0
  59. package/src/audit-core.ts +141 -0
  60. package/src/audit.test.ts +145 -0
  61. package/src/audit.ts +32 -0
  62. package/src/channel-actions.contract.test.ts +45 -0
  63. package/src/channel-actions.runtime.ts +1 -0
  64. package/src/channel-actions.test.ts +275 -0
  65. package/src/channel-actions.ts +203 -0
  66. package/src/channel-api.ts +29 -0
  67. package/src/channel.conversation.ts +159 -0
  68. package/src/channel.loaders.ts +47 -0
  69. package/src/channel.runtime.ts +1 -0
  70. package/src/channel.setup.ts +12 -0
  71. package/src/channel.test.ts +547 -12
  72. package/src/channel.ts +597 -430
  73. package/src/chunk.test.ts +157 -0
  74. package/src/chunk.ts +321 -0
  75. package/src/client.proxy.test.ts +176 -0
  76. package/src/client.test.ts +76 -0
  77. package/src/client.ts +132 -0
  78. package/src/component-custom-id.ts +72 -0
  79. package/src/components-registry.ts +356 -0
  80. package/src/components.builders.ts +409 -0
  81. package/src/components.modal.ts +124 -0
  82. package/src/components.parse.ts +407 -0
  83. package/src/components.test.ts +312 -0
  84. package/src/components.ts +54 -0
  85. package/src/components.types.ts +187 -0
  86. package/src/config-schema.test.ts +325 -0
  87. package/src/config-schema.ts +6 -0
  88. package/src/config-ui-hints.ts +249 -0
  89. package/src/conversation-identity.ts +58 -0
  90. package/src/delivery-retry.ts +56 -0
  91. package/src/directory-cache.ts +116 -0
  92. package/src/directory-config.ts +58 -0
  93. package/src/directory-contract.test.ts +129 -0
  94. package/src/directory-live.test.ts +126 -0
  95. package/src/directory-live.ts +135 -0
  96. package/src/doctor-contract.ts +477 -0
  97. package/src/doctor-shared.ts +5 -0
  98. package/src/doctor.test.ts +405 -0
  99. package/src/doctor.ts +340 -0
  100. package/src/draft-chunking.test.ts +64 -0
  101. package/src/draft-chunking.ts +43 -0
  102. package/src/draft-stream.test.ts +159 -0
  103. package/src/draft-stream.ts +154 -0
  104. package/src/error-body.ts +38 -0
  105. package/src/exec-approvals.test.ts +88 -0
  106. package/src/exec-approvals.ts +110 -0
  107. package/src/gateway-logging.test.ts +98 -0
  108. package/src/gateway-logging.ts +67 -0
  109. package/src/group-policy.ts +113 -0
  110. package/src/guilds.ts +29 -0
  111. package/src/inbound-context.contract.test.ts +11 -0
  112. package/src/interactive-dispatch.ts +104 -0
  113. package/src/internal/api.commands.ts +51 -0
  114. package/src/internal/api.guild.ts +164 -0
  115. package/src/internal/api.interactions.ts +53 -0
  116. package/src/internal/api.messages.ts +113 -0
  117. package/src/internal/api.reactions.ts +38 -0
  118. package/src/internal/api.test.ts +262 -0
  119. package/src/internal/api.ts +61 -0
  120. package/src/internal/api.users.ts +19 -0
  121. package/src/internal/api.webhooks.ts +13 -0
  122. package/src/internal/client.test.ts +408 -0
  123. package/src/internal/client.ts +308 -0
  124. package/src/internal/command-deploy.ts +237 -0
  125. package/src/internal/commands.ts +188 -0
  126. package/src/internal/components.base.ts +65 -0
  127. package/src/internal/components.message.ts +279 -0
  128. package/src/internal/components.modal.ts +95 -0
  129. package/src/internal/components.ts +31 -0
  130. package/src/internal/discord.ts +11 -0
  131. package/src/internal/embeds.ts +35 -0
  132. package/src/internal/entity-cache.ts +98 -0
  133. package/src/internal/event-queue.ts +162 -0
  134. package/src/internal/gateway-close-codes.ts +25 -0
  135. package/src/internal/gateway-dispatch.ts +96 -0
  136. package/src/internal/gateway-identify-limiter.ts +26 -0
  137. package/src/internal/gateway-lifecycle.ts +61 -0
  138. package/src/internal/gateway-rate-limit.ts +104 -0
  139. package/src/internal/gateway.test.ts +603 -0
  140. package/src/internal/gateway.ts +476 -0
  141. package/src/internal/interaction-dispatch.test.ts +148 -0
  142. package/src/internal/interaction-dispatch.ts +162 -0
  143. package/src/internal/interaction-options.ts +98 -0
  144. package/src/internal/interaction-response.ts +53 -0
  145. package/src/internal/interactions.test.ts +325 -0
  146. package/src/internal/interactions.ts +378 -0
  147. package/src/internal/listeners.ts +85 -0
  148. package/src/internal/live-smoke.live.test.ts +26 -0
  149. package/src/internal/modal-fields.ts +95 -0
  150. package/src/internal/payload.ts +69 -0
  151. package/src/internal/rest-body.ts +115 -0
  152. package/src/internal/rest-errors.ts +88 -0
  153. package/src/internal/rest-routes.ts +50 -0
  154. package/src/internal/rest-scheduler.ts +557 -0
  155. package/src/internal/rest.test.ts +673 -0
  156. package/src/internal/rest.ts +322 -0
  157. package/src/internal/schemas.ts +36 -0
  158. package/src/internal/structures.test.ts +43 -0
  159. package/src/internal/structures.ts +280 -0
  160. package/src/internal/test-builders.test-support.ts +163 -0
  161. package/src/internal/voice.ts +49 -0
  162. package/src/media-detection.ts +28 -0
  163. package/src/mentions.test.ts +111 -0
  164. package/src/mentions.ts +147 -0
  165. package/src/monitor/access-groups.ts +55 -0
  166. package/src/monitor/ack-reactions.ts +70 -0
  167. package/src/monitor/acp-bind-here.integration.test.ts +211 -0
  168. package/src/monitor/agent-components-auth.ts +7 -0
  169. package/src/monitor/agent-components-context.ts +154 -0
  170. package/src/monitor/agent-components-data.ts +224 -0
  171. package/src/monitor/agent-components-dm-auth.ts +221 -0
  172. package/src/monitor/agent-components-guild-auth.ts +322 -0
  173. package/src/monitor/agent-components-helpers.runtime.ts +5 -0
  174. package/src/monitor/agent-components-helpers.ts +34 -0
  175. package/src/monitor/agent-components-reply.ts +10 -0
  176. package/src/monitor/agent-components.deps.runtime.ts +2 -0
  177. package/src/monitor/agent-components.dispatch.ts +366 -0
  178. package/src/monitor/agent-components.handlers.ts +303 -0
  179. package/src/monitor/agent-components.modal.ts +160 -0
  180. package/src/monitor/agent-components.plugin-interactive.ts +187 -0
  181. package/src/monitor/agent-components.runtime.ts +14 -0
  182. package/src/monitor/agent-components.system-controls.ts +211 -0
  183. package/src/monitor/agent-components.ts +70 -0
  184. package/src/monitor/agent-components.types.ts +58 -0
  185. package/src/monitor/agent-components.wildcard-controls.ts +168 -0
  186. package/src/monitor/agent-components.wildcard.test.ts +71 -0
  187. package/src/monitor/allow-list.test.ts +14 -0
  188. package/src/monitor/allow-list.ts +633 -0
  189. package/src/monitor/auto-presence.test.ts +156 -0
  190. package/src/monitor/auto-presence.ts +356 -0
  191. package/src/monitor/channel-access.test.ts +99 -0
  192. package/src/monitor/channel-access.ts +102 -0
  193. package/src/monitor/commands.test.ts +24 -0
  194. package/src/monitor/commands.ts +9 -0
  195. package/src/monitor/dm-command-auth.test.ts +197 -0
  196. package/src/monitor/dm-command-auth.ts +158 -0
  197. package/src/monitor/dm-command-decision.test.ts +113 -0
  198. package/src/monitor/dm-command-decision.ts +49 -0
  199. package/src/monitor/exec-approvals.test.ts +226 -0
  200. package/src/monitor/exec-approvals.ts +158 -0
  201. package/src/monitor/format.ts +45 -0
  202. package/src/monitor/gateway-handle.ts +34 -0
  203. package/src/monitor/gateway-metadata.test.ts +29 -0
  204. package/src/monitor/gateway-metadata.ts +298 -0
  205. package/src/monitor/gateway-plugin.test.ts +297 -0
  206. package/src/monitor/gateway-plugin.ts +294 -0
  207. package/src/monitor/gateway-registry.ts +37 -0
  208. package/src/monitor/gateway-supervisor.test.ts +150 -0
  209. package/src/monitor/gateway-supervisor.ts +206 -0
  210. package/src/monitor/inbound-context.test-helpers.ts +37 -0
  211. package/src/monitor/inbound-context.test.ts +106 -0
  212. package/src/monitor/inbound-context.ts +103 -0
  213. package/src/monitor/inbound-dedupe.ts +79 -0
  214. package/src/monitor/inbound-job.test.ts +203 -0
  215. package/src/monitor/inbound-job.ts +118 -0
  216. package/src/monitor/listeners.queue.ts +91 -0
  217. package/src/monitor/listeners.reactions.ts +610 -0
  218. package/src/monitor/listeners.test.ts +200 -0
  219. package/src/monitor/listeners.ts +150 -0
  220. package/src/monitor/message-channel-info.ts +96 -0
  221. package/src/monitor/message-forwarded.ts +107 -0
  222. package/src/monitor/message-handler.batch-gate.test.ts +22 -0
  223. package/src/monitor/message-handler.batch-gate.ts +19 -0
  224. package/src/monitor/message-handler.bot-self-filter.test.ts +68 -0
  225. package/src/monitor/message-handler.context.ts +406 -0
  226. package/src/monitor/message-handler.dm-preflight.ts +123 -0
  227. package/src/monitor/message-handler.draft-preview.ts +246 -0
  228. package/src/monitor/message-handler.hydration.test.ts +80 -0
  229. package/src/monitor/message-handler.hydration.ts +198 -0
  230. package/src/monitor/message-handler.inbound-context.test.ts +59 -0
  231. package/src/monitor/message-handler.module-test-helpers.ts +31 -0
  232. package/src/monitor/message-handler.preflight-channel-access.ts +86 -0
  233. package/src/monitor/message-handler.preflight-channel-context.test.ts +18 -0
  234. package/src/monitor/message-handler.preflight-channel-context.ts +58 -0
  235. package/src/monitor/message-handler.preflight-context.ts +54 -0
  236. package/src/monitor/message-handler.preflight-helpers.ts +164 -0
  237. package/src/monitor/message-handler.preflight-history.ts +23 -0
  238. package/src/monitor/message-handler.preflight-logging.ts +36 -0
  239. package/src/monitor/message-handler.preflight-pluralkit.ts +26 -0
  240. package/src/monitor/message-handler.preflight-runtime.ts +28 -0
  241. package/src/monitor/message-handler.preflight-thread.ts +49 -0
  242. package/src/monitor/message-handler.preflight.acp-bindings.test.ts +369 -0
  243. package/src/monitor/message-handler.preflight.test-helpers.ts +111 -0
  244. package/src/monitor/message-handler.preflight.test.ts +1623 -0
  245. package/src/monitor/message-handler.preflight.ts +679 -0
  246. package/src/monitor/message-handler.preflight.types.ts +110 -0
  247. package/src/monitor/message-handler.process.test.ts +1369 -0
  248. package/src/monitor/message-handler.process.ts +686 -0
  249. package/src/monitor/message-handler.queue.test.ts +496 -0
  250. package/src/monitor/message-handler.routing-preflight.ts +112 -0
  251. package/src/monitor/message-handler.test-harness.ts +99 -0
  252. package/src/monitor/message-handler.test-helpers.ts +75 -0
  253. package/src/monitor/message-handler.ts +274 -0
  254. package/src/monitor/message-media.ts +509 -0
  255. package/src/monitor/message-run-queue.ts +101 -0
  256. package/src/monitor/message-text.ts +171 -0
  257. package/src/monitor/message-utils.test.ts +1157 -0
  258. package/src/monitor/message-utils.ts +32 -0
  259. package/src/monitor/model-picker-preferences.test.ts +67 -0
  260. package/src/monitor/model-picker-preferences.ts +184 -0
  261. package/src/monitor/model-picker.state.ts +364 -0
  262. package/src/monitor/model-picker.test-utils.ts +26 -0
  263. package/src/monitor/model-picker.test.ts +794 -0
  264. package/src/monitor/model-picker.ts +38 -0
  265. package/src/monitor/model-picker.view.ts +695 -0
  266. package/src/monitor/monitor.agent-components.test.ts +375 -0
  267. package/src/monitor/monitor.test.ts +849 -0
  268. package/src/monitor/monitor.threading-utils.test.ts +598 -0
  269. package/src/monitor/native-command-agent-reply.ts +125 -0
  270. package/src/monitor/native-command-arg-ui.ts +233 -0
  271. package/src/monitor/native-command-auth.ts +308 -0
  272. package/src/monitor/native-command-bypass.ts +13 -0
  273. package/src/monitor/native-command-context.test.ts +98 -0
  274. package/src/monitor/native-command-context.ts +103 -0
  275. package/src/monitor/native-command-dispatch.ts +35 -0
  276. package/src/monitor/native-command-model-picker-apply.ts +177 -0
  277. package/src/monitor/native-command-model-picker-interaction.ts +461 -0
  278. package/src/monitor/native-command-model-picker-ui.ts +368 -0
  279. package/src/monitor/native-command-reply.test.ts +68 -0
  280. package/src/monitor/native-command-reply.ts +185 -0
  281. package/src/monitor/native-command-route.ts +91 -0
  282. package/src/monitor/native-command-status.ts +76 -0
  283. package/src/monitor/native-command-ui.ts +26 -0
  284. package/src/monitor/native-command-ui.types.ts +20 -0
  285. package/src/monitor/native-command.args.ts +45 -0
  286. package/src/monitor/native-command.command-arg.test.ts +99 -0
  287. package/src/monitor/native-command.commands-allowfrom.test.ts +490 -0
  288. package/src/monitor/native-command.model-picker.test.ts +767 -0
  289. package/src/monitor/native-command.options.test.ts +369 -0
  290. package/src/monitor/native-command.options.ts +153 -0
  291. package/src/monitor/native-command.plugin-dispatch.test.ts +961 -0
  292. package/src/monitor/native-command.runtime.ts +50 -0
  293. package/src/monitor/native-command.status-direct.test.ts +272 -0
  294. package/src/monitor/native-command.test-helpers.ts +64 -0
  295. package/src/monitor/native-command.think-autocomplete.test.ts +416 -0
  296. package/src/monitor/native-command.ts +700 -0
  297. package/src/monitor/native-command.types.ts +9 -0
  298. package/src/monitor/native-interaction-channel-context.ts +50 -0
  299. package/src/monitor/preflight-audio.runtime.ts +9 -0
  300. package/src/monitor/preflight-audio.test.ts +157 -0
  301. package/src/monitor/preflight-audio.ts +130 -0
  302. package/src/monitor/presence-cache.ts +61 -0
  303. package/src/monitor/presence.test.ts +44 -0
  304. package/src/monitor/presence.ts +50 -0
  305. package/src/monitor/provider-session.runtime.ts +12 -0
  306. package/src/monitor/provider.acp.ts +89 -0
  307. package/src/monitor/provider.allowlist.test.ts +149 -0
  308. package/src/monitor/provider.allowlist.ts +394 -0
  309. package/src/monitor/provider.cleanup.ts +41 -0
  310. package/src/monitor/provider.commands.ts +129 -0
  311. package/src/monitor/provider.config-log.ts +45 -0
  312. package/src/monitor/provider.deploy-errors.ts +362 -0
  313. package/src/monitor/provider.deploy.ts +221 -0
  314. package/src/monitor/provider.interactions.ts +160 -0
  315. package/src/monitor/provider.lifecycle.test.ts +713 -0
  316. package/src/monitor/provider.lifecycle.ts +552 -0
  317. package/src/monitor/provider.proxy.test.ts +745 -0
  318. package/src/monitor/provider.rest-proxy.test.ts +121 -0
  319. package/src/monitor/provider.runtime.ts +1 -0
  320. package/src/monitor/provider.skill-dedupe.test.ts +42 -0
  321. package/src/monitor/provider.startup-log.ts +32 -0
  322. package/src/monitor/provider.startup.test.ts +426 -0
  323. package/src/monitor/provider.startup.ts +323 -0
  324. package/src/monitor/provider.test.ts +1111 -0
  325. package/src/monitor/provider.ts +713 -0
  326. package/src/monitor/reply-context.ts +64 -0
  327. package/src/monitor/reply-delivery.test.ts +244 -0
  328. package/src/monitor/reply-delivery.ts +203 -0
  329. package/src/monitor/rest-fetch.ts +43 -0
  330. package/src/monitor/route-resolution.test.ts +204 -0
  331. package/src/monitor/route-resolution.ts +140 -0
  332. package/src/monitor/sender-identity.ts +81 -0
  333. package/src/monitor/startup-status.test.ts +30 -0
  334. package/src/monitor/startup-status.ts +10 -0
  335. package/src/monitor/status.ts +22 -0
  336. package/src/monitor/system-events.ts +55 -0
  337. package/src/monitor/thread-bindings.config.ts +35 -0
  338. package/src/monitor/thread-bindings.discord-api.test.ts +229 -0
  339. package/src/monitor/thread-bindings.discord-api.ts +310 -0
  340. package/src/monitor/thread-bindings.lifecycle.test.ts +1871 -0
  341. package/src/monitor/thread-bindings.lifecycle.ts +354 -0
  342. package/src/monitor/thread-bindings.manager.ts +553 -0
  343. package/src/monitor/thread-bindings.messages.ts +6 -0
  344. package/src/monitor/thread-bindings.persona.test.ts +34 -0
  345. package/src/monitor/thread-bindings.persona.ts +25 -0
  346. package/src/monitor/thread-bindings.session-adapter.ts +229 -0
  347. package/src/monitor/thread-bindings.session-shared.ts +59 -0
  348. package/src/monitor/thread-bindings.session-updates.ts +35 -0
  349. package/src/monitor/thread-bindings.shared-state.test.ts +36 -0
  350. package/src/monitor/thread-bindings.state.ts +540 -0
  351. package/src/monitor/thread-bindings.ts +48 -0
  352. package/src/monitor/thread-bindings.types.ts +83 -0
  353. package/src/monitor/thread-channel-context.ts +112 -0
  354. package/src/monitor/thread-session-close.test.ts +180 -0
  355. package/src/monitor/thread-session-close.ts +63 -0
  356. package/src/monitor/thread-title.generate.test.ts +197 -0
  357. package/src/monitor/thread-title.test.ts +31 -0
  358. package/src/monitor/thread-title.ts +181 -0
  359. package/src/monitor/threading.auto-thread.test.ts +327 -0
  360. package/src/monitor/threading.auto-thread.ts +287 -0
  361. package/src/monitor/threading.cache.ts +45 -0
  362. package/src/monitor/threading.parent-info.test.ts +156 -0
  363. package/src/monitor/threading.starter.test.ts +260 -0
  364. package/src/monitor/threading.starter.ts +287 -0
  365. package/src/monitor/threading.ts +20 -0
  366. package/src/monitor/threading.types.ts +102 -0
  367. package/src/monitor/timeouts.ts +84 -0
  368. package/src/monitor/typing.test.ts +42 -0
  369. package/src/monitor/typing.ts +17 -0
  370. package/src/monitor.gateway.test.ts +187 -0
  371. package/src/monitor.gateway.ts +75 -0
  372. package/src/monitor.test.ts +1397 -0
  373. package/src/monitor.ts +28 -0
  374. package/src/normalize.test.ts +56 -0
  375. package/src/normalize.ts +86 -0
  376. package/src/outbound-adapter.interactive-order.test.ts +64 -0
  377. package/src/outbound-adapter.test-harness.ts +207 -0
  378. package/src/outbound-adapter.test.ts +696 -0
  379. package/src/outbound-adapter.ts +291 -0
  380. package/src/outbound-approval.ts +29 -0
  381. package/src/outbound-components.ts +81 -0
  382. package/src/outbound-payload.contract.test.ts +38 -0
  383. package/src/outbound-payload.ts +134 -0
  384. package/src/outbound-send-context.ts +92 -0
  385. package/src/outbound-session-route.test.ts +34 -0
  386. package/src/outbound-session-route.ts +72 -0
  387. package/src/pluralkit.test.ts +67 -0
  388. package/src/pluralkit.ts +58 -0
  389. package/src/preview-streaming.ts +32 -0
  390. package/src/probe.intents.test.ts +94 -0
  391. package/src/probe.parse-token.test.ts +43 -0
  392. package/src/probe.runtime.ts +1 -0
  393. package/src/probe.ts +237 -0
  394. package/src/proxy-fetch.ts +92 -0
  395. package/src/proxy-request-client.test.ts +78 -0
  396. package/src/proxy-request-client.ts +21 -0
  397. package/src/recipient-resolution.ts +39 -0
  398. package/src/resolve-allowlist-common.test.ts +36 -0
  399. package/src/resolve-allowlist-common.ts +39 -0
  400. package/src/resolve-channels.test.ts +340 -0
  401. package/src/resolve-channels.ts +369 -0
  402. package/src/resolve-users.test.ts +222 -0
  403. package/src/resolve-users.ts +184 -0
  404. package/src/retry.test.ts +83 -0
  405. package/src/retry.ts +98 -0
  406. package/src/runtime-api.ts +64 -0
  407. package/src/runtime.ts +22 -5
  408. package/src/secret-config-contract.ts +140 -0
  409. package/src/security-audit.runtime.ts +1 -0
  410. package/src/security-audit.test.ts +246 -0
  411. package/src/security-audit.ts +208 -0
  412. package/src/security-contract.ts +47 -0
  413. package/src/security-doctor.test.ts +25 -0
  414. package/src/security-doctor.ts +20 -0
  415. package/src/security.ts +60 -0
  416. package/src/send-target-parsing.ts +14 -0
  417. package/src/send.channels.ts +139 -0
  418. package/src/send.components.test.ts +275 -0
  419. package/src/send.components.ts +381 -0
  420. package/src/send.creates-thread.test.ts +643 -0
  421. package/src/send.emojis-stickers.ts +57 -0
  422. package/src/send.guild.ts +170 -0
  423. package/src/send.message-request.ts +97 -0
  424. package/src/send.messages.test.ts +53 -0
  425. package/src/send.messages.ts +225 -0
  426. package/src/send.outbound.ts +413 -0
  427. package/src/send.permissions.authz.test.ts +188 -0
  428. package/src/send.permissions.ts +283 -0
  429. package/src/send.reactions.ts +155 -0
  430. package/src/send.sends-basic-channel-messages.test.ts +941 -0
  431. package/src/send.shared.ts +447 -0
  432. package/src/send.test-harness.ts +56 -0
  433. package/src/send.ts +82 -0
  434. package/src/send.types.ts +188 -0
  435. package/src/send.typing.test.ts +41 -0
  436. package/src/send.typing.ts +9 -0
  437. package/src/send.voice.ts +134 -0
  438. package/src/send.webhook-activity.test.ts +105 -0
  439. package/src/send.webhook.proxy.test.ts +191 -0
  440. package/src/send.webhook.ts +133 -0
  441. package/src/session-contract.ts +3 -0
  442. package/src/session-key-normalization.test.ts +44 -0
  443. package/src/session-key-normalization.ts +47 -0
  444. package/src/setup-account-state.test.ts +91 -0
  445. package/src/setup-account-state.ts +144 -0
  446. package/src/setup-adapter.ts +12 -0
  447. package/src/setup-core.ts +212 -0
  448. package/src/setup-runtime-helpers.ts +10 -0
  449. package/src/setup-surface.test.ts +137 -0
  450. package/src/setup-surface.ts +129 -0
  451. package/src/shared-interactive.test.ts +153 -0
  452. package/src/shared-interactive.ts +124 -0
  453. package/src/shared.test.ts +165 -0
  454. package/src/shared.ts +190 -0
  455. package/src/status-issues.test.ts +70 -0
  456. package/src/status-issues.ts +169 -0
  457. package/src/subagent-hooks.test.ts +130 -81
  458. package/src/subagent-hooks.ts +184 -122
  459. package/src/target-parsing.ts +53 -0
  460. package/src/target-resolver.ts +129 -0
  461. package/src/targets.test.ts +367 -0
  462. package/src/targets.ts +12 -0
  463. package/src/test-http-helpers.ts +10 -0
  464. package/src/test-support/component-runtime.ts +190 -0
  465. package/src/test-support/config.ts +7 -0
  466. package/src/test-support/configured-binding-runtime.ts +29 -0
  467. package/src/test-support/partial-channel.ts +26 -0
  468. package/src/test-support/provider.test-support.ts +545 -0
  469. package/src/token.test.ts +107 -0
  470. package/src/token.ts +60 -0
  471. package/src/ui-colors.ts +27 -0
  472. package/src/ui.ts +20 -0
  473. package/src/voice/access.test.ts +217 -0
  474. package/src/voice/access.ts +124 -0
  475. package/src/voice/audio.ts +173 -0
  476. package/src/voice/capture-state.test.ts +48 -0
  477. package/src/voice/capture-state.ts +120 -0
  478. package/src/voice/command.test.ts +164 -0
  479. package/src/voice/command.ts +283 -0
  480. package/src/voice/config.ts +8 -0
  481. package/src/voice/manager.e2e.test.ts +928 -0
  482. package/src/voice/manager.ready-listener.test.ts +37 -0
  483. package/src/voice/manager.runtime.ts +11 -0
  484. package/src/voice/manager.ts +691 -0
  485. package/src/voice/prompt.test.ts +16 -0
  486. package/src/voice/prompt.ts +17 -0
  487. package/src/voice/receive-recovery.test.ts +79 -0
  488. package/src/voice/receive-recovery.ts +159 -0
  489. package/src/voice/sanitize.test.ts +34 -0
  490. package/src/voice/sanitize.ts +32 -0
  491. package/src/voice/sdk-runtime.ts +14 -0
  492. package/src/voice/segment.ts +156 -0
  493. package/src/voice/session.ts +50 -0
  494. package/src/voice/speaker-context.ts +127 -0
  495. package/src/voice/tts.ts +125 -0
  496. package/src/voice-message.test.ts +234 -0
  497. package/src/voice-message.ts +444 -0
  498. package/subagent-hooks-api.ts +27 -0
  499. package/test-api.ts +4 -0
  500. package/thread-binding-api.ts +1 -0
  501. package/timeouts.ts +6 -0
  502. package/tsconfig.json +16 -0
@@ -0,0 +1,112 @@
1
+ import { ChannelType } from "../internal/discord.js";
2
+ import { normalizeDiscordSlug } from "./allow-list.js";
3
+ import {
4
+ resolveDiscordChannelIdSafe,
5
+ resolveDiscordChannelInfoSafe,
6
+ resolveDiscordChannelParentIdSafe,
7
+ } from "./channel-access.js";
8
+ import {
9
+ resolveDiscordChannelInfo,
10
+ type DiscordChannelInfo,
11
+ type DiscordChannelInfoClient,
12
+ } from "./message-utils.js";
13
+ import { resolveDiscordThreadParentInfo } from "./threading.js";
14
+
15
+ type DiscordThreadLikeChannelContext = {
16
+ channelType?: ChannelType;
17
+ isThreadChannel: boolean;
18
+ channelId: string;
19
+ channelName?: string;
20
+ channelSlug: string;
21
+ parentId?: string;
22
+ threadParentId?: string;
23
+ threadParentName?: string;
24
+ threadParentSlug: string;
25
+ channelInfo: DiscordChannelInfo | null;
26
+ };
27
+
28
+ function isDiscordThreadChannelType(type: ChannelType | number | undefined): boolean {
29
+ return (
30
+ type === ChannelType.PublicThread ||
31
+ type === ChannelType.PrivateThread ||
32
+ type === ChannelType.AnnouncementThread
33
+ );
34
+ }
35
+
36
+ function buildFetchedChannelInfo(channel: unknown): DiscordChannelInfo | null {
37
+ const channelInfo = resolveDiscordChannelInfoSafe(channel);
38
+ if (channelInfo.type === undefined) {
39
+ return null;
40
+ }
41
+ return {
42
+ type: channelInfo.type as ChannelType,
43
+ name: channelInfo.name,
44
+ topic: channelInfo.topic,
45
+ parentId: channelInfo.parentId,
46
+ ownerId: channelInfo.ownerId,
47
+ };
48
+ }
49
+
50
+ export async function resolveDiscordThreadLikeChannelContext(params: {
51
+ client: DiscordChannelInfoClient;
52
+ channel: unknown;
53
+ channelIdFallback?: string;
54
+ channelInfo?: DiscordChannelInfo | null;
55
+ }): Promise<DiscordThreadLikeChannelContext> {
56
+ const safeChannelInfo = resolveDiscordChannelInfoSafe(params.channel);
57
+ const channelId = resolveDiscordChannelIdSafe(params.channel) ?? params.channelIdFallback ?? "";
58
+ const channelInfo =
59
+ params.channelInfo !== undefined
60
+ ? params.channelInfo
61
+ : channelId
62
+ ? await resolveDiscordChannelInfo(params.client, channelId)
63
+ : null;
64
+ const channelType = (safeChannelInfo.type as ChannelType | undefined) ?? channelInfo?.type;
65
+ const channelName = safeChannelInfo.name ?? channelInfo?.name;
66
+ const channelSlug = channelName ? normalizeDiscordSlug(channelName) : "";
67
+ const parentId = resolveDiscordChannelParentIdSafe(params.channel) ?? channelInfo?.parentId;
68
+ const isThreadChannel = isDiscordThreadChannelType(channelType);
69
+
70
+ let threadParentId: string | undefined;
71
+ let threadParentName: string | undefined;
72
+ let threadParentSlug = "";
73
+ if (channelId && isThreadChannel) {
74
+ const parentInfo = await resolveDiscordThreadParentInfo({
75
+ client: params.client,
76
+ threadChannel: {
77
+ id: channelId,
78
+ name: channelName,
79
+ parentId,
80
+ parent: undefined,
81
+ },
82
+ channelInfo,
83
+ });
84
+ threadParentId = parentInfo.id;
85
+ threadParentName = parentInfo.name;
86
+ threadParentSlug = threadParentName ? normalizeDiscordSlug(threadParentName) : "";
87
+ }
88
+
89
+ return {
90
+ channelType,
91
+ isThreadChannel,
92
+ channelId,
93
+ channelName,
94
+ channelSlug,
95
+ parentId,
96
+ threadParentId,
97
+ threadParentName,
98
+ threadParentSlug,
99
+ channelInfo,
100
+ };
101
+ }
102
+
103
+ export async function resolveFetchedDiscordThreadLikeChannelContext(params: {
104
+ client: DiscordChannelInfoClient;
105
+ channel: unknown;
106
+ channelIdFallback?: string;
107
+ }): Promise<DiscordThreadLikeChannelContext> {
108
+ return await resolveDiscordThreadLikeChannelContext({
109
+ ...params,
110
+ channelInfo: buildFetchedChannelInfo(params.channel),
111
+ });
112
+ }
@@ -0,0 +1,180 @@
1
+ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ const hoisted = vi.hoisted(() => {
4
+ const updateSessionStore = vi.fn();
5
+ const resolveStorePath = vi.fn(() => "/tmp/openclaw-sessions.json");
6
+ return { updateSessionStore, resolveStorePath };
7
+ });
8
+
9
+ vi.mock("openclaw/plugin-sdk/session-store-runtime", async () => {
10
+ const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/session-store-runtime")>(
11
+ "openclaw/plugin-sdk/session-store-runtime",
12
+ );
13
+ return {
14
+ ...actual,
15
+ updateSessionStore: hoisted.updateSessionStore,
16
+ resolveStorePath: hoisted.resolveStorePath,
17
+ };
18
+ });
19
+
20
+ let closeDiscordThreadSessions: typeof import("./thread-session-close.js").closeDiscordThreadSessions;
21
+
22
+ function setupStore(store: Record<string, { updatedAt: number }>) {
23
+ hoisted.updateSessionStore.mockImplementation(
24
+ async (_storePath: string, mutator: (s: typeof store) => unknown) => mutator(store),
25
+ );
26
+ }
27
+
28
+ const THREAD_ID = "999";
29
+ const OTHER_ID = "111";
30
+
31
+ const MATCHED_KEY = `agent:main:discord:channel:${THREAD_ID}`;
32
+ const UNMATCHED_KEY = `agent:main:discord:channel:${OTHER_ID}`;
33
+
34
+ describe("closeDiscordThreadSessions", () => {
35
+ beforeAll(async () => {
36
+ ({ closeDiscordThreadSessions } = await import("./thread-session-close.js"));
37
+ });
38
+
39
+ beforeEach(() => {
40
+ hoisted.updateSessionStore.mockClear();
41
+ hoisted.resolveStorePath.mockClear();
42
+ hoisted.resolveStorePath.mockReturnValue("/tmp/openclaw-sessions.json");
43
+ });
44
+
45
+ it("resets updatedAt to 0 for sessions whose key contains the threadId", async () => {
46
+ const store = {
47
+ [MATCHED_KEY]: { updatedAt: 1_700_000_000_000 },
48
+ [UNMATCHED_KEY]: { updatedAt: 1_700_000_000_001 },
49
+ };
50
+ setupStore(store);
51
+
52
+ const count = await closeDiscordThreadSessions({
53
+ cfg: {},
54
+ accountId: "default",
55
+ threadId: THREAD_ID,
56
+ });
57
+
58
+ expect(count).toBe(1);
59
+ expect(store[MATCHED_KEY].updatedAt).toBe(0);
60
+ expect(store[UNMATCHED_KEY].updatedAt).toBe(1_700_000_000_001);
61
+ });
62
+
63
+ it("returns 0 and leaves store unchanged when no session matches", async () => {
64
+ const store = {
65
+ [UNMATCHED_KEY]: { updatedAt: 1_700_000_000_001 },
66
+ };
67
+ setupStore(store);
68
+
69
+ const count = await closeDiscordThreadSessions({
70
+ cfg: {},
71
+ accountId: "default",
72
+ threadId: THREAD_ID,
73
+ });
74
+
75
+ expect(count).toBe(0);
76
+ expect(store[UNMATCHED_KEY].updatedAt).toBe(1_700_000_000_001);
77
+ });
78
+
79
+ it("resets all matching sessions when multiple keys contain the threadId", async () => {
80
+ const keyA = `agent:main:discord:channel:${THREAD_ID}`;
81
+ const keyB = `agent:work:discord:channel:${THREAD_ID}`;
82
+ const keyC = `agent:main:discord:channel:${OTHER_ID}`;
83
+ const store = {
84
+ [keyA]: { updatedAt: 1_000 },
85
+ [keyB]: { updatedAt: 2_000 },
86
+ [keyC]: { updatedAt: 3_000 },
87
+ };
88
+ setupStore(store);
89
+
90
+ const count = await closeDiscordThreadSessions({
91
+ cfg: {},
92
+ accountId: "default",
93
+ threadId: THREAD_ID,
94
+ });
95
+
96
+ expect(count).toBe(2);
97
+ expect(store[keyA].updatedAt).toBe(0);
98
+ expect(store[keyB].updatedAt).toBe(0);
99
+ expect(store[keyC].updatedAt).toBe(3_000);
100
+ });
101
+
102
+ it("does not match a key that contains the threadId as a substring of a longer snowflake", async () => {
103
+ const longerSnowflake = `${THREAD_ID}00`;
104
+ const noMatchKey = `agent:main:discord:channel:${longerSnowflake}`;
105
+ const store = {
106
+ [noMatchKey]: { updatedAt: 9_999 },
107
+ };
108
+ setupStore(store);
109
+
110
+ const count = await closeDiscordThreadSessions({
111
+ cfg: {},
112
+ accountId: "default",
113
+ threadId: THREAD_ID,
114
+ });
115
+
116
+ expect(count).toBe(0);
117
+ expect(store[noMatchKey].updatedAt).toBe(9_999);
118
+ });
119
+
120
+ it("matching is case-insensitive for the session key", async () => {
121
+ const uppercaseKey = `agent:main:discord:channel:${THREAD_ID.toUpperCase()}`;
122
+ const store = {
123
+ [uppercaseKey]: { updatedAt: 5_000 },
124
+ };
125
+ setupStore(store);
126
+
127
+ const count = await closeDiscordThreadSessions({
128
+ cfg: {},
129
+ accountId: "default",
130
+ threadId: THREAD_ID.toLowerCase(),
131
+ });
132
+
133
+ expect(count).toBe(1);
134
+ expect(store[uppercaseKey].updatedAt).toBe(0);
135
+ });
136
+
137
+ it("returns 0 immediately when threadId is empty without touching the store", async () => {
138
+ const count = await closeDiscordThreadSessions({
139
+ cfg: {},
140
+ accountId: "default",
141
+ threadId: " ",
142
+ });
143
+
144
+ expect(count).toBe(0);
145
+ expect(hoisted.updateSessionStore).not.toHaveBeenCalled();
146
+ });
147
+
148
+ it("does not recount sessions that were already reset", async () => {
149
+ const store = {
150
+ [MATCHED_KEY]: { updatedAt: 0 },
151
+ [UNMATCHED_KEY]: { updatedAt: 1_700_000_000_001 },
152
+ };
153
+ setupStore(store);
154
+
155
+ const count = await closeDiscordThreadSessions({
156
+ cfg: {},
157
+ accountId: "default",
158
+ threadId: THREAD_ID,
159
+ });
160
+
161
+ expect(count).toBe(0);
162
+ expect(store[MATCHED_KEY].updatedAt).toBe(0);
163
+ expect(store[UNMATCHED_KEY].updatedAt).toBe(1_700_000_000_001);
164
+ });
165
+
166
+ it("resolves the store path using cfg.session.store and accountId", async () => {
167
+ const store = {};
168
+ setupStore(store);
169
+
170
+ await closeDiscordThreadSessions({
171
+ cfg: { session: { store: "/custom/path/sessions.json" } },
172
+ accountId: "my-bot",
173
+ threadId: THREAD_ID,
174
+ });
175
+
176
+ expect(hoisted.resolveStorePath).toHaveBeenCalledWith("/custom/path/sessions.json", {
177
+ agentId: "my-bot",
178
+ });
179
+ });
180
+ });
@@ -0,0 +1,63 @@
1
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
2
+ import { resolveStorePath, updateSessionStore } from "openclaw/plugin-sdk/session-store-runtime";
3
+ import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
4
+
5
+ /**
6
+ * Marks every session entry in the store whose key contains {@link threadId}
7
+ * as "reset" by setting `updatedAt` to 0.
8
+ *
9
+ * This mirrors how the daily / idle session reset works: zeroing `updatedAt`
10
+ * makes `evaluateSessionFreshness` treat the session as stale on the next
11
+ * inbound message, so the bot starts a fresh conversation without deleting
12
+ * any on-disk transcript history.
13
+ */
14
+ export async function closeDiscordThreadSessions(params: {
15
+ cfg: OpenClawConfig;
16
+ accountId: string;
17
+ threadId: string;
18
+ }): Promise<number> {
19
+ const { cfg, accountId, threadId } = params;
20
+
21
+ const normalizedThreadId = normalizeOptionalLowercaseString(threadId) ?? "";
22
+ if (!normalizedThreadId) {
23
+ return 0;
24
+ }
25
+
26
+ // Match when the threadId appears as a complete colon-separated segment.
27
+ // e.g. "999" must be followed by ":" (middle) or end-of-string (final).
28
+ // Using a regex avoids false-positives where one snowflake is a prefix of
29
+ // another (e.g. searching for "999" must not match ":99900").
30
+ //
31
+ // Session key shapes:
32
+ // agent:<agentId>:discord:channel:<threadId>
33
+ // agent:<agentId>:discord:channel:<parentId>:thread:<threadId>
34
+ const segmentRe = new RegExp(`:${normalizedThreadId}(?::|$)`, "i");
35
+
36
+ function sessionKeyContainsThreadId(key: string): boolean {
37
+ return segmentRe.test(key);
38
+ }
39
+
40
+ // Resolve the store file. We pass `accountId` as `agentId` here to mirror
41
+ // how other Discord subsystems resolve their per-account sessions stores.
42
+ const storePath = resolveStorePath(cfg.session?.store, { agentId: accountId });
43
+
44
+ let resetCount = 0;
45
+
46
+ await updateSessionStore(storePath, (store) => {
47
+ for (const [key, entry] of Object.entries(store)) {
48
+ if (!entry || !sessionKeyContainsThreadId(key)) {
49
+ continue;
50
+ }
51
+ if (entry.updatedAt === 0) {
52
+ continue;
53
+ }
54
+ // Setting updatedAt to 0 signals that this session is stale.
55
+ // evaluateSessionFreshness will create a new session on the next message.
56
+ entry.updatedAt = 0;
57
+ resetCount += 1;
58
+ }
59
+ return resetCount;
60
+ });
61
+
62
+ return resetCount;
63
+ }
@@ -0,0 +1,197 @@
1
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
2
+ import * as agentRuntimeModule from "openclaw/plugin-sdk/simple-completion-runtime";
3
+ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { EMPTY_DISCORD_TEST_CONFIG } from "../test-support/config.js";
5
+
6
+ const completeWithPreparedSimpleCompletionModelMock =
7
+ vi.fn<typeof agentRuntimeModule.completeWithPreparedSimpleCompletionModel>();
8
+ const prepareSimpleCompletionModelForAgentMock =
9
+ vi.fn<typeof agentRuntimeModule.prepareSimpleCompletionModelForAgent>();
10
+ const extractAssistantTextMock = vi.fn<typeof agentRuntimeModule.extractAssistantText>();
11
+
12
+ let generateThreadTitle: typeof import("./thread-title.js").generateThreadTitle;
13
+
14
+ beforeAll(async () => {
15
+ ({ generateThreadTitle } = await import("./thread-title.js"));
16
+ });
17
+
18
+ beforeEach(() => {
19
+ vi.restoreAllMocks();
20
+ completeWithPreparedSimpleCompletionModelMock.mockReset();
21
+ prepareSimpleCompletionModelForAgentMock.mockReset();
22
+ extractAssistantTextMock.mockReset();
23
+
24
+ prepareSimpleCompletionModelForAgentMock.mockResolvedValue({
25
+ selection: {
26
+ provider: "anthropic",
27
+ modelId: "claude-sonnet-4-6",
28
+ agentDir: "/tmp/openclaw-agent",
29
+ },
30
+ model: {
31
+ provider: "anthropic",
32
+ id: "claude-sonnet-4-6",
33
+ },
34
+ auth: {
35
+ apiKey: "sk-test",
36
+ source: "env:TEST_API_KEY",
37
+ mode: "api-key",
38
+ },
39
+ } as Awaited<ReturnType<typeof agentRuntimeModule.prepareSimpleCompletionModelForAgent>>);
40
+ completeWithPreparedSimpleCompletionModelMock.mockResolvedValue(
41
+ {} as Awaited<ReturnType<typeof agentRuntimeModule.completeWithPreparedSimpleCompletionModel>>,
42
+ );
43
+ extractAssistantTextMock.mockReturnValue("Generated title");
44
+ vi.spyOn(agentRuntimeModule, "prepareSimpleCompletionModelForAgent").mockImplementation(
45
+ (...args) => prepareSimpleCompletionModelForAgentMock(...args),
46
+ );
47
+ vi.spyOn(agentRuntimeModule, "completeWithPreparedSimpleCompletionModel").mockImplementation(
48
+ (...args) => completeWithPreparedSimpleCompletionModelMock(...args),
49
+ );
50
+ vi.spyOn(agentRuntimeModule, "extractAssistantText").mockImplementation((...args) =>
51
+ extractAssistantTextMock(...args),
52
+ );
53
+ });
54
+
55
+ describe("generateThreadTitle", () => {
56
+ it("calls shared one-shot model prep with aws-sdk allowance", async () => {
57
+ prepareSimpleCompletionModelForAgentMock.mockResolvedValueOnce({
58
+ selection: {
59
+ provider: "openrouter",
60
+ modelId: "anthropic/claude-sonnet-4-5",
61
+ profileId: "work",
62
+ agentDir: "/tmp/openclaw-agent",
63
+ },
64
+ model: {
65
+ provider: "openrouter",
66
+ id: "anthropic/claude-sonnet-4-5",
67
+ },
68
+ auth: {
69
+ apiKey: "sk-openrouter",
70
+ source: "profile:work",
71
+ mode: "api-key",
72
+ },
73
+ } as Awaited<ReturnType<typeof agentRuntimeModule.prepareSimpleCompletionModelForAgent>>);
74
+ const cfg = {
75
+ agents: {
76
+ defaults: {
77
+ model: "openrouter/anthropic/claude-sonnet-4-5@work",
78
+ },
79
+ },
80
+ } as OpenClawConfig;
81
+
82
+ await generateThreadTitle({
83
+ cfg,
84
+ agentId: "main",
85
+ messageText: "Need a generated title.",
86
+ });
87
+
88
+ expect(prepareSimpleCompletionModelForAgentMock).toHaveBeenCalledWith({
89
+ cfg,
90
+ agentId: "main",
91
+ allowMissingApiKeyModes: ["aws-sdk"],
92
+ });
93
+ });
94
+
95
+ it("passes model override refs into shared model prep", async () => {
96
+ const cfg = EMPTY_DISCORD_TEST_CONFIG;
97
+ await generateThreadTitle({
98
+ cfg,
99
+ agentId: "main",
100
+ modelRef: "openai/gpt-4.1-mini@local",
101
+ messageText: "Need a generated title.",
102
+ });
103
+
104
+ expect(prepareSimpleCompletionModelForAgentMock).toHaveBeenCalledWith({
105
+ cfg,
106
+ agentId: "main",
107
+ modelRef: "openai/gpt-4.1-mini@local",
108
+ allowMissingApiKeyModes: ["aws-sdk"],
109
+ });
110
+ });
111
+
112
+ it("returns null when shared model prep cannot resolve selection", async () => {
113
+ prepareSimpleCompletionModelForAgentMock.mockResolvedValueOnce({
114
+ error: "No model configured for agent main.",
115
+ } as Awaited<ReturnType<typeof agentRuntimeModule.prepareSimpleCompletionModelForAgent>>);
116
+
117
+ const result = await generateThreadTitle({
118
+ cfg: EMPTY_DISCORD_TEST_CONFIG,
119
+ agentId: "main",
120
+ messageText: "Need a thread title.",
121
+ });
122
+
123
+ expect(result).toBeNull();
124
+ expect(completeWithPreparedSimpleCompletionModelMock).not.toHaveBeenCalled();
125
+ });
126
+
127
+ it("returns null when shared completion prep fails", async () => {
128
+ prepareSimpleCompletionModelForAgentMock.mockResolvedValue({
129
+ error: 'No API key resolved for provider "anthropic" (auth mode: api-key).',
130
+ selection: {
131
+ provider: "anthropic",
132
+ modelId: "claude-sonnet-4-6",
133
+ agentDir: "/tmp/openclaw-agent",
134
+ },
135
+ } as Awaited<ReturnType<typeof agentRuntimeModule.prepareSimpleCompletionModelForAgent>>);
136
+
137
+ const result = await generateThreadTitle({
138
+ cfg: EMPTY_DISCORD_TEST_CONFIG,
139
+ agentId: "main",
140
+ messageText: "Need a thread title.",
141
+ });
142
+
143
+ expect(result).toBeNull();
144
+ expect(completeWithPreparedSimpleCompletionModelMock).not.toHaveBeenCalled();
145
+ });
146
+
147
+ it("builds contextual prompt and forwards completion options", async () => {
148
+ const result = await generateThreadTitle({
149
+ cfg: EMPTY_DISCORD_TEST_CONFIG,
150
+ agentId: "main",
151
+ messageText: "Summarize deployment blockers and owner follow-ups.",
152
+ channelName: "release-status",
153
+ channelDescription: "Deploy updates and incident notes",
154
+ });
155
+
156
+ expect(result).toBe("Generated title");
157
+ expect(completeWithPreparedSimpleCompletionModelMock).toHaveBeenCalledTimes(1);
158
+ expect(completeWithPreparedSimpleCompletionModelMock.mock.calls[0]?.[0]?.context).toEqual(
159
+ expect.objectContaining({
160
+ systemPrompt:
161
+ "Generate a concise Discord thread title (3-6 words). Return only the title. Use channel context when provided and avoid redundant channel-name words unless needed for clarity.",
162
+ messages: [
163
+ expect.objectContaining({
164
+ role: "user",
165
+ content: expect.stringContaining("Channel: release-status"),
166
+ }),
167
+ ],
168
+ }),
169
+ );
170
+ expect(
171
+ completeWithPreparedSimpleCompletionModelMock.mock.calls[0]?.[0]?.context?.messages?.[0]
172
+ ?.content,
173
+ ).toContain("Channel description: Deploy updates and incident notes");
174
+ expect(completeWithPreparedSimpleCompletionModelMock.mock.calls[0]?.[0]?.options).toEqual(
175
+ expect.objectContaining({
176
+ maxTokens: 512,
177
+ }),
178
+ );
179
+ expect(
180
+ completeWithPreparedSimpleCompletionModelMock.mock.calls[0]?.[0]?.options,
181
+ ).not.toHaveProperty("temperature");
182
+ });
183
+
184
+ it("returns null when completion throws", async () => {
185
+ completeWithPreparedSimpleCompletionModelMock.mockRejectedValueOnce(
186
+ new Error("network timeout"),
187
+ );
188
+
189
+ const result = await generateThreadTitle({
190
+ cfg: EMPTY_DISCORD_TEST_CONFIG,
191
+ agentId: "main",
192
+ messageText: "Generate title.",
193
+ });
194
+
195
+ expect(result).toBeNull();
196
+ });
197
+ });
@@ -0,0 +1,31 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { normalizeGeneratedThreadTitle } from "./thread-title.js";
3
+
4
+ describe("normalizeGeneratedThreadTitle", () => {
5
+ it("strips quotes and keeps the first non-empty line", () => {
6
+ expect(normalizeGeneratedThreadTitle(' "Weekly Release Summary"\nExtra text')).toBe(
7
+ "Weekly Release Summary",
8
+ );
9
+ });
10
+
11
+ it("skips leading blank lines before selecting a title", () => {
12
+ expect(normalizeGeneratedThreadTitle('\n\n "Weekly Release Summary"\nExtra text')).toBe(
13
+ "Weekly Release Summary",
14
+ );
15
+ });
16
+
17
+ it("skips leading markdown fence lines before selecting a title", () => {
18
+ expect(normalizeGeneratedThreadTitle("```markdown\nWeekly Release Summary\n```")).toBe(
19
+ "Weekly Release Summary",
20
+ );
21
+ });
22
+
23
+ it("strips markdown emphasis wrappers around the full title", () => {
24
+ expect(normalizeGeneratedThreadTitle("**Scaling ArcherScore Development Roadmap**")).toBe(
25
+ "Scaling ArcherScore Development Roadmap",
26
+ );
27
+ expect(normalizeGeneratedThreadTitle('"__Weekly Release Summary__"')).toBe(
28
+ "Weekly Release Summary",
29
+ );
30
+ });
31
+ });