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,427 @@
1
+ /**
2
+ * slash-dispatch.mjs — the runSlashCommand slash-command dispatcher.
3
+ *
4
+ * Extracted from App.jsx behavior-preservingly as a dependency-injection
5
+ * factory: the switch body reads live store/state and drives many pickers +
6
+ * openers, so it can't be pure. The switch body below is the original App
7
+ * logic verbatim (case ordering + fallthrough semantics byte-identical), with
8
+ * every closure identifier threaded in through the factory argument. Openers
9
+ * defined later in the App factory zone are passed as lazy getters so their
10
+ * live binding is used at call time.
11
+ */
12
+ export function createSlashDispatch({
13
+ state,
14
+ store,
15
+ normalizeSlashCommandName,
16
+ setContextPanel,
17
+ closeUsagePanel,
18
+ openModelPicker,
19
+ modelSwitchNotice,
20
+ toggleVoice,
21
+ setVoiceEnabled,
22
+ isVoiceEnabled,
23
+ openSearchPicker,
24
+ openAgentsPicker,
25
+ openWorkflowPicker,
26
+ workflowSwitchNotice,
27
+ openOutputStylePicker,
28
+ outputStyleNotice,
29
+ openThemePicker,
30
+ themeNotice,
31
+ openEffortPicker,
32
+ projectNameFromPath,
33
+ enterProject,
34
+ openProjectPicker,
35
+ openToolsPicker,
36
+ openMcpPicker,
37
+ openSkillsPicker,
38
+ openPluginsPicker,
39
+ openHooksPicker,
40
+ openProviderSetupPicker,
41
+ openChannelSetupPicker,
42
+ openMemoryCorePicker,
43
+ parseMemoryCommand,
44
+ openSettingsPicker,
45
+ openAutoClearPicker,
46
+ formatDuration,
47
+ openResumePicker,
48
+ openUsagePanel,
49
+ openContextPicker,
50
+ openProfilePicker,
51
+ openUpdatePicker,
52
+ requestExit,
53
+ }) {
54
+ const runSlashCommand = (cmd, arg = '') => {
55
+ const rawName = String(cmd || '').toLowerCase();
56
+ cmd = normalizeSlashCommandName(cmd);
57
+ if (cmd !== 'context') setContextPanel(null);
58
+ if (cmd !== 'usage') closeUsagePanel();
59
+ switch (cmd) {
60
+ case 'clear':
61
+ if (state.busy) {
62
+ store.pushNotice('wait for the current turn to finish before /clear', 'warn');
63
+ return false;
64
+ }
65
+ if (rawName === 'new') {
66
+ void store.newSession().then(() => {}).catch((e) => store.pushNotice(`new session failed: ${e?.message || e}`, 'error'));
67
+ } else {
68
+ void store.clear().then(() => {}).catch((e) => store.pushNotice(`clear failed: ${e?.message || e}`, 'error'));
69
+ }
70
+ return true;
71
+ case 'model':
72
+ if (!arg) {
73
+ openModelPicker();
74
+ return true;
75
+ }
76
+ void store.setModel(arg)
77
+ .then(ok => store.pushNotice(ok ? modelSwitchNotice() : 'Model switch is already running.', ok ? 'info' : 'warn'))
78
+ .catch((e) => store.pushNotice(`Couldn’t switch model: ${e?.message || e}`, 'error'));
79
+ return true;
80
+ case 'remote': {
81
+ // /remote = force-claim, not toggle: always turns remote ON for THIS
82
+ // session and steals the seat from any other session (which flips
83
+ // itself OFF via the superseded notification). Turn off via /channels.
84
+ const enabled = store.claimRemote?.() === true;
85
+ store.pushNotice(enabled ? 'Remote mode ON — this session owns remote now.' : 'Remote mode unavailable.', 'info');
86
+ return true;
87
+ }
88
+ case 'voice': {
89
+ // Step1 only: toggleVoice() owns config persistence (voice.enabled) +
90
+ // the missing-component install sequence + its own notices (OFF/ON/
91
+ // progress/failure). We don't push a redundant notice here; a null
92
+ // return means "already running" or "failed", both already noticed.
93
+ void toggleVoice({ pushNotice: store.pushNotice, setProgressHint: store.setProgressHint })
94
+ .then((result) => {
95
+ // true/false = new enabled state; null = failed/busy (unchanged).
96
+ if (result === true || result === false) setVoiceEnabled(result);
97
+ else if (result && typeof result === 'object') setVoiceEnabled(isVoiceEnabled());
98
+ });
99
+ return true;
100
+ }
101
+ case 'search':
102
+ if (state.busy) {
103
+ store.pushNotice('wait for the current turn to finish before /search', 'warn');
104
+ return false;
105
+ }
106
+ if (arg) store.pushNotice('/search sets the search provider/model; the search tool uses that model when called.', 'warn');
107
+ openSearchPicker();
108
+ return true;
109
+ case 'agents':
110
+ openAgentsPicker();
111
+ return true;
112
+ case 'workflow':
113
+ if (!arg) {
114
+ openWorkflowPicker();
115
+ return true;
116
+ }
117
+ void store.setWorkflow?.(arg.trim())
118
+ .then((result) => {
119
+ if (!result) {
120
+ store.pushNotice('Workflow switch is already running.', 'warn');
121
+ return;
122
+ }
123
+ store.pushNotice(workflowSwitchNotice(result), 'info');
124
+ })
125
+ .catch((e) => store.pushNotice(`Couldn’t switch workflow: ${e?.message || e}`, 'error'));
126
+ return true;
127
+ case 'outputstyle': {
128
+ if (state.busy) {
129
+ store.pushNotice('wait for the current turn to finish before /OutputStyle', 'warn');
130
+ return false;
131
+ }
132
+ const value = arg.trim();
133
+ const lower = value.toLowerCase();
134
+ if (!value) {
135
+ openOutputStylePicker();
136
+ return true;
137
+ }
138
+ if (lower === 'status' || lower === 'current' || lower === 'show') {
139
+ try {
140
+ const status = store.getOutputStyle?.();
141
+ const label = status?.current?.label || status?.current?.id || status?.configured || 'Default';
142
+ store.pushNotice(`Output style: ${label}`, 'info');
143
+ } catch (e) {
144
+ store.pushNotice(`Couldn’t read output style: ${e?.message || e}`, 'error');
145
+ }
146
+ return true;
147
+ }
148
+ void store.setOutputStyle?.(value)
149
+ .then((result) => {
150
+ if (!result) {
151
+ store.pushNotice('Output style switch is already running.', 'warn');
152
+ return;
153
+ }
154
+ store.pushNotice(outputStyleNotice(result), 'info');
155
+ })
156
+ .catch((e) => store.pushNotice(`Couldn’t switch output style: ${e?.message || e}`, 'error'));
157
+ return true;
158
+ }
159
+ case 'theme': {
160
+ const value = arg.trim();
161
+ const lower = value.toLowerCase();
162
+ if (!value) {
163
+ openThemePicker();
164
+ return true;
165
+ }
166
+ let themes = [];
167
+ try { themes = store.listThemes?.() || []; } catch (e) {
168
+ store.pushNotice(`could not list themes: ${e?.message || e}`, 'error');
169
+ return true;
170
+ }
171
+ if (lower === 'status' || lower === 'current' || lower === 'show') {
172
+ const id = store.getTheme?.();
173
+ const entry = themes.find((t) => t.id === id);
174
+ store.pushNotice(`Theme: ${entry?.label || id || 'default'}`, 'info');
175
+ return true;
176
+ }
177
+ const match = themes.find((t) => t.id.toLowerCase() === lower)
178
+ || themes.find((t) => String(t.label || '').toLowerCase() === lower);
179
+ if (!match) {
180
+ const ids = themes.map((t) => t.id).join(', ');
181
+ store.pushNotice(`usage: /theme [id]. Available: ${ids}`, 'warn');
182
+ return true;
183
+ }
184
+ try {
185
+ const applied = store.setTheme?.(match.id, { persist: true });
186
+ store.pushNotice(themeNotice(applied || match), 'info');
187
+ } catch (e) {
188
+ store.pushNotice(`Couldn’t set theme: ${e?.message || e}`, 'error');
189
+ }
190
+ return true;
191
+ }
192
+ case 'effort':
193
+ if (state.busy) {
194
+ store.pushNotice('wait for the current turn to finish before /effort', 'warn');
195
+ return false;
196
+ }
197
+ if (!arg) {
198
+ openEffortPicker();
199
+ return true;
200
+ }
201
+ void store.setEffort(arg)
202
+ .then(result => store.pushNotice(result ? `Effort set to ${result}` : 'Effort switch is already running.', result ? 'info' : 'warn'))
203
+ .catch((e) => store.pushNotice(`Couldn’t switch effort: ${e?.message || e}`, 'error'));
204
+ return true;
205
+ case 'fast': {
206
+ if (state.busy) {
207
+ store.pushNotice('wait for the current turn to finish before /fast', 'warn');
208
+ return false;
209
+ }
210
+ const value = String(arg || '').trim().toLowerCase();
211
+ const setTo = value
212
+ ? ['1', 'true', 'yes', 'on', 'enable', 'enabled'].includes(value)
213
+ ? true
214
+ : ['0', 'false', 'no', 'off', 'disable', 'disabled'].includes(value)
215
+ ? false
216
+ : null
217
+ : undefined;
218
+ if (setTo === null) {
219
+ store.pushNotice('usage: /fast [on|off]', 'warn');
220
+ return true;
221
+ }
222
+ const action = setTo === undefined ? store.toggleFast?.() : store.setFast?.(setTo);
223
+ void Promise.resolve(action)
224
+ .then((enabled) => {
225
+ if (enabled === null || enabled === undefined) {
226
+ store.pushNotice('Fast mode switch is already running.', 'warn');
227
+ return;
228
+ }
229
+ store.pushNotice(`Fast mode ${enabled ? 'on' : 'off'} for ${state.provider}/${state.model}`, 'info');
230
+ })
231
+ .catch((e) => store.pushNotice(`Couldn’t update fast mode: ${e?.message || e}`, 'error'));
232
+ return true;
233
+ }
234
+ case 'cwd': {
235
+ const nextPath = arg.trim();
236
+ if (!nextPath) {
237
+ store.pushNotice(`Project path: ${state.cwd}`, 'info');
238
+ return true;
239
+ }
240
+ try {
241
+ store.setCwd?.(nextPath, { message: `Project set: ${projectNameFromPath(nextPath)}` });
242
+ } catch (e) {
243
+ store.pushNotice(`project switch failed: ${e?.message || e}`, 'error');
244
+ }
245
+ return true;
246
+ }
247
+ case 'project': {
248
+ const target = arg.trim();
249
+ if (target) {
250
+ enterProject(target);
251
+ return true;
252
+ }
253
+ openProjectPicker();
254
+ return true;
255
+ }
256
+ case 'tools':
257
+ openToolsPicker(arg.trim());
258
+ return true;
259
+ case 'mcp':
260
+ openMcpPicker();
261
+ return true;
262
+ case 'skills':
263
+ openSkillsPicker();
264
+ return true;
265
+ case 'plugins':
266
+ openPluginsPicker();
267
+ return true;
268
+ case 'hooks':
269
+ openHooksPicker();
270
+ return true;
271
+ case 'providers':
272
+ void openProviderSetupPicker();
273
+ return true;
274
+ case 'channels':
275
+ void openChannelSetupPicker('all');
276
+ return true;
277
+ case 'schedules':
278
+ void openChannelSetupPicker('schedules');
279
+ return true;
280
+ case 'webhooks':
281
+ void openChannelSetupPicker('webhooks');
282
+ return true;
283
+ case 'auth':
284
+ store.pushNotice('/auth moved to /providers', 'info');
285
+ void openProviderSetupPicker();
286
+ return true;
287
+ case 'auth-forget': {
288
+ const providerId = arg.trim();
289
+ if (!providerId) {
290
+ store.pushNotice('usage: /auth-forget <provider>', 'warn');
291
+ return true;
292
+ }
293
+ try {
294
+ store.forgetProviderAuth(providerId);
295
+ } catch (e) {
296
+ store.pushNotice(`auth-forget failed: ${e?.message || e}`, 'error');
297
+ }
298
+ return true;
299
+ }
300
+ case 'memory': {
301
+ if (!arg.trim()) {
302
+ openMemoryCorePicker({ returnTo: null });
303
+ return true;
304
+ }
305
+ void store.memoryControl?.(parseMemoryCommand(arg))
306
+ .catch((e) => store.pushNotice(`memory failed: ${e?.message || e}`, 'error'));
307
+ return true;
308
+ }
309
+ case 'recap': {
310
+ const value = arg.trim().toLowerCase();
311
+ if (value === 'on' || value === 'off') {
312
+ void Promise.resolve(store.setMemoryEnabled?.(value === 'on'))
313
+ .then((next) => {
314
+ if (!next) store.pushNotice('recap setting is busy', 'warn');
315
+ else store.pushNotice(`Recap ${next.enabled ? 'on' : 'off'}`, 'info');
316
+ })
317
+ .catch((e) => store.pushNotice(`recap failed: ${e?.message || e}`, 'error'));
318
+ return true;
319
+ }
320
+ // Bare /recap opens the Memory picker: its top row is the same
321
+ // background-cycles toggle, plus the curated entries below.
322
+ openMemoryCorePicker({ returnTo: null });
323
+ return true;
324
+ }
325
+ case 'recall': {
326
+ const query = arg.trim();
327
+ if (!query) {
328
+ store.pushNotice('usage: /recall <query>', 'warn');
329
+ return true;
330
+ }
331
+ void store.recall?.(query)
332
+ .catch((e) => store.pushNotice(`recall failed: ${e?.message || e}`, 'error'));
333
+ return true;
334
+ }
335
+ case 'autoclear': {
336
+ const value = arg.trim().toLowerCase();
337
+ if (!value) {
338
+ openAutoClearPicker();
339
+ return true;
340
+ }
341
+ try {
342
+ let next;
343
+ if (value === 'status') {
344
+ next = store.getAutoClear?.();
345
+ } else if (value === 'on' || value === 'enable' || value === 'enabled') {
346
+ next = store.setAutoClear?.({ enabled: true });
347
+ } else if (value === 'off' || value === 'disable' || value === 'disabled') {
348
+ next = store.setAutoClear?.({ enabled: false });
349
+ } else {
350
+ next = store.setAutoClear?.({ duration: value });
351
+ }
352
+ if (!next) {
353
+ store.pushNotice('autoclear unavailable', 'warn');
354
+ return true;
355
+ }
356
+ store.pushNotice(`autoclear ${next.enabled ? 'on' : 'off'} · idle ${formatDuration(next.idleMs)}`, 'info');
357
+ } catch (e) {
358
+ store.pushNotice(`autoclear failed: ${e?.message || e}`, 'error');
359
+ }
360
+ return true;
361
+ }
362
+ case 'compact':
363
+ if (state.busy) {
364
+ store.pushNotice('wait for the current turn to finish before /compact', 'warn');
365
+ return false;
366
+ }
367
+ void store.compact()
368
+ .then((r) => {
369
+ if (!r) {
370
+ store.pushNotice('Compact failed.', 'warn');
371
+ return;
372
+ }
373
+ if (r.error) {
374
+ store.pushNotice('Compact failed.', 'error');
375
+ return;
376
+ }
377
+ if (r.changed === false && r.reason) {
378
+ store.pushNotice(r.reason, 'warn');
379
+ return;
380
+ }
381
+ if (r.changed === false) {
382
+ store.pushNotice('nothing to compact', 'warn');
383
+ return;
384
+ }
385
+ store.pushNotice('Compact done.', 'info');
386
+ })
387
+ .catch(() => store.pushNotice('Compact failed.', 'error'));
388
+ return true;
389
+ case 'resume':
390
+ if (state.busy) {
391
+ store.pushNotice('wait for the current turn to finish before /resume', 'warn');
392
+ return false;
393
+ }
394
+ if (arg) {
395
+ void store.resume(arg)
396
+ .then(ok => store.pushNotice(ok ? `Resumed ${arg}` : 'Couldn’t resume chat.', ok ? 'info' : 'warn'))
397
+ .catch((e) => store.pushNotice(`Couldn’t resume chat: ${e?.message || e}`, 'error'));
398
+ } else {
399
+ openResumePicker();
400
+ }
401
+ return true;
402
+ case 'usage':
403
+ openUsagePanel(arg);
404
+ return true;
405
+ case 'context':
406
+ openContextPicker();
407
+ return true;
408
+ case 'settings':
409
+ case 'config':
410
+ openSettingsPicker();
411
+ return true;
412
+ case 'profile':
413
+ openProfilePicker();
414
+ return true;
415
+ case 'update':
416
+ openUpdatePicker();
417
+ return true;
418
+ case 'quit':
419
+ requestExit();
420
+ return true;
421
+ default:
422
+ store.pushNotice(`unknown command: /${cmd}`, 'warn');
423
+ return true;
424
+ }
425
+ };
426
+ return { runSlashCommand };
427
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * text-layout.mjs — prompt-box row-reservation helpers extracted verbatim from
3
+ * App.jsx. Pure grapheme wrap math (displayWidth policy); no React, no App state.
4
+ */
5
+ import { displayWidth } from '../display-width.mjs';
6
+
7
+ const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
8
+
9
+ export function wrappedTextRows(value, width) {
10
+ const w = Math.max(1, Math.floor(Number(width) || 1));
11
+ let row = 0;
12
+ let col = 0;
13
+ const segments = [...graphemeSegmenter.segment(String(value ?? ''))];
14
+ for (let i = 0; i < segments.length; i += 1) {
15
+ const { segment } = segments[i];
16
+ if (segment === '\n') {
17
+ row += 1;
18
+ col = 0;
19
+ continue;
20
+ }
21
+ const segmentWidth = displayWidth(segment);
22
+ if (segmentWidth === 0) continue;
23
+ // Same wrap-ansi hard/wordWrap:false wrapping as caretPosition — keep the
24
+ // reserved row count in lock-step with the caret math so the prompt box
25
+ // height matches ink's actual wrap for wide-char lines.
26
+ if (col > 0 && col + segmentWidth > w) {
27
+ row += 1;
28
+ col = 0;
29
+ }
30
+ col += segmentWidth;
31
+ if (col === w && i < segments.length - 1) {
32
+ row += 1;
33
+ col = 0;
34
+ }
35
+ }
36
+ return row + 1;
37
+ }
38
+
39
+ export function promptContentRows(value, contentColumns) {
40
+ // PromptInput appends a blank trailing cell when the caret is at end-of-input
41
+ // so the native cursor always has a rendered cell. App does not own the cursor
42
+ // offset, so reserve for the worst common case: caret at the end. This can
43
+ // over-reserve by one row at exact wrap boundaries, but never under-reserves
44
+ // and therefore prevents transcript rows from bleeding into the prompt box.
45
+ return wrappedTextRows(`${String(value ?? '')} `, contentColumns);
46
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * theme-effort-pickers.mjs — the Theme picker + Effort picker cluster.
3
+ *
4
+ * Extracted from App.jsx behavior-preservingly as a dependency-injection
5
+ * factory (these openers drive setPicker + the prompt/pane setters and read
6
+ * live App state, so they can't be pure). Every function body is the original
7
+ * App logic verbatim, with closure identifiers threaded through the factory
8
+ * argument. themeNotice moves here alongside its sole external callers being
9
+ * re-exported so the runSlashCommand /theme-by-id path keeps using it.
10
+ */
11
+ import { theme } from '../theme.mjs';
12
+
13
+ export const themeNotice = (applied) => `Theme set to ${applied?.label || applied?.id || 'default'}`;
14
+
15
+ export function createThemeEffortPickers({
16
+ state,
17
+ store,
18
+ setPicker,
19
+ setProviderPrompt,
20
+ setChannelPrompt,
21
+ setHookPrompt,
22
+ setSettingsPrompt,
23
+ setContextPanel,
24
+ closeUsagePanel,
25
+ clean,
26
+ }) {
27
+ const openThemePicker = (options = {}) => {
28
+ const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
29
+ const onboarding = options.onboarding || null;
30
+ let themes = [];
31
+ try {
32
+ themes = store.listThemes?.() || [];
33
+ } catch (e) {
34
+ store.pushNotice(`could not list themes: ${e?.message || e}`, 'error');
35
+ return;
36
+ }
37
+ if (!themes.length) {
38
+ store.pushNotice('no themes available', 'warn');
39
+ return;
40
+ }
41
+ const currentId = store.getTheme?.() || themes.find((t) => t.current)?.id || themes[0]?.id;
42
+ let highlightedThemeId = currentId;
43
+ const items = themes.map((entry) => ({
44
+ value: entry.id,
45
+ label: entry.label || entry.id,
46
+ marker: entry.id === currentId ? '✓' : '',
47
+ description: entry.description || 'color theme',
48
+ _theme: entry,
49
+ }));
50
+ setProviderPrompt(null);
51
+ setChannelPrompt(null);
52
+ setHookPrompt(null);
53
+ setSettingsPrompt(null);
54
+ setContextPanel(null);
55
+ closeUsagePanel();
56
+ const applyTheme = (id, { persist = true } = {}) => {
57
+ try {
58
+ return store.setTheme?.(id, { persist });
59
+ } catch (e) {
60
+ store.pushNotice(`Couldn’t set theme: ${e?.message || e}`, 'error');
61
+ return null;
62
+ }
63
+ };
64
+ // Onboarding: Enter (row) and ConfirmBar Next both persist the highlighted
65
+ // theme, then advance; Back restores the original palette then steps back.
66
+ const saveTheme = (id, { advance = false } = {}) => {
67
+ setPicker(null);
68
+ const applied = applyTheme(id, { persist: true });
69
+ store.pushNotice(themeNotice(applied || { id }), 'info');
70
+ if (advance && onboarding) onboarding.onAdvance?.();
71
+ else if (returnTo) returnTo();
72
+ };
73
+ setPicker({
74
+ title: 'Theme',
75
+ description: 'Choose the color theme that looks best with your terminal.',
76
+ help: onboarding ? undefined : (returnTo ? '↑/↓ Preview · Enter Choose · Esc Settings' : '↑/↓ Preview · Enter Choose · Esc Back'),
77
+ labelWidth: 22,
78
+ initialIndex: Math.max(0, items.findIndex((item) => item.value === currentId)),
79
+ items,
80
+ confirmBar: onboarding ? {
81
+ buttons: [
82
+ { value: 'back', label: '◀ Back' },
83
+ { value: 'next', label: 'Next ▶' },
84
+ ],
85
+ onConfirm: (button) => {
86
+ if (button.value === 'back') {
87
+ // Restore the palette active before the picker opened, then step back.
88
+ if (currentId) applyTheme(currentId, { persist: false });
89
+ onboarding.onBack?.();
90
+ return;
91
+ }
92
+ saveTheme(highlightedThemeId, { advance: true });
93
+ },
94
+ } : (options.confirmBar || null),
95
+ // Live preview while moving: apply (no persist) so the surface re-tones
96
+ // as the selection moves. Enter persists; Esc restores the original.
97
+ onHighlight: (_value, item) => {
98
+ if (item?._theme?.id) {
99
+ highlightedThemeId = item._theme.id;
100
+ applyTheme(item._theme.id, { persist: false });
101
+ }
102
+ },
103
+ onSelect: (_value, item) => {
104
+ const entry = item?._theme;
105
+ if (!entry) return;
106
+ saveTheme(entry.id, { advance: Boolean(onboarding) });
107
+ },
108
+ onCancel: () => {
109
+ setPicker(null);
110
+ // Restore the theme that was active before the picker opened.
111
+ if (currentId) applyTheme(currentId, { persist: false });
112
+ if (onboarding) onboarding.onCancel?.();
113
+ else if (returnTo) returnTo();
114
+ },
115
+ });
116
+ };
117
+
118
+ const openEffortPicker = () => {
119
+ setProviderPrompt(null);
120
+ setChannelPrompt(null);
121
+ setHookPrompt(null);
122
+ setSettingsPrompt(null);
123
+ const items = Array.isArray(state.effortOptions) && state.effortOptions.length > 0
124
+ ? state.effortOptions
125
+ : [];
126
+ if (!items.length) {
127
+ store.pushNotice('Current model has no effort levels.', 'warn');
128
+ return;
129
+ }
130
+ const current = state.effort || items[0]?.value || '';
131
+ const pickerItems = items.map((item) => ({
132
+ ...item,
133
+ marker: item?.value === current ? '✓' : '',
134
+ markerColor: theme.success,
135
+ description: clean(item?.description).toLowerCase() === 'current' ? '' : item?.description,
136
+ }));
137
+ setPicker({
138
+ title: 'Effort',
139
+ description: 'Reasoning effort for the current model.',
140
+ items: pickerItems,
141
+ onSelect: (value) => {
142
+ setPicker(null);
143
+ void store.setEffort(value)
144
+ .then(result => store.pushNotice(result ? `Effort set to ${result}` : 'Effort switch is already running.', result ? 'info' : 'warn'))
145
+ .catch((e) => store.pushNotice(`Couldn’t switch effort: ${e?.message || e}`, 'error'));
146
+ },
147
+ onCancel: () => {
148
+ setPicker(null);
149
+ },
150
+ });
151
+ };
152
+
153
+ return { openThemePicker, openEffortPicker };
154
+ }