@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,359 @@
1
+ import { resolveHumanDelayConfig } from "autobot/plugin-sdk/agent-runtime";
2
+ import {
3
+ formatInboundEnvelope,
4
+ resolveEnvelopeFormatOptions,
5
+ } from "autobot/plugin-sdk/channel-inbound";
6
+ import { isDangerousNameMatchingEnabled } from "autobot/plugin-sdk/dangerous-name-runtime";
7
+ import { runInboundReplyTurn } from "autobot/plugin-sdk/inbound-reply-dispatch";
8
+ import { logError } from "autobot/plugin-sdk/logging-core";
9
+ import { resolveMarkdownTableMode } from "autobot/plugin-sdk/markdown-table-runtime";
10
+ import { getAgentScopedMediaLocalRoots } from "autobot/plugin-sdk/media-runtime";
11
+ import { createNonExitingRuntime, logVerbose } from "autobot/plugin-sdk/runtime-env";
12
+ import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
13
+ import { createDiscordRestClient } from "../client.js";
14
+ import { resolveDiscordConversationIdentity } from "../conversation-identity.js";
15
+ import {
16
+ resolveAgentComponentRoute,
17
+ resolveComponentCommandAuthorized,
18
+ resolvePinnedMainDmOwnerFromAllowlist,
19
+ type AgentComponentContext,
20
+ type AgentComponentInteraction,
21
+ type ComponentInteractionContext,
22
+ type DiscordChannelContext,
23
+ } from "./agent-components-helpers.js";
24
+ import { readSessionUpdatedAt, resolveStorePath } from "./agent-components.deps.runtime.js";
25
+ import {
26
+ normalizeDiscordAllowList,
27
+ resolveDiscordChannelConfigWithFallback,
28
+ resolveDiscordGuildEntry,
29
+ } from "./allow-list.js";
30
+ import { formatDiscordUserTag } from "./format.js";
31
+ import {
32
+ buildDiscordGroupSystemPrompt,
33
+ buildDiscordInboundAccessContext,
34
+ } from "./inbound-context.js";
35
+ import { buildDirectLabel, buildGuildLabel } from "./reply-context.js";
36
+ import { deliverDiscordReply } from "./reply-delivery.js";
37
+
38
+ let conversationRuntimePromise: Promise<typeof import("./agent-components.runtime.js")> | undefined;
39
+ let typingRuntimePromise: Promise<typeof import("./typing.js")> | undefined;
40
+
41
+ async function loadConversationRuntime() {
42
+ conversationRuntimePromise ??= import("./agent-components.runtime.js");
43
+ return await conversationRuntimePromise;
44
+ }
45
+
46
+ async function loadTypingRuntime() {
47
+ typingRuntimePromise ??= import("./typing.js");
48
+ return await typingRuntimePromise;
49
+ }
50
+
51
+ function buildDiscordComponentConversationLabel(params: {
52
+ interactionCtx: ComponentInteractionContext;
53
+ interaction: AgentComponentInteraction;
54
+ channelCtx: DiscordChannelContext;
55
+ }) {
56
+ if (params.interactionCtx.isDirectMessage) {
57
+ return buildDirectLabel(params.interactionCtx.user);
58
+ }
59
+ if (params.interactionCtx.isGroupDm) {
60
+ return `Group DM #${params.channelCtx.channelName ?? params.interactionCtx.channelId} channel id:${params.interactionCtx.channelId}`;
61
+ }
62
+ return buildGuildLabel({
63
+ guild: params.interaction.guild ?? undefined,
64
+ channelName: params.channelCtx.channelName ?? params.interactionCtx.channelId,
65
+ channelId: params.interactionCtx.channelId,
66
+ });
67
+ }
68
+
69
+ function resolveDiscordComponentChatType(interactionCtx: ComponentInteractionContext) {
70
+ if (interactionCtx.isDirectMessage) {
71
+ return "direct";
72
+ }
73
+ if (interactionCtx.isGroupDm) {
74
+ return "group";
75
+ }
76
+ return "channel";
77
+ }
78
+
79
+ export function resolveDiscordComponentOriginatingTo(
80
+ interactionCtx: Pick<ComponentInteractionContext, "isDirectMessage" | "userId" | "channelId">,
81
+ ) {
82
+ return resolveDiscordConversationIdentity({
83
+ isDirectMessage: interactionCtx.isDirectMessage,
84
+ userId: interactionCtx.userId,
85
+ channelId: interactionCtx.channelId,
86
+ });
87
+ }
88
+
89
+ export async function dispatchDiscordComponentEvent(params: {
90
+ ctx: AgentComponentContext;
91
+ interaction: AgentComponentInteraction;
92
+ interactionCtx: ComponentInteractionContext;
93
+ channelCtx: DiscordChannelContext;
94
+ guildInfo: ReturnType<typeof resolveDiscordGuildEntry>;
95
+ eventText: string;
96
+ replyToId?: string;
97
+ routeOverrides?: { sessionKey?: string; agentId?: string; accountId?: string };
98
+ }): Promise<void> {
99
+ const { ctx, interaction, interactionCtx, channelCtx, guildInfo, eventText } = params;
100
+ const runtime = ctx.runtime ?? createNonExitingRuntime();
101
+ const route = resolveAgentComponentRoute({
102
+ ctx,
103
+ rawGuildId: interactionCtx.rawGuildId,
104
+ memberRoleIds: interactionCtx.memberRoleIds,
105
+ isDirectMessage: interactionCtx.isDirectMessage,
106
+ isGroupDm: interactionCtx.isGroupDm,
107
+ userId: interactionCtx.userId,
108
+ channelId: interactionCtx.channelId,
109
+ parentId: channelCtx.parentId,
110
+ });
111
+ const sessionKey = params.routeOverrides?.sessionKey ?? route.sessionKey;
112
+ const agentId = params.routeOverrides?.agentId ?? route.agentId;
113
+ const accountId = params.routeOverrides?.accountId ?? route.accountId;
114
+ const inboundLastRouteSessionKey = sessionKey;
115
+ const fromLabel = buildDiscordComponentConversationLabel({
116
+ interactionCtx,
117
+ interaction,
118
+ channelCtx,
119
+ });
120
+ const chatType = resolveDiscordComponentChatType(interactionCtx);
121
+ const senderName = interactionCtx.user.globalName ?? interactionCtx.user.username;
122
+ const senderUsername = interactionCtx.user.username;
123
+ const senderTag = formatDiscordUserTag(interactionCtx.user);
124
+ const groupChannel =
125
+ !interactionCtx.isDirectMessage && channelCtx.displayChannelSlug
126
+ ? `#${channelCtx.displayChannelSlug}`
127
+ : undefined;
128
+ const groupSubject = interactionCtx.isDirectMessage ? undefined : groupChannel;
129
+ const channelConfig = resolveDiscordChannelConfigWithFallback({
130
+ guildInfo,
131
+ channelId: interactionCtx.channelId,
132
+ channelName: channelCtx.channelName,
133
+ channelSlug: channelCtx.channelSlug,
134
+ parentId: channelCtx.parentId,
135
+ parentName: channelCtx.parentName,
136
+ parentSlug: channelCtx.parentSlug,
137
+ scope: channelCtx.isThread ? "thread" : "channel",
138
+ });
139
+ const allowNameMatching = isDangerousNameMatchingEnabled(ctx.discordConfig);
140
+ const { ownerAllowFrom } = buildDiscordInboundAccessContext({
141
+ channelConfig,
142
+ guildInfo,
143
+ sender: { id: interactionCtx.user.id, name: interactionCtx.user.username, tag: senderTag },
144
+ allowNameMatching,
145
+ isGuild: !interactionCtx.isDirectMessage,
146
+ });
147
+ const groupSystemPrompt = buildDiscordGroupSystemPrompt(channelConfig);
148
+ const pinnedMainDmOwner = interactionCtx.isDirectMessage
149
+ ? resolvePinnedMainDmOwnerFromAllowlist({
150
+ dmScope: ctx.cfg.session?.dmScope,
151
+ allowFrom: channelConfig?.users ?? guildInfo?.users,
152
+ normalizeEntry: (entry: string) => {
153
+ const normalized = normalizeDiscordAllowList([entry], ["discord:", "user:", "pk:"]);
154
+ const candidate = normalized?.ids.values().next().value;
155
+ return typeof candidate === "string" && /^\d+$/.test(candidate) ? candidate : undefined;
156
+ },
157
+ })
158
+ : null;
159
+ const commandAuthorized = await resolveComponentCommandAuthorized({
160
+ ctx,
161
+ interactionCtx,
162
+ channelConfig,
163
+ guildInfo,
164
+ allowNameMatching,
165
+ });
166
+ const storePath = resolveStorePath(ctx.cfg.session?.store, { agentId });
167
+ const envelopeOptions = resolveEnvelopeFormatOptions(ctx.cfg);
168
+ const previousTimestamp = readSessionUpdatedAt({
169
+ storePath,
170
+ sessionKey,
171
+ });
172
+ const timestamp = Date.now();
173
+ const combinedBody = formatInboundEnvelope({
174
+ channel: "Discord",
175
+ from: fromLabel,
176
+ timestamp,
177
+ body: eventText,
178
+ chatType,
179
+ senderLabel: senderName,
180
+ previousTimestamp,
181
+ envelope: envelopeOptions,
182
+ });
183
+
184
+ const {
185
+ createReplyReferencePlanner,
186
+ dispatchReplyWithBufferedBlockDispatcher,
187
+ finalizeInboundContext,
188
+ resolveChunkMode,
189
+ resolveTextChunkLimit,
190
+ recordInboundSession,
191
+ } = await (async () => {
192
+ const conversationRuntime = await loadConversationRuntime();
193
+ return {
194
+ ...conversationRuntime,
195
+ };
196
+ })();
197
+
198
+ const ctxPayload = finalizeInboundContext({
199
+ Body: combinedBody,
200
+ BodyForAgent: eventText,
201
+ RawBody: eventText,
202
+ CommandBody: eventText,
203
+ From: interactionCtx.isDirectMessage
204
+ ? `discord:${interactionCtx.userId}`
205
+ : interactionCtx.isGroupDm
206
+ ? `discord:group:${interactionCtx.channelId}`
207
+ : `discord:channel:${interactionCtx.channelId}`,
208
+ To: `channel:${interactionCtx.channelId}`,
209
+ SessionKey: sessionKey,
210
+ AccountId: accountId,
211
+ ChatType: chatType,
212
+ ConversationLabel: fromLabel,
213
+ SenderName: senderName,
214
+ SenderId: interactionCtx.userId,
215
+ SenderUsername: senderUsername,
216
+ SenderTag: senderTag,
217
+ GroupSubject: groupSubject,
218
+ GroupChannel: groupChannel,
219
+ MemberRoleIds: interactionCtx.memberRoleIds,
220
+ GroupSystemPrompt: interactionCtx.isDirectMessage ? undefined : groupSystemPrompt,
221
+ GroupSpace: guildInfo?.id ?? guildInfo?.slug ?? interactionCtx.rawGuildId ?? undefined,
222
+ OwnerAllowFrom: ownerAllowFrom,
223
+ Provider: "discord" as const,
224
+ Surface: "discord" as const,
225
+ WasMentioned: true,
226
+ CommandAuthorized: commandAuthorized,
227
+ CommandTurn: {
228
+ kind: "text-slash" as const,
229
+ source: "text" as const,
230
+ authorized: commandAuthorized,
231
+ body: eventText,
232
+ },
233
+ CommandSource: "text" as const,
234
+ MessageSid: interaction.rawData.id,
235
+ Timestamp: timestamp,
236
+ OriginatingChannel: "discord" as const,
237
+ OriginatingTo:
238
+ resolveDiscordComponentOriginatingTo(interactionCtx) ?? `channel:${interactionCtx.channelId}`,
239
+ });
240
+
241
+ const deliverTarget = `channel:${interactionCtx.channelId}`;
242
+ const typingChannelId = interactionCtx.channelId;
243
+ const tableMode = resolveMarkdownTableMode({
244
+ cfg: ctx.cfg,
245
+ channel: "discord",
246
+ accountId,
247
+ });
248
+ const textLimit = resolveTextChunkLimit(ctx.cfg, "discord", accountId, {
249
+ fallbackLimit: 2000,
250
+ });
251
+ const token = ctx.token ?? "";
252
+ const feedbackRest = createDiscordRestClient({
253
+ cfg: ctx.cfg,
254
+ token,
255
+ accountId,
256
+ }).rest;
257
+ const mediaLocalRoots = getAgentScopedMediaLocalRoots(ctx.cfg, agentId);
258
+ const replyToMode =
259
+ ctx.discordConfig?.replyToMode ?? ctx.cfg.channels?.discord?.replyToMode ?? "off";
260
+ const replyReference = createReplyReferencePlanner({
261
+ replyToMode,
262
+ startId: params.replyToId,
263
+ });
264
+
265
+ await runInboundReplyTurn({
266
+ channel: "discord",
267
+ accountId,
268
+ raw: interaction,
269
+ adapter: {
270
+ ingest: () => ({
271
+ id: interaction.id,
272
+ rawText: ctxPayload.RawBody ?? "",
273
+ textForAgent: ctxPayload.BodyForAgent,
274
+ textForCommands: ctxPayload.CommandBody,
275
+ raw: interaction,
276
+ }),
277
+ resolveTurn: () => ({
278
+ cfg: ctx.cfg,
279
+ channel: "discord",
280
+ accountId,
281
+ agentId,
282
+ routeSessionKey: sessionKey,
283
+ storePath,
284
+ ctxPayload,
285
+ recordInboundSession,
286
+ dispatchReplyWithBufferedBlockDispatcher,
287
+ record: {
288
+ updateLastRoute: interactionCtx.isDirectMessage
289
+ ? {
290
+ sessionKey: inboundLastRouteSessionKey,
291
+ channel: "discord",
292
+ to:
293
+ resolveDiscordComponentOriginatingTo(interactionCtx) ??
294
+ `user:${interactionCtx.userId}`,
295
+ accountId,
296
+ mainDmOwnerPin:
297
+ inboundLastRouteSessionKey === route.mainSessionKey && pinnedMainDmOwner
298
+ ? {
299
+ ownerRecipient: pinnedMainDmOwner,
300
+ senderRecipient: interactionCtx.userId,
301
+ onSkip: ({ ownerRecipient, senderRecipient }) => {
302
+ logVerbose(
303
+ `discord: skip main-session last route for ${senderRecipient} (pinned owner ${ownerRecipient})`,
304
+ );
305
+ },
306
+ }
307
+ : undefined,
308
+ }
309
+ : undefined,
310
+ onRecordError: (err) => {
311
+ logVerbose(`discord: failed updating component session meta: ${String(err)}`);
312
+ },
313
+ },
314
+ delivery: {
315
+ deliver: async (payload, info) => {
316
+ const replyToId = replyReference.use();
317
+ await deliverDiscordReply({
318
+ cfg: ctx.cfg,
319
+ replies: [payload],
320
+ target: deliverTarget,
321
+ token,
322
+ accountId,
323
+ rest: interaction.client.rest,
324
+ runtime,
325
+ replyToId,
326
+ replyToMode,
327
+ textLimit,
328
+ maxLinesPerMessage: resolveDiscordMaxLinesPerMessage({
329
+ cfg: ctx.cfg,
330
+ discordConfig: ctx.discordConfig,
331
+ accountId,
332
+ }),
333
+ tableMode,
334
+ chunkMode: resolveChunkMode(ctx.cfg, "discord", accountId),
335
+ mediaLocalRoots,
336
+ kind: info.kind,
337
+ });
338
+ replyReference.markSent();
339
+ },
340
+ onError: (err) => {
341
+ logError(`discord component dispatch failed: ${String(err)}`);
342
+ },
343
+ },
344
+ replyPipeline: {},
345
+ dispatcherOptions: {
346
+ humanDelay: resolveHumanDelayConfig(ctx.cfg, agentId),
347
+ onReplyStart: async () => {
348
+ try {
349
+ const { sendTyping } = await loadTypingRuntime();
350
+ await sendTyping({ rest: feedbackRest, channelId: typingChannelId });
351
+ } catch (err) {
352
+ logVerbose(`discord: typing failed for component reply: ${String(err)}`);
353
+ }
354
+ },
355
+ },
356
+ }),
357
+ },
358
+ });
359
+ }
@@ -0,0 +1,303 @@
1
+ import { logError } from "autobot/plugin-sdk/logging-core";
2
+ import {
3
+ resolveDiscordComponentEntryWithPersistence,
4
+ resolveDiscordModalEntryWithPersistence,
5
+ } from "../components-registry.js";
6
+ import type { ButtonInteraction, ComponentData } from "../internal/discord.js";
7
+ import {
8
+ type AgentComponentContext,
9
+ type AgentComponentMessageInteraction,
10
+ ensureComponentUserAllowed,
11
+ mapSelectValues,
12
+ parseDiscordComponentData,
13
+ resolveAuthorizedComponentInteraction,
14
+ resolveInteractionCustomId,
15
+ } from "./agent-components-helpers.js";
16
+ import { dispatchDiscordComponentEvent } from "./agent-components.dispatch.js";
17
+ import { dispatchPluginDiscordInteractiveEvent } from "./agent-components.plugin-interactive.js";
18
+ import type { DiscordComponentControlHandlers } from "./agent-components.wildcard-controls.js";
19
+
20
+ let componentsRuntimePromise: Promise<typeof import("../components.js")> | undefined;
21
+
22
+ async function loadComponentsRuntime() {
23
+ componentsRuntimePromise ??= import("../components.js");
24
+ return await componentsRuntimePromise;
25
+ }
26
+
27
+ async function handleDiscordComponentEvent(params: {
28
+ ctx: AgentComponentContext;
29
+ interaction: AgentComponentMessageInteraction;
30
+ data: ComponentData;
31
+ componentLabel: string;
32
+ values?: string[];
33
+ label: string;
34
+ }): Promise<void> {
35
+ const parsed = parseDiscordComponentData(
36
+ params.data,
37
+ resolveInteractionCustomId(params.interaction),
38
+ );
39
+ if (!parsed) {
40
+ logError(`${params.label}: failed to parse component data`);
41
+ try {
42
+ await params.interaction.reply({
43
+ content: "This component is no longer valid.",
44
+ ephemeral: true,
45
+ });
46
+ } catch {
47
+ // Interaction may have expired
48
+ }
49
+ return;
50
+ }
51
+
52
+ const entry = await resolveDiscordComponentEntryWithPersistence({
53
+ id: parsed.componentId,
54
+ consume: false,
55
+ });
56
+ if (!entry) {
57
+ try {
58
+ await params.interaction.reply({
59
+ content: "This component has expired.",
60
+ ephemeral: true,
61
+ });
62
+ } catch {
63
+ // Interaction may have expired
64
+ }
65
+ return;
66
+ }
67
+
68
+ const unauthorizedReply = `You are not authorized to use this ${params.componentLabel}.`;
69
+ const authorized = await resolveAuthorizedComponentInteraction({
70
+ ctx: params.ctx,
71
+ interaction: params.interaction,
72
+ label: params.label,
73
+ componentLabel: params.componentLabel,
74
+ unauthorizedReply,
75
+ defer: false,
76
+ });
77
+ if (!authorized) {
78
+ return;
79
+ }
80
+ const {
81
+ interactionCtx,
82
+ channelCtx,
83
+ guildInfo,
84
+ allowNameMatching,
85
+ commandAuthorized,
86
+ user,
87
+ replyOpts,
88
+ } = authorized;
89
+
90
+ const componentAllowed = await ensureComponentUserAllowed({
91
+ entry,
92
+ interaction: params.interaction,
93
+ user,
94
+ replyOpts,
95
+ componentLabel: params.componentLabel,
96
+ unauthorizedReply,
97
+ allowNameMatching,
98
+ });
99
+ if (!componentAllowed) {
100
+ return;
101
+ }
102
+ const consumed = await resolveDiscordComponentEntryWithPersistence({
103
+ id: parsed.componentId,
104
+ consume: !entry.reusable,
105
+ });
106
+ if (!consumed) {
107
+ try {
108
+ await params.interaction.reply({
109
+ content: "This component has expired.",
110
+ ephemeral: true,
111
+ });
112
+ } catch {
113
+ // Interaction may have expired
114
+ }
115
+ return;
116
+ }
117
+
118
+ if (consumed.kind === "modal-trigger") {
119
+ try {
120
+ await params.interaction.reply({
121
+ content: "This form is no longer available.",
122
+ ephemeral: true,
123
+ });
124
+ } catch {
125
+ // Interaction may have expired
126
+ }
127
+ return;
128
+ }
129
+
130
+ const values = params.values ? mapSelectValues(consumed, params.values) : undefined;
131
+ if (consumed.callbackData) {
132
+ const pluginDispatch = await dispatchPluginDiscordInteractiveEvent({
133
+ ctx: params.ctx,
134
+ interaction: params.interaction,
135
+ interactionCtx,
136
+ channelCtx,
137
+ isAuthorizedSender: commandAuthorized,
138
+ data: consumed.callbackData,
139
+ kind: consumed.kind === "select" ? "select" : "button",
140
+ values,
141
+ messageId: consumed.messageId ?? params.interaction.message?.id,
142
+ });
143
+ if (pluginDispatch === "handled") {
144
+ return;
145
+ }
146
+ }
147
+ // Preserve explicit callback payloads for button fallbacks so Discord
148
+ // behaves like Telegram when buttons carry synthetic command text. Select
149
+ // fallbacks still need their chosen values in the synthesized event text.
150
+ const eventText =
151
+ (consumed.kind === "button" ? consumed.callbackData?.trim() : undefined) ||
152
+ (await loadComponentsRuntime()).formatDiscordComponentEventText({
153
+ kind: consumed.kind === "select" ? "select" : "button",
154
+ label: consumed.label,
155
+ values,
156
+ });
157
+
158
+ try {
159
+ await params.interaction.reply({ content: "✓", ...replyOpts });
160
+ } catch (err) {
161
+ logError(`${params.label}: failed to acknowledge interaction: ${String(err)}`);
162
+ }
163
+
164
+ await dispatchDiscordComponentEvent({
165
+ ctx: params.ctx,
166
+ interaction: params.interaction,
167
+ interactionCtx,
168
+ channelCtx,
169
+ guildInfo,
170
+ eventText,
171
+ replyToId: consumed.messageId ?? params.interaction.message?.id,
172
+ routeOverrides: {
173
+ sessionKey: consumed.sessionKey,
174
+ agentId: consumed.agentId,
175
+ accountId: consumed.accountId,
176
+ },
177
+ });
178
+ }
179
+
180
+ async function handleDiscordModalTrigger(params: {
181
+ ctx: AgentComponentContext;
182
+ interaction: ButtonInteraction;
183
+ data: ComponentData;
184
+ label: string;
185
+ }): Promise<void> {
186
+ const parsed = parseDiscordComponentData(
187
+ params.data,
188
+ resolveInteractionCustomId(params.interaction),
189
+ );
190
+ if (!parsed) {
191
+ logError(`${params.label}: failed to parse modal trigger data`);
192
+ try {
193
+ await params.interaction.reply({
194
+ content: "This button is no longer valid.",
195
+ ephemeral: true,
196
+ });
197
+ } catch {
198
+ // Interaction may have expired
199
+ }
200
+ return;
201
+ }
202
+ const entry = await resolveDiscordComponentEntryWithPersistence({
203
+ id: parsed.componentId,
204
+ consume: false,
205
+ });
206
+ if (!entry || entry.kind !== "modal-trigger") {
207
+ try {
208
+ await params.interaction.reply({
209
+ content: "This button has expired.",
210
+ ephemeral: true,
211
+ });
212
+ } catch {
213
+ // Interaction may have expired
214
+ }
215
+ return;
216
+ }
217
+
218
+ const modalId = entry.modalId ?? parsed.modalId;
219
+ if (!modalId) {
220
+ try {
221
+ await params.interaction.reply({
222
+ content: "This form is no longer available.",
223
+ ephemeral: true,
224
+ });
225
+ } catch {
226
+ // Interaction may have expired
227
+ }
228
+ return;
229
+ }
230
+
231
+ const unauthorizedReply = "You are not authorized to use this form.";
232
+ const authorized = await resolveAuthorizedComponentInteraction({
233
+ ctx: params.ctx,
234
+ interaction: params.interaction,
235
+ label: params.label,
236
+ componentLabel: "form",
237
+ unauthorizedReply,
238
+ defer: false,
239
+ });
240
+ if (!authorized) {
241
+ return;
242
+ }
243
+ const { user, replyOpts, allowNameMatching } = authorized;
244
+
245
+ const componentAllowed = await ensureComponentUserAllowed({
246
+ entry,
247
+ interaction: params.interaction,
248
+ user,
249
+ replyOpts,
250
+ componentLabel: "form",
251
+ unauthorizedReply,
252
+ allowNameMatching,
253
+ });
254
+ if (!componentAllowed) {
255
+ return;
256
+ }
257
+
258
+ const consumed = await resolveDiscordComponentEntryWithPersistence({
259
+ id: parsed.componentId,
260
+ consume: !entry.reusable,
261
+ });
262
+ if (!consumed) {
263
+ try {
264
+ await params.interaction.reply({
265
+ content: "This form has expired.",
266
+ ephemeral: true,
267
+ });
268
+ } catch {
269
+ // Interaction may have expired
270
+ }
271
+ return;
272
+ }
273
+
274
+ const resolvedModalId = consumed.modalId ?? modalId;
275
+ const modalEntry = await resolveDiscordModalEntryWithPersistence({
276
+ id: resolvedModalId,
277
+ consume: false,
278
+ });
279
+ if (!modalEntry) {
280
+ try {
281
+ await params.interaction.reply({
282
+ content: "This form has expired.",
283
+ ephemeral: true,
284
+ });
285
+ } catch {
286
+ // Interaction may have expired
287
+ }
288
+ return;
289
+ }
290
+
291
+ try {
292
+ await params.interaction.showModal(
293
+ (await loadComponentsRuntime()).createDiscordFormModal(modalEntry),
294
+ );
295
+ } catch (err) {
296
+ logError(`${params.label}: failed to show modal: ${String(err)}`);
297
+ }
298
+ }
299
+
300
+ export const discordComponentControlHandlers: DiscordComponentControlHandlers = {
301
+ handleComponentEvent: handleDiscordComponentEvent,
302
+ handleModalTrigger: handleDiscordModalTrigger,
303
+ };