mixdog 0.9.2 → 0.9.4

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 (417) hide show
  1. package/package.json +8 -3
  2. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/review-tasks.json +20 -0
  7. package/scripts/bench/round-codex.json +114 -0
  8. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  9. package/scripts/bench/round-mixdog-lead.json +269 -0
  10. package/scripts/bench/round-mixdog.json +126 -0
  11. package/scripts/bench/round-r10-bigsample.json +679 -0
  12. package/scripts/bench/round-r11-codexalign.json +257 -0
  13. package/scripts/bench/round-r4-codex.json +114 -0
  14. package/scripts/bench/round-r4-mixed.json +225 -0
  15. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  16. package/scripts/bench/round-r6-codex.json +114 -0
  17. package/scripts/bench/round-r6-solo.json +257 -0
  18. package/scripts/bench/round-r7-full.json +254 -0
  19. package/scripts/bench/round-r8-fulldefault.json +255 -0
  20. package/scripts/bench-run.mjs +215 -29
  21. package/scripts/build-tui.mjs +13 -1
  22. package/scripts/explore-bench.mjs +124 -0
  23. package/scripts/freevar-smoke.mjs +95 -0
  24. package/scripts/hook-bus-test.mjs +191 -0
  25. package/scripts/internal-comms-bench.mjs +1 -0
  26. package/scripts/internal-comms-smoke.mjs +10 -9
  27. package/scripts/mouse-probe.mjs +45 -0
  28. package/scripts/output-style-bench.mjs +13 -6
  29. package/scripts/output-style-smoke.mjs +4 -4
  30. package/scripts/path-suffix-test.mjs +57 -0
  31. package/scripts/provider-toolcall-test.mjs +7 -3
  32. package/scripts/recall-bench.mjs +207 -0
  33. package/scripts/recall-usecase-cases.json +18 -0
  34. package/scripts/recall-usecase-probe.json +6 -0
  35. package/scripts/session-bench.mjs +152 -6
  36. package/scripts/tool-smoke.mjs +30 -67
  37. package/scripts/tui-render-smoke.mjs +90 -0
  38. package/scripts/webhook-smoke.mjs +208 -0
  39. package/src/agents/debugger/AGENT.md +5 -2
  40. package/src/agents/heavy-worker/AGENT.md +21 -11
  41. package/src/agents/maintainer/AGENT.md +4 -0
  42. package/src/agents/reviewer/AGENT.md +3 -2
  43. package/src/agents/worker/AGENT.md +21 -11
  44. package/src/lib/rules-builder.cjs +4 -0
  45. package/src/mixdog-session-runtime.mjs +933 -3731
  46. package/src/output-styles/default.md +34 -9
  47. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  48. package/src/output-styles/minimal.md +4 -1
  49. package/src/output-styles/simple.md +22 -7
  50. package/src/repl.mjs +5 -5
  51. package/src/rules/agent/00-common.md +2 -0
  52. package/src/rules/agent/30-explorer.md +8 -11
  53. package/src/rules/lead/lead-brief.md +12 -0
  54. package/src/rules/lead/lead-tool.md +2 -9
  55. package/src/rules/shared/01-tool.md +11 -5
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  59. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  60. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  61. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  62. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  63. package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
  65. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
  66. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +97 -1343
  70. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  71. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
  72. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
  73. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  74. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  75. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  76. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  77. package/src/runtime/agent/orchestrator/providers/gemini.mjs +44 -1014
  78. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
  79. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  80. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  81. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  82. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  83. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  84. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
  85. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  86. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  88. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +41 -1142
  89. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  90. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  91. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +303 -2119
  92. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
  93. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  94. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  95. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  96. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  97. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  98. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
  99. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
  100. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  101. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  102. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  103. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  107. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  108. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  109. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
  110. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  111. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  112. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  113. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  114. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +47 -0
  115. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  116. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  117. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  118. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  119. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  120. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  121. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  122. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  123. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  124. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  125. package/src/runtime/agent/orchestrator/session/loop.mjs +409 -1304
  126. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  127. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
  128. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  129. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
  130. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  131. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  132. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  133. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
  136. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  137. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  138. package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
  139. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  140. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  141. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +33 -42
  142. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  143. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  144. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  145. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  146. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
  147. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  148. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  149. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -87
  152. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +78 -304
  155. package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
  156. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  157. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  158. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  159. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +551 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  161. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  163. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  164. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  165. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  166. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1080 -0
  167. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  168. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  169. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  170. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  171. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  172. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  173. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
  174. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  175. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  176. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  177. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  178. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  179. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  180. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  181. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  182. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  183. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  184. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -23
  185. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
  186. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  187. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  188. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  189. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  190. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  191. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  192. package/src/runtime/channels/backends/discord.mjs +12 -292
  193. package/src/runtime/channels/index.mjs +241 -894
  194. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  195. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  196. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  197. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  198. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  199. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  200. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  201. package/src/runtime/channels/lib/output-forwarder.mjs +9 -1
  202. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  203. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  204. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  205. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  206. package/src/runtime/channels/lib/telegram-format.mjs +19 -22
  207. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  208. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  209. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  210. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  211. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  212. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  213. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  214. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  215. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  216. package/src/runtime/channels/lib/webhook.mjs +36 -570
  217. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  218. package/src/runtime/channels/tool-defs.mjs +11 -130
  219. package/src/runtime/memory/index.mjs +258 -2050
  220. package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
  221. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  222. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  223. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  224. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  225. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  226. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  227. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  228. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  229. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  230. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  231. package/src/runtime/memory/lib/memory-cycle2.mjs +72 -837
  232. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  233. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  234. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  235. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  236. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  237. package/src/runtime/memory/lib/memory.mjs +20 -0
  238. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  239. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  240. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  241. package/src/runtime/memory/lib/recall-format.mjs +238 -0
  242. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  243. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  244. package/src/runtime/memory/tool-defs.mjs +6 -14
  245. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  246. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  247. package/src/runtime/search/lib/web-tools.mjs +24 -602
  248. package/src/runtime/shared/abort-controller.mjs +1 -1
  249. package/src/runtime/shared/atomic-file.mjs +26 -1
  250. package/src/runtime/shared/background-tasks.mjs +2 -3
  251. package/src/runtime/shared/buffered-appender.mjs +149 -0
  252. package/src/runtime/shared/launcher-control.mjs +2 -2
  253. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  254. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  255. package/src/runtime/shared/tool-primitives.mjs +308 -0
  256. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  257. package/src/runtime/shared/tool-surface.mjs +80 -898
  258. package/src/runtime/shared/transcript-writer.mjs +52 -2
  259. package/src/runtime/shared/update-checker.mjs +7 -4
  260. package/src/session-runtime/config-helpers.mjs +291 -0
  261. package/src/session-runtime/config-lifecycle.mjs +232 -0
  262. package/src/session-runtime/cwd-plugins.mjs +226 -0
  263. package/src/session-runtime/effort.mjs +128 -0
  264. package/src/session-runtime/fs-utils.mjs +10 -0
  265. package/src/session-runtime/mcp-glue.mjs +177 -0
  266. package/src/session-runtime/model-capabilities.mjs +130 -0
  267. package/src/session-runtime/model-recency.mjs +111 -0
  268. package/src/session-runtime/native-search.mjs +247 -0
  269. package/src/session-runtime/output-styles.mjs +126 -0
  270. package/src/session-runtime/plugin-mcp.mjs +114 -0
  271. package/src/session-runtime/prewarm.mjs +142 -0
  272. package/src/session-runtime/provider-models.mjs +278 -0
  273. package/src/session-runtime/provider-usage.mjs +120 -0
  274. package/src/session-runtime/quick-model-rows.mjs +170 -0
  275. package/src/session-runtime/quick-search-models.mjs +46 -0
  276. package/src/session-runtime/session-hooks.mjs +93 -0
  277. package/src/session-runtime/session-text.mjs +100 -0
  278. package/src/session-runtime/settings-api.mjs +319 -0
  279. package/src/session-runtime/statusline-route.mjs +35 -0
  280. package/src/session-runtime/tool-catalog.mjs +720 -0
  281. package/src/session-runtime/tool-defs.mjs +84 -0
  282. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  283. package/src/session-runtime/workflow.mjs +358 -0
  284. package/src/standalone/agent-tool/helpers.mjs +237 -0
  285. package/src/standalone/agent-tool/notify.mjs +107 -0
  286. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  287. package/src/standalone/agent-tool/render.mjs +152 -0
  288. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  289. package/src/standalone/agent-tool.mjs +155 -677
  290. package/src/standalone/channel-worker.mjs +7 -9
  291. package/src/standalone/explore-tool.mjs +40 -12
  292. package/src/standalone/hook-bus/config.mjs +207 -0
  293. package/src/standalone/hook-bus/constants.mjs +90 -0
  294. package/src/standalone/hook-bus/handlers.mjs +481 -0
  295. package/src/standalone/hook-bus/payload.mjs +31 -0
  296. package/src/standalone/hook-bus/rules.mjs +77 -0
  297. package/src/standalone/hook-bus.mjs +110 -746
  298. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  299. package/src/standalone/opencode-go-login.mjs +125 -0
  300. package/src/standalone/provider-admin.mjs +15 -19
  301. package/src/standalone/usage-dashboard.mjs +3 -1
  302. package/src/tui/App.jsx +1163 -7571
  303. package/src/tui/app/app-format.mjs +206 -0
  304. package/src/tui/app/channel-pickers.mjs +510 -0
  305. package/src/tui/app/clipboard.mjs +67 -0
  306. package/src/tui/app/core-memory-picker.mjs +210 -0
  307. package/src/tui/app/extension-pickers.mjs +506 -0
  308. package/src/tui/app/input-parsers.mjs +193 -0
  309. package/src/tui/app/maintenance-pickers.mjs +324 -0
  310. package/src/tui/app/model-options.mjs +330 -0
  311. package/src/tui/app/model-picker.mjs +365 -0
  312. package/src/tui/app/onboarding-steps.mjs +400 -0
  313. package/src/tui/app/project-picker.mjs +247 -0
  314. package/src/tui/app/provider-setup-picker.mjs +580 -0
  315. package/src/tui/app/resume-picker.mjs +55 -0
  316. package/src/tui/app/route-pickers.mjs +419 -0
  317. package/src/tui/app/settings-picker.mjs +490 -0
  318. package/src/tui/app/slash-commands.mjs +101 -0
  319. package/src/tui/app/slash-dispatch.mjs +427 -0
  320. package/src/tui/app/text-layout.mjs +46 -0
  321. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  322. package/src/tui/app/transcript-window.mjs +671 -0
  323. package/src/tui/app/use-mouse-input.mjs +460 -0
  324. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  325. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  326. package/src/tui/app/use-transcript-window.mjs +589 -0
  327. package/src/tui/components/ConfirmBar.jsx +1 -1
  328. package/src/tui/components/Picker.jsx +32 -4
  329. package/src/tui/components/PromptInput.jsx +259 -80
  330. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  331. package/src/tui/components/StatusLine.jsx +63 -12
  332. package/src/tui/components/TextEntryPanel.jsx +11 -0
  333. package/src/tui/components/ToolExecution.jsx +56 -588
  334. package/src/tui/components/TranscriptItem.jsx +105 -0
  335. package/src/tui/components/UsagePanel.jsx +18 -4
  336. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  337. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  338. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  339. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  340. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  341. package/src/tui/components/tool-output-format.mjs +2 -2
  342. package/src/tui/display-width.mjs +20 -3
  343. package/src/tui/dist/index.mjs +18034 -17188
  344. package/src/tui/engine/agent-envelope.mjs +296 -0
  345. package/src/tui/engine/agent-job-feed.mjs +133 -0
  346. package/src/tui/engine/boot-profile.mjs +21 -0
  347. package/src/tui/engine/labels.mjs +67 -0
  348. package/src/tui/engine/notice-text.mjs +112 -0
  349. package/src/tui/engine/notification-plan.mjs +76 -0
  350. package/src/tui/engine/queue-helpers.mjs +161 -0
  351. package/src/tui/engine/render-timing.mjs +17 -0
  352. package/src/tui/engine/session-stats.mjs +46 -0
  353. package/src/tui/engine/tool-approval.mjs +94 -0
  354. package/src/tui/engine/tool-call-fields.mjs +23 -0
  355. package/src/tui/engine/tool-card-results.mjs +234 -0
  356. package/src/tui/engine/tool-result-status.mjs +135 -0
  357. package/src/tui/engine/tool-result-text.mjs +126 -0
  358. package/src/tui/engine.mjs +405 -1385
  359. package/src/tui/figures.mjs +5 -0
  360. package/src/tui/index.jsx +105 -0
  361. package/src/tui/input-editing.mjs +60 -10
  362. package/src/tui/keyboard-protocol.mjs +2 -2
  363. package/src/tui/lib/voice-recorder.mjs +35 -19
  364. package/src/tui/markdown/format-token.mjs +11 -9
  365. package/src/tui/paste-attachments.mjs +38 -0
  366. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  367. package/src/tui/theme.mjs +6 -0
  368. package/src/tui/themes/base.mjs +2 -2
  369. package/src/tui/themes/kanagawa.mjs +4 -4
  370. package/src/tui/themes/teal.mjs +4 -5
  371. package/src/tui/themes/utils.mjs +1 -1
  372. package/src/ui/statusline-agents.mjs +213 -0
  373. package/src/ui/statusline-format.mjs +146 -0
  374. package/src/ui/statusline-segments.mjs +148 -0
  375. package/src/ui/statusline.mjs +77 -462
  376. package/src/ui/tool-card.mjs +0 -1
  377. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  378. package/src/workflows/default/WORKFLOW.md +16 -9
  379. package/src/workflows/sequential/WORKFLOW.md +16 -11
  380. package/src/workflows/solo/WORKFLOW.md +5 -1
  381. package/vendor/ink/build/display-width.js +19 -3
  382. package/vendor/ink/build/ink.js +103 -6
  383. package/vendor/ink/build/log-update.js +17 -3
  384. package/vendor/ink/build/wrap-text.js +125 -0
  385. package/scripts/_test-folder-dialog.mjs +0 -30
  386. package/scripts/fix-brief-fn.mjs +0 -35
  387. package/scripts/fix-format-tool-surface.mjs +0 -24
  388. package/scripts/fix-tool-exec-visible.mjs +0 -42
  389. package/scripts/patch-agent-brief.mjs +0 -48
  390. package/scripts/patch-app.mjs +0 -21
  391. package/scripts/patch-app2.mjs +0 -18
  392. package/scripts/patch-dist-brief.mjs +0 -96
  393. package/scripts/patch-tool-exec.mjs +0 -70
  394. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  395. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  396. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  397. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  398. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  399. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  400. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  401. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  402. package/src/standalone/agent-task-status.test.mjs +0 -76
  403. package/src/tui/components/tool-output-format.test.mjs +0 -399
  404. package/src/tui/display-width.test.mjs +0 -35
  405. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  406. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  407. package/src/tui/markdown/format-token.test.mjs +0 -354
  408. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  409. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  410. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  411. package/src/tui/prompt-history-store.test.mjs +0 -52
  412. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  413. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  414. package/src/ui/markdown.test.mjs +0 -70
  415. package/src/ui/statusline-context-label.test.mjs +0 -15
  416. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  417. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -0,0 +1,233 @@
