@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
package/src/shared.ts ADDED
@@ -0,0 +1,197 @@
1
+ import { describeAccountSnapshot } from "autobot/plugin-sdk/account-helpers";
2
+ import { normalizeAccountId } from "autobot/plugin-sdk/account-id";
3
+ import { formatAllowFromLowercase } from "autobot/plugin-sdk/allow-from";
4
+ import { adaptScopedAccountAccessor } from "autobot/plugin-sdk/channel-config-helpers";
5
+ import { createScopedChannelConfigAdapter } from "autobot/plugin-sdk/channel-config-helpers";
6
+ import type { ChannelDoctorAdapter } from "autobot/plugin-sdk/channel-contract";
7
+ import { inspectDiscordAccount } from "./account-inspect.js";
8
+ import {
9
+ isDiscordAccountEnabledForRuntime,
10
+ listDiscordAccountIds,
11
+ mergeDiscordAccountConfig,
12
+ resolveDefaultDiscordAccountId,
13
+ resolveDiscordAccount,
14
+ resolveDiscordAccountAllowFrom,
15
+ resolveDiscordAccountDisabledReason,
16
+ type ResolvedDiscordAccount,
17
+ } from "./accounts.js";
18
+ import {
19
+ getChatChannelMeta,
20
+ resolveConfiguredFromCredentialStatuses,
21
+ type ChannelPlugin,
22
+ } from "./channel-api.js";
23
+ import { DiscordChannelConfigSchema } from "./config-schema.js";
24
+ import { normalizeCompatibilityConfig } from "./doctor-contract.js";
25
+ import { DISCORD_LEGACY_CONFIG_RULES } from "./doctor-shared.js";
26
+ import type { AutoBotConfig } from "./runtime-api.js";
27
+ import {
28
+ collectRuntimeConfigAssignments,
29
+ secretTargetRegistryEntries,
30
+ } from "./secret-config-contract.js";
31
+ import {
32
+ collectUnsupportedSecretRefConfigCandidates,
33
+ unsupportedSecretRefSurfacePatterns,
34
+ } from "./security-contract.js";
35
+ import { discordSecurityAdapter } from "./security.js";
36
+ import { deriveLegacySessionChatType } from "./session-contract.js";
37
+
38
+ const DISCORD_CHANNEL = "discord" as const;
39
+
40
+ type DiscordDoctorModule = typeof import("./doctor.js");
41
+ type DiscordConfigAccessorAccount = {
42
+ allowFrom: string[] | undefined;
43
+ defaultTo: string | undefined;
44
+ };
45
+
46
+ let discordDoctorModulePromise: Promise<DiscordDoctorModule> | undefined;
47
+
48
+ async function loadDiscordDoctorModule(): Promise<DiscordDoctorModule> {
49
+ discordDoctorModulePromise ??= import("./doctor.js");
50
+ return await discordDoctorModulePromise;
51
+ }
52
+
53
+ const discordDoctor: ChannelDoctorAdapter = {
54
+ dmAllowFromMode: "topOnly",
55
+ groupModel: "route",
56
+ groupAllowFromFallbackToAllowFrom: false,
57
+ warnOnEmptyGroupSenderAllowlist: false,
58
+ legacyConfigRules: DISCORD_LEGACY_CONFIG_RULES,
59
+ normalizeCompatibilityConfig,
60
+ collectPreviewWarnings: async (params) =>
61
+ (await loadDiscordDoctorModule()).discordDoctor.collectPreviewWarnings?.(params) ?? [],
62
+ collectMutableAllowlistWarnings: async (params) =>
63
+ (await loadDiscordDoctorModule()).discordDoctor.collectMutableAllowlistWarnings?.(params) ?? [],
64
+ repairConfig: async (params) =>
65
+ (await loadDiscordDoctorModule()).discordDoctor.repairConfig?.(params) ?? {
66
+ config: params.cfg,
67
+ changes: [],
68
+ },
69
+ };
70
+
71
+ function resolveDiscordConfigAccessorAccount(params: {
72
+ cfg: AutoBotConfig;
73
+ accountId?: string | null;
74
+ }): DiscordConfigAccessorAccount {
75
+ const accountId = normalizeAccountId(
76
+ params.accountId ?? resolveDefaultDiscordAccountId(params.cfg),
77
+ );
78
+ const config = mergeDiscordAccountConfig(params.cfg, accountId);
79
+ return {
80
+ allowFrom: resolveDiscordAccountAllowFrom({ cfg: params.cfg, accountId }),
81
+ defaultTo: config.defaultTo,
82
+ };
83
+ }
84
+
85
+ export const discordConfigAdapter = createScopedChannelConfigAdapter<
86
+ ResolvedDiscordAccount,
87
+ DiscordConfigAccessorAccount
88
+ >({
89
+ sectionKey: DISCORD_CHANNEL,
90
+ listAccountIds: listDiscordAccountIds,
91
+ resolveAccount: adaptScopedAccountAccessor(resolveDiscordAccount),
92
+ resolveAccessorAccount: resolveDiscordConfigAccessorAccount,
93
+ inspectAccount: adaptScopedAccountAccessor(inspectDiscordAccount),
94
+ defaultAccountId: resolveDefaultDiscordAccountId,
95
+ clearBaseFields: ["token", "name"],
96
+ resolveAllowFrom: (account) => account.allowFrom,
97
+ formatAllowFrom: (allowFrom) => formatAllowFromLowercase({ allowFrom }),
98
+ resolveDefaultTo: (account) => account.defaultTo,
99
+ });
100
+
101
+ export function createDiscordPluginBase(params: {
102
+ setup: NonNullable<ChannelPlugin<ResolvedDiscordAccount>["setup"]>;
103
+ setupWizard?: ChannelPlugin<ResolvedDiscordAccount>["setupWizard"];
104
+ }): Pick<
105
+ ChannelPlugin<ResolvedDiscordAccount>,
106
+ | "id"
107
+ | "meta"
108
+ | "setupWizard"
109
+ | "capabilities"
110
+ | "commands"
111
+ | "doctor"
112
+ | "streaming"
113
+ | "reload"
114
+ | "configSchema"
115
+ | "config"
116
+ | "setup"
117
+ | "messaging"
118
+ | "security"
119
+ | "secrets"
120
+ > {
121
+ return {
122
+ id: DISCORD_CHANNEL,
123
+ ...(params.setupWizard ? { setupWizard: params.setupWizard } : {}),
124
+ meta: { ...getChatChannelMeta(DISCORD_CHANNEL) },
125
+ capabilities: {
126
+ chatTypes: ["direct", "channel", "thread"],
127
+ polls: true,
128
+ reactions: true,
129
+ threads: true,
130
+ media: true,
131
+ tts: {
132
+ voice: {
133
+ synthesisTarget: "voice-note",
134
+ },
135
+ },
136
+ nativeCommands: true,
137
+ },
138
+ commands: {
139
+ nativeCommandsAutoEnabled: true,
140
+ nativeSkillsAutoEnabled: true,
141
+ resolveNativeCommandName: ({ commandKey, defaultName }) =>
142
+ commandKey === "tts" ? "voice" : defaultName,
143
+ },
144
+ doctor: discordDoctor,
145
+ streaming: {
146
+ blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 },
147
+ },
148
+ reload: { configPrefixes: ["channels.discord"] },
149
+ configSchema: DiscordChannelConfigSchema,
150
+ config: {
151
+ ...discordConfigAdapter,
152
+ hasConfiguredState: ({ env }) =>
153
+ typeof env?.DISCORD_BOT_TOKEN === "string" && env.DISCORD_BOT_TOKEN.trim().length > 0,
154
+ isEnabled: (account, cfg) => isDiscordAccountEnabledForRuntime(account, cfg),
155
+ disabledReason: (account, cfg) => resolveDiscordAccountDisabledReason(account, cfg),
156
+ isConfigured: (account) =>
157
+ resolveConfiguredFromCredentialStatuses(account) ?? Boolean(account.token?.trim()),
158
+ describeAccount: (account) =>
159
+ describeAccountSnapshot({
160
+ account,
161
+ configured:
162
+ resolveConfiguredFromCredentialStatuses(account) ?? Boolean(account.token?.trim()),
163
+ extra: {
164
+ tokenSource: account.tokenSource,
165
+ tokenStatus: account.tokenStatus,
166
+ },
167
+ }),
168
+ },
169
+ messaging: {
170
+ deriveLegacySessionChatType,
171
+ },
172
+ security: discordSecurityAdapter,
173
+ secrets: {
174
+ secretTargetRegistryEntries,
175
+ unsupportedSecretRefSurfacePatterns,
176
+ collectUnsupportedSecretRefConfigCandidates,
177
+ collectRuntimeConfigAssignments,
178
+ },
179
+ setup: params.setup,
180
+ } as Pick<
181
+ ChannelPlugin<ResolvedDiscordAccount>,
182
+ | "id"
183
+ | "meta"
184
+ | "setupWizard"
185
+ | "capabilities"
186
+ | "commands"
187
+ | "doctor"
188
+ | "streaming"
189
+ | "reload"
190
+ | "configSchema"
191
+ | "config"
192
+ | "setup"
193
+ | "messaging"
194
+ | "security"
195
+ | "secrets"
196
+ >;
197
+ }
@@ -0,0 +1,201 @@
1
+ import type {
2
+ ChannelAccountSnapshot,
3
+ ChannelStatusIssue,
4
+ } from "autobot/plugin-sdk/channel-contract";
5
+ import {
6
+ appendMatchMetadata,
7
+ asString,
8
+ isRecord,
9
+ resolveEnabledConfiguredAccountId,
10
+ } from "autobot/plugin-sdk/status-helpers";
11
+
12
+ type DiscordIntentSummary = {
13
+ messageContent?: "enabled" | "limited" | "disabled";
14
+ };
15
+
16
+ type DiscordApplicationSummary = {
17
+ intents?: DiscordIntentSummary;
18
+ };
19
+
20
+ type DiscordAccountStatus = {
21
+ accountId?: unknown;
22
+ enabled?: unknown;
23
+ configured?: unknown;
24
+ running?: unknown;
25
+ connected?: unknown;
26
+ healthState?: unknown;
27
+ application?: unknown;
28
+ audit?: unknown;
29
+ };
30
+
31
+ type DiscordPermissionsAuditSummary = {
32
+ unresolvedChannels?: number;
33
+ channels?: Array<{
34
+ channelId: string;
35
+ ok?: boolean;
36
+ missing?: string[];
37
+ error?: string | null;
38
+ matchKey?: string;
39
+ matchSource?: string;
40
+ }>;
41
+ };
42
+
43
+ function readDiscordAccountStatus(value: ChannelAccountSnapshot): DiscordAccountStatus | null {
44
+ if (!isRecord(value)) {
45
+ return null;
46
+ }
47
+ return {
48
+ accountId: value.accountId,
49
+ enabled: value.enabled,
50
+ configured: value.configured,
51
+ running: value.running,
52
+ connected: value.connected,
53
+ healthState: value.healthState,
54
+ application: value.application,
55
+ audit: value.audit,
56
+ };
57
+ }
58
+
59
+ function readDiscordApplicationSummary(value: unknown): DiscordApplicationSummary {
60
+ if (!isRecord(value)) {
61
+ return {};
62
+ }
63
+ const intentsRaw = value.intents;
64
+ if (!isRecord(intentsRaw)) {
65
+ return {};
66
+ }
67
+ return {
68
+ intents: {
69
+ messageContent:
70
+ intentsRaw.messageContent === "enabled" ||
71
+ intentsRaw.messageContent === "limited" ||
72
+ intentsRaw.messageContent === "disabled"
73
+ ? intentsRaw.messageContent
74
+ : undefined,
75
+ },
76
+ };
77
+ }
78
+
79
+ function readDiscordPermissionsAuditSummary(value: unknown): DiscordPermissionsAuditSummary {
80
+ if (!isRecord(value)) {
81
+ return {};
82
+ }
83
+ const unresolvedChannels =
84
+ typeof value.unresolvedChannels === "number" && Number.isFinite(value.unresolvedChannels)
85
+ ? value.unresolvedChannels
86
+ : undefined;
87
+ const channelsRaw = value.channels;
88
+ const channels = Array.isArray(channelsRaw)
89
+ ? (channelsRaw
90
+ .map((entry) => {
91
+ if (!isRecord(entry)) {
92
+ return null;
93
+ }
94
+ const channelId = asString(entry.channelId);
95
+ if (!channelId) {
96
+ return null;
97
+ }
98
+ const ok = typeof entry.ok === "boolean" ? entry.ok : undefined;
99
+ const missing = Array.isArray(entry.missing)
100
+ ? entry.missing.map((v) => asString(v)).filter(Boolean)
101
+ : undefined;
102
+ const error = asString(entry.error) ?? null;
103
+ const matchKey = asString(entry.matchKey) ?? undefined;
104
+ const matchSource = asString(entry.matchSource) ?? undefined;
105
+ return {
106
+ channelId,
107
+ ok,
108
+ missing: missing?.length ? missing : undefined,
109
+ error,
110
+ matchKey,
111
+ matchSource,
112
+ };
113
+ })
114
+ .filter(Boolean) as DiscordPermissionsAuditSummary["channels"])
115
+ : undefined;
116
+ return { unresolvedChannels, channels };
117
+ }
118
+
119
+ export function collectDiscordStatusIssues(
120
+ accounts: ChannelAccountSnapshot[],
121
+ ): ChannelStatusIssue[] {
122
+ const issues: ChannelStatusIssue[] = [];
123
+ for (const entry of accounts) {
124
+ const account = readDiscordAccountStatus(entry);
125
+ if (!account) {
126
+ continue;
127
+ }
128
+ const accountId = resolveEnabledConfiguredAccountId(account);
129
+ if (!accountId) {
130
+ continue;
131
+ }
132
+
133
+ const running = account.running === true;
134
+ const healthState = asString(account.healthState);
135
+ if (
136
+ healthState === "stale-socket" ||
137
+ healthState === "stuck" ||
138
+ healthState === "disconnected" ||
139
+ healthState === "not-running"
140
+ ) {
141
+ const runningLabel = running ? "running" : "not running";
142
+ issues.push({
143
+ channel: "discord",
144
+ accountId,
145
+ kind: "runtime",
146
+ message: `Discord gateway transport is degraded (${healthState}; account is ${runningLabel}).`,
147
+ fix: "Check gateway event-loop health and Discord connectivity, then restart the Discord channel or gateway if the transport does not recover.",
148
+ });
149
+ } else if (running && account.connected === false) {
150
+ issues.push({
151
+ channel: "discord",
152
+ accountId,
153
+ kind: "runtime",
154
+ message: "Discord gateway transport is running but disconnected.",
155
+ fix: "Check gateway logs for Discord websocket errors and wait for reconnect; restart the Discord channel or gateway if it does not recover.",
156
+ });
157
+ }
158
+
159
+ const app = readDiscordApplicationSummary(account.application);
160
+ const messageContent = app.intents?.messageContent;
161
+ if (messageContent === "disabled") {
162
+ issues.push({
163
+ channel: "discord",
164
+ accountId,
165
+ kind: "intent",
166
+ message: "Message Content Intent is disabled. Bot may not see normal channel messages.",
167
+ fix: "Enable Message Content Intent in Discord Dev Portal → Bot → Privileged Gateway Intents, or require mention-only operation.",
168
+ });
169
+ }
170
+
171
+ const audit = readDiscordPermissionsAuditSummary(account.audit);
172
+ if (audit.unresolvedChannels && audit.unresolvedChannels > 0) {
173
+ issues.push({
174
+ channel: "discord",
175
+ accountId,
176
+ kind: "config",
177
+ message: `Some configured guild channels are not numeric IDs (unresolvedChannels=${audit.unresolvedChannels}). Permission audit can only check numeric channel IDs.`,
178
+ fix: "Use numeric channel IDs as keys in channels.discord.guilds.*.channels (then rerun channels status --probe).",
179
+ });
180
+ }
181
+ for (const channel of audit.channels ?? []) {
182
+ if (channel.ok === true) {
183
+ continue;
184
+ }
185
+ const missing = channel.missing?.length ? ` missing ${channel.missing.join(", ")}` : "";
186
+ const error = channel.error ? `: ${channel.error}` : "";
187
+ const baseMessage = `Channel ${channel.channelId} permission check failed.${missing}${error}`;
188
+ issues.push({
189
+ channel: "discord",
190
+ accountId,
191
+ kind: "permissions",
192
+ message: appendMatchMetadata(baseMessage, {
193
+ matchKey: channel.matchKey,
194
+ matchSource: channel.matchSource,
195
+ }),
196
+ fix: "Ensure the bot role can view + send in this channel (and that channel overrides don't deny it).",
197
+ });
198
+ }
199
+ }
200
+ return issues;
201
+ }
@@ -0,0 +1,232 @@
1
+ import type { AutoBotPluginApi } from "autobot/plugin-sdk/channel-plugin-common";
2
+ import {
3
+ formatThreadBindingDisabledError,
4
+ formatThreadBindingSpawnDisabledError,
5
+ resolveThreadBindingSpawnPolicy,
6
+ } from "autobot/plugin-sdk/conversation-runtime";
7
+ import {
8
+ normalizeOptionalLowercaseString,
9
+ normalizeOptionalStringifiedId,
10
+ } from "autobot/plugin-sdk/string-coerce-runtime";
11
+ import { resolveDiscordAccount } from "./accounts.js";
12
+ import {
13
+ autoBindSpawnedDiscordSubagent,
14
+ listThreadBindingsBySessionKey,
15
+ type ThreadBindingTargetKind,
16
+ unbindThreadBindingsBySessionKey,
17
+ } from "./monitor/thread-bindings.js";
18
+
19
+ function summarizeError(err: unknown): string {
20
+ if (err instanceof Error) {
21
+ return err.message;
22
+ }
23
+ if (typeof err === "string") {
24
+ return err;
25
+ }
26
+ return "error";
27
+ }
28
+
29
+ type DiscordSubagentSpawningEvent = {
30
+ threadRequested?: boolean;
31
+ requester?: {
32
+ channel?: string;
33
+ accountId?: string;
34
+ to?: string;
35
+ threadId?: string | number;
36
+ };
37
+ childSessionKey: string;
38
+ agentId: string;
39
+ label?: string;
40
+ };
41
+
42
+ type DiscordSubagentEndedEvent = {
43
+ targetSessionKey: string;
44
+ accountId?: string;
45
+ targetKind?: ThreadBindingTargetKind;
46
+ reason?: string;
47
+ sendFarewell?: boolean;
48
+ };
49
+
50
+ type DiscordSubagentDeliveryTargetEvent = {
51
+ expectsCompletionMessage?: boolean;
52
+ childSessionKey: string;
53
+ requesterOrigin?: {
54
+ channel?: string;
55
+ accountId?: string;
56
+ threadId?: string | number;
57
+ };
58
+ };
59
+
60
+ type DiscordSubagentSpawningResult =
61
+ | {
62
+ status: "ok";
63
+ threadBindingReady?: boolean;
64
+ deliveryOrigin?: {
65
+ channel: "discord";
66
+ accountId?: string;
67
+ to: string;
68
+ threadId?: string | number;
69
+ };
70
+ }
71
+ | { status: "error"; error: string }
72
+ | undefined;
73
+
74
+ type DiscordSubagentDeliveryTargetResult =
75
+ | {
76
+ origin: {
77
+ channel: "discord";
78
+ accountId?: string;
79
+ to: string;
80
+ threadId?: string | number;
81
+ };
82
+ }
83
+ | undefined;
84
+
85
+ function normalizeThreadBindingTargetKind(raw?: string): ThreadBindingTargetKind | undefined {
86
+ const normalized = normalizeOptionalLowercaseString(raw);
87
+ if (normalized === "subagent" || normalized === "acp") {
88
+ return normalized;
89
+ }
90
+ return undefined;
91
+ }
92
+
93
+ export async function handleDiscordSubagentSpawning(
94
+ api: AutoBotPluginApi,
95
+ event: DiscordSubagentSpawningEvent,
96
+ ): Promise<DiscordSubagentSpawningResult> {
97
+ if (!event.threadRequested) {
98
+ return undefined;
99
+ }
100
+ const channel = normalizeOptionalLowercaseString(event.requester?.channel);
101
+ if (channel !== "discord") {
102
+ return undefined;
103
+ }
104
+ const account = resolveDiscordAccount({
105
+ cfg: api.config,
106
+ accountId: event.requester?.accountId,
107
+ });
108
+ const threadBindingPolicy = resolveThreadBindingSpawnPolicy({
109
+ cfg: api.config,
110
+ channel: "discord",
111
+ accountId: account.accountId,
112
+ kind: "subagent",
113
+ });
114
+ if (!threadBindingPolicy.enabled) {
115
+ return {
116
+ status: "error" as const,
117
+ error: formatThreadBindingDisabledError({
118
+ channel: threadBindingPolicy.channel,
119
+ accountId: threadBindingPolicy.accountId,
120
+ kind: "subagent",
121
+ }),
122
+ };
123
+ }
124
+ if (!threadBindingPolicy.spawnEnabled) {
125
+ return {
126
+ status: "error" as const,
127
+ error: formatThreadBindingSpawnDisabledError({
128
+ channel: threadBindingPolicy.channel,
129
+ accountId: threadBindingPolicy.accountId,
130
+ kind: "subagent",
131
+ }),
132
+ };
133
+ }
134
+ try {
135
+ const agentId = event.agentId?.trim() || "subagent";
136
+ const binding = await autoBindSpawnedDiscordSubagent({
137
+ cfg: api.config,
138
+ accountId: account.accountId,
139
+ channel: event.requester?.channel,
140
+ to: event.requester?.to,
141
+ threadId: event.requester?.threadId,
142
+ childSessionKey: event.childSessionKey,
143
+ agentId,
144
+ label: event.label,
145
+ boundBy: "system",
146
+ });
147
+ if (!binding) {
148
+ return {
149
+ status: "error" as const,
150
+ error:
151
+ "Unable to create or bind a Discord thread for this subagent session. Session mode is unavailable for this target.",
152
+ };
153
+ }
154
+ return {
155
+ status: "ok" as const,
156
+ threadBindingReady: true,
157
+ deliveryOrigin: {
158
+ channel: "discord",
159
+ accountId: account.accountId,
160
+ to: `channel:${binding.threadId}`,
161
+ threadId: binding.threadId,
162
+ },
163
+ };
164
+ } catch (err) {
165
+ return {
166
+ status: "error" as const,
167
+ error: `Discord thread bind failed: ${summarizeError(err)}`,
168
+ };
169
+ }
170
+ }
171
+
172
+ export function handleDiscordSubagentEnded(event: DiscordSubagentEndedEvent) {
173
+ unbindThreadBindingsBySessionKey({
174
+ targetSessionKey: event.targetSessionKey,
175
+ accountId: event.accountId,
176
+ targetKind: normalizeThreadBindingTargetKind(event.targetKind),
177
+ reason: event.reason,
178
+ sendFarewell: event.sendFarewell,
179
+ });
180
+ }
181
+
182
+ export function handleDiscordSubagentDeliveryTarget(
183
+ event: DiscordSubagentDeliveryTargetEvent,
184
+ ): DiscordSubagentDeliveryTargetResult {
185
+ if (!event.expectsCompletionMessage) {
186
+ return undefined;
187
+ }
188
+ const requesterChannel = normalizeOptionalLowercaseString(event.requesterOrigin?.channel);
189
+ if (requesterChannel !== "discord") {
190
+ return undefined;
191
+ }
192
+ const requesterAccountId = event.requesterOrigin?.accountId?.trim();
193
+ const requesterThreadId =
194
+ event.requesterOrigin?.threadId != null && event.requesterOrigin.threadId !== ""
195
+ ? (normalizeOptionalStringifiedId(event.requesterOrigin.threadId) ?? "")
196
+ : "";
197
+ const bindings = listThreadBindingsBySessionKey({
198
+ targetSessionKey: event.childSessionKey,
199
+ ...(requesterAccountId ? { accountId: requesterAccountId } : {}),
200
+ targetKind: "subagent",
201
+ });
202
+ if (bindings.length === 0) {
203
+ return undefined;
204
+ }
205
+
206
+ let binding: (typeof bindings)[number] | undefined;
207
+ if (requesterThreadId) {
208
+ binding = bindings.find((entry) => {
209
+ if (entry.threadId !== requesterThreadId) {
210
+ return false;
211
+ }
212
+ if (requesterAccountId && entry.accountId !== requesterAccountId) {
213
+ return false;
214
+ }
215
+ return true;
216
+ });
217
+ }
218
+ if (!binding && bindings.length === 1) {
219
+ binding = bindings[0];
220
+ }
221
+ if (!binding) {
222
+ return undefined;
223
+ }
224
+ return {
225
+ origin: {
226
+ channel: "discord" as const,
227
+ accountId: binding.accountId,
228
+ to: `channel:${binding.threadId}`,
229
+ threadId: binding.threadId,
230
+ },
231
+ };
232
+ }
@@ -0,0 +1,70 @@
1
+ import {
2
+ buildMessagingTarget,
3
+ parseMentionPrefixOrAtUserTarget,
4
+ requireTargetKind,
5
+ type MessagingTarget,
6
+ type MessagingTargetKind,
7
+ type MessagingTargetParseOptions,
8
+ } from "autobot/plugin-sdk/messaging-targets";
9
+
10
+ export type DiscordTargetKind = MessagingTargetKind;
11
+
12
+ export type DiscordTarget = MessagingTarget;
13
+
14
+ export type DiscordTargetParseOptions = MessagingTargetParseOptions;
15
+
16
+ export function parseDiscordTarget(
17
+ raw: string,
18
+ options: DiscordTargetParseOptions = {},
19
+ ): DiscordTarget | undefined {
20
+ const trimmed = raw.trim();
21
+ if (!trimmed) {
22
+ return undefined;
23
+ }
24
+ const providerPrefixedTarget = parseDiscordProviderPrefixedTarget(trimmed);
25
+ if (providerPrefixedTarget) {
26
+ return providerPrefixedTarget;
27
+ }
28
+ const userTarget = parseMentionPrefixOrAtUserTarget({
29
+ raw: trimmed,
30
+ mentionPattern: /^<@!?(\d+)>$/,
31
+ prefixes: [
32
+ { prefix: "user:", kind: "user" },
33
+ { prefix: "channel:", kind: "channel" },
34
+ { prefix: "discord:", kind: "user" },
35
+ ],
36
+ atUserPattern: /^\d+$/,
37
+ atUserErrorMessage: "Discord DMs require a user id (use user:<id> or a <@id> mention)",
38
+ });
39
+ if (userTarget) {
40
+ return userTarget;
41
+ }
42
+ if (/^\d+$/.test(trimmed)) {
43
+ if (options.defaultKind) {
44
+ return buildMessagingTarget(options.defaultKind, trimmed, trimmed);
45
+ }
46
+ throw new Error(
47
+ options.ambiguousMessage ??
48
+ `Ambiguous Discord recipient "${trimmed}". For DMs use "user:${trimmed}" or "<@${trimmed}>"; for channels use "channel:${trimmed}".`,
49
+ );
50
+ }
51
+ return buildMessagingTarget("channel", trimmed, trimmed);
52
+ }
53
+
54
+ function parseDiscordProviderPrefixedTarget(raw: string): DiscordTarget | undefined {
55
+ const match = /^discord:(channel|user):(.+)$/i.exec(raw);
56
+ if (!match) {
57
+ return undefined;
58
+ }
59
+ const kind = match[1]?.toLowerCase() as "channel" | "user" | undefined;
60
+ const id = match[2]?.trim();
61
+ if (!kind || !id) {
62
+ return undefined;
63
+ }
64
+ return buildMessagingTarget(kind, id, `${kind}:${id}`);
65
+ }
66
+
67
+ export function resolveDiscordChannelId(raw: string): string {
68
+ const target = parseDiscordTarget(raw, { defaultKind: "channel" });
69
+ return requireTargetKind({ platform: "Discord", target, kind: "channel" });
70
+ }