mixdog 0.9.3 → 0.9.5

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 (382) hide show
  1. package/README.md +112 -38
  2. package/package.json +10 -3
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/r5-orchestrated-task.json +7 -0
  7. package/scripts/bench/review-tasks.json +20 -0
  8. package/scripts/bench/round-codex.json +114 -0
  9. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  10. package/scripts/bench/round-mixdog-lead.json +269 -0
  11. package/scripts/bench/round-mixdog.json +126 -0
  12. package/scripts/bench/round-r10-bigsample.json +679 -0
  13. package/scripts/bench/round-r11-codexalign.json +257 -0
  14. package/scripts/bench/round-r13-clientmeta.json +464 -0
  15. package/scripts/bench/round-r14-betafeatures.json +466 -0
  16. package/scripts/bench/round-r15-fulldefault.json +462 -0
  17. package/scripts/bench/round-r16-sessionid.json +466 -0
  18. package/scripts/bench/round-r17-wirebytes.json +456 -0
  19. package/scripts/bench/round-r18-prewarm.json +468 -0
  20. package/scripts/bench/round-r19-clean.json +472 -0
  21. package/scripts/bench/round-r20-prewarm-clean.json +475 -0
  22. package/scripts/bench/round-r21-delta-retry.json +473 -0
  23. package/scripts/bench/round-r22-full-probe.json +693 -0
  24. package/scripts/bench/round-r23-itemprobe.json +701 -0
  25. package/scripts/bench/round-r24-shapefix.json +677 -0
  26. package/scripts/bench/round-r25-serial.json +464 -0
  27. package/scripts/bench/round-r26-parallel3.json +671 -0
  28. package/scripts/bench/round-r27-parallel10.json +894 -0
  29. package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
  30. package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
  31. package/scripts/bench/round-r30-instid.json +253 -0
  32. package/scripts/bench/round-r31-upgradeprobe.json +256 -0
  33. package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
  34. package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
  35. package/scripts/bench/round-r34-orchestrated.json +120 -0
  36. package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
  37. package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
  38. package/scripts/bench/round-r4-codex.json +114 -0
  39. package/scripts/bench/round-r4-mixed.json +225 -0
  40. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  41. package/scripts/bench/round-r6-codex.json +114 -0
  42. package/scripts/bench/round-r6-solo.json +257 -0
  43. package/scripts/bench/round-r7-full.json +254 -0
  44. package/scripts/bench/round-r8-fulldefault.json +255 -0
  45. package/scripts/bench-run.mjs +251 -32
  46. package/scripts/freevar-smoke.mjs +95 -0
  47. package/scripts/internal-comms-bench.mjs +3 -4
  48. package/scripts/internal-comms-smoke.mjs +10 -9
  49. package/scripts/model-catalog-audit.mjs +209 -0
  50. package/scripts/model-list-sanitize-test.mjs +37 -0
  51. package/scripts/mouse-probe.mjs +45 -0
  52. package/scripts/output-style-bench.mjs +13 -6
  53. package/scripts/output-style-smoke.mjs +4 -4
  54. package/scripts/provider-toolcall-test.mjs +7 -3
  55. package/scripts/recall-bench.mjs +76 -13
  56. package/scripts/recall-quality-cases.json +12 -0
  57. package/scripts/recall-usecase-cases.json +18 -0
  58. package/scripts/session-bench.mjs +152 -6
  59. package/scripts/tool-smoke.mjs +25 -65
  60. package/scripts/tui-render-smoke.mjs +90 -0
  61. package/scripts/webhook-smoke.mjs +208 -0
  62. package/src/agents/debugger/AGENT.md +4 -1
  63. package/src/agents/heavy-worker/AGENT.md +9 -8
  64. package/src/agents/maintainer/AGENT.md +4 -0
  65. package/src/agents/reviewer/AGENT.md +2 -1
  66. package/src/agents/scheduler-task/AGENT.md +2 -3
  67. package/src/agents/webhook-handler/AGENT.md +2 -3
  68. package/src/agents/worker/AGENT.md +10 -7
  69. package/src/app.mjs +12 -1
  70. package/src/headless-role.mjs +7 -1
  71. package/src/lib/rules-builder.cjs +4 -0
  72. package/src/mixdog-session-runtime.mjs +647 -2056
  73. package/src/output-styles/default.md +30 -9
  74. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  75. package/src/output-styles/minimal.md +8 -6
  76. package/src/output-styles/simple.md +21 -7
  77. package/src/rules/agent/00-common.md +6 -3
  78. package/src/rules/agent/30-explorer.md +16 -5
  79. package/src/rules/lead/01-general.md +5 -5
  80. package/src/rules/lead/lead-brief.md +15 -0
  81. package/src/rules/lead/lead-tool.md +6 -15
  82. package/src/rules/shared/01-tool.md +17 -21
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
  84. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  85. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  86. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  87. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  88. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  89. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  90. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  91. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  92. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  93. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  95. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  97. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  100. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  101. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  102. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  103. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
  104. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  109. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  110. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
  111. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  112. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
  113. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -0
  117. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  119. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
  120. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  121. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  122. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  123. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  124. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  125. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  126. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  127. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  128. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  129. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
  132. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  133. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  134. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  135. package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
  136. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  137. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  138. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  139. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  140. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  143. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  144. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  145. package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
  146. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  147. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +35 -44
  149. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  152. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  155. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  157. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  158. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  159. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  161. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  163. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  164. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  165. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  166. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  167. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  168. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  169. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  170. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  171. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  172. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  173. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  174. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  175. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  176. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  177. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  178. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  179. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  180. package/src/runtime/channels/backends/discord.mjs +27 -318
  181. package/src/runtime/channels/backends/telegram.mjs +8 -12
  182. package/src/runtime/channels/index.mjs +247 -701
  183. package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
  184. package/src/runtime/channels/lib/config.mjs +37 -149
  185. package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
  186. package/src/runtime/channels/lib/event-queue.mjs +78 -13
  187. package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
  188. package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
  189. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  190. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  191. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  192. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  193. package/src/runtime/channels/lib/scheduler.mjs +27 -113
  194. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  195. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  196. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  197. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  198. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  199. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  200. package/src/runtime/channels/lib/webhook/deliveries.mjs +313 -0
  201. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  202. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  203. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  204. package/src/runtime/channels/lib/webhook.mjs +43 -616
  205. package/src/runtime/channels/tool-defs.mjs +11 -130
  206. package/src/runtime/memory/index.mjs +210 -1948
  207. package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
  208. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  209. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  210. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  211. package/src/runtime/memory/lib/ko-morph.mjs +195 -0
  212. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  213. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  214. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  215. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  216. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  217. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  218. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  219. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  220. package/src/runtime/memory/lib/memory-recall-store.mjs +69 -12
  221. package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
  222. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  223. package/src/runtime/memory/lib/query-handlers.mjs +802 -0
  224. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  225. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  226. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  227. package/src/runtime/memory/tool-defs.mjs +5 -13
  228. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  229. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  230. package/src/runtime/search/lib/web-tools.mjs +24 -602
  231. package/src/runtime/shared/atomic-file.mjs +26 -1
  232. package/src/runtime/shared/config.mjs +14 -4
  233. package/src/runtime/shared/launcher-control.mjs +2 -2
  234. package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
  235. package/src/runtime/shared/schedules-store.mjs +13 -3
  236. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  237. package/src/runtime/shared/tool-primitives.mjs +308 -0
  238. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  239. package/src/runtime/shared/tool-surface.mjs +80 -898
  240. package/src/runtime/shared/transcript-writer.mjs +23 -0
  241. package/src/runtime/shared/update-checker.mjs +7 -4
  242. package/src/session-runtime/config-helpers.mjs +119 -2
  243. package/src/session-runtime/config-lifecycle.mjs +232 -0
  244. package/src/session-runtime/cwd-plugins.mjs +226 -0
  245. package/src/session-runtime/mcp-glue.mjs +177 -0
  246. package/src/session-runtime/model-recency.mjs +111 -0
  247. package/src/session-runtime/native-search.mjs +247 -0
  248. package/src/session-runtime/output-styles.mjs +11 -9
  249. package/src/session-runtime/prewarm.mjs +142 -0
  250. package/src/session-runtime/provider-models.mjs +278 -0
  251. package/src/session-runtime/provider-usage.mjs +120 -0
  252. package/src/session-runtime/quick-model-rows.mjs +205 -0
  253. package/src/session-runtime/quick-search-models.mjs +47 -0
  254. package/src/session-runtime/session-hooks.mjs +93 -0
  255. package/src/session-runtime/settings-api.mjs +352 -0
  256. package/src/session-runtime/tool-catalog.mjs +29 -29
  257. package/src/session-runtime/tool-defs.mjs +84 -0
  258. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  259. package/src/session-runtime/workflow.mjs +1 -1
  260. package/src/standalone/agent-tool/helpers.mjs +237 -0
  261. package/src/standalone/agent-tool/notify.mjs +107 -0
  262. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  263. package/src/standalone/agent-tool/render.mjs +152 -0
  264. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  265. package/src/standalone/agent-tool.mjs +138 -669
  266. package/src/standalone/channel-admin.mjs +102 -90
  267. package/src/standalone/channel-worker.mjs +4 -7
  268. package/src/standalone/explore-tool.mjs +64 -14
  269. package/src/standalone/hook-bus/config.mjs +207 -0
  270. package/src/standalone/hook-bus/constants.mjs +90 -0
  271. package/src/standalone/hook-bus/handlers.mjs +481 -0
  272. package/src/standalone/hook-bus/payload.mjs +31 -0
  273. package/src/standalone/hook-bus/rules.mjs +77 -0
  274. package/src/standalone/hook-bus.mjs +77 -870
  275. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  276. package/src/standalone/opencode-go-login.mjs +5 -1
  277. package/src/standalone/provider-admin.mjs +1 -16
  278. package/src/standalone/usage-dashboard.mjs +3 -1
  279. package/src/tui/App.jsx +1059 -8110
  280. package/src/tui/app/app-format.mjs +213 -0
  281. package/src/tui/app/channel-pickers.mjs +508 -0
  282. package/src/tui/app/clipboard.mjs +67 -0
  283. package/src/tui/app/core-memory-picker.mjs +210 -0
  284. package/src/tui/app/extension-pickers.mjs +506 -0
  285. package/src/tui/app/input-parsers.mjs +193 -0
  286. package/src/tui/app/maintenance-pickers.mjs +356 -0
  287. package/src/tui/app/model-options.mjs +334 -0
  288. package/src/tui/app/model-picker.mjs +365 -0
  289. package/src/tui/app/onboarding-steps.mjs +400 -0
  290. package/src/tui/app/project-picker.mjs +247 -0
  291. package/src/tui/app/provider-setup-picker.mjs +580 -0
  292. package/src/tui/app/resume-picker.mjs +55 -0
  293. package/src/tui/app/route-pickers.mjs +419 -0
  294. package/src/tui/app/settings-picker.mjs +489 -0
  295. package/src/tui/app/slash-commands.mjs +101 -0
  296. package/src/tui/app/slash-dispatch.mjs +427 -0
  297. package/src/tui/app/text-layout.mjs +46 -0
  298. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  299. package/src/tui/app/transcript-window.mjs +677 -0
  300. package/src/tui/app/use-mouse-input.mjs +460 -0
  301. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  302. package/src/tui/app/use-transcript-scroll.mjs +512 -0
  303. package/src/tui/app/use-transcript-window.mjs +607 -0
  304. package/src/tui/components/ConfirmBar.jsx +10 -7
  305. package/src/tui/components/Picker.jsx +64 -15
  306. package/src/tui/components/PromptInput.jsx +33 -102
  307. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  308. package/src/tui/components/StatusLine.jsx +69 -15
  309. package/src/tui/components/TextEntryPanel.jsx +11 -0
  310. package/src/tui/components/ToolExecution.jsx +52 -594
  311. package/src/tui/components/TranscriptItem.jsx +105 -0
  312. package/src/tui/components/UsagePanel.jsx +18 -4
  313. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  314. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  315. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  316. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  317. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  318. package/src/tui/display-width.mjs +20 -3
  319. package/src/tui/dist/index.mjs +13553 -12384
  320. package/src/tui/engine/agent-job-feed.mjs +133 -0
  321. package/src/tui/engine/notification-plan.mjs +76 -0
  322. package/src/tui/engine/render-timing.mjs +17 -0
  323. package/src/tui/engine/tool-approval.mjs +94 -0
  324. package/src/tui/engine/tool-card-results.mjs +234 -0
  325. package/src/tui/engine/tool-result-status.mjs +135 -0
  326. package/src/tui/engine.mjs +170 -574
  327. package/src/tui/figures.mjs +5 -0
  328. package/src/tui/index.jsx +65 -1
  329. package/src/tui/input-editing.mjs +2 -2
  330. package/src/tui/markdown/format-token.mjs +4 -1
  331. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  332. package/src/tui/theme.mjs +6 -0
  333. package/src/ui/statusline-agents.mjs +213 -0
  334. package/src/ui/statusline-format.mjs +146 -0
  335. package/src/ui/statusline-segments.mjs +148 -0
  336. package/src/ui/statusline.mjs +77 -501
  337. package/src/ui/tool-card.mjs +0 -1
  338. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  339. package/src/workflows/default/WORKFLOW.md +16 -18
  340. package/src/workflows/sequential/WORKFLOW.md +16 -18
  341. package/vendor/ink/build/display-width.js +19 -3
  342. package/vendor/ink/build/ink.js +112 -7
  343. package/vendor/ink/build/log-update.js +17 -3
  344. package/vendor/ink/build/wrap-text.js +125 -0
  345. package/scripts/_test-folder-dialog.mjs +0 -30
  346. package/scripts/fix-brief-fn.mjs +0 -35
  347. package/scripts/fix-format-tool-surface.mjs +0 -24
  348. package/scripts/fix-tool-exec-visible.mjs +0 -42
  349. package/scripts/patch-agent-brief.mjs +0 -48
  350. package/scripts/patch-app.mjs +0 -21
  351. package/scripts/patch-app2.mjs +0 -18
  352. package/scripts/patch-dist-brief.mjs +0 -96
  353. package/scripts/patch-tool-exec.mjs +0 -70
  354. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  355. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  356. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  357. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  358. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  359. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  360. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  361. package/src/runtime/channels/lib/holidays.mjs +0 -138
  362. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  363. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  364. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  365. package/src/standalone/agent-task-status.test.mjs +0 -76
  366. package/src/tui/components/tool-output-format.test.mjs +0 -399
  367. package/src/tui/display-width.test.mjs +0 -35
  368. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  369. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  370. package/src/tui/input-editing.selection.test.mjs +0 -75
  371. package/src/tui/markdown/format-token.test.mjs +0 -354
  372. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  373. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  374. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  375. package/src/tui/paste-fix.test.mjs +0 -119
  376. package/src/tui/prompt-history-store.test.mjs +0 -52
  377. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  378. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  379. package/src/ui/markdown.test.mjs +0 -70
  380. package/src/ui/statusline-context-label.test.mjs +0 -15
  381. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  382. 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
+ }