@gakr-gakr/discord 0.1.0

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 (353) hide show
  1. package/account-inspect-api.ts +6 -0
  2. package/action-runtime-api.ts +1 -0
  3. package/api.ts +130 -0
  4. package/autobot.plugin.json +15 -0
  5. package/channel-config-api.ts +1 -0
  6. package/channel-plugin-api.ts +3 -0
  7. package/config-api.ts +4 -0
  8. package/configured-state.ts +6 -0
  9. package/contract-api.ts +21 -0
  10. package/directory-contract-api.ts +4 -0
  11. package/doctor-contract-api.ts +1 -0
  12. package/index.ts +24 -0
  13. package/package.json +79 -0
  14. package/runtime-api.actions.ts +15 -0
  15. package/runtime-api.lookup.ts +22 -0
  16. package/runtime-api.monitor.ts +50 -0
  17. package/runtime-api.send.ts +79 -0
  18. package/runtime-api.threads.ts +31 -0
  19. package/runtime-api.ts +181 -0
  20. package/runtime-setter-api.ts +3 -0
  21. package/secret-contract-api.ts +4 -0
  22. package/security-audit-contract-api.ts +1 -0
  23. package/security-contract-api.ts +4 -0
  24. package/session-key-api.ts +1 -0
  25. package/setup-entry.ts +9 -0
  26. package/setup-plugin-api.ts +3 -0
  27. package/src/account-inspect.ts +131 -0
  28. package/src/accounts.ts +205 -0
  29. package/src/actions/handle-action.guild-admin.ts +421 -0
  30. package/src/actions/handle-action.ts +402 -0
  31. package/src/actions/runtime.guild.ts +446 -0
  32. package/src/actions/runtime.messaging.messages.ts +226 -0
  33. package/src/actions/runtime.messaging.reactions.ts +67 -0
  34. package/src/actions/runtime.messaging.runtime.ts +73 -0
  35. package/src/actions/runtime.messaging.send.ts +336 -0
  36. package/src/actions/runtime.messaging.shared.ts +97 -0
  37. package/src/actions/runtime.messaging.ts +37 -0
  38. package/src/actions/runtime.moderation-shared.ts +48 -0
  39. package/src/actions/runtime.moderation.ts +116 -0
  40. package/src/actions/runtime.presence.ts +117 -0
  41. package/src/actions/runtime.shared.ts +86 -0
  42. package/src/actions/runtime.ts +87 -0
  43. package/src/api.ts +219 -0
  44. package/src/approval-handler.runtime.ts +636 -0
  45. package/src/approval-native.ts +219 -0
  46. package/src/approval-runtime.ts +14 -0
  47. package/src/approval-shared.ts +56 -0
  48. package/src/audit-core.ts +178 -0
  49. package/src/audit.ts +32 -0
  50. package/src/channel-actions.runtime.ts +1 -0
  51. package/src/channel-actions.ts +254 -0
  52. package/src/channel-api.ts +29 -0
  53. package/src/channel.conversation.ts +159 -0
  54. package/src/channel.loaders.ts +50 -0
  55. package/src/channel.runtime.ts +1 -0
  56. package/src/channel.setup.ts +12 -0
  57. package/src/channel.ts +728 -0
  58. package/src/chunk.ts +321 -0
  59. package/src/client.ts +143 -0
  60. package/src/component-custom-id.ts +72 -0
  61. package/src/components-registry.ts +356 -0
  62. package/src/components.builders.ts +410 -0
  63. package/src/components.modal.ts +124 -0
  64. package/src/components.parse.ts +407 -0
  65. package/src/components.ts +54 -0
  66. package/src/components.types.ts +187 -0
  67. package/src/config-schema.ts +6 -0
  68. package/src/config-ui-hints.ts +354 -0
  69. package/src/conversation-identity.ts +58 -0
  70. package/src/delivery-retry.ts +56 -0
  71. package/src/directory-cache.ts +116 -0
  72. package/src/directory-config.ts +58 -0
  73. package/src/directory-live.ts +135 -0
  74. package/src/doctor-contract.ts +477 -0
  75. package/src/doctor-shared.ts +5 -0
  76. package/src/doctor.ts +340 -0
  77. package/src/draft-chunking.ts +43 -0
  78. package/src/draft-stream.ts +162 -0
  79. package/src/error-body.ts +38 -0
  80. package/src/exec-approvals.ts +110 -0
  81. package/src/gateway-logging.ts +67 -0
  82. package/src/group-policy.ts +113 -0
  83. package/src/guilds.ts +29 -0
  84. package/src/inbound-event-delivery.ts +135 -0
  85. package/src/interactive-dispatch.ts +104 -0
  86. package/src/internal/api.commands.ts +51 -0
  87. package/src/internal/api.guild.ts +164 -0
  88. package/src/internal/api.interactions.ts +53 -0
  89. package/src/internal/api.messages.ts +113 -0
  90. package/src/internal/api.reactions.ts +38 -0
  91. package/src/internal/api.ts +61 -0
  92. package/src/internal/api.users.ts +19 -0
  93. package/src/internal/api.webhooks.ts +13 -0
  94. package/src/internal/client.ts +310 -0
  95. package/src/internal/command-deploy.ts +352 -0
  96. package/src/internal/commands.ts +188 -0
  97. package/src/internal/components.base.ts +65 -0
  98. package/src/internal/components.message.ts +279 -0
  99. package/src/internal/components.modal.ts +95 -0
  100. package/src/internal/components.ts +31 -0
  101. package/src/internal/discord.ts +11 -0
  102. package/src/internal/embeds.ts +35 -0
  103. package/src/internal/entity-cache.ts +98 -0
  104. package/src/internal/event-queue.ts +185 -0
  105. package/src/internal/gateway-close-codes.ts +25 -0
  106. package/src/internal/gateway-dispatch.ts +96 -0
  107. package/src/internal/gateway-identify-limiter.ts +26 -0
  108. package/src/internal/gateway-lifecycle.ts +75 -0
  109. package/src/internal/gateway-rate-limit.ts +104 -0
  110. package/src/internal/gateway.ts +479 -0
  111. package/src/internal/interaction-dispatch.ts +162 -0
  112. package/src/internal/interaction-options.ts +98 -0
  113. package/src/internal/interaction-response.ts +53 -0
  114. package/src/internal/interactions.ts +378 -0
  115. package/src/internal/listeners.ts +91 -0
  116. package/src/internal/modal-fields.ts +95 -0
  117. package/src/internal/payload.ts +69 -0
  118. package/src/internal/rest-body.ts +115 -0
  119. package/src/internal/rest-errors.ts +88 -0
  120. package/src/internal/rest-routes.ts +50 -0
  121. package/src/internal/rest-scheduler.ts +557 -0
  122. package/src/internal/rest.ts +322 -0
  123. package/src/internal/schemas.ts +36 -0
  124. package/src/internal/structures.ts +280 -0
  125. package/src/internal/test-builders.test-support.ts +167 -0
  126. package/src/internal/voice.ts +49 -0
  127. package/src/media-detection.ts +28 -0
  128. package/src/mentions.ts +147 -0
  129. package/src/monitor/ack-reactions.ts +70 -0
  130. package/src/monitor/agent-components-auth.ts +7 -0
  131. package/src/monitor/agent-components-context.ts +154 -0
  132. package/src/monitor/agent-components-data.ts +224 -0
  133. package/src/monitor/agent-components-dm-auth.ts +177 -0
  134. package/src/monitor/agent-components-guild-auth.ts +322 -0
  135. package/src/monitor/agent-components-helpers.runtime.ts +3 -0
  136. package/src/monitor/agent-components-helpers.ts +34 -0
  137. package/src/monitor/agent-components-reply.ts +10 -0
  138. package/src/monitor/agent-components.deps.runtime.ts +2 -0
  139. package/src/monitor/agent-components.dispatch.ts +359 -0
  140. package/src/monitor/agent-components.handlers.ts +303 -0
  141. package/src/monitor/agent-components.modal.ts +160 -0
  142. package/src/monitor/agent-components.plugin-interactive.ts +187 -0
  143. package/src/monitor/agent-components.runtime.ts +14 -0
  144. package/src/monitor/agent-components.system-controls.ts +215 -0
  145. package/src/monitor/agent-components.ts +70 -0
  146. package/src/monitor/agent-components.types.ts +58 -0
  147. package/src/monitor/agent-components.wildcard-controls.ts +171 -0
  148. package/src/monitor/allow-list.ts +631 -0
  149. package/src/monitor/auto-presence.ts +356 -0
  150. package/src/monitor/channel-access.ts +102 -0
  151. package/src/monitor/commands.ts +9 -0
  152. package/src/monitor/dm-command-auth.ts +259 -0
  153. package/src/monitor/dm-command-decision.ts +49 -0
  154. package/src/monitor/exec-approvals.ts +161 -0
  155. package/src/monitor/format.ts +45 -0
  156. package/src/monitor/gateway-handle.ts +34 -0
  157. package/src/monitor/gateway-metadata.ts +298 -0
  158. package/src/monitor/gateway-plugin.ts +302 -0
  159. package/src/monitor/gateway-registry.ts +37 -0
  160. package/src/monitor/gateway-supervisor.ts +206 -0
  161. package/src/monitor/inbound-context.ts +95 -0
  162. package/src/monitor/inbound-dedupe.ts +79 -0
  163. package/src/monitor/inbound-job.ts +118 -0
  164. package/src/monitor/listeners.queue.ts +91 -0
  165. package/src/monitor/listeners.reactions.ts +594 -0
  166. package/src/monitor/listeners.ts +150 -0
  167. package/src/monitor/message-channel-info.ts +96 -0
  168. package/src/monitor/message-forwarded.ts +114 -0
  169. package/src/monitor/message-handler.batch-gate.ts +19 -0
  170. package/src/monitor/message-handler.context.ts +492 -0
  171. package/src/monitor/message-handler.dm-preflight.ts +119 -0
  172. package/src/monitor/message-handler.draft-preview.ts +436 -0
  173. package/src/monitor/message-handler.hydration.ts +198 -0
  174. package/src/monitor/message-handler.module-test-helpers.ts +31 -0
  175. package/src/monitor/message-handler.preflight-channel-access.ts +86 -0
  176. package/src/monitor/message-handler.preflight-channel-context.ts +58 -0
  177. package/src/monitor/message-handler.preflight-context.ts +54 -0
  178. package/src/monitor/message-handler.preflight-helpers.ts +164 -0
  179. package/src/monitor/message-handler.preflight-history.ts +23 -0
  180. package/src/monitor/message-handler.preflight-logging.ts +36 -0
  181. package/src/monitor/message-handler.preflight-pluralkit.ts +28 -0
  182. package/src/monitor/message-handler.preflight-runtime.ts +28 -0
  183. package/src/monitor/message-handler.preflight-thread.ts +49 -0
  184. package/src/monitor/message-handler.preflight.ts +822 -0
  185. package/src/monitor/message-handler.preflight.types.ts +115 -0
  186. package/src/monitor/message-handler.process.ts +1033 -0
  187. package/src/monitor/message-handler.routing-preflight.ts +112 -0
  188. package/src/monitor/message-handler.ts +309 -0
  189. package/src/monitor/message-media.ts +536 -0
  190. package/src/monitor/message-run-queue.ts +101 -0
  191. package/src/monitor/message-text.ts +171 -0
  192. package/src/monitor/message-utils.ts +34 -0
  193. package/src/monitor/model-picker-preferences.ts +184 -0
  194. package/src/monitor/model-picker.state.ts +364 -0
  195. package/src/monitor/model-picker.test-utils.ts +26 -0
  196. package/src/monitor/model-picker.ts +38 -0
  197. package/src/monitor/model-picker.view.ts +722 -0
  198. package/src/monitor/native-command-agent-reply.ts +125 -0
  199. package/src/monitor/native-command-arg-ui.ts +233 -0
  200. package/src/monitor/native-command-auth.ts +309 -0
  201. package/src/monitor/native-command-bypass.ts +13 -0
  202. package/src/monitor/native-command-context.ts +109 -0
  203. package/src/monitor/native-command-dispatch.ts +35 -0
  204. package/src/monitor/native-command-model-picker-apply.ts +209 -0
  205. package/src/monitor/native-command-model-picker-interaction.ts +516 -0
  206. package/src/monitor/native-command-model-picker-ui.ts +357 -0
  207. package/src/monitor/native-command-reply.ts +185 -0
  208. package/src/monitor/native-command-route.ts +91 -0
  209. package/src/monitor/native-command-status.ts +76 -0
  210. package/src/monitor/native-command-ui.ts +26 -0
  211. package/src/monitor/native-command-ui.types.ts +20 -0
  212. package/src/monitor/native-command.args.ts +45 -0
  213. package/src/monitor/native-command.options.ts +153 -0
  214. package/src/monitor/native-command.runtime.ts +51 -0
  215. package/src/monitor/native-command.ts +747 -0
  216. package/src/monitor/native-command.types.ts +9 -0
  217. package/src/monitor/native-interaction-channel-context.ts +50 -0
  218. package/src/monitor/preflight-audio.runtime.ts +9 -0
  219. package/src/monitor/preflight-audio.ts +130 -0
  220. package/src/monitor/presence-cache.ts +61 -0
  221. package/src/monitor/presence.ts +50 -0
  222. package/src/monitor/provider-session.runtime.ts +12 -0
  223. package/src/monitor/provider.acp.ts +89 -0
  224. package/src/monitor/provider.allowlist.ts +398 -0
  225. package/src/monitor/provider.cleanup.ts +41 -0
  226. package/src/monitor/provider.commands.ts +129 -0
  227. package/src/monitor/provider.config-log.ts +45 -0
  228. package/src/monitor/provider.deploy-errors.ts +362 -0
  229. package/src/monitor/provider.deploy.ts +221 -0
  230. package/src/monitor/provider.interactions.ts +160 -0
  231. package/src/monitor/provider.lifecycle.ts +562 -0
  232. package/src/monitor/provider.runtime.ts +1 -0
  233. package/src/monitor/provider.startup-log.ts +32 -0
  234. package/src/monitor/provider.startup.ts +323 -0
  235. package/src/monitor/provider.ts +688 -0
  236. package/src/monitor/reply-context.ts +64 -0
  237. package/src/monitor/reply-delivery.ts +216 -0
  238. package/src/monitor/reply-safety.ts +96 -0
  239. package/src/monitor/rest-fetch.ts +97 -0
  240. package/src/monitor/route-resolution.ts +140 -0
  241. package/src/monitor/sender-identity.ts +81 -0
  242. package/src/monitor/startup-status.ts +10 -0
  243. package/src/monitor/status.ts +22 -0
  244. package/src/monitor/system-events.ts +55 -0
  245. package/src/monitor/thread-bindings.config.ts +35 -0
  246. package/src/monitor/thread-bindings.discord-api.ts +310 -0
  247. package/src/monitor/thread-bindings.lifecycle.ts +354 -0
  248. package/src/monitor/thread-bindings.manager.ts +554 -0
  249. package/src/monitor/thread-bindings.messages.ts +6 -0
  250. package/src/monitor/thread-bindings.persona.ts +25 -0
  251. package/src/monitor/thread-bindings.session-adapter.ts +229 -0
  252. package/src/monitor/thread-bindings.session-shared.ts +59 -0
  253. package/src/monitor/thread-bindings.session-updates.ts +35 -0
  254. package/src/monitor/thread-bindings.state.ts +540 -0
  255. package/src/monitor/thread-bindings.ts +48 -0
  256. package/src/monitor/thread-bindings.types.ts +83 -0
  257. package/src/monitor/thread-channel-context.ts +112 -0
  258. package/src/monitor/thread-session-close.ts +63 -0
  259. package/src/monitor/thread-title.ts +181 -0
  260. package/src/monitor/threading.auto-thread.ts +287 -0
  261. package/src/monitor/threading.cache.ts +45 -0
  262. package/src/monitor/threading.starter.ts +288 -0
  263. package/src/monitor/threading.ts +20 -0
  264. package/src/monitor/threading.types.ts +102 -0
  265. package/src/monitor/timeouts.ts +84 -0
  266. package/src/monitor/typing.ts +17 -0
  267. package/src/monitor.gateway.ts +75 -0
  268. package/src/monitor.ts +28 -0
  269. package/src/network-config.ts +79 -0
  270. package/src/normalize.ts +86 -0
  271. package/src/outbound-adapter.ts +327 -0
  272. package/src/outbound-approval.ts +29 -0
  273. package/src/outbound-components.ts +86 -0
  274. package/src/outbound-payload.ts +208 -0
  275. package/src/outbound-send-context.ts +92 -0
  276. package/src/outbound-session-route.ts +72 -0
  277. package/src/pluralkit.ts +58 -0
  278. package/src/preview-streaming.ts +18 -0
  279. package/src/probe.runtime.ts +1 -0
  280. package/src/probe.ts +237 -0
  281. package/src/proxy-fetch.ts +92 -0
  282. package/src/proxy-request-client.ts +21 -0
  283. package/src/recipient-resolution.ts +39 -0
  284. package/src/resolve-allowlist-common.ts +39 -0
  285. package/src/resolve-channels.ts +369 -0
  286. package/src/resolve-users.ts +184 -0
  287. package/src/retry.ts +98 -0
  288. package/src/runtime-api.ts +64 -0
  289. package/src/runtime-config.ts +16 -0
  290. package/src/runtime.ts +23 -0
  291. package/src/secret-config-contract.ts +140 -0
  292. package/src/security-audit.runtime.ts +1 -0
  293. package/src/security-audit.ts +208 -0
  294. package/src/security-contract.ts +47 -0
  295. package/src/security-doctor.ts +20 -0
  296. package/src/security.ts +60 -0
  297. package/src/send-target-parsing.ts +14 -0
  298. package/src/send.channels.ts +139 -0
  299. package/src/send.components.ts +391 -0
  300. package/src/send.emojis-stickers.ts +57 -0
  301. package/src/send.guild.ts +170 -0
  302. package/src/send.message-request.ts +112 -0
  303. package/src/send.messages.ts +229 -0
  304. package/src/send.outbound.ts +459 -0
  305. package/src/send.permissions.ts +283 -0
  306. package/src/send.reactions.ts +155 -0
  307. package/src/send.receipt.ts +69 -0
  308. package/src/send.shared.ts +469 -0
  309. package/src/send.ts +82 -0
  310. package/src/send.types.ts +191 -0
  311. package/src/send.typing.ts +9 -0
  312. package/src/send.voice.ts +140 -0
  313. package/src/send.webhook.ts +137 -0
  314. package/src/session-contract.ts +3 -0
  315. package/src/session-key-normalization.ts +47 -0
  316. package/src/setup-account-state.ts +144 -0
  317. package/src/setup-adapter.ts +14 -0
  318. package/src/setup-core.ts +215 -0
  319. package/src/setup-runtime-helpers.ts +10 -0
  320. package/src/setup-surface.ts +132 -0
  321. package/src/shared-interactive.ts +167 -0
  322. package/src/shared.ts +197 -0
  323. package/src/status-issues.ts +201 -0
  324. package/src/subagent-hooks.ts +232 -0
  325. package/src/target-parsing.ts +70 -0
  326. package/src/target-resolver.ts +129 -0
  327. package/src/targets.ts +12 -0
  328. package/src/token.ts +107 -0
  329. package/src/ui-colors.ts +27 -0
  330. package/src/ui.ts +20 -0
  331. package/src/voice/access.ts +126 -0
  332. package/src/voice/audio.ts +249 -0
  333. package/src/voice/capture-state.ts +120 -0
  334. package/src/voice/command.ts +284 -0
  335. package/src/voice/config.ts +8 -0
  336. package/src/voice/ingress.ts +164 -0
  337. package/src/voice/manager.runtime.ts +14 -0
  338. package/src/voice/manager.ts +1155 -0
  339. package/src/voice/prompt.ts +22 -0
  340. package/src/voice/realtime.ts +1370 -0
  341. package/src/voice/receive-recovery.ts +159 -0
  342. package/src/voice/sanitize.ts +29 -0
  343. package/src/voice/sdk-runtime.ts +14 -0
  344. package/src/voice/segment.ts +160 -0
  345. package/src/voice/session.ts +81 -0
  346. package/src/voice/speaker-context.ts +127 -0
  347. package/src/voice/tts.ts +151 -0
  348. package/src/voice-message.ts +474 -0
  349. package/subagent-hooks-api.ts +27 -0
  350. package/test-api.ts +4 -0
  351. package/thread-binding-api.ts +1 -0
  352. package/timeouts.ts +6 -0
  353. 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,63 @@
