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,490 @@
1
+ /**
2
+ * settings-picker.mjs — the SETTINGS picker cluster.
3
+ *
4
+ * Extracted from App.jsx behavior-preservingly as a dependency-injection
5
+ * factory. The function body is the original App logic verbatim, with closure
6
+ * identifiers threaded through the factory argument. openSettingsPicker
7
+ * self-references (for light refresh) via the local const; all other openers it
8
+ * routes to are threaded as lazy getter wrappers so they resolve the live
9
+ * binding at call time.
10
+ */
11
+ import { outputStyleNotice } from './route-pickers.mjs';
12
+
13
+ export function createSettingsPicker({
14
+ store,
15
+ state,
16
+ setPicker,
17
+ setProviderPrompt,
18
+ setChannelPrompt,
19
+ setHookPrompt,
20
+ setSettingsPrompt,
21
+ settingsHeavyCacheRef,
22
+ formatDuration,
23
+ displayModelName,
24
+ routeModelLabel,
25
+ workflowDisplayName,
26
+ workflowSwitchNotice,
27
+ themeNotice,
28
+ openModelPicker,
29
+ openSearchPicker,
30
+ openAgentsPicker,
31
+ openWorkflowPicker,
32
+ openOutputStylePicker,
33
+ openBridgePicker,
34
+ openToolsPicker,
35
+ openProviderSetupPicker,
36
+ openThemePicker,
37
+ openAutoClearPicker,
38
+ openProfilePicker,
39
+ openMemoryCorePicker,
40
+ openMcpPicker,
41
+ openPluginsPicker,
42
+ openHooksPicker,
43
+ openSkillsPicker,
44
+ openUpdatePicker,
45
+ openChannelSettingTypePicker,
46
+ }) {
47
+ const openSettingsPicker = (opts = {}) => {
48
+ const light = opts.light === true;
49
+ const overrides = opts.overrides || null;
50
+ const heavyCache = light ? settingsHeavyCacheRef.current : null;
51
+ const autoClear = store.getAutoClear?.() || {};
52
+ const compaction = store.getCompactionSettings?.() || {};
53
+ const memory = store.getMemorySettings?.() || { enabled: true };
54
+ const channels = store.getChannelSettings?.({ includeStatus: false }) || { enabled: true };
55
+ const outputStyle = store.getOutputStyle?.() || store.listOutputStyles?.() || {};
56
+ const workflow = state.workflow || {};
57
+ const mcp = heavyCache ? heavyCache.mcp : (store.mcpStatus?.() || { connectedCount: 0, configuredCount: 0, failedCount: 0 });
58
+ const hooks = heavyCache ? heavyCache.hooks : (store.hooksStatus?.() || { ruleCount: 0 });
59
+ const plugins = heavyCache ? heavyCache.plugins : (store.pluginsStatus?.() || { count: 0 });
60
+ const skills = heavyCache ? heavyCache.skills : (store.skillsStatus?.() || { count: 0 });
61
+ const channelWorker = store.getChannelWorkerStatus?.();
62
+ let channelBackend = 'discord';
63
+ if (heavyCache) {
64
+ channelBackend = heavyCache.channelBackend || 'discord';
65
+ } else {
66
+ try {
67
+ channelBackend = store.getChannelSetup?.()?.backend || 'discord';
68
+ } catch {
69
+ channelBackend = 'discord';
70
+ }
71
+ }
72
+ if (overrides && Object.prototype.hasOwnProperty.call(overrides, 'channelBackend')) {
73
+ channelBackend = overrides.channelBackend;
74
+ }
75
+ // Refresh the cache every build (light or full) so the next light
76
+ // refresh reuses whatever was most recently known, and so an
77
+ // optimistic override (e.g. channel backend cycle) sticks without
78
+ // re-running the heavy getter it came from.
79
+ settingsHeavyCacheRef.current = { mcp, hooks, plugins, skills, channelBackend };
80
+ const channelBackendLabel = channelBackend === 'telegram' ? 'Telegram' : 'Discord';
81
+ const remoteEnabled = store.isRemoteEnabled?.() === true;
82
+ const remoteRuntimeDescription = channelWorker?.running
83
+ ? `runtime running · pid ${channelWorker.pid}`
84
+ : 'runtime stopped';
85
+ const compactType = compaction.compactType || compaction.type || 'semantic';
86
+ const compactTypeLabel = compactType === 'recall-fasttrack' ? 'Fast-track' : 'Default';
87
+ const outputStyleLabel = outputStyle?.current?.label || outputStyle?.current?.id || outputStyle?.configured || 'Default';
88
+ const workflowLabel = workflowDisplayName(workflow);
89
+ const boolLabel = (enabled) => enabled ? 'On' : 'Off';
90
+ const compactTypeDescription = compactType === 'recall-fasttrack'
91
+ ? (memory.enabled === false
92
+ ? 'Injects raw transcript lines (Recap off: no LLM chunking).'
93
+ : 'Uses Memory recall to rebuild context faster on large histories.')
94
+ : 'Uses semantic summarization for predictable context compaction.';
95
+ const applyAutoClear = (patch = {}) => {
96
+ try {
97
+ const next = store.setAutoClear?.(patch);
98
+ if (!next) store.pushNotice('autoclear unavailable', 'warn');
99
+ else store.pushNotice(next.enabled ? `Auto-clear on · idle ${formatDuration(next.idleMs)}` : 'Auto-clear off', 'info');
100
+ } catch (e) {
101
+ store.pushNotice(`autoclear failed: ${e?.message || e}`, 'error');
102
+ }
103
+ openSettingsPicker({ light: true });
104
+ };
105
+ // On/Off toggle only — idle-window override lives in the Advanced picker
106
+ // (openAutoClearPicker), opened via Enter on this row.
107
+ const autoClearEnabled = autoClear.enabled !== false;
108
+ const toggleAutoClear = () => applyAutoClear({ enabled: !autoClearEnabled });
109
+ const applyCompaction = (patch = {}) => {
110
+ void Promise.resolve(store.setCompactionSettings?.(patch))
111
+ .then((next) => {
112
+ if (!next) {
113
+ store.pushNotice('compaction setting is busy', 'warn');
114
+ return;
115
+ }
116
+ store.pushNotice(`Compaction ${next.auto !== false ? 'auto on' : 'auto off'} · ${next.compactType === 'recall-fasttrack' ? 'Fast-track' : 'Default'}`, 'info');
117
+ })
118
+ .catch((e) => store.pushNotice(`compaction failed: ${e?.message || e}`, 'error'))
119
+ .finally(() => openSettingsPicker({ light: true }));
120
+ };
121
+ const applyMemory = (enabled) => {
122
+ void Promise.resolve(store.setMemoryEnabled?.(enabled))
123
+ .then((next) => {
124
+ if (!next) {
125
+ store.pushNotice('recap setting is busy', 'warn');
126
+ return;
127
+ }
128
+ store.pushNotice(`Recap ${next.enabled ? 'on' : 'off'}`, 'info');
129
+ })
130
+ .catch((e) => store.pushNotice(`recap setting failed: ${e?.message || e}`, 'error'))
131
+ .finally(() => openSettingsPicker({ light: true }));
132
+ };
133
+ const applyChannels = (enabled) => {
134
+ void Promise.resolve(store.setChannelsEnabled?.(enabled))
135
+ .then((next) => {
136
+ if (!next) {
137
+ store.pushNotice('channel setting is busy', 'warn');
138
+ return;
139
+ }
140
+ store.pushNotice(`Channels ${next.enabled ? 'on' : 'off'}`, 'info');
141
+ })
142
+ .catch((e) => store.pushNotice(`channel setting failed: ${e?.message || e}`, 'error'))
143
+ .finally(() => openSettingsPicker({ light: true }));
144
+ };
145
+ const cycleOutputStyle = (direction = 1) => {
146
+ let status = null;
147
+ try { status = store.listOutputStyles?.() || null; } catch (e) {
148
+ store.pushNotice(`could not list output styles: ${e?.message || e}`, 'error');
149
+ return;
150
+ }
151
+ const styles = Array.isArray(status?.styles) ? status.styles : [];
152
+ if (!styles.length) {
153
+ store.pushNotice('no output styles available', 'warn');
154
+ return;
155
+ }
156
+ const currentId = status?.current?.id || 'default';
157
+ const currentIndex = Math.max(0, styles.findIndex((style) => style.id === currentId));
158
+ const next = styles[(currentIndex + direction + styles.length) % styles.length];
159
+ void store.setOutputStyle?.(next.id)
160
+ .then((result) => {
161
+ if (!result) {
162
+ store.pushNotice('Output style switch is already running.', 'warn');
163
+ return;
164
+ }
165
+ store.pushNotice(outputStyleNotice(result), 'info');
166
+ })
167
+ .catch((e) => store.pushNotice(`Couldn’t switch output style: ${e?.message || e}`, 'error'))
168
+ .finally(() => openSettingsPicker({ light: true }));
169
+ };
170
+ const cycleWorkflow = (direction = 1) => {
171
+ let workflows = [];
172
+ try { workflows = store.listWorkflows?.() || []; } catch (e) {
173
+ store.pushNotice(`could not list workflows: ${e?.message || e}`, 'error');
174
+ return;
175
+ }
176
+ if (!workflows.length) {
177
+ store.pushNotice('no workflows available', 'warn');
178
+ return;
179
+ }
180
+ const activeIndex = workflows.findIndex((item) => item.active);
181
+ const currentIndex = activeIndex >= 0 ? activeIndex : Math.max(0, workflows.findIndex((item) => item.id === workflow.id));
182
+ const next = workflows[(currentIndex + direction + workflows.length) % workflows.length];
183
+ void store.setWorkflow?.(next.id)
184
+ .then((result) => {
185
+ if (!result) {
186
+ store.pushNotice('Workflow switch is already running.', 'warn');
187
+ return;
188
+ }
189
+ store.pushNotice(workflowSwitchNotice(result), 'info');
190
+ })
191
+ .catch((e) => store.pushNotice(`Couldn’t switch workflow: ${e?.message || e}`, 'error'))
192
+ .finally(() => openSettingsPicker({ light: true }));
193
+ };
194
+ const cycleTheme = (direction = 1) => {
195
+ let themes = [];
196
+ try { themes = store.listThemes?.() || []; } catch (e) {
197
+ store.pushNotice(`could not list themes: ${e?.message || e}`, 'error');
198
+ return;
199
+ }
200
+ if (!themes.length) {
201
+ store.pushNotice('no themes available', 'warn');
202
+ return;
203
+ }
204
+ const currentId = store.getTheme?.() || themes.find((t) => t.current)?.id || themes[0]?.id;
205
+ const currentIndex = Math.max(0, themes.findIndex((t) => t.id === currentId));
206
+ const next = themes[(currentIndex + direction + themes.length) % themes.length];
207
+ try {
208
+ const applied = store.setTheme?.(next.id, { persist: true });
209
+ store.pushNotice(themeNotice(applied || next), 'info');
210
+ } catch (e) {
211
+ store.pushNotice(`Couldn’t set theme: ${e?.message || e}`, 'error');
212
+ }
213
+ openSettingsPicker({ light: true });
214
+ };
215
+ const applyRemoteRuntime = () => {
216
+ const enabled = store.toggleRemote?.() === true;
217
+ store.pushNotice(enabled ? 'Remote mode ON' : 'Remote mode OFF', 'info');
218
+ openSettingsPicker({ light: true });
219
+ };
220
+ const cycleChannelBackend = (direction = 1) => {
221
+ const backends = ['discord', 'telegram'];
222
+ const currentIndex = Math.max(0, backends.indexOf(channelBackend));
223
+ const chosen = backends[(currentIndex + direction + backends.length) % backends.length];
224
+ if (chosen === channelBackend) {
225
+ openSettingsPicker({ light: true, overrides: { channelBackend } });
226
+ return;
227
+ }
228
+ try {
229
+ store.setBackend(chosen);
230
+ const label = chosen === 'telegram' ? 'Telegram' : 'Discord';
231
+ const restartHint = (store.isRemoteEnabled?.() === true || channelWorker?.running)
232
+ ? `Channel set to ${label}. Restart remote to apply.`
233
+ : `Channel set to ${label}.`;
234
+ store.pushNotice(restartHint, 'info');
235
+ } catch (e) {
236
+ store.pushNotice(`channel backend failed: ${e?.message || e}`, 'error');
237
+ }
238
+ openSettingsPicker({ light: true, overrides: { channelBackend: chosen } });
239
+ };
240
+ const items = [
241
+ {
242
+ value: 'profile',
243
+ label: 'Profile',
244
+ meta: (() => {
245
+ try {
246
+ const p = store.getProfile?.();
247
+ const lang = p?.languageEntry?.label || 'System';
248
+ return p?.title ? `${p.title} · ${lang}` : lang;
249
+ } catch { return 'System'; }
250
+ })(),
251
+ description: 'Your title and response language.',
252
+ _action: 'profile',
253
+ },
254
+ {
255
+ value: 'autoclear',
256
+ label: 'Auto-clear',
257
+ meta: autoClearEnabled ? `On (${formatDuration(autoClear.idleMs)})` : 'Off',
258
+ description: autoClearEnabled
259
+ ? `Clear idle sessions after ${formatDuration(autoClear.idleMs)}${autoClear.custom ? '' : ` (${autoClear.provider || 'default'} default)`}. Enter for options.`
260
+ : 'Idle auto-clear disabled. Enter for options.',
261
+ _action: 'autoclear',
262
+ },
263
+ {
264
+ value: 'autocompact',
265
+ label: 'Auto-compact',
266
+ meta: boolLabel(compaction.auto !== false),
267
+ description: 'Compact when context is high.',
268
+ _action: 'autocompact',
269
+ },
270
+ {
271
+ value: 'compact-type',
272
+ label: 'Compact type',
273
+ meta: compactTypeLabel,
274
+ description: compactTypeDescription,
275
+ _action: 'compact-type',
276
+ },
277
+ {
278
+ value: 'core-memory',
279
+ label: 'Recap',
280
+ meta: boolLabel(memory.enabled !== false),
281
+ description: 'Recap on/off, list and edit memories.',
282
+ _action: 'core-memory',
283
+ },
284
+ {
285
+ value: 'channels',
286
+ label: 'Channels enabled',
287
+ meta: boolLabel(channels.enabled !== false),
288
+ description: channels.enabled === false
289
+ ? 'Channel tools disabled.'
290
+ : 'Discord, schedules, and webhooks.',
291
+ _action: 'channels',
292
+ },
293
+ {
294
+ value: 'remote-runtime',
295
+ label: 'Remote Runtime',
296
+ meta: boolLabel(remoteEnabled),
297
+ description: remoteRuntimeDescription,
298
+ _action: 'remote-runtime',
299
+ },
300
+ {
301
+ value: 'channel-backend',
302
+ label: 'Channel',
303
+ meta: channelBackendLabel,
304
+ description: 'Left/Right or Enter changes channel type (Discord or Telegram).',
305
+ _action: 'channel-backend',
306
+ },
307
+ {
308
+ value: 'channel-setting',
309
+ label: 'Setting',
310
+ description: 'Configure credentials and main channel/chat for the active type.',
311
+ _action: 'channel-setting',
312
+ },
313
+ {
314
+ value: 'output-style',
315
+ label: 'Output style',
316
+ meta: outputStyleLabel,
317
+ description: 'Response tone and format.',
318
+ _action: 'output-style',
319
+ },
320
+ {
321
+ value: 'theme',
322
+ label: 'Theme',
323
+ meta: (() => {
324
+ try {
325
+ const id = store.getTheme?.();
326
+ const entry = (store.listThemes?.() || []).find((t) => t.id === id);
327
+ return entry?.label || id || 'Default';
328
+ } catch { return 'Default'; }
329
+ })(),
330
+ description: 'TUI color theme.',
331
+ _action: 'theme',
332
+ },
333
+ {
334
+ value: 'workflow',
335
+ label: 'Workflow',
336
+ meta: workflowLabel,
337
+ description: 'Active agent routing profile.',
338
+ _action: 'workflow',
339
+ },
340
+ {
341
+ value: 'model',
342
+ label: 'Model',
343
+ meta: displayModelName(state.model, state.provider),
344
+ description: 'Main chat model.',
345
+ _action: 'model',
346
+ },
347
+ {
348
+ value: 'search',
349
+ label: 'Search model',
350
+ meta: routeModelLabel(store.getSearchRoute?.()),
351
+ description: 'Native search model.',
352
+ _action: 'search',
353
+ },
354
+ {
355
+ value: 'providers',
356
+ label: 'Providers',
357
+ description: 'Auth, API keys, OAuth, local.',
358
+ _action: 'providers',
359
+ },
360
+ {
361
+ value: 'mcp',
362
+ label: 'MCP servers',
363
+ description: `${mcp.connectedCount || 0}/${mcp.configuredCount || 0} connected${mcp.failedCount ? ` · ${mcp.failedCount} failed` : ''}`,
364
+ _action: 'mcp',
365
+ },
366
+ {
367
+ value: 'plugins',
368
+ label: 'Plugins',
369
+ description: `${plugins.count || 0} detected`,
370
+ _action: 'plugins',
371
+ },
372
+ {
373
+ value: 'hooks',
374
+ label: 'Hooks',
375
+ description: `${hooks.ruleCount || 0} before-tool rules`,
376
+ _action: 'hooks',
377
+ },
378
+ {
379
+ value: 'skills',
380
+ label: 'Skills',
381
+ description: `${skills.count || 0} available`,
382
+ _action: 'skills',
383
+ },
384
+ {
385
+ value: 'update',
386
+ label: 'Update',
387
+ meta: (() => {
388
+ try {
389
+ const upd = store.getUpdateSettings?.() || {};
390
+ const current = upd.currentVersion || 'unknown';
391
+ if (upd.updateAvailable && upd.latestVersion) return `${current} → ${upd.latestVersion}`;
392
+ if (!upd.currentVersion) return 'unknown';
393
+ return `${current} (latest)`;
394
+ } catch { return 'unknown'; }
395
+ })(),
396
+ description: 'Check version and update mixdog.',
397
+ _action: 'update',
398
+ },
399
+ ];
400
+ setProviderPrompt(null);
401
+ setChannelPrompt(null);
402
+ setHookPrompt(null);
403
+ setSettingsPrompt(null);
404
+ setPicker({
405
+ title: 'Settings',
406
+ description: 'Runtime, model, tools, and integrations.',
407
+ help: '↑/↓ Select · ←/→ Change · Enter Open/Toggle · Esc Close',
408
+ indexMode: 'always',
409
+ labelWidth: 18,
410
+ metaWidth: 18,
411
+ items,
412
+ initialIndex: opts.focus ? Math.max(0, items.findIndex((item) => item.value === opts.focus)) : undefined,
413
+ onLeft: (item) => {
414
+ if (item?._action === 'autoclear') toggleAutoClear();
415
+ else if (item?._action === 'autocompact') applyCompaction({ auto: !(compaction.auto !== false) });
416
+ else if (item?._action === 'compact-type') {
417
+ const nextType = compactType === 'recall-fasttrack' ? 'semantic' : 'recall-fasttrack';
418
+ applyCompaction({ compactType: nextType });
419
+ }
420
+ else if (item?._action === 'core-memory') applyMemory(!(memory.enabled !== false));
421
+ else if (item?._action === 'channels') applyChannels(!(channels.enabled !== false));
422
+ else if (item?._action === 'remote-runtime') applyRemoteRuntime();
423
+ else if (item?._action === 'channel-backend') cycleChannelBackend(-1);
424
+ else if (item?._action === 'output-style') cycleOutputStyle(-1);
425
+ else if (item?._action === 'theme') cycleTheme(-1);
426
+ else if (item?._action === 'workflow') cycleWorkflow(-1);
427
+ },
428
+ onRight: (item) => {
429
+ if (item?._action === 'autoclear') toggleAutoClear();
430
+ else if (item?._action === 'autocompact') applyCompaction({ auto: !(compaction.auto !== false) });
431
+ else if (item?._action === 'compact-type') {
432
+ const nextType = compactType === 'recall-fasttrack' ? 'semantic' : 'recall-fasttrack';
433
+ applyCompaction({ compactType: nextType });
434
+ }
435
+ else if (item?._action === 'core-memory') applyMemory(!(memory.enabled !== false));
436
+ else if (item?._action === 'channels') applyChannels(!(channels.enabled !== false));
437
+ else if (item?._action === 'remote-runtime') applyRemoteRuntime();
438
+ else if (item?._action === 'channel-backend') cycleChannelBackend(1);
439
+ else if (item?._action === 'output-style') cycleOutputStyle(1);
440
+ else if (item?._action === 'theme') cycleTheme(1);
441
+ else if (item?._action === 'workflow') cycleWorkflow(1);
442
+ },
443
+ onSelect: (_value, item) => {
444
+ setPicker(null);
445
+ if (item._action === 'autoclear') openAutoClearPicker({ returnTo: openSettingsPicker });
446
+ else if (item._action === 'profile') openProfilePicker({ returnTo: openSettingsPicker });
447
+ else if (item._action === 'autocompact') applyCompaction({ auto: !(compaction.auto !== false) });
448
+ else if (item._action === 'compact-type') {
449
+ const nextType = compactType === 'recall-fasttrack' ? 'semantic' : 'recall-fasttrack';
450
+ applyCompaction({ compactType: nextType });
451
+ }
452
+ else if (item._action === 'core-memory') openMemoryCorePicker({ returnTo: () => openSettingsPicker() });
453
+ else if (item._action === 'channels') applyChannels(!(channels.enabled !== false));
454
+ else if (item._action === 'remote-runtime') applyRemoteRuntime();
455
+ else if (item._action === 'channel-backend') cycleChannelBackend(1);
456
+ else if (item._action === 'channel-setting') openChannelSettingTypePicker({ returnTo: openSettingsPicker });
457
+ else if (item._action === 'output-style') openOutputStylePicker({ returnTo: openSettingsPicker });
458
+ else if (item._action === 'theme') openThemePicker({ returnTo: openSettingsPicker });
459
+ else if (item._action === 'workflow') openWorkflowPicker({ returnTo: openSettingsPicker });
460
+ else if (item._action === 'model') openModelPicker({
461
+ returnTo: openSettingsPicker,
462
+ returnLabel: 'Settings',
463
+ returnOnNestedCancel: true,
464
+ onAfterSelect: openSettingsPicker,
465
+ });
466
+ else if (item._action === 'search') openSearchPicker({
467
+ returnTo: openSettingsPicker,
468
+ returnLabel: 'Settings',
469
+ returnOnNestedCancel: true,
470
+ });
471
+ else if (item._action === 'providers') void openProviderSetupPicker({
472
+ returnTo: openSettingsPicker,
473
+ onCancel: openSettingsPicker,
474
+ continueLabel: 'Back to settings',
475
+ continueDescription: 'return to settings',
476
+ });
477
+ else if (item._action === 'mcp') openMcpPicker();
478
+ else if (item._action === 'plugins') openPluginsPicker();
479
+ else if (item._action === 'hooks') openHooksPicker();
480
+ else if (item._action === 'skills') openSkillsPicker();
481
+ else if (item._action === 'update') openUpdatePicker({ returnTo: openSettingsPicker });
482
+ },
483
+ onCancel: () => {
484
+ setPicker(null);
485
+ },
486
+ });
487
+ };
488
+
489
+ return { openSettingsPicker };
490
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * slash-commands.mjs — the /command registry plus the pure string/matching
3
+ * helpers that operate on it. Extracted verbatim from App.jsx (behavior-
4
+ * preserving): no hooks, no App state, no closures — plain data + pure fns.
5
+ */
6
+ export const SLASH_COMMANDS = [
7
+ { name: 'clear', usage: '/clear', aliases: ['new'], aliasUsage: ['new'], description: 'Start a fresh chat' },
8
+ { name: 'project', usage: '/project', aliases: ['projects'], aliasUsage: ['projects'], showAliasUsage: false, description: 'Switch working directory (project)' },
9
+ { name: 'compact', usage: '/compact', description: 'Compact older conversation context' },
10
+ { name: 'autoclear', usage: '/autoclear', description: 'Reduce cache-miss cost after long idle gaps' },
11
+ { name: 'resume', usage: '/resume', description: 'Resume a saved chat' },
12
+ { name: 'context', usage: '/context', description: 'Show current context surface' },
13
+ { name: 'usage', usage: '/usage', params: '[refresh]', description: 'Show total provider quota / balance' },
14
+ { name: 'model', usage: '/model', description: 'Switch model for subsequent turns' },
15
+ { name: 'search', usage: '/search', description: 'Set the web search provider/model' },
16
+ { name: 'workflow', usage: '/workflow', description: 'Switch the active workflow' },
17
+ { name: 'outputstyle', usage: '/OutputStyle', aliases: ['output-style', 'style'], aliasUsage: ['style'], showAliasUsage: false, params: '[name]', description: 'Switch Lead output style' },
18
+ { name: 'theme', usage: '/theme', params: '[id]', description: 'Change the TUI color theme' },
19
+ { name: 'agents', usage: '/agents', description: 'Show available workflow agents' },
20
+ { name: 'effort', usage: '/effort', params: '[level]', description: 'Set reasoning effort for the current model' },
21
+ { name: 'fast', usage: '/fast', params: '[on|off]', description: 'Toggle Fast mode for the current model' },
22
+ { name: 'mcp', usage: '/mcp', description: 'Manage MCP servers and tools' },
23
+ { name: 'skills', usage: '/skills', description: 'Choose a skill for the next request' },
24
+ { name: 'memory', usage: '/memory', description: 'List and edit core memories' },
25
+ { name: 'recap', usage: '/recap', params: '[on|off]', description: 'Toggle background memory recap cycles' },
26
+ { name: 'plugins', usage: '/plugins', description: 'Manage local plugin integrations' },
27
+ { name: 'hooks', usage: '/hooks', description: 'Manage before-tool hook rules and events' },
28
+ { name: 'providers', usage: '/providers', description: 'Manage auth, API keys, OAuth, and local endpoints' },
29
+ { name: 'channels', usage: '/channels', description: 'Manage Discord, channels, schedules, webhooks' },
30
+ { name: 'remote', usage: '/remote', description: 'Claim remote for this session (takes over from any other session)' },
31
+ { name: 'schedules', usage: '/schedules', description: 'Manage schedules' },
32
+ { name: 'webhooks', usage: '/webhooks', description: 'Manage inbound webhooks' },
33
+ { name: 'settings', usage: '/setting', aliases: ['setting', 'config'], aliasUsage: ['settings', 'config'], showAliasUsage: false, description: 'Open runtime settings' },
34
+ { name: 'profile', usage: '/profile', description: 'Set your title and response language' },
35
+ { name: 'update', usage: '/update', description: 'Check version and update mixdog' },
36
+ { name: 'voice', usage: '/voice', description: 'Toggle voice input (Ctrl+Space to record)' },
37
+ { name: 'quit', usage: '/quit', aliases: ['exit', 'q'], aliasUsage: ['exit', 'q'], description: 'Quit the TUI' },
38
+ ];
39
+
40
+ export function slashQuery(value) {
41
+ const text = String(value ?? '');
42
+ if (!/^\/[^\s]*$/.test(text)) return null;
43
+ return text.slice(1).toLowerCase();
44
+ }
45
+
46
+ export function slashCommandMatches(command, query) {
47
+ const needle = String(query || '').toLowerCase();
48
+ if (!needle) return true;
49
+ if (String(command?.name || '').toLowerCase().startsWith(needle)) return true;
50
+ return (command?.aliases || []).some((alias) => String(alias || '').toLowerCase().startsWith(needle));
51
+ }
52
+
53
+ export function compareSlashCommands(a, b) {
54
+ return String(a?.name || '').localeCompare(String(b?.name || ''), 'en', { sensitivity: 'base' });
55
+ }
56
+
57
+ /** Prompt-owned overlays absorb PageUp/PageDown and wheel scroll instead of the transcript. */
58
+ export function overlayBlocksGlobalTranscriptScroll(owner = {}) {
59
+ return !!(
60
+ owner.slashPaletteOpen ||
61
+ owner.picker ||
62
+ owner.toolApproval ||
63
+ owner.contextPanel ||
64
+ owner.usagePanel ||
65
+ owner.providerPrompt ||
66
+ owner.channelPrompt ||
67
+ owner.hookPrompt ||
68
+ owner.settingsPrompt
69
+ );
70
+ }
71
+
72
+ export function normalizeSlashCommandName(cmd) {
73
+ const name = String(cmd || '').toLowerCase();
74
+ const command = SLASH_COMMANDS.find((item) => item.name === name || (item.aliases || []).includes(name));
75
+ return command?.name || name;
76
+ }
77
+
78
+ export function slashCommandTokenForPaletteAccept(command, draftValue = '') {
79
+ if (!command) return '';
80
+ const text = String(draftValue ?? '').trim();
81
+ const typedToken = text.startsWith('/') ? text.slice(1).split(/\s+/)[0]?.toLowerCase() : '';
82
+ const canonical = String(command.name || '').toLowerCase();
83
+ const aliases = (command.aliases || []).map((alias) => String(alias || '').toLowerCase());
84
+ if (typedToken && (typedToken === canonical || aliases.includes(typedToken))) {
85
+ return typedToken;
86
+ }
87
+ return command.name;
88
+ }
89
+
90
+ export function slashCommandForName(cmd) {
91
+ const name = normalizeSlashCommandName(cmd);
92
+ return SLASH_COMMANDS.find((item) => item.name === name) || null;
93
+ }
94
+
95
+ export function slashArgumentHint(value) {
96
+ const text = String(value ?? '');
97
+ const match = text.match(/^\/([^\s]+)\s+$/);
98
+ if (!match) return '';
99
+ const command = slashCommandForName(match[1]);
100
+ return command?.params ? `${command.usage} ${command.params}` : '';
101
+ }