@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,356 @@
1
+ import {
2
+ clearExpiredCooldowns,
3
+ ensureAuthProfileStore,
4
+ isProfileInCooldown,
5
+ resolveProfilesUnavailableReason,
6
+ type AuthProfileFailureReason,
7
+ type AuthProfileStore,
8
+ } from "autobot/plugin-sdk/agent-runtime";
9
+ import type {
10
+ DiscordAccountConfig,
11
+ DiscordAutoPresenceConfig,
12
+ } from "autobot/plugin-sdk/config-contracts";
13
+ import { warn } from "autobot/plugin-sdk/runtime-env";
14
+ import type { Activity, UpdatePresenceData } from "../internal/gateway.js";
15
+ import { resolveDiscordPresenceUpdate } from "./presence.js";
16
+
17
+ const DEFAULT_CUSTOM_ACTIVITY_TYPE = 4;
18
+ const CUSTOM_STATUS_NAME = "Custom Status";
19
+ const DEFAULT_INTERVAL_MS = 30_000;
20
+ const DEFAULT_MIN_UPDATE_INTERVAL_MS = 15_000;
21
+ const MIN_INTERVAL_MS = 5_000;
22
+ const MIN_UPDATE_INTERVAL_MS = 1_000;
23
+
24
+ type DiscordAutoPresenceState = "healthy" | "degraded" | "exhausted";
25
+
26
+ type ResolvedDiscordAutoPresenceConfig = {
27
+ enabled: boolean;
28
+ intervalMs: number;
29
+ minUpdateIntervalMs: number;
30
+ healthyText?: string;
31
+ degradedText?: string;
32
+ exhaustedText?: string;
33
+ };
34
+
35
+ type DiscordAutoPresenceDecision = {
36
+ state: DiscordAutoPresenceState;
37
+ unavailableReason?: AuthProfileFailureReason | null;
38
+ presence: UpdatePresenceData;
39
+ };
40
+
41
+ type PresenceGateway = {
42
+ isConnected: boolean;
43
+ updatePresence: (payload: UpdatePresenceData) => void;
44
+ };
45
+
46
+ function normalizeOptionalText(value: unknown): string | undefined {
47
+ if (typeof value !== "string") {
48
+ return undefined;
49
+ }
50
+ const trimmed = value.trim();
51
+ return trimmed.length > 0 ? trimmed : undefined;
52
+ }
53
+
54
+ function clampPositiveInt(value: unknown, fallback: number, minValue: number): number {
55
+ if (typeof value !== "number" || !Number.isFinite(value)) {
56
+ return fallback;
57
+ }
58
+ const rounded = Math.round(value);
59
+ if (rounded <= 0) {
60
+ return fallback;
61
+ }
62
+ return Math.max(minValue, rounded);
63
+ }
64
+
65
+ function resolveAutoPresenceConfig(
66
+ config?: DiscordAutoPresenceConfig,
67
+ ): ResolvedDiscordAutoPresenceConfig {
68
+ const intervalMs = clampPositiveInt(config?.intervalMs, DEFAULT_INTERVAL_MS, MIN_INTERVAL_MS);
69
+ const minUpdateIntervalMs = clampPositiveInt(
70
+ config?.minUpdateIntervalMs,
71
+ DEFAULT_MIN_UPDATE_INTERVAL_MS,
72
+ MIN_UPDATE_INTERVAL_MS,
73
+ );
74
+
75
+ return {
76
+ enabled: config?.enabled === true,
77
+ intervalMs,
78
+ minUpdateIntervalMs,
79
+ healthyText: normalizeOptionalText(config?.healthyText),
80
+ degradedText: normalizeOptionalText(config?.degradedText),
81
+ exhaustedText: normalizeOptionalText(config?.exhaustedText),
82
+ };
83
+ }
84
+
85
+ function buildCustomStatusActivity(text: string): Activity {
86
+ return {
87
+ name: CUSTOM_STATUS_NAME,
88
+ type: DEFAULT_CUSTOM_ACTIVITY_TYPE,
89
+ state: text,
90
+ };
91
+ }
92
+
93
+ function renderTemplate(
94
+ template: string,
95
+ vars: Record<string, string | undefined>,
96
+ ): string | undefined {
97
+ const rendered = template
98
+ .replace(/\{([a-zA-Z0-9_]+)\}/g, (_full, key: string) => vars[key] ?? "")
99
+ .replace(/\s+/g, " ")
100
+ .trim();
101
+ return rendered.length > 0 ? rendered : undefined;
102
+ }
103
+
104
+ function isExhaustedUnavailableReason(reason: AuthProfileFailureReason | null): boolean {
105
+ if (!reason) {
106
+ return false;
107
+ }
108
+ return (
109
+ reason === "rate_limit" ||
110
+ reason === "overloaded" ||
111
+ reason === "billing" ||
112
+ reason === "auth" ||
113
+ reason === "auth_permanent"
114
+ );
115
+ }
116
+
117
+ function formatUnavailableReason(reason: AuthProfileFailureReason | null): string {
118
+ if (!reason) {
119
+ return "unknown";
120
+ }
121
+ return reason.replace(/_/g, " ");
122
+ }
123
+
124
+ function resolveAuthAvailability(params: { store: AuthProfileStore; now: number }): {
125
+ state: DiscordAutoPresenceState;
126
+ unavailableReason?: AuthProfileFailureReason | null;
127
+ } {
128
+ const profileIds = Object.keys(params.store.profiles);
129
+ if (profileIds.length === 0) {
130
+ return { state: "degraded", unavailableReason: null };
131
+ }
132
+
133
+ clearExpiredCooldowns(params.store, params.now);
134
+
135
+ const hasUsableProfile = profileIds.some(
136
+ (profileId) => !isProfileInCooldown(params.store, profileId, params.now),
137
+ );
138
+ if (hasUsableProfile) {
139
+ return { state: "healthy", unavailableReason: null };
140
+ }
141
+
142
+ const unavailableReason = resolveProfilesUnavailableReason({
143
+ store: params.store,
144
+ profileIds,
145
+ now: params.now,
146
+ });
147
+
148
+ if (isExhaustedUnavailableReason(unavailableReason)) {
149
+ return {
150
+ state: "exhausted",
151
+ unavailableReason,
152
+ };
153
+ }
154
+
155
+ return {
156
+ state: "degraded",
157
+ unavailableReason,
158
+ };
159
+ }
160
+
161
+ function resolvePresenceActivities(params: {
162
+ state: DiscordAutoPresenceState;
163
+ cfg: ResolvedDiscordAutoPresenceConfig;
164
+ basePresence: UpdatePresenceData | null;
165
+ unavailableReason?: AuthProfileFailureReason | null;
166
+ }): Activity[] {
167
+ const reasonLabel = formatUnavailableReason(params.unavailableReason ?? null);
168
+
169
+ if (params.state === "healthy") {
170
+ if (params.cfg.healthyText) {
171
+ return [buildCustomStatusActivity(params.cfg.healthyText)];
172
+ }
173
+ return params.basePresence?.activities ?? [];
174
+ }
175
+
176
+ if (params.state === "degraded") {
177
+ const template = params.cfg.degradedText ?? "runtime degraded";
178
+ const text = renderTemplate(template, { reason: reasonLabel });
179
+ return text ? [buildCustomStatusActivity(text)] : [];
180
+ }
181
+
182
+ const defaultTemplate = isExhaustedUnavailableReason(params.unavailableReason ?? null)
183
+ ? "token exhausted"
184
+ : "model unavailable ({reason})";
185
+ const template = params.cfg.exhaustedText ?? defaultTemplate;
186
+ const text = renderTemplate(template, { reason: reasonLabel });
187
+ return text ? [buildCustomStatusActivity(text)] : [];
188
+ }
189
+
190
+ function resolvePresenceStatus(state: DiscordAutoPresenceState): UpdatePresenceData["status"] {
191
+ if (state === "healthy") {
192
+ return "online";
193
+ }
194
+ if (state === "exhausted") {
195
+ return "dnd";
196
+ }
197
+ return "idle";
198
+ }
199
+
200
+ export function resolveDiscordAutoPresenceDecision(params: {
201
+ discordConfig: Pick<
202
+ DiscordAccountConfig,
203
+ "autoPresence" | "activity" | "status" | "activityType" | "activityUrl"
204
+ >;
205
+ authStore: AuthProfileStore;
206
+ gatewayConnected: boolean;
207
+ now?: number;
208
+ }): DiscordAutoPresenceDecision | null {
209
+ const autoPresence = resolveAutoPresenceConfig(params.discordConfig.autoPresence);
210
+ if (!autoPresence.enabled) {
211
+ return null;
212
+ }
213
+
214
+ const now = params.now ?? Date.now();
215
+ const basePresence = resolveDiscordPresenceUpdate(params.discordConfig);
216
+
217
+ const availability = resolveAuthAvailability({
218
+ store: params.authStore,
219
+ now,
220
+ });
221
+ const state = params.gatewayConnected ? availability.state : "degraded";
222
+ const unavailableReason = params.gatewayConnected
223
+ ? availability.unavailableReason
224
+ : (availability.unavailableReason ?? "unknown");
225
+
226
+ const activities = resolvePresenceActivities({
227
+ state,
228
+ cfg: autoPresence,
229
+ basePresence,
230
+ unavailableReason,
231
+ });
232
+
233
+ return {
234
+ state,
235
+ unavailableReason,
236
+ presence: {
237
+ since: null,
238
+ activities,
239
+ status: resolvePresenceStatus(state),
240
+ afk: false,
241
+ },
242
+ };
243
+ }
244
+
245
+ function stablePresenceSignature(payload: UpdatePresenceData): string {
246
+ return JSON.stringify({
247
+ status: payload.status,
248
+ afk: payload.afk,
249
+ since: payload.since,
250
+ activities: payload.activities.map((activity) => ({
251
+ type: activity.type,
252
+ name: activity.name,
253
+ state: activity.state,
254
+ url: activity.url,
255
+ })),
256
+ });
257
+ }
258
+
259
+ type DiscordAutoPresenceController = {
260
+ start: () => void;
261
+ stop: () => void;
262
+ refresh: () => void;
263
+ runNow: () => void;
264
+ enabled: boolean;
265
+ };
266
+
267
+ export function createDiscordAutoPresenceController(params: {
268
+ accountId: string;
269
+ discordConfig: Pick<
270
+ DiscordAccountConfig,
271
+ "autoPresence" | "activity" | "status" | "activityType" | "activityUrl"
272
+ >;
273
+ gateway: PresenceGateway;
274
+ loadAuthStore?: () => AuthProfileStore;
275
+ now?: () => number;
276
+ setIntervalFn?: typeof setInterval;
277
+ clearIntervalFn?: typeof clearInterval;
278
+ log?: (message: string) => void;
279
+ }): DiscordAutoPresenceController {
280
+ const autoCfg = resolveAutoPresenceConfig(params.discordConfig.autoPresence);
281
+ if (!autoCfg.enabled) {
282
+ return {
283
+ enabled: false,
284
+ start: () => undefined,
285
+ stop: () => undefined,
286
+ refresh: () => undefined,
287
+ runNow: () => undefined,
288
+ };
289
+ }
290
+
291
+ const loadAuthStore = params.loadAuthStore ?? (() => ensureAuthProfileStore());
292
+ const now = params.now ?? (() => Date.now());
293
+ const setIntervalFn = params.setIntervalFn ?? setInterval;
294
+ const clearIntervalFn = params.clearIntervalFn ?? clearInterval;
295
+
296
+ let timer: ReturnType<typeof setInterval> | undefined;
297
+ let lastAppliedSignature: string | null = null;
298
+ let lastAppliedAt = 0;
299
+
300
+ const runEvaluation = (options?: { force?: boolean }) => {
301
+ let decision: DiscordAutoPresenceDecision | null = null;
302
+ try {
303
+ decision = resolveDiscordAutoPresenceDecision({
304
+ discordConfig: params.discordConfig,
305
+ authStore: loadAuthStore(),
306
+ gatewayConnected: params.gateway.isConnected,
307
+ now: now(),
308
+ });
309
+ } catch (err) {
310
+ params.log?.(
311
+ warn(
312
+ `discord: auto-presence evaluation failed for account ${params.accountId}: ${String(err)}`,
313
+ ),
314
+ );
315
+ return;
316
+ }
317
+
318
+ if (!decision || !params.gateway.isConnected) {
319
+ return;
320
+ }
321
+
322
+ const forceApply = options?.force === true;
323
+ const ts = now();
324
+ const signature = stablePresenceSignature(decision.presence);
325
+ if (!forceApply && signature === lastAppliedSignature) {
326
+ return;
327
+ }
328
+ if (!forceApply && lastAppliedAt > 0 && ts - lastAppliedAt < autoCfg.minUpdateIntervalMs) {
329
+ return;
330
+ }
331
+
332
+ params.gateway.updatePresence(decision.presence);
333
+ lastAppliedSignature = signature;
334
+ lastAppliedAt = ts;
335
+ };
336
+
337
+ return {
338
+ enabled: true,
339
+ runNow: () => runEvaluation(),
340
+ refresh: () => runEvaluation({ force: true }),
341
+ start: () => {
342
+ if (timer) {
343
+ return;
344
+ }
345
+ runEvaluation({ force: true });
346
+ timer = setIntervalFn(() => runEvaluation(), autoCfg.intervalMs);
347
+ },
348
+ stop: () => {
349
+ if (!timer) {
350
+ return;
351
+ }
352
+ clearIntervalFn(timer);
353
+ timer = undefined;
354
+ },
355
+ };
356
+ }
@@ -0,0 +1,102 @@
1
+ function readDiscordChannelPropertySafe(channel: unknown, key: string): unknown {
2
+ if (!channel || typeof channel !== "object") {
3
+ return undefined;
4
+ }
5
+ try {
6
+ if (!(key in channel)) {
7
+ return undefined;
8
+ }
9
+ return (channel as Record<string, unknown>)[key];
10
+ } catch {
11
+ return undefined;
12
+ }
13
+ }
14
+
15
+ function resolveDiscordChannelStringPropertySafe(
16
+ channel: unknown,
17
+ key: string,
18
+ ): string | undefined {
19
+ const value = readDiscordChannelPropertySafe(channel, key);
20
+ return typeof value === "string" ? value : undefined;
21
+ }
22
+
23
+ function resolveDiscordChannelNumberPropertySafe(
24
+ channel: unknown,
25
+ key: string,
26
+ ): number | undefined {
27
+ const value = readDiscordChannelPropertySafe(channel, key);
28
+ return typeof value === "number" ? value : undefined;
29
+ }
30
+
31
+ const DISCORD_CHANNEL_SNAKE_CASE_ALIASES: Record<string, string> = {
32
+ ownerId: "owner_id",
33
+ parentId: "parent_id",
34
+ };
35
+
36
+ function resolveDiscordChannelStringWithAliasSafe(
37
+ channel: unknown,
38
+ camelKey: string,
39
+ ): string | undefined {
40
+ const camelValue = resolveDiscordChannelStringPropertySafe(channel, camelKey);
41
+ if (camelValue !== undefined) {
42
+ return camelValue;
43
+ }
44
+
45
+ const snakeKey = DISCORD_CHANNEL_SNAKE_CASE_ALIASES[camelKey];
46
+ if (!snakeKey) {
47
+ return undefined;
48
+ }
49
+
50
+ const directSnakeValue = resolveDiscordChannelStringPropertySafe(channel, snakeKey);
51
+ if (directSnakeValue !== undefined) {
52
+ return directSnakeValue;
53
+ }
54
+
55
+ const rawData = readDiscordChannelPropertySafe(channel, "rawData");
56
+ return resolveDiscordChannelStringPropertySafe(rawData, snakeKey);
57
+ }
58
+
59
+ export type DiscordChannelInfoSafe = {
60
+ name?: string;
61
+ topic?: string;
62
+ type?: number;
63
+ parentId?: string;
64
+ ownerId?: string;
65
+ parentName?: string;
66
+ };
67
+
68
+ export function resolveDiscordChannelNameSafe(channel: unknown): string | undefined {
69
+ return resolveDiscordChannelStringPropertySafe(channel, "name");
70
+ }
71
+
72
+ export function resolveDiscordChannelIdSafe(channel: unknown): string | undefined {
73
+ return resolveDiscordChannelStringPropertySafe(channel, "id");
74
+ }
75
+
76
+ export function resolveDiscordChannelTopicSafe(channel: unknown): string | undefined {
77
+ return resolveDiscordChannelStringPropertySafe(channel, "topic");
78
+ }
79
+
80
+ export function resolveDiscordChannelParentIdSafe(channel: unknown): string | undefined {
81
+ return resolveDiscordChannelStringWithAliasSafe(channel, "parentId");
82
+ }
83
+
84
+ export function resolveDiscordChannelOwnerIdSafe(channel: unknown): string | undefined {
85
+ return resolveDiscordChannelStringWithAliasSafe(channel, "ownerId");
86
+ }
87
+
88
+ export function resolveDiscordChannelParentSafe(channel: unknown): unknown {
89
+ return readDiscordChannelPropertySafe(channel, "parent");
90
+ }
91
+
92
+ export function resolveDiscordChannelInfoSafe(channel: unknown): DiscordChannelInfoSafe {
93
+ const parent = resolveDiscordChannelParentSafe(channel);
94
+ return {
95
+ name: resolveDiscordChannelNameSafe(channel),
96
+ topic: resolveDiscordChannelTopicSafe(channel),
97
+ type: resolveDiscordChannelNumberPropertySafe(channel, "type"),
98
+ parentId: resolveDiscordChannelParentIdSafe(channel),
99
+ ownerId: resolveDiscordChannelOwnerIdSafe(channel),
100
+ parentName: resolveDiscordChannelNameSafe(parent),
101
+ };
102
+ }
@@ -0,0 +1,9 @@
1
+ import type { DiscordSlashCommandConfig } from "autobot/plugin-sdk/config-contracts";
2
+
3
+ export function resolveDiscordSlashCommandConfig(
4
+ raw?: DiscordSlashCommandConfig,
5
+ ): Required<DiscordSlashCommandConfig> {
6
+ return {
7
+ ephemeral: raw?.ephemeral !== false,
8
+ };
9
+ }