1
+ // Gateway connect / login / event-listener wiring extracted from discord.mjs.
2
+ // Implemented as instance-method installers that operate on a DiscordBackend
3
+ // `self`. Behavior is byte-identical to the originals; only the host object is
4
+ // passed explicitly instead of via `this`.
5
+
6
+ async function connect(self) {
7
+ // Re-entry guard: if a connect() is already in-flight or completed, return
8
+ // the same promise / no-op so concurrent ownership-timer fires cannot
9
+ // overwrite self.client, duplicate listeners, or trigger duplicate logins.
10
+ if (self._connectPromise) return self._connectPromise;
11
+ self._connectPromise = connectInner(self).catch((err) => {
12
+ self._connectPromise = null;
13
+ throw err;
14
+ });
15
+ return self._connectPromise;
16
+ }
17
+
18
+ async function connectInner(self) {
19
+ await buildClient(self);
20
+ applyStaticAccessOverride(self);
21
+ registerEventListeners(self);
22
+ registerSlashCommands(self);
23
+ registerShardListeners(self);
24
+ try {
25
+ await awaitLogin(self);
26
+ } catch (err) {
27
+ // Destroy the partial Client to free the listeners/handles it already
28
+ // attached. Without this, a ready-timeout retry leaks every listener
29
+ // set by registerEventListeners/registerSlashCommands/registerShardListeners.
30
+ try { self.client?.destroy?.(); } catch {}
31
+ self.client = null;
32
+ throw err;
33
+ }
34
+ self.persistAccessFromChannelsConfig();
35
+ }
36
+
37
+ async function buildClient(self) {
38
+ const { Client, GatewayIntentBits, Partials } = await self._ensureDiscord();
39
+ self.client = new Client({
40
+ intents: [
41
+ GatewayIntentBits.DirectMessages,
42
+ GatewayIntentBits.Guilds,
43
+ GatewayIntentBits.GuildMessages,
44
+ GatewayIntentBits.MessageContent
45
+ ],
46
+ partials: [Partials.Channel]
47
+ });
48
+ }
49
+
50
+ function applyStaticAccessOverride(self) {
51
+ if (self.isStatic) {
52
+ self.bootAccess = self.loadAccess();
53
+ }
54
+ }
55
+
56
+ function registerEventListeners(self) {
57
+ self.client.on("error", (err) => {
58
+ process.stderr.write(`mixdog discord: client error: ${err}
59
+ `);
60
+ });
61
+ self.client.on("messageCreate", (msg) => {
62
+ if (msg.author.id === self.client.user?.id) {
63
+ return;
64
+ }
65
+ if (msg.author.bot) return;
66
+ self.handleInbound(msg, Date.now()).catch(
67
+ (e) => process.stderr.write(`mixdog discord: handleInbound failed: ${e}
68
+ `)
69
+ );
70
+ });
71
+ self.client.on("interactionCreate", async (interaction) => {
72
+ try {
73
+ // Trust gate for interactions. Buttons / selects / modal submits used to
74
+ // reach onInteraction / onModalRequest without passing through the
75
+ // message gate(), so a configured allowFrom never applied to them
76
+ // (schedule/quiet/profile modals + perm approvals were openable by any
77
+ // user in the channel). Apply the same allowFrom decision here; an empty
78
+ // allowFrom stays open so current configs are unaffected.
79
+ if (!self._interactionAllowed(interaction.channelId ?? "", interaction.user?.id, interaction)) {
80
+ try {
81
+ if (typeof interaction.reply === "function" && !interaction.replied && !interaction.deferred) {
82
+ await interaction.reply({ content: "⛔ Not authorized for this action.", ephemeral: true });
83
+ } else if (typeof interaction.deferUpdate === "function") {
84
+ await interaction.deferUpdate();
85
+ }
86
+ } catch {}
87
+ return;
88
+ }
89
+ if (interaction.isChatInputCommand() && interaction.commandName === "stop") {
90
+ await interaction.reply({ content: "\u23F9 Stopping...", ephemeral: true });
91
+ if (self.onInteraction) {
92
+ self.onInteraction({
93
+ type: "button",
94
+ customId: "stop_task",
95
+ userId: interaction.user.id,
96
+ channelId: interaction.channelId ?? ""
97
+ });
98
+ }
99
+ return;
100
+ }
101
+ if (interaction.isModalSubmit()) {
102
+ if (self.onInteraction) {
103
+ const fields = {};
104
+ for (const row of interaction.components) {
105
+ for (const comp of row.components ?? []) {
106
+ if (comp.customId && comp.value != null) fields[comp.customId] = String(comp.value);
107
+ }
108
+ }
109
+ self.onInteraction({
110
+ type: "modal",
111
+ customId: interaction.customId,
112
+ userId: interaction.user.id,
113
+ channelId: interaction.channelId ?? "",
114
+ fields,
115
+ message: interaction.message ? { id: interaction.message.id } : void 0
116
+ });
117
+ }
118
+ await interaction.deferUpdate().catch(() => {
119
+ });
120
+ return;
121
+ }
122
+ if (interaction.isButton() || interaction.isStringSelectMenu() || interaction.isRoleSelectMenu() || interaction.isUserSelectMenu() || interaction.isChannelSelectMenu()) {
123
+ const needsModal = interaction.isButton() && (interaction.customId === "sched_add_next" || interaction.customId === "sched_edit_next" || interaction.customId === "quiet_set_next" || interaction.customId === "activity_add_next" || interaction.customId === "profile_edit");
124
+ if (needsModal) {
125
+ if (self.onModalRequest) {
126
+ await Promise.resolve(self.onModalRequest(interaction)).catch((err) => {
127
+ process.stderr.write(`mixdog discord: onModalRequest failed: ${err}\n`);
128
+ });
129
+ }
130
+ return;
131
+ }
132
+ await interaction.deferUpdate().catch(() => {
133
+ });
134
+ if (self.onInteraction) {
135
+ self.onInteraction({
136
+ type: interaction.isButton() ? "button" : "select",
137
+ customId: interaction.customId,
138
+ userId: interaction.user.id,
139
+ channelId: interaction.channelId,
140
+ values: interaction.isStringSelectMenu() ? interaction.values : void 0,
141
+ message: interaction.message ? { id: interaction.message.id } : void 0
142
+ });
143
+ }
144
+ }
145
+ } catch (err) {
146
+ process.stderr.write(`mixdog discord: interaction error: ${err}
147
+ `);
148
+ }
149
+ });
150
+ }
151
+
152
+ function registerSlashCommands(self) {
153
+ self.client.on(self._discordEvents().ClientReady, async (c) => {
154
+ process.stderr.write(`mixdog discord: gateway connected as ${c.user.tag}
155
+ `);
156
+ try {
157
+ // Plugin registers no global commands; clear the global slot so any
158
+ // pre-existing entry from prior installs is not surfaced to users.
159
+ await c.application?.commands.set([]);
160
+ process.stderr.write(`mixdog discord: global application commands cleared
161
+ `);
162
+
163
+ // Replace each guild's command set with just /stop. set() overwrites,
164
+ // so the desired set is the only one that survives.
165
+ const desiredCommands = [
166
+ { name: "stop", description: "Stop the current Mixdog response" },
167
+ ];
168
+ for (const [guildId] of c.guilds.cache) {
169
+ await c.application?.commands.set(desiredCommands, guildId);
170
+ }
171
+ // Register /stop globally so it is available in DM bridge contexts
172
+ // where there is no guild scope.
173
+ try {
174
+ await c.application?.commands.set(desiredCommands);
175
+ } catch (e) {
176
+ process.stderr.write(`mixdog discord: global /stop register failed: ${e?.message}\n`);
177
+ }
178
+ process.stderr.write(`mixdog discord: /stop command registered (${c.guilds.cache.size} guild(s))
179
+ `);
180
+ } catch (err) {
181
+ process.stderr.write(`mixdog discord: slash command registration failed: ${err}
182
+ `);
183
+ }
184
+ });
185
+ }
186
+
187
+ function registerShardListeners(self) {
188
+ self.client.on("shardDisconnect", (ev, id) => {
189
+ process.stderr.write(`mixdog discord: shard ${id} disconnected (code ${ev.code}). Will auto-reconnect.
190
+ `);
191
+ });
192
+ self.client.on("shardReconnecting", (id) => {
193
+ process.stderr.write(`mixdog discord: shard ${id} reconnecting...
194
+ `);
195
+ });
196
+ self.client.on("shardResume", (id, replayedEvents) => {
197
+ process.stderr.write(`mixdog discord: shard ${id} resumed (replayed ${replayedEvents} events)
198
+ `);
199
+ });
200
+ self.client.on("warn", (msg) => {
201
+ process.stderr.write(`mixdog discord: warn: ${msg}
202
+ `);
203
+ });
204
+ }
205
+
206
+ async function awaitLogin(self) {
207
+ let readyTimeout;
208
+ const readyPromise = new Promise((resolve, reject) => {
209
+ readyTimeout = setTimeout(() => reject(new Error("discord ready timeout (30s)")), 3e4);
210
+ self.client.once(self._discordEvents().ClientReady, () => {
211
+ clearTimeout(readyTimeout);
212
+ resolve();
213
+ });
214
+ });
215
+ try {
216
+ await self.client.login(self.token);
217
+ } catch (err) {
218
+ clearTimeout(readyTimeout);
219
+ throw err;
220
+ }
221
+ await readyPromise;
222
+ }
223
+
224
+ export {
225
+ connect,
226
+ connectInner,
227
+ buildClient,
228
+ applyStaticAccessOverride,
229
+ registerEventListeners,
230
+ registerSlashCommands,
231
+ registerShardListeners,
232
+ awaitLogin
233
+ };
@@ -10,7 +10,6 @@ async function ensureDiscord() {
10
10
  return _discord;
11
11
  }
