@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,206 @@
1
+ import type { EventEmitter } from "node:events";
2
+ import { danger } from "autobot/plugin-sdk/runtime-env";
3
+ import type { RuntimeEnv } from "autobot/plugin-sdk/runtime-env";
4
+ import { formatErrorMessage } from "autobot/plugin-sdk/ssrf-runtime";
5
+
6
+ type DiscordGatewayEventType = "disallowed-intents" | "fatal" | "other" | "reconnect-exhausted";
7
+
8
+ export type DiscordGatewayEvent = {
9
+ type: DiscordGatewayEventType;
10
+ err: unknown;
11
+ message: string;
12
+ shouldStopLifecycle: boolean;
13
+ };
14
+
15
+ export class DiscordGatewayLifecycleError extends Error {
16
+ readonly eventType: DiscordGatewayEventType;
17
+
18
+ constructor(event: Pick<DiscordGatewayEvent, "type" | "message" | "err">) {
19
+ super(`discord gateway ${event.type}: ${event.message}`, {
20
+ cause: event.err instanceof Error ? event.err : undefined,
21
+ });
22
+ this.name = "DiscordGatewayLifecycleError";
23
+ this.eventType = event.type;
24
+ }
25
+ }
26
+
27
+ export function getDiscordGatewayEmitter(gateway?: unknown): EventEmitter | undefined {
28
+ return (gateway as { emitter?: EventEmitter } | undefined)?.emitter;
29
+ }
30
+
31
+ export type DiscordGatewaySupervisor = {
32
+ emitter?: EventEmitter;
33
+ attachLifecycle: (handler: (event: DiscordGatewayEvent) => void) => void;
34
+ detachLifecycle: () => void;
35
+ drainPending: (
36
+ handler: (event: DiscordGatewayEvent) => "continue" | "stop",
37
+ ) => "continue" | "stop";
38
+ dispose: () => void;
39
+ };
40
+
41
+ type GatewaySupervisorPhase = "active" | "buffering" | "disposed" | "teardown";
42
+
43
+ function readFirstStackFrame(err: Error): string | undefined {
44
+ const stack = err.stack;
45
+ if (!stack) {
46
+ return undefined;
47
+ }
48
+ const frame = stack
49
+ .split("\n")
50
+ .slice(1)
51
+ .map((line) => line.trim())
52
+ .find(Boolean);
53
+ return frame ? frame.replace(/^at\s+/, "") : undefined;
54
+ }
55
+
56
+ function formatDiscordGatewayErrorMessage(err: unknown): string {
57
+ if (!(err instanceof Error)) {
58
+ return formatErrorMessage(err);
59
+ }
60
+ if (err.message) {
61
+ const detail = formatErrorMessage(err);
62
+ return err.name ? `${err.name}: ${detail}` : detail;
63
+ }
64
+ const detail = formatErrorMessage(err);
65
+ const firstFrame = readFirstStackFrame(err);
66
+ if (firstFrame && detail === (err.name || "Error")) {
67
+ return `${detail} @ ${firstFrame}`;
68
+ }
69
+ return detail;
70
+ }
71
+
72
+ export function classifyDiscordGatewayEvent(params: {
73
+ err: unknown;
74
+ isDisallowedIntentsError: (err: unknown) => boolean;
75
+ }): DiscordGatewayEvent {
76
+ const message = formatDiscordGatewayErrorMessage(params.err);
77
+ if (params.isDisallowedIntentsError(params.err)) {
78
+ return {
79
+ type: "disallowed-intents",
80
+ err: params.err,
81
+ message,
82
+ shouldStopLifecycle: true,
83
+ };
84
+ }
85
+ if (message.includes("Max reconnect attempts")) {
86
+ return {
87
+ type: "reconnect-exhausted",
88
+ err: params.err,
89
+ message,
90
+ shouldStopLifecycle: true,
91
+ };
92
+ }
93
+ if (
94
+ params.err instanceof TypeError ||
95
+ message.includes("Fatal Gateway error") ||
96
+ message.includes("Fatal gateway close code") ||
97
+ message.includes("Gateway HELLO missing heartbeat") ||
98
+ message.includes("Invalid gateway payload") ||
99
+ message.includes("Gateway socket emitted an unknown error")
100
+ ) {
101
+ return {
102
+ type: "fatal",
103
+ err: params.err,
104
+ message,
105
+ shouldStopLifecycle: true,
106
+ };
107
+ }
108
+ return {
109
+ type: "other",
110
+ err: params.err,
111
+ message,
112
+ shouldStopLifecycle: false,
113
+ };
114
+ }
115
+
116
+ export function createDiscordGatewaySupervisor(params: {
117
+ gateway?: unknown;
118
+ isDisallowedIntentsError: (err: unknown) => boolean;
119
+ runtime: RuntimeEnv;
120
+ }): DiscordGatewaySupervisor {
121
+ const emitter = getDiscordGatewayEmitter(params.gateway);
122
+ const pending: DiscordGatewayEvent[] = [];
123
+ if (!emitter) {
124
+ return {
125
+ attachLifecycle: () => {},
126
+ detachLifecycle: () => {},
127
+ drainPending: () => "continue",
128
+ dispose: () => {},
129
+ emitter,
130
+ };
131
+ }
132
+
133
+ let lifecycleHandler: ((event: DiscordGatewayEvent) => void) | undefined;
134
+ let phase: GatewaySupervisorPhase = "buffering";
135
+ const seenLateEventKeys = new Set<string>();
136
+ const logLateEvent =
137
+ (state: Extract<GatewaySupervisorPhase, "disposed" | "teardown">) =>
138
+ (event: DiscordGatewayEvent) => {
139
+ const key = `${state}:${event.type}:${event.message}`;
140
+ if (seenLateEventKeys.has(key)) {
141
+ return;
142
+ }
143
+ seenLateEventKeys.add(key);
144
+ params.runtime.error?.(
145
+ danger(
146
+ `discord: suppressed late gateway ${event.type} error ${
147
+ state === "disposed" ? "after dispose" : "during teardown"
148
+ }: ${event.message}`,
149
+ ),
150
+ );
151
+ };
152
+ const onGatewayError = (err: unknown) => {
153
+ const event = classifyDiscordGatewayEvent({
154
+ err,
155
+ isDisallowedIntentsError: params.isDisallowedIntentsError,
156
+ });
157
+ switch (phase) {
158
+ case "disposed":
159
+ logLateEvent("disposed")(event);
160
+ return;
161
+ case "active":
162
+ lifecycleHandler?.(event);
163
+ return;
164
+ case "teardown":
165
+ logLateEvent("teardown")(event);
166
+ return;
167
+ case "buffering":
168
+ pending.push(event);
169
+ return;
170
+ }
171
+ };
172
+ emitter.on("error", onGatewayError);
173
+
174
+ return {
175
+ emitter,
176
+ attachLifecycle: (handler) => {
177
+ lifecycleHandler = handler;
178
+ phase = "active";
179
+ },
180
+ detachLifecycle: () => {
181
+ lifecycleHandler = undefined;
182
+ phase = "teardown";
183
+ },
184
+ drainPending: (handler) => {
185
+ if (pending.length === 0) {
186
+ return "continue";
187
+ }
188
+ const queued = [...pending];
189
+ pending.length = 0;
190
+ for (const event of queued) {
191
+ if (handler(event) === "stop") {
192
+ return "stop";
193
+ }
194
+ }
195
+ return "continue";
196
+ },
197
+ dispose: () => {
198
+ if (phase === "disposed") {
199
+ return;
200
+ }
201
+ lifecycleHandler = undefined;
202
+ phase = "disposed";
203
+ pending.length = 0;
204
+ },
205
+ };
206
+ }
@@ -0,0 +1,95 @@
1
+ import type { MsgContext } from "autobot/plugin-sdk/reply-runtime";
2
+ import {
3
+ resolveDiscordMemberAllowed,
4
+ resolveDiscordOwnerAllowFrom,
5
+ type DiscordChannelConfigResolved,
6
+ type DiscordGuildEntryResolved,
7
+ } from "./allow-list.js";
8
+
9
+ type DiscordSupplementalContextSender = {
10
+ id?: string;
11
+ name?: string;
12
+ tag?: string;
13
+ memberRoleIds?: string[];
14
+ };
15
+
16
+ export function createDiscordSupplementalContextAccessChecker(params: {
17
+ channelConfig?: DiscordChannelConfigResolved | null;
18
+ guildInfo?: DiscordGuildEntryResolved | null;
19
+ allowNameMatching?: boolean;
20
+ isGuild: boolean;
21
+ }) {
22
+ return (sender: DiscordSupplementalContextSender): boolean => {
23
+ if (!params.isGuild) {
24
+ return true;
25
+ }
26
+ return resolveDiscordMemberAllowed({
27
+ userAllowList: params.channelConfig?.users ?? params.guildInfo?.users,
28
+ roleAllowList: params.channelConfig?.roles ?? params.guildInfo?.roles,
29
+ memberRoleIds: sender.memberRoleIds ?? [],
30
+ userId: sender.id ?? "",
31
+ userName: sender.name,
32
+ userTag: sender.tag,
33
+ allowNameMatching: params.allowNameMatching,
34
+ });
35
+ };
36
+ }
37
+
38
+ export function buildDiscordGroupSystemPrompt(
39
+ channelConfig?: DiscordChannelConfigResolved | null,
40
+ ): string | undefined {
41
+ const systemPromptParts = [channelConfig?.systemPrompt?.trim() || null].filter(
42
+ (entry): entry is string => Boolean(entry),
43
+ );
44
+ return systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
45
+ }
46
+
47
+ export function buildDiscordUntrustedContext(params: {
48
+ isGuild: boolean;
49
+ channelTopic?: string;
50
+ }): MsgContext["UntrustedStructuredContext"] | undefined {
51
+ if (!params.isGuild) {
52
+ return undefined;
53
+ }
54
+ const entries: NonNullable<MsgContext["UntrustedStructuredContext"]> = [];
55
+ if (typeof params.channelTopic === "string" && params.channelTopic.trim().length > 0) {
56
+ entries.push({
57
+ label: "Discord channel metadata",
58
+ source: "discord",
59
+ type: "channel_metadata",
60
+ payload: {
61
+ topic: params.channelTopic.trim(),
62
+ },
63
+ });
64
+ }
65
+ return entries.length > 0 ? entries : undefined;
66
+ }
67
+
68
+ export function buildDiscordInboundAccessContext(params: {
69
+ channelConfig?: DiscordChannelConfigResolved | null;
70
+ guildInfo?: DiscordGuildEntryResolved | null;
71
+ sender: {
72
+ id: string;
73
+ name?: string;
74
+ tag?: string;
75
+ };
76
+ allowNameMatching?: boolean;
77
+ isGuild: boolean;
78
+ channelTopic?: string;
79
+ }) {
80
+ return {
81
+ groupSystemPrompt: params.isGuild
82
+ ? buildDiscordGroupSystemPrompt(params.channelConfig)
83
+ : undefined,
84
+ untrustedContext: buildDiscordUntrustedContext({
85
+ isGuild: params.isGuild,
86
+ channelTopic: params.channelTopic,
87
+ }),
88
+ ownerAllowFrom: resolveDiscordOwnerAllowFrom({
89
+ channelConfig: params.channelConfig,
90
+ guildInfo: params.guildInfo,
91
+ sender: params.sender,
92
+ allowNameMatching: params.allowNameMatching,
93
+ }),
94
+ };
95
+ }
@@ -0,0 +1,79 @@
1
+ import { createClaimableDedupe, type ClaimableDedupe } from "autobot/plugin-sdk/persistent-dedupe";
2
+ import type { DiscordMessageEvent } from "./listeners.js";
3
+ import { resolveDiscordMessageChannelId } from "./message-utils.js";
4
+
5
+ const RECENT_DISCORD_MESSAGE_TTL_MS = 5 * 60_000;
6
+ const RECENT_DISCORD_MESSAGE_MAX = 5000;
7
+
8
+ export function createDiscordInboundReplayGuard(): ClaimableDedupe {
9
+ return createClaimableDedupe({
10
+ ttlMs: RECENT_DISCORD_MESSAGE_TTL_MS,
11
+ memoryMaxSize: RECENT_DISCORD_MESSAGE_MAX,
12
+ });
13
+ }
14
+
15
+ export class DiscordRetryableInboundError extends Error {
16
+ constructor(message: string, options?: ErrorOptions) {
17
+ super(message, options);
18
+ this.name = "DiscordRetryableInboundError";
19
+ }
20
+ }
21
+
22
+ export function buildDiscordInboundReplayKey(params: {
23
+ accountId: string;
24
+ data: DiscordMessageEvent;
25
+ }): string | null {
26
+ const messageId = params.data.message?.id?.trim();
27
+ if (!messageId) {
28
+ return null;
29
+ }
30
+ const channelId = resolveDiscordMessageChannelId({
31
+ message: params.data.message,
32
+ eventChannelId: params.data.channel_id,
33
+ });
34
+ if (!channelId) {
35
+ return null;
36
+ }
37
+ return `${params.accountId}:${channelId}:${messageId}`;
38
+ }
39
+
40
+ export async function claimDiscordInboundReplay(params: {
41
+ replayKey?: string | null;
42
+ replayGuard: ClaimableDedupe;
43
+ }): Promise<boolean> {
44
+ const replayKey = params.replayKey?.trim();
45
+ if (!replayKey) {
46
+ return true;
47
+ }
48
+ const claim = await params.replayGuard.claim(replayKey);
49
+ return claim.kind === "claimed";
50
+ }
51
+
52
+ export async function commitDiscordInboundReplay(params: {
53
+ replayKeys?: readonly (string | null | undefined)[];
54
+ replayGuard: ClaimableDedupe;
55
+ }): Promise<void> {
56
+ const replayKeys = normalizeDiscordInboundReplayKeys(params.replayKeys);
57
+ await Promise.all(replayKeys.map((replayKey) => params.replayGuard.commit(replayKey)));
58
+ }
59
+
60
+ export function releaseDiscordInboundReplay(params: {
61
+ replayKeys?: readonly (string | null | undefined)[];
62
+ replayGuard: ClaimableDedupe;
63
+ error?: unknown;
64
+ }): void {
65
+ const replayKeys = normalizeDiscordInboundReplayKeys(params.replayKeys);
66
+ replayKeys.forEach((replayKey) => params.replayGuard.release(replayKey, { error: params.error }));
67
+ }
68
+
69
+ function normalizeDiscordInboundReplayKeys(
70
+ replayKeys?: readonly (string | null | undefined)[],
71
+ ): string[] {
72
+ return [
73
+ ...new Set(
74
+ (replayKeys ?? [])
75
+ .map((replayKey) => replayKey?.trim())
76
+ .filter((replayKey): replayKey is string => Boolean(replayKey)),
77
+ ),
78
+ ];
79
+ }
@@ -0,0 +1,118 @@
1
+ import {
2
+ resolveDiscordChannelIdSafe,
3
+ resolveDiscordChannelInfoSafe,
4
+ resolveDiscordChannelNameSafe,
5
+ resolveDiscordChannelParentSafe,
6
+ } from "./channel-access.js";
7
+ import type { DiscordMessagePreflightContext } from "./message-handler.preflight.types.js";
8
+
9
+ type DiscordInboundJobRuntimeField =
10
+ | "runtime"
11
+ | "abortSignal"
12
+ | "guildHistories"
13
+ | "client"
14
+ | "threadBindings"
15
+ | "discordRestFetch";
16
+
17
+ type DiscordInboundJobRuntime = Pick<DiscordMessagePreflightContext, DiscordInboundJobRuntimeField>;
18
+
19
+ type DiscordInboundJobPayload = Omit<DiscordMessagePreflightContext, DiscordInboundJobRuntimeField>;
20
+
21
+ export type DiscordInboundJob = {
22
+ queueKey: string;
23
+ payload: DiscordInboundJobPayload;
24
+ runtime: DiscordInboundJobRuntime;
25
+ replayKeys?: string[];
26
+ };
27
+
28
+ export function resolveDiscordInboundJobQueueKey(ctx: DiscordMessagePreflightContext): string {
29
+ const sessionKey = ctx.route.sessionKey?.trim();
30
+ if (sessionKey) {
31
+ return sessionKey;
32
+ }
33
+ const baseSessionKey = ctx.baseSessionKey?.trim();
34
+ if (baseSessionKey) {
35
+ return baseSessionKey;
36
+ }
37
+ return ctx.messageChannelId;
38
+ }
39
+
40
+ export function buildDiscordInboundJob(
41
+ ctx: DiscordMessagePreflightContext,
42
+ options?: { replayKeys?: readonly string[] },
43
+ ): DiscordInboundJob {
44
+ const {
45
+ runtime,
46
+ abortSignal,
47
+ guildHistories,
48
+ client,
49
+ threadBindings,
50
+ discordRestFetch,
51
+ message,
52
+ data,
53
+ threadChannel,
54
+ ...payload
55
+ } = ctx;
56
+
57
+ const sanitizedMessage = sanitizeDiscordInboundMessage(message);
58
+ return {
59
+ queueKey: resolveDiscordInboundJobQueueKey(ctx),
60
+ payload: {
61
+ ...payload,
62
+ message: sanitizedMessage,
63
+ data: {
64
+ ...data,
65
+ message: sanitizedMessage,
66
+ },
67
+ threadChannel: normalizeDiscordThreadChannel(threadChannel),
68
+ },
69
+ runtime: {
70
+ runtime,
71
+ abortSignal,
72
+ guildHistories,
73
+ client,
74
+ threadBindings,
75
+ discordRestFetch,
76
+ },
77
+ replayKeys: options?.replayKeys ? [...options.replayKeys] : undefined,
78
+ };
79
+ }
80
+
81
+ export function materializeDiscordInboundJob(
82
+ job: DiscordInboundJob,
83
+ abortSignal?: AbortSignal,
84
+ ): DiscordMessagePreflightContext {
85
+ return {
86
+ ...job.payload,
87
+ ...job.runtime,
88
+ abortSignal: abortSignal ?? job.runtime.abortSignal,
89
+ };
90
+ }
91
+
92
+ function sanitizeDiscordInboundMessage<T extends object>(message: T): T {
93
+ const descriptors = Object.getOwnPropertyDescriptors(message);
94
+ delete descriptors.channel;
95
+ return Object.create(Object.getPrototypeOf(message), descriptors) as T;
96
+ }
97
+
98
+ function normalizeDiscordThreadChannel(
99
+ threadChannel: DiscordMessagePreflightContext["threadChannel"],
100
+ ): DiscordMessagePreflightContext["threadChannel"] {
101
+ if (!threadChannel) {
102
+ return null;
103
+ }
104
+ const channelInfo = resolveDiscordChannelInfoSafe(threadChannel);
105
+ const parent = resolveDiscordChannelParentSafe(threadChannel);
106
+ return {
107
+ id: threadChannel.id,
108
+ name: channelInfo.name,
109
+ parentId: channelInfo.parentId,
110
+ parent: parent
111
+ ? {
112
+ id: resolveDiscordChannelIdSafe(parent),
113
+ name: resolveDiscordChannelNameSafe(parent),
114
+ }
115
+ : undefined,
116
+ ownerId: channelInfo.ownerId,
117
+ };
118
+ }
@@ -0,0 +1,91 @@
1
+ import { createSubsystemLogger, formatDurationSeconds } from "autobot/plugin-sdk/runtime-env";
2
+
3
+ export type DiscordListenerLogger = ReturnType<
4
+ typeof import("autobot/plugin-sdk/runtime-env").createSubsystemLogger
5
+ >;
6
+
7
+ const DISCORD_SLOW_LISTENER_THRESHOLD_MS = 30_000;
8
+
9
+ export const discordEventQueueLog = createSubsystemLogger("discord/event-queue");
10
+
11
+ function formatListenerContextValue(value: unknown): string | null {
12
+ if (value === undefined || value === null) {
13
+ return null;
14
+ }
15
+ if (typeof value === "string") {
16
+ const trimmed = value.trim();
17
+ return trimmed.length > 0 ? trimmed : null;
18
+ }
19
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
20
+ return String(value);
21
+ }
22
+ return null;
23
+ }
24
+
25
+ function formatListenerContextSuffix(context?: Record<string, unknown>): string {
26
+ if (!context) {
27
+ return "";
28
+ }
29
+ const entries = Object.entries(context).flatMap(([key, value]) => {
30
+ const formatted = formatListenerContextValue(value);
31
+ return formatted ? [`${key}=${formatted}`] : [];
32
+ });
33
+ if (entries.length === 0) {
34
+ return "";
35
+ }
36
+ return ` (${entries.join(" ")})`;
37
+ }
38
+
39
+ function logSlowDiscordListener(params: {
40
+ logger: DiscordListenerLogger | undefined;
41
+ listener: string;
42
+ event: string;
43
+ durationMs: number;
44
+ context?: Record<string, unknown>;
45
+ }) {
46
+ if (params.durationMs < DISCORD_SLOW_LISTENER_THRESHOLD_MS) {
47
+ return;
48
+ }
49
+ const duration = formatDurationSeconds(params.durationMs, {
50
+ decimals: 1,
51
+ unit: "seconds",
52
+ });
53
+ const message = `Slow listener detected: ${params.listener} took ${duration} for event ${params.event}`;
54
+ const logger = params.logger ?? discordEventQueueLog;
55
+ logger.warn("Slow listener detected", {
56
+ listener: params.listener,
57
+ event: params.event,
58
+ durationMs: params.durationMs,
59
+ duration,
60
+ ...params.context,
61
+ consoleMessage: `${message}${formatListenerContextSuffix(params.context)}`,
62
+ });
63
+ }
64
+
65
+ export async function runDiscordListenerWithSlowLog(params: {
66
+ logger: DiscordListenerLogger | undefined;
67
+ listener: string;
68
+ event: string;
69
+ run: () => Promise<void>;
70
+ context?: Record<string, unknown>;
71
+ onError?: (err: unknown) => void;
72
+ }) {
73
+ const startedAt = Date.now();
74
+ try {
75
+ await params.run();
76
+ } catch (err) {
77
+ if (params.onError) {
78
+ params.onError(err);
79
+ return;
80
+ }
81
+ throw err;
82
+ } finally {
83
+ logSlowDiscordListener({
84
+ logger: params.logger,
85
+ listener: params.listener,
86
+ event: params.event,
87
+ durationMs: Date.now() - startedAt,
88
+ context: params.context,
89
+ });
90
+ }
91
+ }