mixdog 0.9.3 → 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 (329) hide show
  1. package/package.json +7 -3
  2. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  3. package/scripts/bench/lead-review-tasks.json +20 -0
  4. package/scripts/bench/r4-mixed-tasks.json +20 -0
  5. package/scripts/bench/review-tasks.json +20 -0
  6. package/scripts/bench/round-codex.json +114 -0
  7. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  8. package/scripts/bench/round-mixdog-lead.json +269 -0
  9. package/scripts/bench/round-mixdog.json +126 -0
  10. package/scripts/bench/round-r10-bigsample.json +679 -0
  11. package/scripts/bench/round-r11-codexalign.json +257 -0
  12. package/scripts/bench/round-r4-codex.json +114 -0
  13. package/scripts/bench/round-r4-mixed.json +225 -0
  14. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  15. package/scripts/bench/round-r6-codex.json +114 -0
  16. package/scripts/bench/round-r6-solo.json +257 -0
  17. package/scripts/bench/round-r7-full.json +254 -0
  18. package/scripts/bench/round-r8-fulldefault.json +255 -0
  19. package/scripts/bench-run.mjs +215 -29
  20. package/scripts/freevar-smoke.mjs +95 -0
  21. package/scripts/internal-comms-bench.mjs +1 -0
  22. package/scripts/internal-comms-smoke.mjs +10 -9
  23. package/scripts/mouse-probe.mjs +45 -0
  24. package/scripts/output-style-bench.mjs +13 -6
  25. package/scripts/output-style-smoke.mjs +4 -4
  26. package/scripts/provider-toolcall-test.mjs +7 -3
  27. package/scripts/recall-usecase-cases.json +18 -0
  28. package/scripts/recall-usecase-probe.json +6 -0
  29. package/scripts/session-bench.mjs +152 -6
  30. package/scripts/tool-smoke.mjs +23 -63
  31. package/scripts/tui-render-smoke.mjs +90 -0
  32. package/scripts/webhook-smoke.mjs +208 -0
  33. package/src/agents/debugger/AGENT.md +4 -1
  34. package/src/agents/heavy-worker/AGENT.md +6 -5
  35. package/src/agents/maintainer/AGENT.md +4 -0
  36. package/src/agents/reviewer/AGENT.md +2 -1
  37. package/src/agents/worker/AGENT.md +8 -4
  38. package/src/lib/rules-builder.cjs +4 -0
  39. package/src/mixdog-session-runtime.mjs +632 -2042
  40. package/src/output-styles/default.md +34 -9
  41. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  42. package/src/output-styles/minimal.md +4 -1
  43. package/src/output-styles/simple.md +22 -7
  44. package/src/rules/agent/00-common.md +2 -0
  45. package/src/rules/lead/lead-brief.md +12 -0
  46. package/src/rules/lead/lead-tool.md +0 -11
  47. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  48. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  49. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  50. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  51. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  52. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  53. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  54. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  55. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  58. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  60. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  61. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  62. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  63. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  64. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  65. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  66. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  67. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  68. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
  76. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
  77. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  78. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  79. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  80. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  82. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  83. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  84. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  85. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  87. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  88. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  89. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  90. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  91. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  93. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  94. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  95. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  97. package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
  98. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  99. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  100. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  101. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  102. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  103. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  104. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  105. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  106. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  107. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  108. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  109. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
  110. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  115. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  118. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  119. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  120. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  122. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  123. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  124. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  125. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  126. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  127. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  128. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  129. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  130. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  131. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  132. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  133. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  134. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  135. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  136. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  137. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  138. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  139. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  140. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  141. package/src/runtime/channels/backends/discord.mjs +12 -292
  142. package/src/runtime/channels/index.mjs +229 -663
  143. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  144. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  145. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  146. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  147. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  148. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  149. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  150. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  151. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  152. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  153. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  154. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  155. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  156. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  157. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  158. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  159. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  160. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  161. package/src/runtime/channels/lib/webhook.mjs +36 -570
  162. package/src/runtime/channels/tool-defs.mjs +11 -130
  163. package/src/runtime/memory/index.mjs +201 -1948
  164. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  165. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  166. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  167. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  168. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  169. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  170. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  171. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  172. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  173. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  174. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  175. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  177. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  178. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  179. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  180. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  181. package/src/runtime/memory/tool-defs.mjs +5 -13
  182. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  183. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  184. package/src/runtime/search/lib/web-tools.mjs +24 -602
  185. package/src/runtime/shared/atomic-file.mjs +26 -1
  186. package/src/runtime/shared/launcher-control.mjs +2 -2
  187. package/src/runtime/shared/tool-primitives.mjs +308 -0
  188. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  189. package/src/runtime/shared/tool-surface.mjs +80 -898
  190. package/src/runtime/shared/transcript-writer.mjs +23 -0
  191. package/src/runtime/shared/update-checker.mjs +7 -4
  192. package/src/session-runtime/config-helpers.mjs +84 -2
  193. package/src/session-runtime/config-lifecycle.mjs +232 -0
  194. package/src/session-runtime/cwd-plugins.mjs +226 -0
  195. package/src/session-runtime/mcp-glue.mjs +177 -0
  196. package/src/session-runtime/model-recency.mjs +111 -0
  197. package/src/session-runtime/native-search.mjs +247 -0
  198. package/src/session-runtime/output-styles.mjs +11 -9
  199. package/src/session-runtime/prewarm.mjs +142 -0
  200. package/src/session-runtime/provider-models.mjs +278 -0
  201. package/src/session-runtime/provider-usage.mjs +120 -0
  202. package/src/session-runtime/quick-model-rows.mjs +170 -0
  203. package/src/session-runtime/quick-search-models.mjs +46 -0
  204. package/src/session-runtime/session-hooks.mjs +93 -0
  205. package/src/session-runtime/settings-api.mjs +319 -0
  206. package/src/session-runtime/tool-catalog.mjs +29 -29
  207. package/src/session-runtime/tool-defs.mjs +84 -0
  208. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  209. package/src/standalone/agent-tool/helpers.mjs +237 -0
  210. package/src/standalone/agent-tool/notify.mjs +107 -0
  211. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  212. package/src/standalone/agent-tool/render.mjs +152 -0
  213. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  214. package/src/standalone/agent-tool.mjs +110 -671
  215. package/src/standalone/channel-worker.mjs +4 -7
  216. package/src/standalone/explore-tool.mjs +30 -9
  217. package/src/standalone/hook-bus/config.mjs +207 -0
  218. package/src/standalone/hook-bus/constants.mjs +90 -0
  219. package/src/standalone/hook-bus/handlers.mjs +481 -0
  220. package/src/standalone/hook-bus/payload.mjs +31 -0
  221. package/src/standalone/hook-bus/rules.mjs +77 -0
  222. package/src/standalone/hook-bus.mjs +77 -870
  223. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  224. package/src/standalone/opencode-go-login.mjs +5 -1
  225. package/src/standalone/provider-admin.mjs +1 -16
  226. package/src/standalone/usage-dashboard.mjs +3 -1
  227. package/src/tui/App.jsx +945 -8094
  228. package/src/tui/app/app-format.mjs +206 -0
  229. package/src/tui/app/channel-pickers.mjs +510 -0
  230. package/src/tui/app/clipboard.mjs +67 -0
  231. package/src/tui/app/core-memory-picker.mjs +210 -0
  232. package/src/tui/app/extension-pickers.mjs +506 -0
  233. package/src/tui/app/input-parsers.mjs +193 -0
  234. package/src/tui/app/maintenance-pickers.mjs +324 -0
  235. package/src/tui/app/model-options.mjs +330 -0
  236. package/src/tui/app/model-picker.mjs +365 -0
  237. package/src/tui/app/onboarding-steps.mjs +400 -0
  238. package/src/tui/app/project-picker.mjs +247 -0
  239. package/src/tui/app/provider-setup-picker.mjs +580 -0
  240. package/src/tui/app/resume-picker.mjs +55 -0
  241. package/src/tui/app/route-pickers.mjs +419 -0
  242. package/src/tui/app/settings-picker.mjs +490 -0
  243. package/src/tui/app/slash-commands.mjs +101 -0
  244. package/src/tui/app/slash-dispatch.mjs +427 -0
  245. package/src/tui/app/text-layout.mjs +46 -0
  246. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  247. package/src/tui/app/transcript-window.mjs +671 -0
  248. package/src/tui/app/use-mouse-input.mjs +460 -0
  249. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  250. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  251. package/src/tui/app/use-transcript-window.mjs +589 -0
  252. package/src/tui/components/ConfirmBar.jsx +1 -1
  253. package/src/tui/components/Picker.jsx +32 -4
  254. package/src/tui/components/PromptInput.jsx +23 -101
  255. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  256. package/src/tui/components/StatusLine.jsx +63 -12
  257. package/src/tui/components/TextEntryPanel.jsx +11 -0
  258. package/src/tui/components/ToolExecution.jsx +52 -594
  259. package/src/tui/components/TranscriptItem.jsx +105 -0
  260. package/src/tui/components/UsagePanel.jsx +18 -4
  261. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  262. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  263. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  264. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  265. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  266. package/src/tui/display-width.mjs +20 -3
  267. package/src/tui/dist/index.mjs +19652 -18630
  268. package/src/tui/engine/agent-job-feed.mjs +133 -0
  269. package/src/tui/engine/notification-plan.mjs +76 -0
  270. package/src/tui/engine/render-timing.mjs +17 -0
  271. package/src/tui/engine/tool-approval.mjs +94 -0
  272. package/src/tui/engine/tool-card-results.mjs +234 -0
  273. package/src/tui/engine/tool-result-status.mjs +135 -0
  274. package/src/tui/engine.mjs +122 -562
  275. package/src/tui/figures.mjs +5 -0
  276. package/src/tui/index.jsx +105 -0
  277. package/src/tui/input-editing.mjs +2 -2
  278. package/src/tui/markdown/format-token.mjs +4 -1
  279. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  280. package/src/tui/theme.mjs +6 -0
  281. package/src/ui/statusline-agents.mjs +213 -0
  282. package/src/ui/statusline-format.mjs +146 -0
  283. package/src/ui/statusline-segments.mjs +148 -0
  284. package/src/ui/statusline.mjs +67 -501
  285. package/src/ui/tool-card.mjs +0 -1
  286. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  287. package/src/workflows/default/WORKFLOW.md +1 -1
  288. package/src/workflows/sequential/WORKFLOW.md +1 -1
  289. package/vendor/ink/build/display-width.js +19 -3
  290. package/vendor/ink/build/ink.js +103 -6
  291. package/vendor/ink/build/log-update.js +17 -3
  292. package/vendor/ink/build/wrap-text.js +125 -0
  293. package/scripts/_test-folder-dialog.mjs +0 -30
  294. package/scripts/fix-brief-fn.mjs +0 -35
  295. package/scripts/fix-format-tool-surface.mjs +0 -24
  296. package/scripts/fix-tool-exec-visible.mjs +0 -42
  297. package/scripts/patch-agent-brief.mjs +0 -48
  298. package/scripts/patch-app.mjs +0 -21
  299. package/scripts/patch-app2.mjs +0 -18
  300. package/scripts/patch-dist-brief.mjs +0 -96
  301. package/scripts/patch-tool-exec.mjs +0 -70
  302. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  303. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  304. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  305. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  306. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  307. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  308. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  309. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  310. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  311. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  312. package/src/standalone/agent-task-status.test.mjs +0 -76
  313. package/src/tui/components/tool-output-format.test.mjs +0 -399
  314. package/src/tui/display-width.test.mjs +0 -35
  315. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  316. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  317. package/src/tui/input-editing.selection.test.mjs +0 -75
  318. package/src/tui/markdown/format-token.test.mjs +0 -354
  319. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  320. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  321. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  322. package/src/tui/paste-fix.test.mjs +0 -119
  323. package/src/tui/prompt-history-store.test.mjs +0 -52
  324. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  325. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  326. package/src/ui/markdown.test.mjs +0 -70
  327. package/src/ui/statusline-context-label.test.mjs +0 -15
  328. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  329. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -0,0 +1,158 @@