12
12
  import {
13
- writeFileSync,
14
13
  mkdirSync,
15
14
  statSync,
16
15
  realpathSync
@@ -20,37 +19,11 @@ import { createHash } from "crypto";
20
19
  import { chunk, formatForDiscord, MAX_DISCORD_MESSAGE } from "../lib/format.mjs";
21
20
  import { withConfigLock } from "../lib/config-lock.mjs";
22
21
  import { readSection, updateSection } from "../../shared/config.mjs";
22
+ import { normalizeAccess, safeAttName } from "./discord-access.mjs";
23
+ import { MAX_ATTACHMENT_BYTES, downloadSingleAttachment } from "./discord-attachments.mjs";
24
+ import * as gateway from "./discord-gateway.mjs";
23
25
  const MAX_CHUNK_LIMIT = MAX_DISCORD_MESSAGE;
24
- const MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
25
26
  const RECENT_SENT_CAP = 200;
26
- function defaultAccess() {
27
- return {
28
- dmPolicy: "allowlist",
29
- allowFrom: [],
30
- channels: {}
31
- };
32
- }
33
- function normalizeAccess(parsed) {
34
- const defaults = defaultAccess();
35
- return {
36
- // Legacy "pairing" policy was removed (its approval flow was never
37
- // completable); normalize it to "allowlist" on load.
38
- dmPolicy: parsed?.dmPolicy === "pairing" ? "allowlist" : (parsed?.dmPolicy ?? defaults.dmPolicy),
39
- allowFrom: parsed?.allowFrom ?? defaults.allowFrom,
40
- channels: parsed?.channels ?? defaults.channels,
41
- mentionPatterns: parsed?.mentionPatterns,
42
- // Setup UI historically saved a boolean toggle; runtime needs an emoji
43
- // string for msg.react(). true → default emoji, non-string → off.
44
- ackReaction: parsed?.ackReaction === true
45
- ? "✅"
46
- : (typeof parsed?.ackReaction === "string" && parsed.ackReaction ? parsed.ackReaction : undefined),
47
- replyToMode: parsed?.replyToMode,
48
- textChunkLimit: parsed?.textChunkLimit,
49
- };
50
- }
51
- function safeAttName(att) {
52
- return (att.name ?? att.id).replace(/[\[\]\r\n;]/g, "_");
53
- }
54
27
  class DiscordBackend {
55
28
  name = "discord";
56
29
  MAX_MESSAGE_LENGTH = MAX_DISCORD_MESSAGE;
@@ -82,216 +55,16 @@ class DiscordBackend {
82
55
  formatOutgoing(text) {
83
56
  return formatForDiscord(text);
84
57
  }
85
- // ── Lifecycle ──────────────────────────────────────────────────────
86
- async connect() {
87
- // Re-entry guard: if a connect() is already in-flight or completed, return
88
- // the same promise / no-op so concurrent ownership-timer fires cannot
89
- // overwrite this.client, duplicate listeners, or trigger duplicate logins.
90
- if (this._connectPromise) return this._connectPromise;
91
- this._connectPromise = this._connectInner().catch((err) => {
92
- this._connectPromise = null;
93
- throw err;
94
- });
95
- return this._connectPromise;
96
- }
97
- async _connectInner() {
98
- await this._buildClient();
99
- this._applyStaticAccessOverride();
100
- this._registerEventListeners();
101
- this._registerSlashCommands();
102
- this._registerShardListeners();
103
- try {
104
- await this._awaitLogin();
105
- } catch (err) {
106
- // Destroy the partial Client to free the listeners/handles it already
107
- // attached. Without this, a ready-timeout retry leaks every listener
108
- // set by _registerEventListeners/_registerSlashCommands/_registerShardListeners.
109
- try { this.client?.destroy?.(); } catch {}
110
- this.client = null;
111
- throw err;
112
- }
113
- this.persistAccessFromChannelsConfig();
114
- }
115
- async _buildClient() {
116
- const { Client, GatewayIntentBits, Partials } = await ensureDiscord();
117
- this.client = new Client({
118
- intents: [
119
- GatewayIntentBits.DirectMessages,
120
- GatewayIntentBits.Guilds,
121
- GatewayIntentBits.GuildMessages,
122
- GatewayIntentBits.MessageContent
123
- ],
124
- partials: [Partials.Channel]
125
- });
58
+ // ── discord.js access bridges for extracted gateway module ─────────
59
+ _ensureDiscord() {
60
+ return ensureDiscord();
126
61
  }
127
- _applyStaticAccessOverride() {
128
- if (this.isStatic) {
129
- this.bootAccess = this.loadAccess();
130
- }
131
- }
132
- _registerEventListeners() {
133
- this.client.on("error", (err) => {
134
- process.stderr.write(`mixdog discord: client error: ${err}
135
- `);
136
- });
137
- this.client.on("messageCreate", (msg) => {
138
- if (msg.author.id === this.client.user?.id) {
139
- return;
140
- }
141
- if (msg.author.bot) return;
142
- this.handleInbound(msg, Date.now()).catch(
143
- (e) => process.stderr.write(`mixdog discord: handleInbound failed: ${e}
144
- `)
145
- );
146
- });
147
- this.client.on("interactionCreate", async (interaction) => {
148
- try {
149
- // Trust gate for interactions. Buttons / selects / modal submits used to
150
- // reach onInteraction / onModalRequest without passing through the
151
- // message gate(), so a configured allowFrom never applied to them
152
- // (schedule/quiet/profile modals + perm approvals were openable by any
153
- // user in the channel). Apply the same allowFrom decision here; an empty
154
- // allowFrom stays open so current configs are unaffected.
155
- if (!this._interactionAllowed(interaction.channelId ?? "", interaction.user?.id, interaction)) {
156
- try {
157
- if (typeof interaction.reply === "function" && !interaction.replied && !interaction.deferred) {
158
- await interaction.reply({ content: "⛔ Not authorized for this action.", ephemeral: true });
159
- } else if (typeof interaction.deferUpdate === "function") {
160
- await interaction.deferUpdate();
161
- }
162
- } catch {}
163
- return;
164
- }
165
- if (interaction.isChatInputCommand() && interaction.commandName === "stop") {
166
- await interaction.reply({ content: "\u23F9 Stopping...", ephemeral: true });
167
- if (this.onInteraction) {
168
- this.onInteraction({
169
- type: "button",
170
- customId: "stop_task",
171
- userId: interaction.user.id,
172
- channelId: interaction.channelId ?? ""
173
- });
174
- }
175
- return;
176
- }
177
- if (interaction.isModalSubmit()) {
178
- if (this.onInteraction) {
179
- const fields = {};
180
- for (const row of interaction.components) {
181
- for (const comp of row.components ?? []) {
182
- if (comp.customId && comp.value != null) fields[comp.customId] = String(comp.value);
183
- }
184
- }
185
- this.onInteraction({
186
- type: "modal",
187
- customId: interaction.customId,
188
- userId: interaction.user.id,
189
- channelId: interaction.channelId ?? "",
190
- fields,
191
- message: interaction.message ? { id: interaction.message.id } : void 0
192
- });
193
- }
194
- await interaction.deferUpdate().catch(() => {
195
- });
196
- return;
197
- }
198
- if (interaction.isButton() || interaction.isStringSelectMenu() || interaction.isRoleSelectMenu() || interaction.isUserSelectMenu() || interaction.isChannelSelectMenu()) {
199
- const needsModal = interaction.isButton() && (interaction.customId === "sched_add_next" || interaction.customId === "sched_edit_next" || interaction.customId === "quiet_set_next" || interaction.customId === "activity_add_next" || interaction.customId === "profile_edit");
200
- if (needsModal) {
201
- if (this.onModalRequest) {
202
- await Promise.resolve(this.onModalRequest(interaction)).catch((err) => {
203
- process.stderr.write(`mixdog discord: onModalRequest failed: ${err}\n`);
204
- });
205
- }
206
- return;
207
- }
208
- await interaction.deferUpdate().catch(() => {
209
- });
210
- if (this.onInteraction) {
211
- this.onInteraction({
212
- type: interaction.isButton() ? "button" : "select",
213
- customId: interaction.customId,
214
- userId: interaction.user.id,
215
- channelId: interaction.channelId,
216
- values: interaction.isStringSelectMenu() ? interaction.values : void 0,
217
- message: interaction.message ? { id: interaction.message.id } : void 0
218
- });
219
- }
220
- }
221
- } catch (err) {
222
- process.stderr.write(`mixdog discord: interaction error: ${err}
223
- `);
224
- }
225
- });
62
+ _discordEvents() {
63
+ return _discord.Events;
226
64
  }
227
- _registerSlashCommands() {
228
- this.client.on(_discord.Events.ClientReady, async (c) => {
229
- process.stderr.write(`mixdog discord: gateway connected as ${c.user.tag}
230
- `);
231
- try {
232
- // Plugin registers no global commands; clear the global slot so any
233
- // pre-existing entry from prior installs is not surfaced to users.
234
- await c.application?.commands.set([]);
235
- process.stderr.write(`mixdog discord: global application commands cleared
236
- `);
237
-
238
- // Replace each guild's command set with just /stop. set() overwrites,
239
- // so the desired set is the only one that survives.
240
- const desiredCommands = [
241
- { name: "stop", description: "Stop the current Mixdog response" },
242
- ];
243
- for (const [guildId] of c.guilds.cache) {
244
- await c.application?.commands.set(desiredCommands, guildId);
245
- }
246
- // Register /stop globally so it is available in DM bridge contexts
247
- // where there is no guild scope.
248
- try {
249
- await c.application?.commands.set(desiredCommands);
250
- } catch (e) {
251
- process.stderr.write(`mixdog discord: global /stop register failed: ${e?.message}\n`);
252
- }
253
- process.stderr.write(`mixdog discord: /stop command registered (${c.guilds.cache.size} guild(s))
254
- `);
255
- } catch (err) {
256
- process.stderr.write(`mixdog discord: slash command registration failed: ${err}
257
- `);
258
- }
259
- });
260
- }
261
- _registerShardListeners() {
262
- this.client.on("shardDisconnect", (ev, id) => {
263
- process.stderr.write(`mixdog discord: shard ${id} disconnected (code ${ev.code}). Will auto-reconnect.
264
- `);
265
- });
266
- this.client.on("shardReconnecting", (id) => {
267
- process.stderr.write(`mixdog discord: shard ${id} reconnecting...
268
- `);
269
- });
270
- this.client.on("shardResume", (id, replayedEvents) => {
271
- process.stderr.write(`mixdog discord: shard ${id} resumed (replayed ${replayedEvents} events)
272
- `);
273
- });
274
- this.client.on("warn", (msg) => {
275
- process.stderr.write(`mixdog discord: warn: ${msg}
276
- `);
277
- });
278
- }
279
- async _awaitLogin() {
280
- let readyTimeout;
281
- const readyPromise = new Promise((resolve, reject) => {
282
- readyTimeout = setTimeout(() => reject(new Error("discord ready timeout (30s)")), 3e4);
283
- this.client.once(_discord.Events.ClientReady, () => {
284
- clearTimeout(readyTimeout);
285
- resolve();
286
- });
287
- });
288
- try {
289
- await this.client.login(this.token);
290
- } catch (err) {
291
- clearTimeout(readyTimeout);
292
- throw err;
293
- }
294
- await readyPromise;
65
+ // ── Lifecycle ──────────────────────────────────────────────────────
66
+ async connect() {
67
+ return gateway.connect(this);
295
68
  }
296
69
  async disconnect() {
297
70
  for (const interval of this.typingIntervals.values()) {
@@ -810,60 +583,7 @@ class DiscordBackend {
810
583
  }
811
584
  }
812
585
  async downloadSingleAttachment(att) {
813
- if (att.size > MAX_ATTACHMENT_BYTES) {
814
- throw new Error(
815
- `attachment too large: ${(att.size / 1024 / 1024).toFixed(1)}MB, max ${MAX_ATTACHMENT_BYTES / 1024 / 1024}MB`
816
- );
817
- }
818
- const res = await fetch(att.url, { signal: AbortSignal.timeout(180_000) });
819
- if (!res.ok) {
820
- throw new Error(`attachment download failed: HTTP ${res.status}`);
821
- }
822
- if (!res.body) {
823
- throw new Error(`attachment download returned empty body: ${att.name ?? att.id}`);
824
- }
825
- // Stream the response so an oversized payload is aborted before the
826
- // full body lands in memory. Buffering via arrayBuffer() first would
827
- // already exceed MAX_ATTACHMENT_BYTES by the time we checked length.
828
- const reader = res.body.getReader();
829
- const chunks = [];
830
- let received = 0;
831
- try {
832
- while (true) {
833
- const { done, value } = await reader.read();
834
- if (done) break;
835
- if (!value) continue;
836
- received += value.byteLength;
837
- if (received > MAX_ATTACHMENT_BYTES) {
838
- try { await reader.cancel(); } catch {}
839
- throw new Error(
840
- `attachment payload too large: exceeded ${MAX_ATTACHMENT_BYTES / 1024 / 1024}MB while streaming (${att.name ?? att.id})`
841
- );
842
- }
843
- chunks.push(value);
844
- }
845
- } finally {
846
- try { reader.releaseLock(); } catch {}
847
- }
848
- if (received === 0) {
849
- throw new Error(`attachment download returned empty buffer: ${att.name ?? att.id}`);
850
- }
851
- const buf = Buffer.concat(chunks.map((c) => Buffer.from(c.buffer, c.byteOffset, c.byteLength)), received);
852
- if (att.size > 0 && buf.length !== att.size) {
853
- process.stderr.write(`mixdog discord: attachment size mismatch: expected ${att.size} got ${buf.length} (${att.name ?? att.id})\n`);
854
- }
855
- const name = att.name ?? `${att.id}`;
856
- const rawExt = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1) : "bin";
857
- const ext = rawExt.replace(/[^a-zA-Z0-9]/g, "") || "bin";
858
- const safeId = String(att.id).replace(/[^a-zA-Z0-9_-]/g, "_");
859
- mkdirSync(this.inboxDir, { recursive: true });
860
- const candidate = join(this.inboxDir, `${Date.now()}-${safeId}.${ext}`);
861
- const resolvedInbox = realpathSync(this.inboxDir);
862
- if (!candidate.startsWith(resolvedInbox + sep)) {
863
- throw new Error(`attachment path traversal rejected: ${candidate}`);
864
- }
865
- writeFileSync(candidate, buf);
866
- return candidate;
586
+ return downloadSingleAttachment(att, this.inboxDir);
867
587
  }
868
588
  }
869
589
  export {