@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,1033 @@
1
+ import path from "node:path";
2
+ import { MessageFlags } from "discord-api-types/v10";
3
+ import {
4
+ formatReasoningMessage,
5
+ resolveAckReaction,
6
+ resolveHumanDelayConfig,
7
+ } from "autobot/plugin-sdk/agent-runtime";
8
+ import {
9
+ createStatusReactionController,
10
+ DEFAULT_TIMING,
11
+ logAckFailure,
12
+ logTypingFailure,
13
+ shouldAckReaction as shouldAckReactionGate,
14
+ } from "autobot/plugin-sdk/channel-feedback";
15
+ import {
16
+ createChannelMessageReplyPipeline,
17
+ defineFinalizableLivePreviewAdapter,
18
+ deliverWithFinalizableLivePreviewAdapter,
19
+ resolveChannelMessageSourceReplyDeliveryMode,
20
+ } from "autobot/plugin-sdk/channel-message";
21
+ import {
22
+ buildChannelProgressDraftLine,
23
+ buildChannelProgressDraftLineForEntry,
24
+ resolveChannelStreamingBlockEnabled,
25
+ resolveTranscriptBackedChannelFinalText,
26
+ } from "autobot/plugin-sdk/channel-streaming";
27
+ import { recordInboundSession } from "autobot/plugin-sdk/conversation-runtime";
28
+ import {
29
+ hasFinalInboundReplyDispatch,
30
+ recordChannelBotPairLoopAndCheckSuppression,
31
+ runPreparedInboundReplyTurn,
32
+ } from "autobot/plugin-sdk/inbound-reply-dispatch";
33
+ import { resolveMarkdownTableMode } from "autobot/plugin-sdk/markdown-table-runtime";
34
+ import { getAgentScopedMediaLocalRoots } from "autobot/plugin-sdk/media-runtime";
35
+ import { resolveChunkMode } from "autobot/plugin-sdk/reply-chunking";
36
+ import type { ReplyPayload } from "autobot/plugin-sdk/reply-dispatch-runtime";
37
+ import { createChannelHistoryWindow } from "autobot/plugin-sdk/reply-history";
38
+ import {
39
+ buildTtsSupplementMediaPayload,
40
+ getReplyPayloadTtsSupplement,
41
+ resolveSendableOutboundReplyParts,
42
+ } from "autobot/plugin-sdk/reply-payload";
43
+ import { danger, logVerbose, shouldLogVerbose } from "autobot/plugin-sdk/runtime-env";
44
+ import {
45
+ loadSessionStore,
46
+ readLatestAssistantTextFromSessionTranscript,
47
+ resolveAndPersistSessionFile,
48
+ resolveSessionStoreEntry,
49
+ resolveStorePath,
50
+ } from "autobot/plugin-sdk/session-store-runtime";
51
+ import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
52
+ import { createDiscordRestClient } from "../client.js";
53
+ import { beginDiscordInboundEventDeliveryCorrelation } from "../inbound-event-delivery.js";
54
+ import { removeReactionDiscord } from "../send.js";
55
+ import { editMessageDiscord } from "../send.messages.js";
56
+ import { resolveDiscordTargetChannelId } from "../send.shared.js";
57
+ import { resolveDiscordChannelId } from "../targets.js";
58
+ import {
59
+ createDiscordAckReactionAdapter,
60
+ createDiscordAckReactionContext,
61
+ queueInitialDiscordAckReaction,
62
+ } from "./ack-reactions.js";
63
+ import { buildDiscordMessageProcessContext } from "./message-handler.context.js";
64
+ import { createDiscordDraftPreviewController } from "./message-handler.draft-preview.js";
65
+ import type { DiscordMessagePreflightContext } from "./message-handler.preflight.js";
66
+ import { resolveForwardedMediaList, resolveMediaList } from "./message-utils.js";
67
+ import { deliverDiscordReply } from "./reply-delivery.js";
68
+ import {
69
+ DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS,
70
+ DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS,
71
+ } from "./timeouts.js";
72
+ import { sendTyping } from "./typing.js";
73
+
74
+ function sleep(ms: number): Promise<void> {
75
+ return new Promise((resolve) => {
76
+ setTimeout(resolve, ms);
77
+ });
78
+ }
79
+
80
+ const DISCORD_TYPING_MAX_DURATION_MS = 20 * 60_000;
81
+ let replyRuntimePromise: Promise<typeof import("autobot/plugin-sdk/reply-runtime")> | undefined;
82
+
83
+ async function loadReplyRuntime() {
84
+ replyRuntimePromise ??= import("autobot/plugin-sdk/reply-runtime");
85
+ return await replyRuntimePromise;
86
+ }
87
+
88
+ function isProcessAborted(abortSignal?: AbortSignal): boolean {
89
+ return Boolean(abortSignal?.aborted);
90
+ }
91
+
92
+ function formatDiscordReplyDeliveryFailure(params: {
93
+ kind: string;
94
+ err: unknown;
95
+ target: string;
96
+ sessionKey?: string;
97
+ }) {
98
+ const context = [
99
+ `target=${params.target}`,
100
+ params.sessionKey ? `session=${params.sessionKey}` : undefined,
101
+ ]
102
+ .filter(Boolean)
103
+ .join(" ");
104
+ return `discord ${params.kind} reply failed (${context}): ${String(params.err)}`;
105
+ }
106
+
107
+ type DiscordMessageProcessObserver = {
108
+ onFinalReplyStart?: () => void;
109
+ onFinalReplyDelivered?: () => void;
110
+ onReplyPlanResolved?: (params: { createdThreadId?: string; sessionKey?: string }) => void;
111
+ };
112
+
113
+ type ToolStartPayload = {
114
+ name?: string;
115
+ phase?: string;
116
+ args?: Record<string, unknown>;
117
+ detailMode?: "explain" | "raw";
118
+ };
119
+
120
+ function readToolStringArg(args: Record<string, unknown>, key: string): string | undefined {
121
+ const value = args[key];
122
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
123
+ }
124
+
125
+ function readToolBooleanArg(args: Record<string, unknown>, key: string): boolean {
126
+ return args[key] === true;
127
+ }
128
+
129
+ export async function processDiscordMessage(
130
+ ctx: DiscordMessagePreflightContext,
131
+ observer?: DiscordMessageProcessObserver,
132
+ ) {
133
+ const dispatchStartedAt = Date.now();
134
+ const {
135
+ cfg,
136
+ discordConfig,
137
+ accountId,
138
+ token,
139
+ runtime,
140
+ guildHistories,
141
+ historyLimit,
142
+ mediaMaxBytes,
143
+ textLimit,
144
+ replyToMode,
145
+ ackReactionScope,
146
+ message,
147
+ messageChannelId,
148
+ isGuildMessage,
149
+ isDirectMessage,
150
+ isGroupDm,
151
+ messageText,
152
+ shouldRequireMention,
153
+ canDetectMention,
154
+ effectiveWasMentioned,
155
+ shouldBypassMention,
156
+ channelConfig,
157
+ threadBindings,
158
+ route,
159
+ discordRestFetch,
160
+ abortSignal,
161
+ botLoopProtection,
162
+ } = ctx;
163
+ if (isProcessAborted(abortSignal)) {
164
+ return;
165
+ }
166
+ if (botLoopProtection) {
167
+ const botLoopResult = recordChannelBotPairLoopAndCheckSuppression(botLoopProtection);
168
+ if (botLoopResult.suppressed) {
169
+ logVerbose(
170
+ `discord: bot-to-bot loop detected before dispatch setup, suppressing for ${Math.max(0, Math.ceil((botLoopResult.cooldownUntilMs - Date.now()) / 1000))}s`,
171
+ );
172
+ return;
173
+ }
174
+ }
175
+
176
+ const ssrfPolicy = cfg.browser?.ssrfPolicy;
177
+ const mediaResolveOptions = {
178
+ fetchImpl: discordRestFetch,
179
+ ssrfPolicy,
180
+ readIdleTimeoutMs: DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS,
181
+ totalTimeoutMs: DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS,
182
+ abortSignal,
183
+ };
184
+ const mediaList = await resolveMediaList(message, mediaMaxBytes, mediaResolveOptions);
185
+ if (isProcessAborted(abortSignal)) {
186
+ return;
187
+ }
188
+ const forwardedMediaList = await resolveForwardedMediaList(
189
+ message,
190
+ mediaMaxBytes,
191
+ mediaResolveOptions,
192
+ );
193
+ if (isProcessAborted(abortSignal)) {
194
+ return;
195
+ }
196
+ mediaList.push(...forwardedMediaList);
197
+ const text = messageText;
198
+ if (!text) {
199
+ logVerbose("discord: drop message " + message.id + " (empty content)");
200
+ return;
201
+ }
202
+
203
+ const boundThreadId = ctx.threadBinding?.conversation?.conversationId?.trim();
204
+ if (boundThreadId && typeof threadBindings.touchThread === "function") {
205
+ threadBindings.touchThread({ threadId: boundThreadId });
206
+ }
207
+ const { createReplyDispatcherWithTyping, dispatchInboundMessage, settleReplyDispatcher } =
208
+ await loadReplyRuntime();
209
+ const sourceReplyDeliveryMode = resolveChannelMessageSourceReplyDeliveryMode({
210
+ cfg,
211
+ ctx: {
212
+ ChatType: isDirectMessage
213
+ ? "direct"
214
+ : isGroupDm
215
+ ? "group"
216
+ : isGuildMessage
217
+ ? "channel"
218
+ : undefined,
219
+ InboundEventKind: ctx.inboundEventKind,
220
+ },
221
+ });
222
+ const sourceRepliesAreToolOnly = sourceReplyDeliveryMode === "message_tool_only";
223
+ const ackReaction = resolveAckReaction(cfg, route.agentId, {
224
+ channel: "discord",
225
+ accountId,
226
+ });
227
+ const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
228
+ const mediaLocalRoots = getAgentScopedMediaLocalRoots(cfg, route.agentId);
229
+ const isRoomEvent = ctx.inboundEventKind === "room_event";
230
+ const shouldAckReaction = () =>
231
+ Boolean(
232
+ !isRoomEvent &&
233
+ ackReaction &&
234
+ shouldAckReactionGate({
235
+ scope: ackReactionScope,
236
+ isDirect: isDirectMessage,
237
+ isGroup: isGuildMessage || isGroupDm,
238
+ isMentionableGroup: isGuildMessage,
239
+ requireMention: shouldRequireMention,
240
+ canDetectMention,
241
+ effectiveWasMentioned,
242
+ shouldBypassMention,
243
+ }),
244
+ );
245
+ const shouldSendAckReaction = shouldAckReaction();
246
+ const statusReactionsExplicitlyEnabled = cfg.messages?.statusReactions?.enabled === true;
247
+ const statusReactionsEnabled =
248
+ !isRoomEvent &&
249
+ shouldSendAckReaction &&
250
+ cfg.messages?.statusReactions?.enabled !== false &&
251
+ (!sourceRepliesAreToolOnly || statusReactionsExplicitlyEnabled);
252
+ const feedbackRest = createDiscordRestClient({
253
+ cfg,
254
+ token,
255
+ accountId,
256
+ }).rest;
257
+ const deliveryRest = createDiscordRestClient({
258
+ cfg,
259
+ token,
260
+ accountId,
261
+ }).rest;
262
+ // Discord outbound helpers expect the internal REST client shape explicitly.
263
+ const ackReactionContext = createDiscordAckReactionContext({
264
+ rest: feedbackRest,
265
+ cfg,
266
+ accountId,
267
+ });
268
+ const discordAdapter = createDiscordAckReactionAdapter({
269
+ channelId: messageChannelId,
270
+ messageId: message.id,
271
+ reactionContext: ackReactionContext,
272
+ });
273
+ let statusReactionTarget = `${messageChannelId}/${message.id}`;
274
+ let statusReactionsActive = statusReactionsEnabled;
275
+ let statusReactions = createStatusReactionController({
276
+ enabled: statusReactionsEnabled,
277
+ adapter: discordAdapter,
278
+ initialEmoji: ackReaction,
279
+ emojis: cfg.messages?.statusReactions?.emojis,
280
+ timing: cfg.messages?.statusReactions?.timing,
281
+ onError: (err) => {
282
+ logAckFailure({
283
+ log: logVerbose,
284
+ channel: "discord",
285
+ target: statusReactionTarget,
286
+ error: err,
287
+ });
288
+ },
289
+ });
290
+ const resolveTrackedReactionChannelId = async (
291
+ args: Record<string, unknown>,
292
+ ): Promise<string> => {
293
+ const target =
294
+ readToolStringArg(args, "channelId") ??
295
+ readToolStringArg(args, "channel_id") ??
296
+ readToolStringArg(args, "to");
297
+ if (!target) {
298
+ return messageChannelId;
299
+ }
300
+ try {
301
+ return resolveDiscordChannelId(target);
302
+ } catch {
303
+ return (
304
+ await resolveDiscordTargetChannelId(target, {
305
+ cfg,
306
+ token,
307
+ accountId,
308
+ })
309
+ ).channelId;
310
+ }
311
+ };
312
+ const maybeBindStatusReactionsToToolReaction = async (payload: ToolStartPayload) => {
313
+ if (
314
+ sourceRepliesAreToolOnly ||
315
+ cfg.messages?.statusReactions?.enabled === false ||
316
+ payload.phase !== "start" ||
317
+ payload.name !== "message" ||
318
+ !payload.args
319
+ ) {
320
+ return;
321
+ }
322
+ const args = payload.args;
323
+ const action = readToolStringArg(args, "action")?.toLowerCase();
324
+ if (action !== "react") {
325
+ return;
326
+ }
327
+ const shouldTrack =
328
+ readToolBooleanArg(args, "trackToolCalls") || readToolBooleanArg(args, "track_tool_calls");
329
+ if (!shouldTrack) {
330
+ return;
331
+ }
332
+ const emoji = readToolStringArg(args, "emoji");
333
+ const remove = readToolBooleanArg(args, "remove");
334
+ if (!emoji || remove) {
335
+ return;
336
+ }
337
+ const trackedMessageId =
338
+ readToolStringArg(args, "messageId") ?? readToolStringArg(args, "message_id") ?? message.id;
339
+ let trackedChannelId: string;
340
+ try {
341
+ trackedChannelId = await resolveTrackedReactionChannelId(args);
342
+ } catch (err) {
343
+ logAckFailure({
344
+ log: logVerbose,
345
+ channel: "discord",
346
+ target: `${readToolStringArg(args, "to") ?? readToolStringArg(args, "channelId") ?? messageChannelId}/${trackedMessageId}`,
347
+ error: err,
348
+ });
349
+ return;
350
+ }
351
+ statusReactionTarget = `${trackedChannelId}/${trackedMessageId}`;
352
+ if (statusReactionsActive) {
353
+ void statusReactions.clear();
354
+ }
355
+ const trackedAdapter = createDiscordAckReactionAdapter({
356
+ channelId: trackedChannelId,
357
+ messageId: trackedMessageId,
358
+ reactionContext: ackReactionContext,
359
+ });
360
+ statusReactions = createStatusReactionController({
361
+ enabled: true,
362
+ adapter: trackedAdapter,
363
+ initialEmoji: emoji,
364
+ emojis: cfg.messages?.statusReactions?.emojis,
365
+ timing: cfg.messages?.statusReactions?.timing,
366
+ onError: (err) => {
367
+ logAckFailure({
368
+ log: logVerbose,
369
+ channel: "discord",
370
+ target: statusReactionTarget,
371
+ error: err,
372
+ });
373
+ },
374
+ });
375
+ statusReactionsActive = true;
376
+ void statusReactions.setQueued();
377
+ };
378
+ queueInitialDiscordAckReaction({
379
+ enabled: statusReactionsEnabled,
380
+ shouldSendAckReaction,
381
+ ackReaction,
382
+ statusReactions,
383
+ reactionAdapter: discordAdapter,
384
+ target: `${messageChannelId}/${message.id}`,
385
+ });
386
+ const processContext = await buildDiscordMessageProcessContext({
387
+ ctx,
388
+ text,
389
+ mediaList,
390
+ });
391
+ if (!processContext) {
392
+ return;
393
+ }
394
+ const {
395
+ ctxPayload,
396
+ persistedSessionKey,
397
+ turn,
398
+ replyPlan,
399
+ deliverTarget,
400
+ replyTarget,
401
+ replyReference,
402
+ } = processContext;
403
+ observer?.onReplyPlanResolved?.({
404
+ createdThreadId: replyPlan.createdThreadId,
405
+ sessionKey: persistedSessionKey,
406
+ });
407
+
408
+ const typingChannelId = deliverTarget.startsWith("channel:")
409
+ ? deliverTarget.slice("channel:".length)
410
+ : messageChannelId;
411
+
412
+ const { onModelSelected, ...replyPipeline } = createChannelMessageReplyPipeline({
413
+ cfg,
414
+ agentId: route.agentId,
415
+ channel: "discord",
416
+ accountId: route.accountId,
417
+ typing: {
418
+ start: () => sendTyping({ rest: feedbackRest, channelId: typingChannelId }),
419
+ onStartError: (err) => {
420
+ logTypingFailure({
421
+ log: logVerbose,
422
+ channel: "discord",
423
+ target: typingChannelId,
424
+ error: err,
425
+ });
426
+ },
427
+ // Long tool-heavy runs are expected on Discord; keep heartbeats alive.
428
+ maxDurationMs: DISCORD_TYPING_MAX_DURATION_MS,
429
+ },
430
+ });
431
+ const tableMode = resolveMarkdownTableMode({
432
+ cfg,
433
+ channel: "discord",
434
+ accountId,
435
+ });
436
+ const maxLinesPerMessage = resolveDiscordMaxLinesPerMessage({
437
+ cfg,
438
+ discordConfig,
439
+ accountId,
440
+ });
441
+ const chunkMode = resolveChunkMode(cfg, "discord", accountId);
442
+ const clearGroupHistory = () => {
443
+ if (isDirectMessage) {
444
+ return;
445
+ }
446
+ createChannelHistoryWindow({ historyMap: guildHistories }).clear({
447
+ historyKey: messageChannelId,
448
+ limit: historyLimit,
449
+ });
450
+ };
451
+ const beginDeliveryCorrelation = () =>
452
+ isRoomEvent
453
+ ? beginDiscordInboundEventDeliveryCorrelation(
454
+ ctxPayload.SessionKey,
455
+ {
456
+ outboundTo: messageChannelId,
457
+ outboundAccountId: route.accountId,
458
+ markInboundEventDelivered: clearGroupHistory,
459
+ },
460
+ { inboundEventKind: ctxPayload.InboundEventKind },
461
+ )
462
+ : () => {};
463
+ const endDiscordInboundEventDeliveryCorrelation = beginDeliveryCorrelation();
464
+ const resolveCurrentTurnTranscriptFinalText = async (): Promise<string | undefined> => {
465
+ const sessionKey = ctxPayload.SessionKey;
466
+ if (!sessionKey) {
467
+ return undefined;
468
+ }
469
+ try {
470
+ const storePath = resolveStorePath(cfg.session?.store, { agentId: route.agentId });
471
+ const store = loadSessionStore(storePath, { clone: false });
472
+ const sessionEntry = resolveSessionStoreEntry({ store, sessionKey }).existing;
473
+ if (!sessionEntry?.sessionId) {
474
+ return undefined;
475
+ }
476
+ const { sessionFile } = await resolveAndPersistSessionFile({
477
+ sessionId: sessionEntry.sessionId,
478
+ sessionKey,
479
+ sessionStore: store,
480
+ storePath,
481
+ sessionEntry,
482
+ agentId: route.agentId,
483
+ sessionsDir: path.dirname(storePath),
484
+ });
485
+ const latest = await readLatestAssistantTextFromSessionTranscript(sessionFile);
486
+ if (!latest?.timestamp || latest.timestamp < dispatchStartedAt) {
487
+ return undefined;
488
+ }
489
+ return latest.text;
490
+ } catch (err) {
491
+ logVerbose(`discord transcript final candidate lookup failed: ${String(err)}`);
492
+ return undefined;
493
+ }
494
+ };
495
+
496
+ const deliverChannelId = deliverTarget.startsWith("channel:")
497
+ ? deliverTarget.slice("channel:".length)
498
+ : messageChannelId;
499
+ const draftPreview = createDiscordDraftPreviewController({
500
+ cfg,
501
+ discordConfig,
502
+ accountId,
503
+ sourceRepliesAreToolOnly,
504
+ textLimit,
505
+ deliveryRest,
506
+ deliverChannelId,
507
+ replyReference,
508
+ tableMode,
509
+ maxLinesPerMessage,
510
+ chunkMode,
511
+ log: logVerbose,
512
+ });
513
+ const finalPreviewFlags =
514
+ (discordConfig?.suppressEmbeds ?? true) ? MessageFlags.SuppressEmbeds : undefined;
515
+ let finalReplyStartNotified = false;
516
+ const notifyFinalReplyStart = () => {
517
+ if (finalReplyStartNotified) {
518
+ return;
519
+ }
520
+ finalReplyStartNotified = true;
521
+ draftPreview.markFinalReplyStarted();
522
+ observer?.onFinalReplyStart?.();
523
+ };
524
+
525
+ const { dispatcher, replyOptions, markDispatchIdle, markRunComplete } =
526
+ createReplyDispatcherWithTyping({
527
+ ...replyPipeline,
528
+ humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
529
+ deliver: async (payload: ReplyPayload, info) => {
530
+ if (isProcessAborted(abortSignal)) {
531
+ return;
532
+ }
533
+ const isFinal = info.kind === "final";
534
+ if (payload.isReasoning) {
535
+ // Reasoning/thinking payloads should not be delivered to Discord.
536
+ return;
537
+ }
538
+ if (isFinal) {
539
+ draftPreview.markFinalReplyStarted();
540
+ }
541
+ const finalText =
542
+ isFinal && typeof payload.text === "string"
543
+ ? await resolveTranscriptBackedChannelFinalText({
544
+ finalText: payload.text,
545
+ resolveCandidateText: resolveCurrentTurnTranscriptFinalText,
546
+ })
547
+ : payload.text;
548
+ const effectivePayload =
549
+ finalText !== payload.text ? { ...payload, text: finalText } : payload;
550
+ const draftStream = draftPreview.draftStream;
551
+ if (draftStream && draftPreview.isProgressMode && info.kind === "block") {
552
+ const reply = resolveSendableOutboundReplyParts(effectivePayload);
553
+ if (!reply.hasMedia && !payload.isError) {
554
+ return;
555
+ }
556
+ }
557
+ const shouldFinalizeDraftPreview =
558
+ draftStream &&
559
+ isFinal &&
560
+ (!draftPreview.isProgressMode || draftPreview.hasProgressDraftStarted) &&
561
+ !payload.isError;
562
+ if (shouldFinalizeDraftPreview) {
563
+ const reply = resolveSendableOutboundReplyParts(effectivePayload);
564
+ const hasMedia = reply.hasMedia;
565
+ const ttsSupplement = getReplyPayloadTtsSupplement(effectivePayload);
566
+ const previewSourceText = finalText ?? ttsSupplement?.spokenText;
567
+ const previewFinalText = draftPreview.resolvePreviewFinalText(previewSourceText);
568
+ const previewReplyToId = replyReference.peek();
569
+ const hasExplicitReplyDirective =
570
+ Boolean(effectivePayload.replyToTag || effectivePayload.replyToCurrent) ||
571
+ (typeof previewSourceText === "string" &&
572
+ /\[\[\s*reply_to(?:_current|\s*:)/i.test(previewSourceText));
573
+
574
+ const result = await deliverWithFinalizableLivePreviewAdapter({
575
+ kind: info.kind,
576
+ payload: effectivePayload,
577
+ adapter: defineFinalizableLivePreviewAdapter({
578
+ draft: {
579
+ flush: () => draftPreview.flush(),
580
+ clear: () => draftStream.clear(),
581
+ discardPending: () => draftStream.discardPending(),
582
+ seal: () => draftStream.seal(),
583
+ id: draftStream.messageId,
584
+ },
585
+ buildFinalEdit: () => {
586
+ if (
587
+ draftPreview.finalizedViaPreviewMessage ||
588
+ (hasMedia && !ttsSupplement) ||
589
+ typeof previewFinalText !== "string" ||
590
+ hasExplicitReplyDirective ||
591
+ payload.isError
592
+ ) {
593
+ return undefined;
594
+ }
595
+ return {
596
+ content: previewFinalText,
597
+ ...(finalPreviewFlags ? { flags: finalPreviewFlags } : {}),
598
+ };
599
+ },
600
+ editFinal: async (previewMessageId, edit) => {
601
+ if (isProcessAborted(abortSignal)) {
602
+ throw new Error("process aborted");
603
+ }
604
+ notifyFinalReplyStart();
605
+ await editMessageDiscord(deliverChannelId, previewMessageId, edit, {
606
+ cfg,
607
+ accountId,
608
+ rest: deliveryRest,
609
+ });
610
+ },
611
+ onPreviewFinalized: () => {
612
+ draftPreview.markFinalReplyDelivered();
613
+ draftPreview.markPreviewFinalized();
614
+ replyReference.markSent();
615
+ observer?.onFinalReplyDelivered?.();
616
+ },
617
+ buildSupplementalPayload: () =>
618
+ ttsSupplement ? buildTtsSupplementMediaPayload(effectivePayload) : undefined,
619
+ deliverSupplemental: async (supplementalPayload) => {
620
+ if (isProcessAborted(abortSignal)) {
621
+ return false;
622
+ }
623
+ const supplementalReplyToId =
624
+ previewReplyToId ??
625
+ replyReference.peek() ??
626
+ (replyToMode === "all"
627
+ ? typeof message.id === "string" && message.id
628
+ ? message.id
629
+ : ctxPayload.MessageSid
630
+ : undefined);
631
+ await deliverDiscordReply({
632
+ cfg,
633
+ replies: [supplementalPayload],
634
+ target: deliverTarget,
635
+ token,
636
+ accountId,
637
+ rest: deliveryRest,
638
+ runtime,
639
+ replyToId: supplementalReplyToId,
640
+ replyToMode,
641
+ textLimit,
642
+ maxLinesPerMessage,
643
+ tableMode,
644
+ chunkMode,
645
+ sessionKey: ctxPayload.SessionKey,
646
+ threadBindings,
647
+ mediaLocalRoots,
648
+ kind: info.kind,
649
+ });
650
+ return true;
651
+ },
652
+ logPreviewEditFailure: (err) => {
653
+ logVerbose(
654
+ `discord: preview final edit failed; falling back to standard send (${String(err)})`,
655
+ );
656
+ },
657
+ }),
658
+ deliverNormally: async () => {
659
+ if (isProcessAborted(abortSignal)) {
660
+ return false;
661
+ }
662
+ const fallbackPayload =
663
+ ttsSupplement &&
664
+ ttsSupplement.visibleTextAlreadyDelivered !== true &&
665
+ !effectivePayload.text?.trim()
666
+ ? { ...effectivePayload, text: ttsSupplement.spokenText }
667
+ : effectivePayload;
668
+ const replyToId = replyReference.use();
669
+ notifyFinalReplyStart();
670
+ await deliverDiscordReply({
671
+ cfg,
672
+ replies: [fallbackPayload],
673
+ target: deliverTarget,
674
+ token,
675
+ accountId,
676
+ rest: deliveryRest,
677
+ runtime,
678
+ replyToId,
679
+ replyToMode,
680
+ textLimit,
681
+ maxLinesPerMessage,
682
+ tableMode,
683
+ chunkMode,
684
+ sessionKey: ctxPayload.SessionKey,
685
+ threadBindings,
686
+ mediaLocalRoots,
687
+ kind: info.kind,
688
+ });
689
+ return true;
690
+ },
691
+ onNormalDelivered: () => {
692
+ draftPreview.markFinalReplyDelivered();
693
+ replyReference.markSent();
694
+ observer?.onFinalReplyDelivered?.();
695
+ },
696
+ });
697
+ if (result.kind !== "normal-skipped") {
698
+ return;
699
+ }
700
+ }
701
+ if (isProcessAborted(abortSignal)) {
702
+ return;
703
+ }
704
+
705
+ const replyToId = replyReference.use();
706
+ if (isFinal) {
707
+ notifyFinalReplyStart();
708
+ }
709
+ await deliverDiscordReply({
710
+ cfg,
711
+ replies: [effectivePayload],
712
+ target: deliverTarget,
713
+ token,
714
+ accountId,
715
+ rest: deliveryRest,
716
+ runtime,
717
+ replyToId,
718
+ replyToMode,
719
+ textLimit,
720
+ maxLinesPerMessage,
721
+ tableMode,
722
+ chunkMode,
723
+ sessionKey: ctxPayload.SessionKey,
724
+ threadBindings,
725
+ mediaLocalRoots,
726
+ kind: info.kind,
727
+ });
728
+ replyReference.markSent();
729
+ if (isFinal && payload.isError !== true) {
730
+ draftPreview.markFinalReplyDelivered();
731
+ observer?.onFinalReplyDelivered?.();
732
+ }
733
+ },
734
+ onError: (err, info) => {
735
+ runtime.error?.(
736
+ danger(
737
+ formatDiscordReplyDeliveryFailure({
738
+ kind: info.kind,
739
+ err,
740
+ target: deliverTarget,
741
+ sessionKey: ctxPayload.SessionKey,
742
+ }),
743
+ ),
744
+ );
745
+ },
746
+ onReplyStart: async () => {
747
+ if (isProcessAborted(abortSignal)) {
748
+ return;
749
+ }
750
+ await replyPipeline.typingCallbacks?.onReplyStart();
751
+ await statusReactions.setThinking();
752
+ },
753
+ });
754
+
755
+ const resolvedBlockStreamingEnabled = resolveChannelStreamingBlockEnabled(discordConfig);
756
+ let dispatchResult: Awaited<ReturnType<typeof dispatchInboundMessage>> | null = null;
757
+ let dispatchError = false;
758
+ let dispatchAborted = false;
759
+ let dispatchSettledBeforeStart = false;
760
+ const settleDispatchBeforeStart = async () => {
761
+ dispatchSettledBeforeStart = true;
762
+ await settleReplyDispatcher({
763
+ dispatcher,
764
+ onSettled: () => {
765
+ markRunComplete();
766
+ markDispatchIdle();
767
+ },
768
+ });
769
+ };
770
+ try {
771
+ if (isProcessAborted(abortSignal)) {
772
+ dispatchAborted = true;
773
+ await settleDispatchBeforeStart();
774
+ return;
775
+ }
776
+ const preparedResult = await runPreparedInboundReplyTurn({
777
+ channel: "discord",
778
+ accountId: route.accountId,
779
+ routeSessionKey: persistedSessionKey,
780
+ storePath: turn.storePath,
781
+ ctxPayload,
782
+ recordInboundSession,
783
+ record: turn.record,
784
+ history: isRoomEvent
785
+ ? undefined
786
+ : {
787
+ isGroup: isGuildMessage,
788
+ historyKey: messageChannelId,
789
+ historyMap: guildHistories,
790
+ limit: historyLimit,
791
+ },
792
+ onPreDispatchFailure: settleDispatchBeforeStart,
793
+ runDispatch: async () =>
794
+ await dispatchInboundMessage({
795
+ ctx: ctxPayload,
796
+ cfg,
797
+ dispatcher,
798
+ replyOptions: {
799
+ ...replyOptions,
800
+ abortSignal,
801
+ skillFilter: channelConfig?.skills,
802
+ sourceReplyDeliveryMode,
803
+ queuedDeliveryCorrelations: isRoomEvent
804
+ ? [{ begin: beginDeliveryCorrelation }]
805
+ : undefined,
806
+ suppressTyping: isRoomEvent ? true : undefined,
807
+ allowProgressCallbacksWhenSourceDeliverySuppressed:
808
+ sourceRepliesAreToolOnly && draftPreview.draftStream && draftPreview.isProgressMode
809
+ ? true
810
+ : undefined,
811
+ disableBlockStreaming: sourceRepliesAreToolOnly
812
+ ? true
813
+ : (draftPreview.disableBlockStreamingForDraft ??
814
+ (typeof resolvedBlockStreamingEnabled === "boolean"
815
+ ? !resolvedBlockStreamingEnabled
816
+ : undefined)),
817
+ onPartialReply:
818
+ draftPreview.draftStream && !draftPreview.isProgressMode
819
+ ? (payload) => draftPreview.updateFromPartial(payload.text)
820
+ : undefined,
821
+ onAssistantMessageStart: draftPreview.draftStream
822
+ ? () => draftPreview.handleAssistantMessageBoundary()
823
+ : undefined,
824
+ onReasoningEnd: draftPreview.draftStream
825
+ ? () => draftPreview.handleAssistantMessageBoundary()
826
+ : undefined,
827
+ onModelSelected,
828
+ suppressDefaultToolProgressMessages: draftPreview.suppressDefaultToolProgressMessages
829
+ ? true
830
+ : undefined,
831
+ onReasoningStream: async (payload) => {
832
+ await statusReactions.setThinking();
833
+ const formattedText = payload?.text
834
+ ? formatReasoningMessage(payload.text)
835
+ : undefined;
836
+ await draftPreview.pushReasoningProgress(formattedText);
837
+ },
838
+ onToolStart: async (payload) => {
839
+ if (isProcessAborted(abortSignal)) {
840
+ return;
841
+ }
842
+ await maybeBindStatusReactionsToToolReaction(payload);
843
+ await statusReactions.setTool(payload.name);
844
+ await draftPreview.pushToolProgress(
845
+ buildChannelProgressDraftLineForEntry(
846
+ discordConfig,
847
+ {
848
+ event: "tool",
849
+ name: payload.name,
850
+ phase: payload.phase,
851
+ args: payload.args,
852
+ },
853
+ payload.detailMode ? { detailMode: payload.detailMode } : undefined,
854
+ ),
855
+ { toolName: payload.name },
856
+ );
857
+ },
858
+ onItemEvent: async (payload) => {
859
+ await draftPreview.pushToolProgress(
860
+ buildChannelProgressDraftLineForEntry(discordConfig, {
861
+ event: "item",
862
+ itemId: payload.itemId,
863
+ itemKind: payload.kind,
864
+ title: payload.title,
865
+ name: payload.name,
866
+ phase: payload.phase,
867
+ status: payload.status,
868
+ summary: payload.summary,
869
+ progressText: payload.progressText,
870
+ meta: payload.meta,
871
+ }),
872
+ );
873
+ },
874
+ onPlanUpdate: async (payload) => {
875
+ if (payload.phase !== "update") {
876
+ return;
877
+ }
878
+ await draftPreview.pushToolProgress(
879
+ buildChannelProgressDraftLine({
880
+ event: "plan",
881
+ phase: payload.phase,
882
+ title: payload.title,
883
+ explanation: payload.explanation,
884
+ steps: payload.steps,
885
+ }),
886
+ );
887
+ },
888
+ onApprovalEvent: async (payload) => {
889
+ if (payload.phase !== "requested") {
890
+ return;
891
+ }
892
+ await draftPreview.pushToolProgress(
893
+ buildChannelProgressDraftLine({
894
+ event: "approval",
895
+ phase: payload.phase,
896
+ title: payload.title,
897
+ command: payload.command,
898
+ reason: payload.reason,
899
+ message: payload.message,
900
+ }),
901
+ );
902
+ },
903
+ onCommandOutput: async (payload) => {
904
+ if (payload.phase !== "end") {
905
+ return;
906
+ }
907
+ await draftPreview.pushToolProgress(
908
+ buildChannelProgressDraftLine({
909
+ event: "command-output",
910
+ phase: payload.phase,
911
+ title: payload.title,
912
+ name: payload.name,
913
+ status: payload.status,
914
+ exitCode: payload.exitCode,
915
+ }),
916
+ );
917
+ },
918
+ onPatchSummary: async (payload) => {
919
+ if (payload.phase !== "end") {
920
+ return;
921
+ }
922
+ await draftPreview.pushToolProgress(
923
+ buildChannelProgressDraftLine({
924
+ event: "patch",
925
+ phase: payload.phase,
926
+ title: payload.title,
927
+ name: payload.name,
928
+ added: payload.added,
929
+ modified: payload.modified,
930
+ deleted: payload.deleted,
931
+ summary: payload.summary,
932
+ }),
933
+ );
934
+ },
935
+ onCompactionStart: async () => {
936
+ if (isProcessAborted(abortSignal)) {
937
+ return;
938
+ }
939
+ await statusReactions.setCompacting();
940
+ },
941
+ onCompactionEnd: async () => {
942
+ if (isProcessAborted(abortSignal)) {
943
+ return;
944
+ }
945
+ statusReactions.cancelPending();
946
+ await statusReactions.setThinking();
947
+ },
948
+ },
949
+ }),
950
+ });
951
+ if (!preparedResult.dispatched) {
952
+ return;
953
+ }
954
+ dispatchResult = preparedResult.dispatchResult;
955
+ if (isProcessAborted(abortSignal)) {
956
+ dispatchAborted = true;
957
+ return;
958
+ }
959
+ } catch (err) {
960
+ if (isProcessAborted(abortSignal)) {
961
+ dispatchAborted = true;
962
+ return;
963
+ }
964
+ dispatchError = true;
965
+ throw err;
966
+ } finally {
967
+ endDiscordInboundEventDeliveryCorrelation();
968
+ try {
969
+ await draftPreview.cleanup();
970
+ } finally {
971
+ if (!dispatchSettledBeforeStart) {
972
+ markRunComplete();
973
+ markDispatchIdle();
974
+ }
975
+ }
976
+ const finalDeliveryFailed = (dispatchResult?.failedCounts?.final ?? 0) > 0;
977
+ if (statusReactionsActive) {
978
+ if (dispatchAborted) {
979
+ if (removeAckAfterReply) {
980
+ void statusReactions.clear();
981
+ } else {
982
+ void statusReactions.restoreInitial();
983
+ }
984
+ } else {
985
+ if (dispatchError || finalDeliveryFailed) {
986
+ await statusReactions.setError();
987
+ } else {
988
+ await statusReactions.setDone();
989
+ }
990
+ if (removeAckAfterReply) {
991
+ void (async () => {
992
+ await sleep(
993
+ dispatchError || finalDeliveryFailed
994
+ ? DEFAULT_TIMING.errorHoldMs
995
+ : DEFAULT_TIMING.doneHoldMs,
996
+ );
997
+ await statusReactions.clear();
998
+ })();
999
+ } else {
1000
+ void statusReactions.restoreInitial();
1001
+ }
1002
+ }
1003
+ } else if (shouldSendAckReaction && ackReaction && removeAckAfterReply) {
1004
+ void removeReactionDiscord(
1005
+ messageChannelId,
1006
+ message.id,
1007
+ ackReaction,
1008
+ ackReactionContext,
1009
+ ).catch((err: unknown) => {
1010
+ logAckFailure({
1011
+ log: logVerbose,
1012
+ channel: "discord",
1013
+ target: `${messageChannelId}/${message.id}`,
1014
+ error: err,
1015
+ });
1016
+ });
1017
+ }
1018
+ }
1019
+ if (dispatchAborted) {
1020
+ return;
1021
+ }
1022
+
1023
+ const finalDispatchResult = dispatchResult;
1024
+ if (!finalDispatchResult || !hasFinalInboundReplyDispatch(finalDispatchResult)) {
1025
+ return;
1026
+ }
1027
+ if (shouldLogVerbose()) {
1028
+ const finalCount = finalDispatchResult.counts.final;
1029
+ logVerbose(
1030
+ `discord: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${replyTarget}`,
1031
+ );
1032
+ }
1033
+ }