1
+ import type { AutoBotConfig } from "autobot/plugin-sdk/config-contracts";
2
+ import { resolveStorePath, updateSessionStore } from "autobot/plugin-sdk/session-store-runtime";
3
+ import { normalizeOptionalLowercaseString } from "autobot/plugin-sdk/string-coerce-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: AutoBotConfig;
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,181 @@
1
+ import type { AutoBotConfig } from "autobot/plugin-sdk/config-contracts";
2
+ import { logVerbose } from "autobot/plugin-sdk/runtime-env";
3
+ import {
4
+ completeWithPreparedSimpleCompletionModel,
5
+ extractAssistantText,
6
+ prepareSimpleCompletionModelForAgent,
7
+ } from "autobot/plugin-sdk/simple-completion-runtime";
8
+ import { withAbortTimeout } from "./timeouts.js";
9
+
10
+ const DEFAULT_THREAD_TITLE_TIMEOUT_MS = 10_000;
11
+ const MAX_THREAD_TITLE_SOURCE_CHARS = 600;
12
+ const MAX_THREAD_TITLE_CHANNEL_NAME_CHARS = 120;
13
+ const MAX_THREAD_TITLE_CHANNEL_DESCRIPTION_CHARS = 320;
14
+ // Budget generous enough to cover reasoning-model thinking tokens plus the
15
+ // short text output. Lower values (e.g. 24) starve reasoning models of output
16
+ // capacity: the entire budget is consumed by the thinking block before any
17
+ // text is emitted, so extractAssistantText returns empty and the rename is
18
+ // silently skipped.
19
+ const DISCORD_THREAD_TITLE_MAX_TOKENS = 512;
20
+ const DISCORD_THREAD_TITLE_SYSTEM_PROMPT =
21
+ "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.";
22
+
23
+ export async function generateThreadTitle(params: {
24
+ cfg: AutoBotConfig;
25
+ agentId: string;
26
+ messageText: string;
27
+ modelRef?: string;
28
+ channelName?: string;
29
+ channelDescription?: string;
30
+ timeoutMs?: number;
31
+ }): Promise<string | null> {
32
+ const sourceText = params.messageText.trim();
33
+ if (!sourceText) {
34
+ return null;
35
+ }
36
+
37
+ const prepared = await prepareSimpleCompletionModelForAgent({
38
+ cfg: params.cfg,
39
+ agentId: params.agentId,
40
+ ...(params.modelRef ? { modelRef: params.modelRef } : {}),
41
+ allowMissingApiKeyModes: ["aws-sdk"],
42
+ });
43
+ if ("error" in prepared) {
44
+ const modelLabel = prepared.selection
45
+ ? `${prepared.selection.provider}/${prepared.selection.modelId}`
46
+ : "unknown";
47
+ logVerbose(`thread-title: ${prepared.error} (agent=${params.agentId}, model=${modelLabel})`);
48
+ return null;
49
+ }
50
+
51
+ try {
52
+ const promptText = truncateThreadTitleSourceText(sourceText);
53
+ const userMessage = buildThreadTitleUserMessage({
54
+ sourceText: promptText,
55
+ channelName: params.channelName,
56
+ channelDescription: params.channelDescription,
57
+ });
58
+ const timeoutMs = resolveThreadTitleTimeoutMs(params.timeoutMs);
59
+ const response = await completeThreadTitle({
60
+ model: prepared.model,
61
+ auth: prepared.auth,
62
+ userMessage,
63
+ timeoutMs,
64
+ });
65
+ const generated = normalizeGeneratedThreadTitle(extractAssistantText(response));
66
+ return generated || null;
67
+ } catch (err) {
68
+ logVerbose(`thread-title: title generation failed for agent ${params.agentId}: ${String(err)}`);
69
+ return null;
70
+ }
71
+ }
72
+
73
+ async function completeThreadTitle(params: {
74
+ model: Parameters<typeof completeWithPreparedSimpleCompletionModel>[0]["model"];
75
+ auth: Parameters<typeof completeWithPreparedSimpleCompletionModel>[0]["auth"];
76
+ userMessage: string;
77
+ timeoutMs: number;
78
+ }) {
79
+ return await withAbortTimeout({
80
+ timeoutMs: params.timeoutMs,
81
+ createTimeoutError: () => new Error(`thread-title timed out after ${params.timeoutMs}ms`),
82
+ run: async (signal) =>
83
+ await completeWithPreparedSimpleCompletionModel({
84
+ model: params.model,
85
+ auth: params.auth,
86
+ context: {
87
+ systemPrompt: DISCORD_THREAD_TITLE_SYSTEM_PROMPT,
88
+ messages: [
89
+ {
90
+ role: "user",
91
+ content: params.userMessage,
92
+ timestamp: Date.now(),
93
+ },
94
+ ],
95
+ },
96
+ options: {
97
+ maxTokens: DISCORD_THREAD_TITLE_MAX_TOKENS,
98
+ signal,
99
+ },
100
+ }),
101
+ });
102
+ }
103
+
104
+ function buildThreadTitleUserMessage(params: {
105
+ sourceText: string;
106
+ channelName?: string;
107
+ channelDescription?: string;
108
+ }): string {
109
+ const channelName = normalizeTitleContextField(
110
+ params.channelName,
111
+ MAX_THREAD_TITLE_CHANNEL_NAME_CHARS,
112
+ );
113
+ const channelDescription = normalizeTitleContextField(
114
+ params.channelDescription,
115
+ MAX_THREAD_TITLE_CHANNEL_DESCRIPTION_CHARS,
116
+ );
117
+ const messageLines: string[] = [];
118
+ if (channelName) {
119
+ messageLines.push(`Channel: ${channelName}`);
120
+ }
121
+ if (channelDescription) {
122
+ messageLines.push(`Channel description: ${channelDescription}`);
123
+ }
124
+ messageLines.push(`Message:\n${params.sourceText}`);
125
+ return messageLines.join("\n\n");
126
+ }
127
+
128
+ function truncateThreadTitleSourceText(sourceText: string): string {
129
+ if (sourceText.length <= MAX_THREAD_TITLE_SOURCE_CHARS) {
130
+ return sourceText;
131
+ }
132
+ return `${sourceText.slice(0, MAX_THREAD_TITLE_SOURCE_CHARS)}...`;
133
+ }
134
+
135
+ function resolveThreadTitleTimeoutMs(timeoutMs: number | undefined): number {
136
+ return Math.max(100, Math.floor(timeoutMs ?? DEFAULT_THREAD_TITLE_TIMEOUT_MS));
137
+ }
138
+
139
+ export function normalizeGeneratedThreadTitle(raw: string): string {
140
+ const lines = raw.replace(/\r/g, "").split("\n");
141
+ let firstLine = "";
142
+ for (const line of lines) {
143
+ const trimmed = line.trim();
144
+ if (!trimmed) {
145
+ continue;
146
+ }
147
+ if (!firstLine && trimmed.startsWith("```")) {
148
+ continue;
149
+ }
150
+ firstLine = trimmed;
151
+ break;
152
+ }
153
+ return stripThreadTitleWrappers(firstLine);
154
+ }
155
+
156
+ function stripThreadTitleWrappers(raw: string): string {
157
+ let current = raw.trim();
158
+ let previous = "";
159
+ while (current && current !== previous) {
160
+ previous = current;
161
+ current = current.replace(/^["'`]+|["'`]+$/g, "").trim();
162
+ current = current.replace(/^\*\*(.+)\*\*$/u, "$1").trim();
163
+ current = current.replace(/^__(.+)__$/u, "$1").trim();
164
+ current = current.replace(/^\*(.+)\*$/u, "$1").trim();
165
+ current = current.replace(/^_(.+)_$/u, "$1").trim();
166
+ current = current.replace(/^~~(.+)~~$/u, "$1").trim();
167
+ }
168
+ return current;
169
+ }
170
+
171
+ function normalizeTitleContextField(raw: string | undefined, maxChars: number): string | undefined {
172
+ const value = raw?.trim();
173
+ if (!value) {
174
+ return undefined;
175
+ }
176
+ const singleLine = value.replace(/\s+/g, " ");
177
+ if (singleLine.length <= maxChars) {
178
+ return singleLine;
179
+ }
180
+ return `${singleLine.slice(0, maxChars)}...`;
181
+ }
@@ -0,0 +1,287 @@
1
+ import type { AutoBotConfig, ReplyToMode } from "autobot/plugin-sdk/config-contracts";
2
+ import { resolveChannelModelOverride } from "autobot/plugin-sdk/model-session-runtime";
3
+ import { buildAgentSessionKey } from "autobot/plugin-sdk/routing";
4
+ import { logVerbose } from "autobot/plugin-sdk/runtime-env";
5
+ import {
6
+ normalizeOptionalString,
7
+ normalizeOptionalStringifiedId,
8
+ } from "autobot/plugin-sdk/string-coerce-runtime";
9
+ import {
10
+ ChannelType,
11
+ createThread,
12
+ editChannel,
13
+ getChannelMessage,
14
+ type Client,
15
+ } from "../internal/discord.js";
16
+ import { resolveDiscordMessageChannelId } from "./message-utils.js";
17
+ import { generateThreadTitle } from "./thread-title.js";
18
+ import { resolveDiscordReplyDeliveryPlan, sanitizeDiscordThreadName } from "./threading.starter.js";
19
+ import type {
20
+ DiscordAutoThreadContext,
21
+ DiscordAutoThreadReplyPlan,
22
+ DiscordMessageEvent,
23
+ MaybeCreateDiscordAutoThreadParams,
24
+ } from "./threading.types.js";
25
+
26
+ function resolveTrimmedDiscordMessageChannelId(params: {
27
+ message: DiscordMessageEvent["message"];
28
+ messageChannelId?: string;
29
+ }) {
30
+ return (
31
+ params.messageChannelId ||
32
+ resolveDiscordMessageChannelId({
33
+ message: params.message,
34
+ })
35
+ ).trim();
36
+ }
37
+
38
+ export function resolveDiscordAutoThreadContext(params: {
39
+ agentId: string;
40
+ channel: string;
41
+ messageChannelId: string;
42
+ createdThreadId?: string | null;
43
+ parentInheritanceEnabled?: boolean;
44
+ }): DiscordAutoThreadContext | null {
45
+ const createdThreadId = normalizeOptionalStringifiedId(params.createdThreadId) ?? "";
46
+ if (!createdThreadId) {
47
+ return null;
48
+ }
49
+ const messageChannelId = normalizeOptionalString(params.messageChannelId) ?? "";
50
+ if (!messageChannelId) {
51
+ return null;
52
+ }
53
+
54
+ const threadSessionKey = buildAgentSessionKey({
55
+ agentId: params.agentId,
56
+ channel: params.channel,
57
+ peer: { kind: "channel", id: createdThreadId },
58
+ });
59
+ const parentSessionKey = buildAgentSessionKey({
60
+ agentId: params.agentId,
61
+ channel: params.channel,
62
+ peer: { kind: "channel", id: messageChannelId },
63
+ });
64
+
65
+ return {
66
+ createdThreadId,
67
+ From: `${params.channel}:channel:${createdThreadId}`,
68
+ To: `channel:${createdThreadId}`,
69
+ OriginatingTo: `channel:${createdThreadId}`,
70
+ SessionKey: threadSessionKey,
71
+ ModelParentSessionKey: parentSessionKey,
72
+ ...(params.parentInheritanceEnabled === true ? { ParentSessionKey: parentSessionKey } : {}),
73
+ };
74
+ }
75
+
76
+ export async function resolveDiscordAutoThreadReplyPlan(
77
+ params: MaybeCreateDiscordAutoThreadParams & {
78
+ replyToMode: ReplyToMode;
79
+ agentId: string;
80
+ channel: string;
81
+ cfg: AutoBotConfig;
82
+ threadParentInheritanceEnabled?: boolean;
83
+ },
84
+ ): Promise<DiscordAutoThreadReplyPlan> {
85
+ const messageChannelId = resolveTrimmedDiscordMessageChannelId(params);
86
+ const targetChannelId = params.threadChannel?.id ?? (messageChannelId || "unknown");
87
+ const originalReplyTarget = `channel:${targetChannelId}`;
88
+ const createdThreadId = await maybeCreateDiscordAutoThread({
89
+ client: params.client,
90
+ message: params.message,
91
+ messageChannelId: messageChannelId || undefined,
92
+ channel: params.channel,
93
+ isGuildMessage: params.isGuildMessage,
94
+ channelConfig: params.channelConfig,
95
+ threadChannel: params.threadChannel,
96
+ channelType: params.channelType,
97
+ channelName: params.channelName,
98
+ channelDescription: params.channelDescription,
99
+ baseText: params.baseText,
100
+ combinedBody: params.combinedBody,
101
+ cfg: params.cfg,
102
+ agentId: params.agentId,
103
+ });
104
+ const deliveryPlan = resolveDiscordReplyDeliveryPlan({
105
+ replyTarget: originalReplyTarget,
106
+ replyToMode: params.replyToMode,
107
+ messageId: params.message.id,
108
+ threadChannel: params.threadChannel,
109
+ createdThreadId,
110
+ });
111
+ const autoThreadContext = params.isGuildMessage
112
+ ? resolveDiscordAutoThreadContext({
113
+ agentId: params.agentId,
114
+ channel: params.channel,
115
+ messageChannelId,
116
+ createdThreadId,
117
+ parentInheritanceEnabled: params.threadParentInheritanceEnabled,
118
+ })
119
+ : null;
120
+ return { ...deliveryPlan, createdThreadId, autoThreadContext };
121
+ }
122
+
123
+ export async function maybeCreateDiscordAutoThread(
124
+ params: MaybeCreateDiscordAutoThreadParams,
125
+ ): Promise<string | undefined> {
126
+ if (!params.isGuildMessage) {
127
+ return undefined;
128
+ }
129
+ if (!params.channelConfig?.autoThread) {
130
+ return undefined;
131
+ }
132
+ if (params.threadChannel) {
133
+ return undefined;
134
+ }
135
+ if (
136
+ params.channelType === ChannelType.GuildForum ||
137
+ params.channelType === ChannelType.GuildMedia ||
138
+ params.channelType === ChannelType.GuildVoice ||
139
+ params.channelType === ChannelType.GuildStageVoice
140
+ ) {
141
+ return undefined;
142
+ }
143
+
144
+ const messageChannelId = resolveTrimmedDiscordMessageChannelId(params);
145
+ if (!messageChannelId) {
146
+ return undefined;
147
+ }
148
+ try {
149
+ const rawThreadSource = params.baseText || params.combinedBody || "Thread";
150
+ const threadName = sanitizeDiscordThreadName(rawThreadSource, params.message.id);
151
+ const archiveDuration = params.channelConfig?.autoArchiveDuration
152
+ ? Number(params.channelConfig.autoArchiveDuration)
153
+ : 60;
154
+
155
+ const created = await createThread<{ id?: string }>(
156
+ params.client.rest,
157
+ messageChannelId,
158
+ {
159
+ body: {
160
+ name: threadName,
161
+ auto_archive_duration: archiveDuration,
162
+ },
163
+ },
164
+ params.message.id,
165
+ );
166
+ const createdId = created?.id || "";
167
+ if (
168
+ createdId &&
169
+ params.channelConfig?.autoThreadName === "generated" &&
170
+ params.cfg &&
171
+ params.agentId
172
+ ) {
173
+ const modelRef = resolveDiscordThreadTitleModelRef({
174
+ cfg: params.cfg,
175
+ channel: params.channel,
176
+ agentId: params.agentId,
177
+ threadId: createdId,
178
+ messageChannelId,
179
+ channelName: params.channelName,
180
+ });
181
+ void maybeRenameDiscordAutoThread({
182
+ client: params.client,
183
+ threadId: createdId,
184
+ currentName: threadName,
185
+ fallbackId: params.message.id,
186
+ sourceText: rawThreadSource,
187
+ modelRef,
188
+ channelName: params.channelName,
189
+ channelDescription: params.channelDescription,
190
+ cfg: params.cfg,
191
+ agentId: params.agentId,
192
+ });
193
+ }
194
+ return createdId || undefined;
195
+ } catch (err) {
196
+ logVerbose(
197
+ `discord: autoThread creation failed for ${messageChannelId}/${params.message.id}: ${String(err)}`,
198
+ );
199
+ try {
200
+ const msg = (await getChannelMessage(
201
+ params.client.rest,
202
+ messageChannelId,
203
+ params.message.id,
204
+ )) as {
205
+ thread?: { id?: string };
206
+ };
207
+ const existingThreadId = msg?.thread?.id || "";
208
+ if (existingThreadId) {
209
+ logVerbose(
210
+ `discord: autoThread reusing existing thread ${existingThreadId} on ${messageChannelId}/${params.message.id}`,
211
+ );
212
+ return existingThreadId;
213
+ }
214
+ } catch {
215
+ // If the refetch also fails, fall through to return undefined.
216
+ }
217
+ return undefined;
218
+ }
219
+ }
220
+
221
+ function resolveDiscordThreadTitleModelRef(params: {
222
+ cfg: AutoBotConfig;
223
+ channel?: string;
224
+ agentId: string;
225
+ threadId: string;
226
+ messageChannelId: string;
227
+ channelName?: string;
228
+ }): string | undefined {
229
+ const channel = params.channel?.trim();
230
+ if (!channel) {
231
+ return undefined;
232
+ }
233
+ const parentSessionKey = buildAgentSessionKey({
234
+ agentId: params.agentId,
235
+ channel,
236
+ peer: { kind: "channel", id: params.messageChannelId },
237
+ });
238
+ const channelLabel = params.channelName?.trim();
239
+ const groupChannel = channelLabel ? `#${channelLabel}` : undefined;
240
+ const channelOverride = resolveChannelModelOverride({
241
+ cfg: params.cfg,
242
+ channel,
243
+ groupId: params.threadId,
244
+ groupChatType: "channel",
245
+ groupChannel,
246
+ groupSubject: groupChannel,
247
+ parentSessionKey,
248
+ });
249
+ return channelOverride?.model;
250
+ }
251
+
252
+ async function maybeRenameDiscordAutoThread(params: {
253
+ client: Client;
254
+ threadId: string;
255
+ currentName: string;
256
+ fallbackId: string;
257
+ sourceText: string;
258
+ modelRef?: string;
259
+ channelName?: string;
260
+ channelDescription?: string;
261
+ cfg: AutoBotConfig;
262
+ agentId: string;
263
+ }): Promise<void> {
264
+ try {
265
+ const fallbackName = sanitizeDiscordThreadName("", params.fallbackId);
266
+ const generated = await generateThreadTitle({
267
+ cfg: params.cfg,
268
+ agentId: params.agentId,
269
+ messageText: params.sourceText,
270
+ modelRef: params.modelRef,
271
+ channelName: params.channelName,
272
+ channelDescription: params.channelDescription,
273
+ });
274
+ if (!generated) {
275
+ return;
276
+ }
277
+ const nextName = sanitizeDiscordThreadName(generated, params.fallbackId);
278
+ if (!nextName || nextName === params.currentName || nextName === fallbackName) {
279
+ return;
280
+ }
281
+ await editChannel(params.client.rest, params.threadId, {
282
+ body: { name: nextName },
283
+ });
284
+ } catch (err) {
285
+ logVerbose(`discord: autoThread rename failed for ${params.threadId}: ${String(err)}`);
286
+ }
287
+ }
@@ -0,0 +1,45 @@
1
+ import type { DiscordThreadStarter } from "./threading.types.js";
2
+
3
+ type DiscordThreadStarterCacheEntry = {
4
+ value: DiscordThreadStarter;
5
+ updatedAt: number;
6
+ };
7
+
8
+ const DISCORD_THREAD_STARTER_CACHE_TTL_MS = 5 * 60 * 1000;
9
+ const DISCORD_THREAD_STARTER_CACHE_MAX = 500;
10
+
11
+ const DISCORD_THREAD_STARTER_CACHE = new Map<string, DiscordThreadStarterCacheEntry>();
12
+
13
+ export function resetDiscordThreadStarterCacheForTest() {
14
+ DISCORD_THREAD_STARTER_CACHE.clear();
15
+ }
16
+
17
+ export function getCachedThreadStarter(key: string, now: number): DiscordThreadStarter | undefined {
18
+ const entry = DISCORD_THREAD_STARTER_CACHE.get(key);
19
+ if (!entry) {
20
+ return undefined;
21
+ }
22
+ if (now - entry.updatedAt > DISCORD_THREAD_STARTER_CACHE_TTL_MS) {
23
+ DISCORD_THREAD_STARTER_CACHE.delete(key);
24
+ return undefined;
25
+ }
26
+ DISCORD_THREAD_STARTER_CACHE.delete(key);
27
+ DISCORD_THREAD_STARTER_CACHE.set(key, { ...entry, updatedAt: now });
28
+ return entry.value;
29
+ }
30
+
31
+ export function setCachedThreadStarter(
32
+ key: string,
33
+ value: DiscordThreadStarter,
34
+ now: number,
35
+ ): void {
36
+ DISCORD_THREAD_STARTER_CACHE.delete(key);
37
+ DISCORD_THREAD_STARTER_CACHE.set(key, { value, updatedAt: now });
38
+ while (DISCORD_THREAD_STARTER_CACHE.size > DISCORD_THREAD_STARTER_CACHE_MAX) {
39
+ const iter = DISCORD_THREAD_STARTER_CACHE.keys().next();
40
+ if (iter.done) {
41
+ break;
42
+ }
43
+ DISCORD_THREAD_STARTER_CACHE.delete(iter.value);
44
+ }
45
+ }