@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,159 @@
1
+ import { formatErrorMessage } from "autobot/plugin-sdk/ssrf-runtime";
2
+
3
+ const DECRYPT_FAILURE_WINDOW_MS = 30_000;
4
+ const DECRYPT_FAILURE_RECONNECT_THRESHOLD = 3;
5
+ const DECRYPT_FAILURE_MARKER = "DecryptionFailed(";
6
+ const DAVE_PASSTHROUGH_DISABLED_MARKER = "UnencryptedWhenPassthroughDisabled";
7
+
8
+ export const DAVE_RECEIVE_PASSTHROUGH_INITIAL_EXPIRY_SECONDS = 30;
9
+ export const DAVE_RECEIVE_PASSTHROUGH_REARM_EXPIRY_SECONDS = 15;
10
+
11
+ export type VoiceReceiveRecoveryState = {
12
+ decryptFailureCount: number;
13
+ lastDecryptFailureAt: number;
14
+ decryptRecoveryInFlight: boolean;
15
+ };
16
+
17
+ type VoiceReceiveErrorAnalysis = {
18
+ message: string;
19
+ isAbortLike: boolean;
20
+ shouldAttemptPassthrough: boolean;
21
+ countsAsDecryptFailure: boolean;
22
+ };
23
+
24
+ type DavePassthroughTarget = {
25
+ guildId: string;
26
+ channelId: string;
27
+ connection: {
28
+ state: {
29
+ status: unknown;
30
+ networking?: {
31
+ state?: {
32
+ code?: unknown;
33
+ dave?: {
34
+ session?: {
35
+ setPassthroughMode: (passthrough: boolean, expirySeconds: number) => void;
36
+ };
37
+ };
38
+ };
39
+ };
40
+ };
41
+ };
42
+ };
43
+
44
+ type DavePassthroughSdk = {
45
+ VoiceConnectionStatus: {
46
+ Ready: unknown;
47
+ };
48
+ NetworkingStatusCode: {
49
+ Ready: unknown;
50
+ Resuming: unknown;
51
+ };
52
+ };
53
+
54
+ export function createVoiceReceiveRecoveryState(): VoiceReceiveRecoveryState {
55
+ return {
56
+ decryptFailureCount: 0,
57
+ lastDecryptFailureAt: 0,
58
+ decryptRecoveryInFlight: false,
59
+ };
60
+ }
61
+
62
+ function isAbortLikeReceiveError(err: unknown): boolean {
63
+ if (!err || typeof err !== "object") {
64
+ return false;
65
+ }
66
+ const name =
67
+ "name" in err && typeof (err as { name?: unknown }).name === "string"
68
+ ? (err as { name: string }).name
69
+ : "";
70
+ const message =
71
+ "message" in err && typeof (err as { message?: unknown }).message === "string"
72
+ ? (err as { message: string }).message
73
+ : "";
74
+ return (
75
+ name === "AbortError" ||
76
+ message.includes("The operation was aborted") ||
77
+ message.includes("aborted")
78
+ );
79
+ }
80
+
81
+ export function analyzeVoiceReceiveError(err: unknown): VoiceReceiveErrorAnalysis {
82
+ const message = formatErrorMessage(err);
83
+ const shouldAttemptPassthrough = message.includes(DAVE_PASSTHROUGH_DISABLED_MARKER);
84
+ return {
85
+ message,
86
+ isAbortLike: isAbortLikeReceiveError(err),
87
+ shouldAttemptPassthrough,
88
+ countsAsDecryptFailure: message.includes(DECRYPT_FAILURE_MARKER) || shouldAttemptPassthrough,
89
+ };
90
+ }
91
+
92
+ export function noteVoiceDecryptFailure(
93
+ state: VoiceReceiveRecoveryState,
94
+ now: number = Date.now(),
95
+ ): {
96
+ firstFailure: boolean;
97
+ shouldRecover: boolean;
98
+ } {
99
+ if (now - state.lastDecryptFailureAt > DECRYPT_FAILURE_WINDOW_MS) {
100
+ state.decryptFailureCount = 0;
101
+ }
102
+ state.lastDecryptFailureAt = now;
103
+ state.decryptFailureCount += 1;
104
+ const firstFailure = state.decryptFailureCount === 1;
105
+ if (
106
+ state.decryptFailureCount < DECRYPT_FAILURE_RECONNECT_THRESHOLD ||
107
+ state.decryptRecoveryInFlight
108
+ ) {
109
+ return { firstFailure, shouldRecover: false };
110
+ }
111
+ state.decryptRecoveryInFlight = true;
112
+ resetVoiceReceiveRecoveryState(state);
113
+ return { firstFailure, shouldRecover: true };
114
+ }
115
+
116
+ export function resetVoiceReceiveRecoveryState(state: VoiceReceiveRecoveryState): void {
117
+ state.decryptFailureCount = 0;
118
+ state.lastDecryptFailureAt = 0;
119
+ }
120
+
121
+ export function finishVoiceDecryptRecovery(state: VoiceReceiveRecoveryState): void {
122
+ state.decryptRecoveryInFlight = false;
123
+ }
124
+
125
+ export function enableDaveReceivePassthrough(params: {
126
+ target: DavePassthroughTarget;
127
+ sdk: DavePassthroughSdk;
128
+ reason: string;
129
+ expirySeconds: number;
130
+ onVerbose: (message: string) => void;
131
+ onWarn: (message: string) => void;
132
+ }): boolean {
133
+ const { target, sdk, reason, expirySeconds, onVerbose, onWarn } = params;
134
+ const networkingState = target.connection.state.networking?.state;
135
+ if (
136
+ target.connection.state.status !== sdk.VoiceConnectionStatus.Ready ||
137
+ !networkingState ||
138
+ (networkingState.code !== sdk.NetworkingStatusCode.Ready &&
139
+ networkingState.code !== sdk.NetworkingStatusCode.Resuming)
140
+ ) {
141
+ return false;
142
+ }
143
+ const daveSession = networkingState.dave?.session;
144
+ if (!daveSession) {
145
+ return false;
146
+ }
147
+ try {
148
+ daveSession.setPassthroughMode(true, expirySeconds);
149
+ onVerbose(
150
+ `enabled DAVE receive passthrough: guild ${target.guildId} channel ${target.channelId} expiry=${expirySeconds}s reason=${reason}`,
151
+ );
152
+ return true;
153
+ } catch (err) {
154
+ onWarn(
155
+ `discord voice: failed to enable DAVE passthrough guild=${target.guildId} channel=${target.channelId} reason=${reason}: ${formatErrorMessage(err)}`,
156
+ );
157
+ return false;
158
+ }
159
+ }
@@ -0,0 +1,29 @@
1
+ import { stripInlineDirectiveTagsForDisplay } from "autobot/plugin-sdk/text-chunking";
2
+ import { escapeRegExp } from "autobot/plugin-sdk/text-utility-runtime";
3
+
4
+ const SPEECH_EMOJI_RE =
5
+ /(?:\p{Extended_Pictographic}(?:\uFE0F|\u200D|\p{Extended_Pictographic}|\p{Emoji_Modifier})*)+/gu;
6
+
7
+ function stripEmojiForSpeech(text: string): string {
8
+ return text
9
+ .replace(SPEECH_EMOJI_RE, " ")
10
+ .replace(/\s+([?!.,:;])/g, "$1")
11
+ .replace(/[ \t]{2,}/g, " ")
12
+ .replace(/ *\n */g, "\n")
13
+ .trim();
14
+ }
15
+
16
+ export function sanitizeVoiceReplyTextForSpeech(text: string, speakerLabel?: string): string {
17
+ let cleaned = stripInlineDirectiveTagsForDisplay(text).text.trim();
18
+ if (!cleaned) {
19
+ return "";
20
+ }
21
+
22
+ const label = speakerLabel?.trim();
23
+ if (label) {
24
+ const prefix = new RegExp(`^${escapeRegExp(label)}\\s*:\\s*`, "i");
25
+ cleaned = cleaned.replace(prefix, "").trim();
26
+ }
27
+
28
+ return stripEmojiForSpeech(cleaned);
29
+ }
@@ -0,0 +1,14 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ type DiscordVoiceSdk = typeof import("@discordjs/voice");
4
+
5
+ let cachedDiscordVoiceSdk: DiscordVoiceSdk | null = null;
6
+
7
+ export function loadDiscordVoiceSdk(): DiscordVoiceSdk {
8
+ if (cachedDiscordVoiceSdk) {
9
+ return cachedDiscordVoiceSdk;
10
+ }
11
+ const req = createRequire(import.meta.url);
12
+ cachedDiscordVoiceSdk = req("@discordjs/voice") as DiscordVoiceSdk;
13
+ return cachedDiscordVoiceSdk;
14
+ }
@@ -0,0 +1,160 @@
1
+ import path from "node:path";
2
+ import { Readable } from "node:stream";
3
+ import type { DiscordAccountConfig, AutoBotConfig } from "autobot/plugin-sdk/config-contracts";
4
+ import type { RuntimeEnv } from "autobot/plugin-sdk/runtime-env";
5
+ import { createSubsystemLogger } from "autobot/plugin-sdk/runtime-env";
6
+ import { resolveDiscordVoiceIngressContext, runDiscordVoiceAgentTurn } from "./ingress.js";
7
+ import { formatVoiceIngressPrompt } from "./prompt.js";
8
+ import { loadDiscordVoiceSdk } from "./sdk-runtime.js";
9
+ import {
10
+ logVoiceVerbose,
11
+ PLAYBACK_READY_TIMEOUT_MS,
12
+ SPEAKING_READY_TIMEOUT_MS,
13
+ type VoiceSessionEntry,
14
+ } from "./session.js";
15
+ import type { DiscordVoiceSpeakerContextResolver } from "./speaker-context.js";
16
+ import { synthesizeVoiceReplyAudio, transcribeVoiceAudio } from "./tts.js";
17
+
18
+ const VOICE_TRANSCRIPT_LOG_PREVIEW_CHARS = 500;
19
+ const logger = createSubsystemLogger("discord/voice");
20
+
21
+ function formatVoiceTranscriptLogPreview(text: string): string {
22
+ const oneLine = text.replace(/\s+/g, " ").trim();
23
+ if (oneLine.length <= VOICE_TRANSCRIPT_LOG_PREVIEW_CHARS) {
24
+ return oneLine;
25
+ }
26
+ return `${oneLine.slice(0, VOICE_TRANSCRIPT_LOG_PREVIEW_CHARS)}...`;
27
+ }
28
+
29
+ export async function processDiscordVoiceSegment(params: {
30
+ entry: VoiceSessionEntry;
31
+ wavPath: string;
32
+ userId: string;
33
+ durationSeconds: number;
34
+ cfg: AutoBotConfig;
35
+ discordConfig: DiscordAccountConfig;
36
+ runtime: RuntimeEnv;
37
+ ownerAllowFrom?: string[];
38
+ fetchGuildName: (guildId: string) => Promise<string | undefined>;
39
+ speakerContext: DiscordVoiceSpeakerContextResolver;
40
+ enqueuePlayback: (entry: VoiceSessionEntry, task: () => Promise<void>) => void;
41
+ }) {
42
+ const { entry, wavPath, userId, durationSeconds } = params;
43
+ logVoiceVerbose(
44
+ `segment processing (${durationSeconds.toFixed(2)}s): guild ${entry.guildId} channel ${entry.channelId}`,
45
+ );
46
+ const ingress = await resolveDiscordVoiceIngressContext({
47
+ entry,
48
+ userId,
49
+ cfg: params.cfg,
50
+ discordConfig: params.discordConfig,
51
+ ownerAllowFrom: params.ownerAllowFrom,
52
+ fetchGuildName: params.fetchGuildName,
53
+ speakerContext: params.speakerContext,
54
+ });
55
+ if (!ingress) {
56
+ logVoiceVerbose(
57
+ `segment unauthorized: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`,
58
+ );
59
+ return;
60
+ }
61
+ const transcript = await transcribeVoiceAudio({
62
+ cfg: params.cfg,
63
+ agentId: entry.route.agentId,
64
+ filePath: wavPath,
65
+ });
66
+ if (!transcript) {
67
+ logVoiceVerbose(
68
+ `transcription empty: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`,
69
+ );
70
+ return;
71
+ }
72
+ logVoiceVerbose(
73
+ `transcription ok (${transcript.length} chars): guild ${entry.guildId} channel ${entry.channelId}`,
74
+ );
75
+ logVoiceVerbose(
76
+ `transcript from ${ingress.speakerLabel} (${userId}) in guild ${entry.guildId} channel ${entry.channelId}: ${formatVoiceTranscriptLogPreview(transcript)}`,
77
+ );
78
+
79
+ const prompt = formatVoiceIngressPrompt(transcript, ingress.speakerLabel);
80
+ const turn = await runDiscordVoiceAgentTurn({
81
+ entry,
82
+ userId,
83
+ message: prompt,
84
+ cfg: params.cfg,
85
+ discordConfig: params.discordConfig,
86
+ runtime: params.runtime,
87
+ context: ingress,
88
+ ownerAllowFrom: params.ownerAllowFrom,
89
+ fetchGuildName: params.fetchGuildName,
90
+ speakerContext: params.speakerContext,
91
+ });
92
+ if (!turn) {
93
+ logVoiceVerbose(
94
+ `segment unauthorized before agent turn: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`,
95
+ );
96
+ return;
97
+ }
98
+ const replyText = turn.text;
99
+
100
+ if (!replyText) {
101
+ logVoiceVerbose(
102
+ `reply empty: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`,
103
+ );
104
+ return;
105
+ }
106
+ logVoiceVerbose(
107
+ `reply ok (${replyText.length} chars): guild ${entry.guildId} channel ${entry.channelId}`,
108
+ );
109
+
110
+ const voiceReplyAudio = await synthesizeVoiceReplyAudio({
111
+ cfg: params.cfg,
112
+ override: params.discordConfig.voice?.tts,
113
+ replyText,
114
+ speakerLabel: ingress.speakerLabel,
115
+ });
116
+ if (voiceReplyAudio.status === "empty") {
117
+ logVoiceVerbose(
118
+ `tts skipped (empty): guild ${entry.guildId} channel ${entry.channelId} user ${userId}`,
119
+ );
120
+ return;
121
+ }
122
+ if (voiceReplyAudio.status === "failed") {
123
+ logger.warn(`discord voice: TTS failed: ${voiceReplyAudio.error ?? "unknown error"}`);
124
+ return;
125
+ }
126
+ logVoiceVerbose(
127
+ `tts ok (${voiceReplyAudio.speakText.length} chars): guild ${entry.guildId} channel ${entry.channelId}`,
128
+ );
129
+
130
+ params.enqueuePlayback(entry, async () => {
131
+ const voiceSdk = loadDiscordVoiceSdk();
132
+ const releaseAudioStream =
133
+ voiceReplyAudio.mode === "stream" ? voiceReplyAudio.release : undefined;
134
+ try {
135
+ if (voiceReplyAudio.mode === "stream") {
136
+ logVoiceVerbose(`playback start: guild ${entry.guildId} channel ${entry.channelId} stream`);
137
+ const nodeStream = Readable.fromWeb(
138
+ voiceReplyAudio.audioStream as import("node:stream/web").ReadableStream<Uint8Array>,
139
+ );
140
+ const resource = voiceSdk.createAudioResource(nodeStream);
141
+ entry.player.play(resource);
142
+ } else {
143
+ logVoiceVerbose(
144
+ `playback start: guild ${entry.guildId} channel ${entry.channelId} file ${path.basename(voiceReplyAudio.audioPath)}`,
145
+ );
146
+ const resource = voiceSdk.createAudioResource(voiceReplyAudio.audioPath);
147
+ entry.player.play(resource);
148
+ }
149
+ await voiceSdk
150
+ .entersState(entry.player, voiceSdk.AudioPlayerStatus.Playing, PLAYBACK_READY_TIMEOUT_MS)
151
+ .catch(() => undefined);
152
+ await voiceSdk
153
+ .entersState(entry.player, voiceSdk.AudioPlayerStatus.Idle, SPEAKING_READY_TIMEOUT_MS)
154
+ .catch(() => undefined);
155
+ logVoiceVerbose(`playback done: guild ${entry.guildId} channel ${entry.channelId}`);
156
+ } finally {
157
+ await releaseAudioStream?.();
158
+ }
159
+ });
160
+ }
@@ -0,0 +1,81 @@
1
+ import type { resolveAgentRoute } from "autobot/plugin-sdk/routing";
2
+ import { logVerbose } from "autobot/plugin-sdk/runtime-env";
3
+ import { ChannelType } from "../internal/discord.js";
4
+ import type { VoiceCaptureState } from "./capture-state.js";
5
+ import type { VoiceReceiveRecoveryState } from "./receive-recovery.js";
6
+
7
+ export const MIN_SEGMENT_SECONDS = 0.35;
8
+ export const CAPTURE_FINALIZE_GRACE_MS = 2_500;
9
+ export const VOICE_CONNECT_READY_TIMEOUT_MS = 30_000;
10
+ export const VOICE_RECONNECT_GRACE_MS = 15_000;
11
+ export const PLAYBACK_READY_TIMEOUT_MS = 60_000;
12
+ export const SPEAKING_READY_TIMEOUT_MS = 60_000;
13
+
14
+ export function resolveVoiceTimeoutMs(value: number | undefined, fallbackMs: number): number {
15
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
16
+ return fallbackMs;
17
+ }
18
+ return Math.floor(value);
19
+ }
20
+
21
+ export type VoiceOperationResult = {
22
+ ok: boolean;
23
+ message: string;
24
+ channelId?: string;
25
+ guildId?: string;
26
+ };
27
+
28
+ export type VoiceRealtimeSpeakerContext = {
29
+ extraSystemPrompt?: string;
30
+ senderIsOwner: boolean;
31
+ speakerLabel: string;
32
+ };
33
+
34
+ export type VoiceRealtimeAgentTurnParams = {
35
+ context: VoiceRealtimeSpeakerContext;
36
+ message: string;
37
+ toolsAllow?: string[];
38
+ userId: string;
39
+ };
40
+
41
+ export type VoiceRealtimeSpeakerTurn = {
42
+ close: () => void;
43
+ sendInputAudio: (discordPcm48kStereo: Buffer) => void;
44
+ };
45
+
46
+ export type VoiceRealtimeSession = {
47
+ beginSpeakerTurn: (
48
+ context: VoiceRealtimeSpeakerContext,
49
+ userId: string,
50
+ ) => VoiceRealtimeSpeakerTurn;
51
+ close: () => void;
52
+ connect: () => Promise<void>;
53
+ handleBargeIn: (reason?: string) => void;
54
+ isBargeInEnabled: () => boolean;
55
+ };
56
+
57
+ export type VoiceSessionEntry = {
58
+ guildId: string;
59
+ guildName?: string;
60
+ channelId: string;
61
+ channelName?: string;
62
+ sessionChannelId: string;
63
+ voiceSessionKey: string;
64
+ route: ReturnType<typeof resolveAgentRoute>;
65
+ connection: import("@discordjs/voice").VoiceConnection;
66
+ player: import("@discordjs/voice").AudioPlayer;
67
+ playbackQueue: Promise<void>;
68
+ processingQueue: Promise<void>;
69
+ capture: VoiceCaptureState;
70
+ realtime?: VoiceRealtimeSession;
71
+ receiveRecovery: VoiceReceiveRecoveryState;
72
+ stop: () => void;
73
+ };
74
+
75
+ export function logVoiceVerbose(message: string): void {
76
+ logVerbose(`discord voice: ${message}`);
77
+ }
78
+
79
+ export function isVoiceChannel(type: ChannelType): boolean {
80
+ return type === ChannelType.GuildVoice || type === ChannelType.GuildStageVoice;
81
+ }
@@ -0,0 +1,127 @@
1
+ import type { Client } from "../internal/discord.js";
2
+ import { resolveDiscordOwnerAccess } from "../monitor/allow-list.js";
3
+ import { formatDiscordUserTag } from "../monitor/format.js";
4
+
5
+ const SPEAKER_CONTEXT_CACHE_TTL_MS = 60_000;
6
+
7
+ type VoiceSpeakerIdentity = {
8
+ id: string;
9
+ label: string;
10
+ name?: string;
11
+ tag?: string;
12
+ memberRoleIds: string[];
13
+ };
14
+
15
+ type VoiceSpeakerContext = Omit<VoiceSpeakerIdentity, "memberRoleIds"> & {
16
+ senderIsOwner: boolean;
17
+ };
18
+
19
+ export class DiscordVoiceSpeakerContextResolver {
20
+ private readonly cache = new Map<
21
+ string,
22
+ VoiceSpeakerContext & {
23
+ expiresAt: number;
24
+ }
25
+ >();
26
+
27
+ constructor(
28
+ private readonly params: {
29
+ client: Client;
30
+ ownerAllowFrom?: string[];
31
+ },
32
+ ) {}
33
+
34
+ async resolveContext(guildId: string, userId: string): Promise<VoiceSpeakerContext> {
35
+ const cached = this.getCachedContext(guildId, userId);
36
+ if (cached) {
37
+ return cached;
38
+ }
39
+ const identity = await this.resolveIdentity(guildId, userId);
40
+ const context = {
41
+ id: identity.id,
42
+ label: identity.label,
43
+ name: identity.name,
44
+ tag: identity.tag,
45
+ senderIsOwner: this.resolveIsOwner(identity),
46
+ };
47
+ this.setCachedContext(guildId, userId, context);
48
+ return context;
49
+ }
50
+
51
+ async resolveIdentity(guildId: string, userId: string): Promise<VoiceSpeakerIdentity> {
52
+ try {
53
+ const member = await this.params.client.fetchMember(guildId, userId);
54
+ const username = member.user?.username ?? undefined;
55
+ return {
56
+ id: userId,
57
+ label: member.nickname ?? member.user?.globalName ?? username ?? userId,
58
+ name: username,
59
+ tag: member.user ? formatDiscordUserTag(member.user) : undefined,
60
+ memberRoleIds: Array.isArray(member.roles)
61
+ ? member.roles
62
+ .map((role) =>
63
+ typeof role === "string" ? role : typeof role?.id === "string" ? role.id : "",
64
+ )
65
+ .filter(Boolean)
66
+ : [],
67
+ };
68
+ } catch {
69
+ try {
70
+ const user = await this.params.client.fetchUser(userId);
71
+ const username = user.username ?? undefined;
72
+ return {
73
+ id: userId,
74
+ label: user.globalName ?? username ?? userId,
75
+ name: username,
76
+ tag: formatDiscordUserTag(user),
77
+ memberRoleIds: [],
78
+ };
79
+ } catch {
80
+ return { id: userId, label: userId, memberRoleIds: [] };
81
+ }
82
+ }
83
+ }
84
+
85
+ private resolveIsOwner(identity: Pick<VoiceSpeakerIdentity, "id" | "name" | "tag">): boolean {
86
+ return resolveDiscordOwnerAccess({
87
+ allowFrom: this.params.ownerAllowFrom,
88
+ sender: {
89
+ id: identity.id,
90
+ name: identity.name,
91
+ tag: identity.tag,
92
+ },
93
+ allowNameMatching: false,
94
+ }).ownerAllowed;
95
+ }
96
+
97
+ private resolveCacheKey(guildId: string, userId: string): string {
98
+ return `${guildId}:${userId}`;
99
+ }
100
+
101
+ private getCachedContext(guildId: string, userId: string): VoiceSpeakerContext | undefined {
102
+ const key = this.resolveCacheKey(guildId, userId);
103
+ const cached = this.cache.get(key);
104
+ if (!cached) {
105
+ return undefined;
106
+ }
107
+ if (cached.expiresAt <= Date.now()) {
108
+ this.cache.delete(key);
109
+ return undefined;
110
+ }
111
+ return {
112
+ id: cached.id,
113
+ label: cached.label,
114
+ name: cached.name,
115
+ tag: cached.tag,
116
+ senderIsOwner: cached.senderIsOwner,
117
+ };
118
+ }
119
+
120
+ private setCachedContext(guildId: string, userId: string, context: VoiceSpeakerContext): void {
121
+ const key = this.resolveCacheKey(guildId, userId);
122
+ this.cache.set(key, {
123
+ ...context,
124
+ expiresAt: Date.now() + SPEAKER_CONTEXT_CACHE_TTL_MS,
125
+ });
126
+ }
127
+ }