1
+ import { OutputForwarder } from "./output-forwarder.mjs";
2
+
3
+ // Worker/HTTP tool-call dispatch + bridge auto-connect retry wrapper.
4
+ // Extracted verbatim from channels/index.mjs (behavior-preserving). The
5
+ // `handleToolCall` switch is entangled with ~8 runtime-lifecycle functions
6
+ // (claim/refresh ownership, stop owned runtime, transcript rebind, config
7
+ // reload) plus mutable owner state (channelBridgeActive, bridgeRuntimeConnected)
8
+ // and the forwarder. Those are threaded in as a `lifecycle` bag of lazy
9
+ // getters/functions so the module reads the live file-level references at
10
+ // call time — matching the original in-file closure semantics.
11
+ function createToolDispatch({
12
+ getForwarder,
13
+ BACKEND_TOOLS,
14
+ isChannelsDegraded,
15
+ dispatchReply,
16
+ dispatchFetch,
17
+ lifecycle,
18
+ }) {
19
+ const {
20
+ getBridgeRuntimeConnected,
21
+ getChannelBridgeActive,
22
+ setChannelBridgeActive,
23
+ writeBridgeState,
24
+ stopServerTyping,
25
+ claimBridgeOwnership,
26
+ refreshBridgeOwnership,
27
+ bindPersistedTranscriptIfAny,
28
+ stopOwnedRuntime,
29
+ reloadRuntimeConfig,
30
+ } = lifecycle;
31
+
32
+ async function handleToolCall(name, args, _signal) {
33
+ if (isChannelsDegraded()) {
34
+ return { content: [{ type: 'text', text: `[channels degraded] ${name} unavailable — restart MCP to recover` }], isError: true }
35
+ }
36
+ let result;
37
+ try {
38
+ switch (name) {
39
+ case "reply":
40
+ result = await dispatchReply(args);
41
+ break;
42
+ case "fetch":
43
+ result = await dispatchFetch(args);
44
+ break;
45
+ case "activate_channel_bridge": {
46
+ const active = args.active === true;
47
+ const wasActive = getChannelBridgeActive();
48
+ setChannelBridgeActive(active);
49
+ writeBridgeState(active);
50
+ if (active) {
51
+ // Force-takeover semantics: activation ALWAYS claims the seat
52
+ // (last-wins overwrite of active-instance.json) and refreshes the
53
+ // output-forwarder binding onto THIS session — even when the
54
+ // bridge was already active. `/remote` relies on this to re-occupy
55
+ // after another session stole ownership, and to re-pin forwarding
56
+ // to the current session transcript.
57
+ claimBridgeOwnership(wasActive ? "re-activate takeover" : "bridge activated");
58
+ try {
59
+ await refreshBridgeOwnership({ restoreBinding: true });
60
+ // An already-connected owner returns early from
61
+ // startOwnedRuntime(), so rebind explicitly to follow the
62
+ // current (parent-chain) session transcript.
63
+ if (getBridgeRuntimeConnected()) await bindPersistedTranscriptIfAny();
64
+ } catch (e) {
65
+ process.stderr.write(`mixdog: bridge activate refresh failed (non-fatal): ${e?.message || e}\n`);
66
+ }
67
+ }
68
+ if (!active && wasActive) {
69
+ stopServerTyping();
70
+ // Tear down the owner-side runtime so Discord/scheduler/webhook/
71
+ // event-pipeline don't keep running on a deactivated bridge.
72
+ try { await stopOwnedRuntime("bridge deactivated"); } catch (e) {
73
+ process.stderr.write(`mixdog: stopOwnedRuntime on deactivate failed: ${e?.message || e}\n`);
74
+ }
75
+ }
76
+ result = { content: [{ type: "text", text: `channel bridge ${active ? "activated" : "deactivated"}` }] };
77
+ break;
78
+ }
79
+ case "reload_config": {
80
+ await reloadRuntimeConfig();
81
+ // Extend reload to the agent module so providers/presets/maintenance
82
+ // hot-reload on the same call (dynamic import: agent/index.mjs does not
83
+ // import channels, so this stays acyclic and tolerant of load order).
84
+ let agentReloadMsg = "";
85
+ if (process.env.MIXDOG_STANDALONE !== '1') {
86
+ try {
87
+ const { reloadAgentConfig } = await import("../../agent/index.mjs");
88
+ await reloadAgentConfig("reload_config tool");
89
+ agentReloadMsg = ", agent providers/presets/maintenance";
90
+ } catch (err) {
91
+ process.stderr.write(`[reload_config] agent reload failed: ${err?.message || String(err)}\n`);
92
+ }
93
+ }
94
+ result = { content: [{ type: "text", text: `config reloaded — schedules, webhooks, events${agentReloadMsg} re-registered` }] };
95
+ break;
96
+ }
97
+ // memory — handled by memory-service.mjs MCP
98
+ default:
99
+ result = {
100
+ content: [{ type: "text", text: `unknown tool: ${name}` }],
101
+ isError: true
102
+ };
103
+ }
104
+ } catch (err) {
105
+ const msg = err instanceof Error ? err.message : String(err);
106
+ result = {
107
+ content: [{ type: "text", text: `${name} failed: ${msg}` }],
108
+ isError: true
109
+ };
110
+ }
111
+ return result;
112
+ }
113
+
114
+ // Bridge auto-connect retry + forwarder-aware tool dispatch wrapper. Used by
115
+ // both the HTTP MCP path (createHttpMcpServer's CallTool handler can call this)
116
+ // and the worker IPC handler at the bottom of index.mjs.
117
+ // Last timestamp a forwardNewText() call was dispatched (debounce for item 4).
118
+ let _lastForwardMs = 0;
119
+
120
+ async function handleToolCallWithBridgeRetry(toolName, args, signal) {
121
+ const forwarder = getForwarder();
122
+ // Debounce: only forward when ≥250 ms have elapsed since the last forward,
123
+ // to avoid one HTTP roundtrip per tool call on rapid-fire sequences.
124
+ const now = Date.now();
125
+ if (now - _lastForwardMs >= 250) {
126
+ _lastForwardMs = now;
127
+ await forwarder.forwardNewText();
128
+ }
129
+ if (BACKEND_TOOLS.has(toolName) && !getBridgeRuntimeConnected()) {
130
+ // Remote-owner startup: ensure this owner's backend is connected.
131
+ for (let i = 0; i < 2 && !getBridgeRuntimeConnected(); i++) {
132
+ try {
133
+ await refreshBridgeOwnership();
134
+ } catch {
135
+ }
136
+ if (!getBridgeRuntimeConnected()) await new Promise((r) => setTimeout(r, 300));
137
+ }
138
+ if (!getBridgeRuntimeConnected()) {
139
+ return {
140
+ content: [{ type: "text", text: `Discord auto-connect failed after retries. Check token and network.` }],
141
+ isError: true
142
+ };
143
+ }
144
+ }
145
+ const result = await handleToolCall(toolName, args, signal);
146
+ const toolLine = OutputForwarder.buildToolLine(toolName, args);
147
+ if (toolLine) {
148
+ // Distinct from the dispatch-log ok line (server-main.mjs): this forwards
149
+ // a human-readable tool summary to Discord for the user, not operator stdout.
150
+ void forwarder.forwardToolLog(toolLine, toolName, args);
151
+ }
152
+ return result;
153
+ }
154
+
155
+ return { handleToolCall, handleToolCallWithBridgeRetry };
156
+ }
157
+
158
+ export { createToolDispatch };
@@ -42,7 +42,7 @@ function isMemoryFile(filePath) {
42
42
  function isHidden(name) {
43
43
  if (HIDDEN_TOOLS.has(name)) return true;
44
44
  if (formatToolSurface(name, {}).label === "Memory") return false;
45
- if (name === "reply" || name === "react" || name === "edit_message" || name === "fetch" || name === "download_attachment") return true;
45
+ if (name === "reply" || name === "fetch") return true;
46
46
  return false;
47
47
  }
48
48
  /**
@@ -174,7 +174,7 @@ function compareTranscriptCandidates(left, right) {
174
174
  if (right.startedAt !== left.startedAt) return right.startedAt - left.startedAt;
175
175
  return (right.claudePid ?? 0) - (left.claudePid ?? 0);
176
176
  }
177
- function detectCurrentSessionTranscript() {
177
+ function detectCurrentSessionTranscript(options = {}) {
178
178
  const candidates = [];
179
179
  const seen = new Set();
180
180
  const addCandidate = (candidate) => {
@@ -185,7 +185,7 @@ function detectCurrentSessionTranscript() {
185
185
  seen.add(key);
186
186
  candidates.push(candidate);
187
187
  };
188
- addCandidate(sessionTranscriptCandidate(discoverCurrentClaudeSession(), "parent-chain"));
188
+ addCandidate(sessionTranscriptCandidate(discoverCurrentClaudeSession(options), "parent-chain"));
189
189
  for (const session of listInteractiveClaudeSessions()) {
190
190
  addCandidate(sessionTranscriptCandidate(session, "sessions-dir"));
191
191
  }
@@ -193,8 +193,8 @@ function detectCurrentSessionTranscript() {
193
193
  candidates.sort(compareTranscriptCandidates);
194
194
  return candidates[0] ?? null;
195
195
  }
196
- function discoverSessionBoundTranscript() {
197
- return detectCurrentSessionTranscript();
196
+ function discoverSessionBoundTranscript(options = {}) {
197
+ return detectCurrentSessionTranscript(options);
198
198
  }
199
199
 
200
200
  export {
@@ -178,13 +178,16 @@ async function _withInstallLock(rootDir, lockName, fn, { pollMs = 250 } = {}) {
178
178
  }
179
179
 
180
180
  async function loadManifest(dataDir) {
181
+ // Bundled manifest FIRST: it always matches the installed mixdog version.
182
+ // A stale cached manifest.json from an older install must never shadow it
183
+ // (caused sha256 mismatches on already-installed machines after upgrades).
184
+ if (existsSync(BUNDLED_MANIFEST_PATH)) {
185
+ return JSON.parse(readFileSync(BUNDLED_MANIFEST_PATH, 'utf8'))
186
+ }
181
187
  const cachedPath = join(dataDir, 'voice-runtime', 'manifest.json')
182
188
  if (existsSync(cachedPath)) {
183
189
  try { return JSON.parse(readFileSync(cachedPath, 'utf8')) } catch {}
184
190
  }
185
- if (existsSync(BUNDLED_MANIFEST_PATH)) {
186
- return JSON.parse(readFileSync(BUNDLED_MANIFEST_PATH, 'utf8'))
187
- }
188
191
  const res = await fetch(MANIFEST_URL, { signal: AbortSignal.timeout(30_000) })
189
192
  if (!res.ok) throw new Error(`[voice-runtime] manifest fetch failed: ${res.status} ${res.statusText}`)
190
193
  const manifest = await res.json()
@@ -0,0 +1,179 @@
1
+ import { spawn } from "child_process";
2
+ import * as fs from "fs";
3
+ import * as os from "os";
4
+ import * as path from "path";
5
+ import { createRequire } from "module";
6
+ import { resolveVoiceRuntime } from "./voice-runtime-fetcher.mjs";
7
+ import { ensureReady, transcribe } from "./whisper-server.mjs";
8
+ import { normalizeWhisperLanguage, detectDeviceLanguage } from "./whisper-language.mjs";
9
+
10
+ const _require = createRequire(import.meta.url);
11
+
12
+ // Voice-attachment transcription pipeline. Extracted verbatim from
13
+ // channels/index.mjs (behavior-preserving). `config` is read live through the
14
+ // injected getter so runtime config reloads (reloadRuntimeConfig) keep the
15
+ // same semantics as the original file-level `let config` reference.
16
+ function isVoiceAttachment(contentType) {
17
+ if (typeof contentType !== 'string') return false;
18
+ const ct = contentType.toLowerCase();
19
+ return ct.startsWith("audio/") || ct.startsWith("application/ogg");
20
+ }
21
+
22
+ function runCmd(cmd, args, capture = false) {
23
+ return new Promise((resolve, reject) => {
24
+ const proc = spawn(cmd, args, {
25
+ stdio: capture ? ["ignore", "pipe", "ignore"] : "ignore",
26
+ windowsHide: true
27
+ });
28
+ let out = "";
29
+ if (capture && proc.stdout) proc.stdout.on("data", (d) => {
30
+ out += d;
31
+ });
32
+ proc.on("close", (code) => code === 0 ? resolve(out) : reject(new Error(`${cmd} exit ${code}`)));
33
+ proc.on("error", reject);
34
+ });
35
+ }
36
+
37
+ // Creates the voice-transcription surface bound to a live config getter and
38
+ // data dir. Returns { isVoiceAttachment, transcribeVoice }.
39
+ function createVoiceTranscription({ getConfig, dataDir }) {
40
+ // ── voice.transcription concurrency queue (max=1 by default, config-driven) ──
41
+ const _voiceTranscriptionQueue = (() => {
42
+ let running = 0;
43
+ const pending = [];
44
+ function drain() {
45
+ const limit = getConfig().voice?.transcription?.maxConcurrency ?? 1;
46
+ while (running < limit && pending.length > 0) {
47
+ const { fn, resolve, reject } = pending.shift();
48
+ running++;
49
+ fn().then(resolve, reject).finally(() => { running--; drain(); });
50
+ }
51
+ }
52
+ return function enqueue(fn) {
53
+ return new Promise((resolve, reject) => { pending.push({ fn, resolve, reject }); drain(); });
54
+ };
55
+ })();
56
+
57
+ // ── wav + transcript cache keyed by attachment id ──
58
+ const _voiceWavCache = new Map(); // attachmentId → wavPath
59
+ const _voiceTranscriptCache = new Map(); // attachmentId → transcript string
60
+ const _voiceInflight = new Map(); // attachmentId → Promise<string|null>
61
+ const _voiceFfmpegInflight = new Map(); // attachmentId|wavPath → Promise<void> single-flight ffmpeg
62
+
63
+ async function _probeAudioDurationSec(filePath) {
64
+ try {
65
+ const ffprobePath = (() => { try { return _require('ffprobe-static').path; } catch { return 'ffprobe'; } })();
66
+ return await new Promise((resolve, reject) => {
67
+ const args = ['-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', filePath];
68
+ let out = '';
69
+ const proc = spawn(ffprobePath, args, { windowsHide: true });
70
+ proc.stdout.on('data', (d) => { out += d; });
71
+ proc.on('close', (code) => { code === 0 ? resolve(parseFloat(out.trim()) || null) : reject(new Error(`ffprobe exit ${code}`)); });
72
+ proc.on('error', reject);
73
+ });
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ async function transcribeVoice(audioPath, { attachmentId } = {}) {
80
+ const config = getConfig();
81
+ // ── size gate (config: voice.transcription.maxFileSizeMB) ──
82
+ const maxSizeBytes = (config.voice?.transcription?.maxFileSizeMB ?? 0) * 1024 * 1024;
83
+ if (maxSizeBytes > 0) {
84
+ try {
85
+ const stat = await fs.promises.stat(audioPath);
86
+ if (stat.size > maxSizeBytes) {
87
+ process.stderr.write(`mixdog: voice.transcription skipped — file too large (${(stat.size / 1024 / 1024).toFixed(1)} MB > ${config.voice.transcription.maxFileSizeMB} MB): ${audioPath}\n`);
88
+ return null;
89
+ }
90
+ } catch { /* stat failure: proceed */ }
91
+ }
92
+ // ── duration gate (config: voice.transcription.maxDurationSec) ──
93
+ const maxDurationSec = config.voice?.transcription?.maxDurationSec ?? 0;
94
+ if (maxDurationSec > 0) {
95
+ const dur = await _probeAudioDurationSec(audioPath);
96
+ if (dur !== null && dur > maxDurationSec) {
97
+ process.stderr.write(`mixdog: voice.transcription skipped — audio too long (${Math.floor(dur)}s > ${maxDurationSec}s): ${audioPath}\n`);
98
+ return null;
99
+ }
100
+ }
101
+ // ── transcript cache hit ──
102
+ if (attachmentId && _voiceTranscriptCache.has(attachmentId)) {
103
+ process.stderr.write(`mixdog: voice.transcription cache hit (${attachmentId})\n`);
104
+ return _voiceTranscriptCache.get(attachmentId);
105
+ }
106
+ if (attachmentId && _voiceInflight.has(attachmentId)) {
107
+ return _voiceInflight.get(attachmentId);
108
+ }
109
+ const p = _voiceTranscriptionQueue(() => _doTranscribeVoice(audioPath, attachmentId));
110
+ if (attachmentId) {
111
+ _voiceInflight.set(attachmentId, p);
112
+ p.catch((err) => {
113
+ try { process.stderr.write(`mixdog: voice.transcription inflight rejection: ${err?.stack || err}\n`); } catch {}
114
+ }).finally(() => _voiceInflight.delete(attachmentId));
115
+ }
116
+ return p;
117
+ }
118
+
119
+ async function _doTranscribeVoice(audioPath, attachmentId) {
120
+ const config = getConfig();
121
+ try {
122
+ const runtime = resolveVoiceRuntime(dataDir);
123
+ if (!runtime?.installed) {
124
+ const missing = [runtime?.binary ? null : 'binary', runtime?.model ? null : 'model', runtime?.ffmpeg ? null : 'ffmpeg'].filter(Boolean).join(' + ');
125
+ throw new Error(`voice runtime not installed (missing: ${missing}) — open the setup wizard and click "Install voice"`);
126
+ }
127
+ const whisperCmd = runtime.whisperCmd;
128
+ const modelPath = runtime.modelPath;
129
+ const ffmpegPath = runtime.ffmpegPath;
130
+ const lang = normalizeWhisperLanguage(config.voice?.language) ?? detectDeviceLanguage();
131
+ const _cpuCount = (() => { try { return os.cpus().length; } catch { return 2; } })();
132
+ const threadCount = config.voice?.transcription?.threadCount ?? Math.max(1, Math.ceil(_cpuCount / 4));
133
+ // ── wav cache keyed by attachment id ──
134
+ let wavPath;
135
+ if (attachmentId && _voiceWavCache.has(attachmentId)) {
136
+ wavPath = _voiceWavCache.get(attachmentId);
137
+ if (!fs.existsSync(wavPath)) {
138
+ _voiceWavCache.delete(attachmentId);
139
+ wavPath = undefined;
140
+ } else {
141
+ process.stderr.write(`mixdog: voice.transcription wav cache hit (${attachmentId})\n`);
142
+ }
143
+ }
144
+ if (!wavPath) {
145
+ wavPath = audioPath.replace(/\.[^.]+$/, ".wav");
146
+ const sampleRate = config.voice?.transcription?.sampleRate ?? 16000;
147
+ const channels = config.voice?.transcription?.channels ?? 1;
148
+ // Single-flight: parallel callers for the same key share one ffmpeg spawn.
149
+ const _ffmpegKey = attachmentId || wavPath;
150
+ if (_voiceFfmpegInflight.has(_ffmpegKey)) {
151
+ await _voiceFfmpegInflight.get(_ffmpegKey);
152
+ } else {
153
+ const _ffmpegPromise = runCmd(ffmpegPath, ["-i", audioPath, "-ar", String(sampleRate), "-ac", String(channels), "-threads", String(threadCount), "-y", wavPath]);
154
+ _voiceFfmpegInflight.set(_ffmpegKey, _ffmpegPromise);
155
+ try {
156
+ await _ffmpegPromise;
157
+ if (attachmentId) _voiceWavCache.set(attachmentId, wavPath);
158
+ } finally {
159
+ _voiceFfmpegInflight.delete(_ffmpegKey);
160
+ }
161
+ }
162
+ }
163
+ process.stderr.write(`mixdog: voice.transcription start runtime=${runtime.kind} cmd=${path.basename(whisperCmd)}\n`);
164
+ await ensureReady({ serverCmd: runtime.serverCmd, modelPath, threadCount, host: '127.0.0.1' });
165
+ const text = await transcribe(wavPath, { language: lang });
166
+ const result = text.trim() || null;
167
+ if (attachmentId && result) _voiceTranscriptCache.set(attachmentId, result);
168
+ return result;
169
+ } catch (err) {
170
+ if (err?.message?.startsWith('voice runtime not installed')) throw err; // propagate setup errors; caller posts user-visible failure
171
+ process.stderr.write(`mixdog: voice.transcription failed: ${err}\n`);
172
+ return null;
173
+ }
174
+ }
175
+
176
+ return { isVoiceAttachment, transcribeVoice };
177
+ }
178
+
179
+ export { isVoiceAttachment, runCmd, createVoiceTranscription };