@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,436 @@
1
+ import { EmbeddedBlockChunker } from "autobot/plugin-sdk/agent-runtime";
2
+ import {
3
+ createChannelProgressDraftGate,
4
+ type ChannelProgressDraftLine,
5
+ formatChannelProgressDraftText,
6
+ isChannelProgressDraftWorkToolName,
7
+ mergeChannelProgressDraftLine,
8
+ normalizeChannelProgressDraftLineIdentity,
9
+ resolveChannelProgressDraftMaxLines,
10
+ resolveChannelStreamingBlockEnabled,
11
+ resolveChannelStreamingPreviewToolProgress,
12
+ resolveChannelStreamingSuppressDefaultToolProgressMessages,
13
+ } from "autobot/plugin-sdk/channel-streaming";
14
+ import type { AutoBotConfig } from "autobot/plugin-sdk/config-contracts";
15
+ import {
16
+ convertMarkdownTables,
17
+ stripInlineDirectiveTagsForDelivery,
18
+ stripReasoningTagsFromText,
19
+ } from "autobot/plugin-sdk/text-chunking";
20
+ import { chunkDiscordTextWithMode } from "../chunk.js";
21
+ import { resolveDiscordDraftStreamingChunking } from "../draft-chunking.js";
22
+ import { createDiscordDraftStream } from "../draft-stream.js";
23
+ import type { RequestClient } from "../internal/discord.js";
24
+ import { resolveDiscordPreviewStreamMode } from "../preview-streaming.js";
25
+
26
+ type DraftReplyReference = {
27
+ peek: () => string | undefined;
28
+ };
29
+
30
+ type DiscordConfig = NonNullable<AutoBotConfig["channels"]>["discord"];
31
+
32
+ export function createDiscordDraftPreviewController(params: {
33
+ cfg: AutoBotConfig;
34
+ discordConfig: DiscordConfig;
35
+ accountId: string;
36
+ sourceRepliesAreToolOnly: boolean;
37
+ textLimit: number;
38
+ deliveryRest: RequestClient;
39
+ deliverChannelId: string;
40
+ replyReference: DraftReplyReference;
41
+ tableMode: Parameters<typeof convertMarkdownTables>[1];
42
+ maxLinesPerMessage: number | undefined;
43
+ chunkMode: Parameters<typeof chunkDiscordTextWithMode>[1]["chunkMode"];
44
+ log: (message: string) => void;
45
+ }) {
46
+ const discordStreamMode = resolveDiscordPreviewStreamMode(params.discordConfig);
47
+ const draftMaxChars = Math.min(params.textLimit, 2000);
48
+ const accountBlockStreamingEnabled =
49
+ resolveChannelStreamingBlockEnabled(params.discordConfig) ??
50
+ params.cfg.agents?.defaults?.blockStreamingDefault === "on";
51
+ const canStreamProgressDraftForToolOnlySource =
52
+ params.sourceRepliesAreToolOnly && discordStreamMode === "progress";
53
+ const canStreamDraft =
54
+ (!params.sourceRepliesAreToolOnly || canStreamProgressDraftForToolOnlySource) &&
55
+ discordStreamMode !== "off" &&
56
+ !accountBlockStreamingEnabled;
57
+ const draftStream = canStreamDraft
58
+ ? createDiscordDraftStream({
59
+ rest: params.deliveryRest,
60
+ channelId: params.deliverChannelId,
61
+ maxChars: draftMaxChars,
62
+ replyToMessageId: () => params.replyReference.peek(),
63
+ minInitialChars: discordStreamMode === "progress" ? 0 : 30,
64
+ suppressEmbeds: params.discordConfig?.suppressEmbeds ?? true,
65
+ throttleMs: 1200,
66
+ log: params.log,
67
+ warn: params.log,
68
+ })
69
+ : undefined;
70
+ const draftChunking =
71
+ draftStream && discordStreamMode === "block"
72
+ ? resolveDiscordDraftStreamingChunking(params.cfg, params.accountId)
73
+ : undefined;
74
+ const shouldSplitPreviewMessages = discordStreamMode === "block";
75
+ const draftChunker = draftChunking ? new EmbeddedBlockChunker(draftChunking) : undefined;
76
+ let lastPartialText = "";
77
+ let draftText = "";
78
+ let hasStreamedMessage = false;
79
+ let finalizedViaPreviewMessage = false;
80
+ let finalReplyStarted = false;
81
+ let finalReplyDelivered = false;
82
+ const previewToolProgressEnabled =
83
+ Boolean(draftStream) && resolveChannelStreamingPreviewToolProgress(params.discordConfig);
84
+ const suppressDefaultToolProgressMessages =
85
+ Boolean(draftStream) &&
86
+ resolveChannelStreamingSuppressDefaultToolProgressMessages(params.discordConfig, {
87
+ draftStreamActive: true,
88
+ previewToolProgressEnabled,
89
+ });
90
+ let previewToolProgressSuppressed = false;
91
+ let previewToolProgressLines: Array<string | ChannelProgressDraftLine> = [];
92
+ let reasoningProgressRawText = "";
93
+ let lastReasoningProgressLine: string | undefined;
94
+ const progressSeed = `${params.accountId}:${params.deliverChannelId}`;
95
+
96
+ const renderProgressDraft = async (options?: { flush?: boolean }) => {
97
+ if (!draftStream || discordStreamMode !== "progress") {
98
+ return;
99
+ }
100
+ const previewText = formatChannelProgressDraftText({
101
+ entry: params.discordConfig,
102
+ lines: previewToolProgressLines,
103
+ seed: progressSeed,
104
+ });
105
+ if (!previewText || previewText === lastPartialText) {
106
+ return;
107
+ }
108
+ lastPartialText = previewText;
109
+ draftText = previewText;
110
+ hasStreamedMessage = true;
111
+ draftChunker?.reset();
112
+ draftStream.update(previewText);
113
+ if (options?.flush) {
114
+ await draftStream.flush();
115
+ }
116
+ };
117
+
118
+ const progressDraftGate = createChannelProgressDraftGate({
119
+ onStart: () => renderProgressDraft({ flush: true }),
120
+ });
121
+
122
+ const resetProgressState = () => {
123
+ lastPartialText = "";
124
+ draftText = "";
125
+ draftChunker?.reset();
126
+ previewToolProgressSuppressed = false;
127
+ previewToolProgressLines = [];
128
+ reasoningProgressRawText = "";
129
+ lastReasoningProgressLine = undefined;
130
+ };
131
+
132
+ const forceNewMessageIfNeeded = () => {
133
+ if (shouldSplitPreviewMessages && hasStreamedMessage) {
134
+ params.log("discord: calling forceNewMessage() for draft stream");
135
+ draftStream?.forceNewMessage();
136
+ }
137
+ resetProgressState();
138
+ };
139
+
140
+ return {
141
+ draftStream,
142
+ previewToolProgressEnabled,
143
+ suppressDefaultToolProgressMessages,
144
+ get isProgressMode() {
145
+ return discordStreamMode === "progress";
146
+ },
147
+ get hasProgressDraftStarted() {
148
+ return progressDraftGate.hasStarted;
149
+ },
150
+ get finalizedViaPreviewMessage() {
151
+ return finalizedViaPreviewMessage;
152
+ },
153
+ markFinalReplyStarted() {
154
+ finalReplyStarted = true;
155
+ },
156
+ markFinalReplyDelivered() {
157
+ finalReplyDelivered = true;
158
+ },
159
+ markPreviewFinalized() {
160
+ finalizedViaPreviewMessage = true;
161
+ },
162
+ disableBlockStreamingForDraft: draftStream ? true : undefined,
163
+ async startProgressDraft() {
164
+ if (!draftStream || discordStreamMode !== "progress") {
165
+ return;
166
+ }
167
+ await progressDraftGate.startNow();
168
+ },
169
+ async pushToolProgress(
170
+ line?: string | ChannelProgressDraftLine,
171
+ options?: { toolName?: string },
172
+ ) {
173
+ if (!draftStream) {
174
+ return;
175
+ }
176
+ if (finalReplyStarted || finalReplyDelivered) {
177
+ return;
178
+ }
179
+ if (
180
+ options?.toolName !== undefined &&
181
+ !isChannelProgressDraftWorkToolName(options.toolName)
182
+ ) {
183
+ return;
184
+ }
185
+ if (isEmptyDiscordProgressLine(line)) {
186
+ return;
187
+ }
188
+ const normalized = normalizeChannelProgressDraftLineIdentity(line);
189
+ if (!normalized) {
190
+ return;
191
+ }
192
+ const progressLine: string | ChannelProgressDraftLine =
193
+ typeof line === "object" && line !== undefined ? line : normalized;
194
+ if (discordStreamMode !== "progress") {
195
+ if (!previewToolProgressEnabled || previewToolProgressSuppressed) {
196
+ return;
197
+ }
198
+ const nextLines = mergeChannelProgressDraftLine(previewToolProgressLines, progressLine, {
199
+ maxLines: resolveChannelProgressDraftMaxLines(params.discordConfig),
200
+ });
201
+ if (nextLines === previewToolProgressLines) {
202
+ return;
203
+ }
204
+ previewToolProgressLines = nextLines;
205
+ const previewText = formatChannelProgressDraftText({
206
+ entry: params.discordConfig,
207
+ lines: previewToolProgressLines,
208
+ seed: progressSeed,
209
+ });
210
+ lastPartialText = previewText;
211
+ draftText = previewText;
212
+ hasStreamedMessage = true;
213
+ draftChunker?.reset();
214
+ draftStream.update(previewText);
215
+ return;
216
+ }
217
+ if (previewToolProgressEnabled && !previewToolProgressSuppressed && normalized) {
218
+ previewToolProgressLines = mergeChannelProgressDraftLine(
219
+ previewToolProgressLines,
220
+ progressLine,
221
+ {
222
+ maxLines: resolveChannelProgressDraftMaxLines(params.discordConfig),
223
+ },
224
+ );
225
+ }
226
+ const alreadyStarted = progressDraftGate.hasStarted;
227
+ if (shouldStartDiscordProgressDraftNow(line)) {
228
+ await progressDraftGate.startNow();
229
+ } else {
230
+ await progressDraftGate.noteWork();
231
+ }
232
+ if (alreadyStarted && progressDraftGate.hasStarted) {
233
+ await renderProgressDraft();
234
+ }
235
+ },
236
+ async pushReasoningProgress(text?: string) {
237
+ if (!draftStream || discordStreamMode !== "progress" || !text) {
238
+ return;
239
+ }
240
+ if (finalReplyDelivered) {
241
+ return;
242
+ }
243
+ reasoningProgressRawText = mergeReasoningProgressText(reasoningProgressRawText, text);
244
+ const normalized = normalizeReasoningProgressLine(reasoningProgressRawText);
245
+ if (!normalized) {
246
+ return;
247
+ }
248
+ if (previewToolProgressEnabled && !previewToolProgressSuppressed) {
249
+ const priorIndex =
250
+ lastReasoningProgressLine === undefined
251
+ ? -1
252
+ : previewToolProgressLines.lastIndexOf(lastReasoningProgressLine);
253
+ if (priorIndex >= 0) {
254
+ previewToolProgressLines = [...previewToolProgressLines];
255
+ previewToolProgressLines[priorIndex] = normalized;
256
+ } else {
257
+ previewToolProgressLines = [...previewToolProgressLines, normalized].slice(
258
+ -resolveChannelProgressDraftMaxLines(params.discordConfig),
259
+ );
260
+ }
261
+ lastReasoningProgressLine = normalized;
262
+ }
263
+ const alreadyStarted = progressDraftGate.hasStarted;
264
+ await progressDraftGate.noteWork();
265
+ if (alreadyStarted && progressDraftGate.hasStarted) {
266
+ await renderProgressDraft();
267
+ }
268
+ },
269
+ resolvePreviewFinalText(text?: string) {
270
+ if (typeof text !== "string") {
271
+ return undefined;
272
+ }
273
+ const formatted = convertMarkdownTables(
274
+ stripInlineDirectiveTagsForDelivery(text).text,
275
+ params.tableMode,
276
+ );
277
+ const chunks = chunkDiscordTextWithMode(formatted, {
278
+ maxChars: draftMaxChars,
279
+ maxLines: params.maxLinesPerMessage,
280
+ chunkMode: params.chunkMode,
281
+ });
282
+ if (!chunks.length && formatted) {
283
+ chunks.push(formatted);
284
+ }
285
+ if (chunks.length !== 1) {
286
+ return undefined;
287
+ }
288
+ const trimmed = chunks[0].trim();
289
+ if (!trimmed) {
290
+ return undefined;
291
+ }
292
+ const currentPreviewText = discordStreamMode === "block" ? draftText : lastPartialText;
293
+ if (
294
+ currentPreviewText &&
295
+ currentPreviewText.startsWith(trimmed) &&
296
+ trimmed.length < currentPreviewText.length
297
+ ) {
298
+ return undefined;
299
+ }
300
+ return trimmed;
301
+ },
302
+ updateFromPartial(text?: string) {
303
+ if (!draftStream || !text) {
304
+ return;
305
+ }
306
+ const cleaned = stripInlineDirectiveTagsForDelivery(
307
+ stripReasoningTagsFromText(text, { mode: "strict", trim: "both" }),
308
+ ).text;
309
+ if (!cleaned || cleaned.startsWith("Reasoning:\n")) {
310
+ return;
311
+ }
312
+ if (cleaned === lastPartialText) {
313
+ return;
314
+ }
315
+ if (discordStreamMode === "progress") {
316
+ return;
317
+ }
318
+ previewToolProgressSuppressed = true;
319
+ previewToolProgressLines = [];
320
+ hasStreamedMessage = true;
321
+ if (discordStreamMode === "partial") {
322
+ if (
323
+ lastPartialText &&
324
+ lastPartialText.startsWith(cleaned) &&
325
+ cleaned.length < lastPartialText.length
326
+ ) {
327
+ return;
328
+ }
329
+ lastPartialText = cleaned;
330
+ draftStream.update(cleaned);
331
+ return;
332
+ }
333
+
334
+ let delta = cleaned;
335
+ if (cleaned.startsWith(lastPartialText)) {
336
+ delta = cleaned.slice(lastPartialText.length);
337
+ } else {
338
+ draftChunker?.reset();
339
+ draftText = "";
340
+ }
341
+ lastPartialText = cleaned;
342
+ if (!delta) {
343
+ return;
344
+ }
345
+ if (!draftChunker) {
346
+ draftText = cleaned;
347
+ draftStream.update(draftText);
348
+ return;
349
+ }
350
+ draftChunker.append(delta);
351
+ draftChunker.drain({
352
+ force: false,
353
+ emit: (chunk) => {
354
+ draftText += chunk;
355
+ draftStream.update(draftText);
356
+ },
357
+ });
358
+ },
359
+ handleAssistantMessageBoundary() {
360
+ if (discordStreamMode === "progress") {
361
+ return;
362
+ }
363
+ forceNewMessageIfNeeded();
364
+ },
365
+ async flush() {
366
+ if (!draftStream) {
367
+ return;
368
+ }
369
+ if (draftChunker?.hasBuffered()) {
370
+ draftChunker.drain({
371
+ force: true,
372
+ emit: (chunk) => {
373
+ draftText += chunk;
374
+ },
375
+ });
376
+ draftChunker.reset();
377
+ if (draftText) {
378
+ draftStream.update(draftText);
379
+ }
380
+ }
381
+ await draftStream.flush();
382
+ },
383
+ async cleanup() {
384
+ try {
385
+ progressDraftGate.cancel();
386
+ if (!finalReplyDelivered) {
387
+ await draftStream?.discardPending();
388
+ }
389
+ if (!finalReplyDelivered && !finalizedViaPreviewMessage && draftStream?.messageId()) {
390
+ await draftStream.clear();
391
+ }
392
+ } catch (err) {
393
+ params.log(`discord: draft cleanup failed: ${String(err)}`);
394
+ }
395
+ },
396
+ };
397
+ }
398
+
399
+ function normalizeReasoningProgressLine(text: string): string {
400
+ return text
401
+ .replace(/^\s*(?:>\s*)?(?:Reasoning:|Thinking\.{0,3})\s*/i, "")
402
+ .replace(/\s+/g, " ")
403
+ .trim();
404
+ }
405
+
406
+ function mergeReasoningProgressText(current: string, incoming: string): string {
407
+ if (!current) {
408
+ return incoming;
409
+ }
410
+ const normalizedCurrent = normalizeReasoningProgressLine(current);
411
+ const normalizedIncoming = normalizeReasoningProgressLine(incoming);
412
+ if (!normalizedIncoming || normalizedIncoming === normalizedCurrent) {
413
+ return current;
414
+ }
415
+ if (isReasoningSnapshotText(incoming) || normalizedIncoming.startsWith(normalizedCurrent)) {
416
+ return incoming;
417
+ }
418
+ return `${current}${incoming}`;
419
+ }
420
+
421
+ function isReasoningSnapshotText(text: string): boolean {
422
+ return /^\s*(?:>\s*)?(?:Reasoning:|Thinking\.{0,3})\s*/i.test(text);
423
+ }
424
+
425
+ function isEmptyDiscordProgressLine(line: string | ChannelProgressDraftLine | undefined): boolean {
426
+ if (!line || typeof line === "string") {
427
+ return false;
428
+ }
429
+ return line.toolName === "apply_patch" && !line.detail && !line.status;
430
+ }
431
+
432
+ function shouldStartDiscordProgressDraftNow(
433
+ line: string | ChannelProgressDraftLine | undefined,
434
+ ): boolean {
435
+ return typeof line === "object" && line?.kind === "patch" && Boolean(line.detail);
436
+ }
@@ -0,0 +1,198 @@
1
+ import type { APIMessage, APIUser } from "discord-api-types/v10";
2
+ import { logVerbose } from "autobot/plugin-sdk/runtime-env";
3
+ import { getChannelMessage, Message as DiscordMessage, type Message } from "../internal/discord.js";
4
+ import { resolveDiscordMessageText, type DiscordChannelInfo } from "./message-utils.js";
5
+
6
+ function mergeFetchedDiscordMessage(base: Message, fetched: APIMessage): Message {
7
+ const baseRawData = readMessageRawData(base);
8
+ const baseFallback = readMessageFallback(base);
9
+ const rawData = {
10
+ ...baseRawData,
11
+ ...fetched,
12
+ id: fetched.id ?? baseRawData.id ?? baseFallback.id,
13
+ channel_id: fetched.channel_id ?? baseRawData.channel_id ?? baseFallback.channel_id,
14
+ content: fetched.content ?? baseRawData.content ?? baseFallback.content,
15
+ author: fetched.author ?? baseRawData.author ?? baseFallback.author,
16
+ attachments: fetched.attachments ?? baseRawData.attachments ?? baseFallback.attachments,
17
+ embeds: fetched.embeds ?? baseRawData.embeds ?? baseFallback.embeds,
18
+ mentions: fetched.mentions ?? baseRawData.mentions ?? baseFallback.mentions,
19
+ mention_roles: fetched.mention_roles ?? baseRawData.mention_roles ?? baseFallback.mention_roles,
20
+ mention_everyone:
21
+ fetched.mention_everyone ?? baseRawData.mention_everyone ?? baseFallback.mention_everyone,
22
+ timestamp: fetched.timestamp ?? baseRawData.timestamp ?? baseFallback.timestamp,
23
+ tts: fetched.tts ?? baseRawData.tts ?? false,
24
+ pinned: fetched.pinned ?? baseRawData.pinned ?? false,
25
+ type: fetched.type ?? baseRawData.type ?? 0,
26
+ message_snapshots:
27
+ fetched.message_snapshots ?? baseRawData.message_snapshots ?? baseFallback.message_snapshots,
28
+ sticker_items:
29
+ (fetched as { sticker_items?: unknown }).sticker_items ??
30
+ (baseRawData as { sticker_items?: unknown }).sticker_items ??
31
+ baseFallback.sticker_items,
32
+ } as APIMessage;
33
+ const hydrated = new DiscordMessage(readMessageClient(base), rawData);
34
+ copyRuntimeMessageFields(base, hydrated);
35
+ return hydrated;
36
+ }
37
+
38
+ function readMessageClient(message: Message): ConstructorParameters<typeof DiscordMessage>[0] {
39
+ return (message as unknown as { client: ConstructorParameters<typeof DiscordMessage>[0] }).client;
40
+ }
41
+
42
+ function readMessageRawData(message: Message): Partial<APIMessage> {
43
+ try {
44
+ const rawData = message.rawData as APIMessage | undefined;
45
+ return rawData && typeof rawData === "object" ? rawData : {};
46
+ } catch {
47
+ return {};
48
+ }
49
+ }
50
+
51
+ type MessageFallback = Partial<Omit<APIMessage, "message_snapshots" | "sticker_items">> & {
52
+ channel_id: string;
53
+ sticker_items?: APIMessage["sticker_items"];
54
+ message_snapshots?: APIMessage["message_snapshots"];
55
+ };
56
+
57
+ function readMessageFallback(message: Message): MessageFallback {
58
+ const value = message as unknown as {
59
+ id?: unknown;
60
+ channelId?: unknown;
61
+ channel_id?: unknown;
62
+ content?: unknown;
63
+ author?: unknown;
64
+ attachments?: unknown;
65
+ embeds?: unknown;
66
+ mentionedUsers?: unknown;
67
+ mentionedRoles?: unknown;
68
+ mentionedEveryone?: unknown;
69
+ timestamp?: unknown;
70
+ stickers?: unknown;
71
+ sticker_items?: unknown;
72
+ message_snapshots?: unknown;
73
+ };
74
+ return {
75
+ id: typeof value.id === "string" ? value.id : "",
76
+ channel_id: readString(value.channel_id) ?? readString(value.channelId) ?? "",
77
+ content: typeof value.content === "string" ? value.content : "",
78
+ author: normalizeApiUser(value.author),
79
+ attachments: Array.isArray(value.attachments) ? value.attachments : [],
80
+ embeds: Array.isArray(value.embeds) ? value.embeds : [],
81
+ mentions: normalizeApiUsers(value.mentionedUsers),
82
+ mention_roles: normalizeStringArray(value.mentionedRoles),
83
+ mention_everyone: value.mentionedEveryone === true,
84
+ timestamp: readString(value.timestamp) ?? "1970-01-01T00:00:00.000Z",
85
+ sticker_items: Array.isArray(value.sticker_items)
86
+ ? (value.sticker_items as APIMessage["sticker_items"])
87
+ : Array.isArray(value.stickers)
88
+ ? (value.stickers as APIMessage["sticker_items"])
89
+ : undefined,
90
+ message_snapshots: Array.isArray(value.message_snapshots)
91
+ ? (value.message_snapshots as APIMessage["message_snapshots"])
92
+ : undefined,
93
+ };
94
+ }
95
+
96
+ function readString(value: unknown): string | undefined {
97
+ return typeof value === "string" ? value : undefined;
98
+ }
99
+
100
+ function normalizeStringArray(value: unknown): string[] {
101
+ return Array.isArray(value)
102
+ ? value.flatMap((entry) => (typeof entry === "string" ? [entry] : []))
103
+ : [];
104
+ }
105
+
106
+ function normalizeApiUsers(value: unknown): APIUser[] {
107
+ return Array.isArray(value)
108
+ ? value.flatMap((entry) => {
109
+ const user = normalizeApiUser(entry);
110
+ return user.id ? [user] : [];
111
+ })
112
+ : [];
113
+ }
114
+
115
+ function normalizeApiUser(value: unknown): APIUser {
116
+ if (!value || typeof value !== "object") {
117
+ return {
118
+ id: "",
119
+ username: "",
120
+ discriminator: "0",
121
+ global_name: null,
122
+ avatar: null,
123
+ };
124
+ }
125
+ const input = value as {
126
+ id?: unknown;
127
+ username?: unknown;
128
+ global_name?: unknown;
129
+ globalName?: unknown;
130
+ discriminator?: unknown;
131
+ avatar?: unknown;
132
+ bot?: unknown;
133
+ };
134
+ return {
135
+ id: readString(input.id) ?? "",
136
+ username: readString(input.username) ?? "",
137
+ discriminator: readString(input.discriminator) ?? "0",
138
+ global_name: readString(input.global_name) ?? readString(input.globalName) ?? null,
139
+ avatar: input.avatar === null ? null : (readString(input.avatar) ?? null),
140
+ ...(typeof input.bot === "boolean" ? { bot: input.bot } : {}),
141
+ };
142
+ }
143
+
144
+ function copyRuntimeMessageFields(source: Message, target: Message): void {
145
+ const channelDescriptor = Object.getOwnPropertyDescriptor(source, "channel");
146
+ if (channelDescriptor) {
147
+ Object.defineProperty(target, "channel", channelDescriptor);
148
+ }
149
+ }
150
+
151
+ function shouldHydrateDiscordMessage(params: { message: Message }) {
152
+ let currentText = "";
153
+ try {
154
+ currentText = resolveDiscordMessageText(params.message, {
155
+ includeForwarded: true,
156
+ });
157
+ } catch {
158
+ return true;
159
+ }
160
+ if (!currentText) {
161
+ return true;
162
+ }
163
+ const hasMentionMetadata =
164
+ (params.message.mentionedUsers?.length ?? 0) > 0 ||
165
+ (params.message.mentionedRoles?.length ?? 0) > 0 ||
166
+ params.message.mentionedEveryone;
167
+ if (hasMentionMetadata) {
168
+ return false;
169
+ }
170
+ return /<@!?\d+>|<@&\d+>|@everyone|@here/u.test(currentText);
171
+ }
172
+
173
+ export async function hydrateDiscordMessageIfNeeded(params: {
174
+ client: { rest: Parameters<typeof getChannelMessage>[0] };
175
+ message: Message;
176
+ messageChannelId: string;
177
+ channelInfo?: DiscordChannelInfo | null;
178
+ }): Promise<Message> {
179
+ void params.channelInfo;
180
+ if (!shouldHydrateDiscordMessage({ message: params.message })) {
181
+ return params.message;
182
+ }
183
+ try {
184
+ const fetched = (await getChannelMessage(
185
+ params.client.rest,
186
+ params.messageChannelId,
187
+ params.message.id,
188
+ )) as APIMessage | null | undefined;
189
+ if (!fetched) {
190
+ return params.message;
191
+ }
192
+ logVerbose(`discord: hydrated inbound payload via REST for ${params.message.id}`);
193
+ return mergeFetchedDiscordMessage(params.message, fetched);
194
+ } catch (err) {
195
+ logVerbose(`discord: failed to hydrate message ${params.message.id}: ${String(err)}`);
196
+ return params.message;
197
+ }
198
+ }
@@ -0,0 +1,31 @@
1
+ import type { MockFn } from "autobot/plugin-sdk/plugin-test-runtime";
2
+ import { vi } from "vitest";
3
+ import type { DiscordMessageRunQueueTestingHooks } from "./message-run-queue.js";
4
+
5
+ export const preflightDiscordMessageMock: MockFn = vi.fn();
6
+ export const processDiscordMessageMock: MockFn = vi.fn();
7
+
8
+ const { createDiscordMessageHandler: createRealDiscordMessageHandler } =
9
+ await import("./message-handler.js");
10
+ type DiscordMessageHandlerParams = Parameters<typeof createRealDiscordMessageHandler>[0];
11
+ type DiscordMessageHandlerTestingHooks = NonNullable<DiscordMessageHandlerParams["testing"]>;
12
+ type PreflightDiscordMessageHook = NonNullable<
13
+ DiscordMessageHandlerTestingHooks["preflightDiscordMessage"]
14
+ >;
15
+ type ProcessDiscordMessageHook = NonNullable<
16
+ DiscordMessageRunQueueTestingHooks["processDiscordMessage"]
17
+ >;
18
+
19
+ export function createDiscordMessageHandler(
20
+ ...args: Parameters<typeof createRealDiscordMessageHandler>
21
+ ) {
22
+ const [params] = args;
23
+ return createRealDiscordMessageHandler({
24
+ ...params,
25
+ testing: {
26
+ ...params.testing,
27
+ preflightDiscordMessage: preflightDiscordMessageMock as PreflightDiscordMessageHook,
28
+ processDiscordMessage: processDiscordMessageMock as ProcessDiscordMessageHook,
29
+ },
30
+ });
31
+ }