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,400 @@
1
+ /**
2
+ * onboarding-steps.mjs — the first-run onboarding wizard step cluster.
3
+ *
4
+ * Extracted from App.jsx behavior-preservingly as a dependency-injection
5
+ * factory. Every function body is the original App logic verbatim, with closure
6
+ * identifiers threaded through the factory argument. Cross-references between
7
+ * steps stay inside this factory; deps pointing at later-defined App fns
8
+ * (openProviderSetupPicker, openThemePicker, openOutputStylePicker) thread as
9
+ * lazy getter wrappers so they resolve the live opener at call time.
10
+ */
11
+ import { theme } from '../theme.mjs';
12
+ import { SEARCH_DEFAULT_ROUTE, isSearchDefaultRoute } from './app-format.mjs';
13
+ import {
14
+ normalizeModelOptions,
15
+ modelDescription,
16
+ agentModelParts,
17
+ routeFromModel,
18
+ } from './model-options.mjs';
19
+
20
+ export function createOnboardingSteps({
21
+ store,
22
+ setPicker,
23
+ setProviderPrompt,
24
+ setChannelPrompt,
25
+ setHookPrompt,
26
+ setSettingsPrompt,
27
+ setOnboardingActive,
28
+ onboardingRef,
29
+ providerModelsCacheRef,
30
+ onboardingPrefetchSeqRef,
31
+ openProviderSetupPicker,
32
+ openThemePicker,
33
+ openOutputStylePicker,
34
+ }) {
35
+ // First-run onboarding is a 4-step wizard. Each step's ROOT screen carries a
36
+ // ConfirmBar (Back/Next, Finish on the last step); the Picker owns the bar
37
+ // focus and only fires onConfirm from the bar. Nested depths (API-key entry,
38
+ // model route picker) render without a ConfirmBar so
39
+ // their own key semantics are untouched and step-switching is disabled there.
40
+ // Esc/cancel during onboarding = confirm skip. Mark onboarding complete
41
+ // (routes/agents/provider untouched) so it does not reopen next launch;
42
+ // `mixdog --onboarding` (forceOnboarding) still reopens regardless.
43
+ const onboardingWarnReopen = () => {
44
+ setOnboardingActive(false);
45
+ try {
46
+ store.skipOnboarding?.();
47
+ store.pushNotice('Setup skipped. Run `mixdog --onboarding` to set up later.', 'info');
48
+ } catch (e) {
49
+ store.pushNotice(`Couldn’t save skip: ${e?.message || e}`, 'error');
50
+ }
51
+ };
52
+
53
+ // Warm the Step 2 data (provider models + agent roster) in the background as
54
+ // soon as Step 1 opens, so advancing to Step 2 renders instantly instead of
55
+ // flashing an empty panel while the async load runs.
56
+ const prefetchOnboardingStep2 = () => {
57
+ if (!Array.isArray(onboardingRef.current.providerModels) || onboardingRef.current.providerModels.length === 0) {
58
+ const seq = onboardingPrefetchSeqRef.current;
59
+ void Promise.resolve(store.listProviderModels?.())
60
+ .then((models) => {
61
+ // Drop a stale result: if the provider cache was invalidated (auth
62
+ // change) while this load was in flight, its generation moved on.
63
+ if (seq !== onboardingPrefetchSeqRef.current) return;
64
+ if (Array.isArray(models) && models.length) {
65
+ onboardingRef.current.providerModels = models;
66
+ providerModelsCacheRef.current = { models, at: Date.now() };
67
+ }
68
+ })
69
+ .catch(() => { /* Step 2 falls back to its own load on entry. */ });
70
+ }
71
+ if (!Array.isArray(onboardingRef.current.agents) || onboardingRef.current.agents.length === 0) {
72
+ try {
73
+ const roster = (store.listAgents?.() || []).map((a) => ({ id: a.id, label: a.label || a.id, description: a.description || '' }));
74
+ if (roster.length) onboardingRef.current.agents = roster;
75
+ } catch { /* Step 2 retries on entry. */ }
76
+ }
77
+ };
78
+
79
+ const openOnboardingAuthStep = async () => {
80
+ prefetchOnboardingStep2();
81
+ // Load the provider setup BEFORE opening the picker so Step 1 renders the
82
+ // real list in one frame instead of flashing the "Checking Providers"
83
+ // placeholder (that swap is what looked like a jump on entry). On failure,
84
+ // fall back to the picker's own in-panel loading path.
85
+ let preloadedSetup = null;
86
+ try {
87
+ preloadedSetup = await store.getProviderSetup?.();
88
+ } catch { /* openProviderSetupPicker will show its loading frame + error. */ }
89
+ void openProviderSetupPicker({
90
+ title: 'First Run · Step 1/4 · Provider Auth',
91
+ returnTo: () => openOnboardingAuthStep(),
92
+ preloadedSetup,
93
+ confirmBar: {
94
+ buttons: [{ value: 'next', label: 'Next ▶' }],
95
+ // Keep Step 1 visible while Step 2's async model load runs; the next
96
+ // step replaces the picker itself, so no blank frame in between.
97
+ onConfirm: () => { void openOnboardingWorkflowStep(); },
98
+ },
99
+ onCancel: onboardingWarnReopen,
100
+ });
101
+ };
102
+
103
+ const openOnboardingThemeStep = () => {
104
+ openThemePicker({
105
+ onboarding: {
106
+ onAdvance: () => openOnboardingOutputStyleStep(),
107
+ onBack: () => void openOnboardingWorkflowStep(),
108
+ onCancel: onboardingWarnReopen,
109
+ },
110
+ });
111
+ };
112
+
113
+ const openOnboardingOutputStyleStep = () => {
114
+ openOutputStylePicker({
115
+ onboarding: {
116
+ isLastStep: true,
117
+ onAdvance: () => finishOnboarding(),
118
+ onBack: () => openOnboardingThemeStep(),
119
+ onCancel: onboardingWarnReopen,
120
+ },
121
+ });
122
+ };
123
+
124
+ const finishOnboarding = () => {
125
+ const defaultRoute = onboardingRef.current.defaultRoute;
126
+ const searchRoute = onboardingRef.current.searchRoute || null;
127
+ const overrides = onboardingRef.current.agentRoutes || {};
128
+ const hasOverrides = Object.keys(overrides).length > 0;
129
+ setPicker(null);
130
+ setOnboardingActive(false);
131
+ const done = () => store.pushNotice('First-run setup complete.', 'info');
132
+ const failed = (e) => store.pushNotice(`Couldn’t save setup: ${e?.message || e}`, 'error');
133
+ // Branch 1 — Main Model set: full persist. Agents without an explicit
134
+ // override are sent; untouched agents are left out so the backend never
135
+ // overwrites them (they follow the Main Model dynamically at runtime).
136
+ if (defaultRoute) {
137
+ void store.completeOnboarding?.({
138
+ defaultRoute,
139
+ defaultProvider: defaultRoute.provider,
140
+ ...(hasOverrides ? { agentRoutes: { ...overrides } } : {}),
141
+ ...(searchRoute ? { searchRoute } : {}),
142
+ }).then(done).catch(failed);
143
+ return;
144
+ }
145
+ // Branch 2 — Main unset but some Search/agent picks exist: partial persist.
146
+ // Only the explicit overrides are sent (no defaultRoute/defaultProvider); the
147
+ // backend skips agents lacking a route and marks onboarding complete.
148
+ if (hasOverrides || searchRoute) {
149
+ void store.completeOnboarding?.({
150
+ ...(hasOverrides ? { agentRoutes: { ...overrides } } : {}),
151
+ ...(searchRoute ? { searchRoute } : {}),
152
+ }).then(done).catch(failed);
153
+ return;
154
+ }
155
+ // Branch 3 — nothing configured: mark done only, leave config untouched.
156
+ try {
157
+ store.skipOnboarding?.();
158
+ } catch (e) {
159
+ failed(e);
160
+ return;
161
+ }
162
+ done();
163
+ };
164
+
165
+ // Onboarding Step 2 per-target model picker. `target` is either the pseudo
166
+ // slot 'lead' (Main Model) or a real agent id from listAgents(). No
167
+ // recommendation logic: the current effective route is pre-highlighted, and
168
+ // the plain model list is shown. Selecting Main Model updates defaultRoute;
169
+ // agents that have no explicit override keep inheriting it.
170
+ const openOnboardingRoleModelPicker = async (target) => {
171
+ const isLead = target === 'lead';
172
+ const isSearch = target === 'search';
173
+ // Search uses the search-capable model list; lead/agent use provider models.
174
+ let models;
175
+ if (isSearch) {
176
+ let searchModels = [];
177
+ try {
178
+ searchModels = await Promise.resolve(store.listSearchModels?.() || []);
179
+ } catch (e) {
180
+ store.pushNotice(`could not list search models: ${e?.message || e}`, 'warn');
181
+ }
182
+ models = normalizeModelOptions(searchModels || []);
183
+ if (models.length === 0) {
184
+ store.pushNotice('no native search models available; connect OpenAI, Grok, Gemini, or Anthropic', 'warn');
185
+ void openOnboardingWorkflowStep();
186
+ return;
187
+ }
188
+ } else {
189
+ models = normalizeModelOptions(onboardingRef.current.providerModels || []);
190
+ if (models.length === 0) {
191
+ store.pushNotice('no provider models available; open /providers to sign in', 'warn');
192
+ openOnboardingAuthStep();
193
+ return;
194
+ }
195
+ }
196
+ const overrides = onboardingRef.current.agentRoutes || {};
197
+ // Current effective route for pre-marking: Main/Search show their own stored
198
+ // route (or none); agents show their explicit override only (unset = none,
199
+ // so we don't falsely mark the Main Model row on an untouched agent).
200
+ const currentRoute = isLead
201
+ ? (onboardingRef.current.defaultRoute || null)
202
+ : isSearch
203
+ ? (onboardingRef.current.searchRoute || null)
204
+ : (overrides[target] || null);
205
+ const routeMatchesModel = (route, m) => route?.provider === m.provider && route?.model === m.id;
206
+ // Non-lead targets get a leading "Default" row that makes the target follow
207
+ // the Main Model at runtime. For agents this clears the override (null);
208
+ // for search this stores the SEARCH_DEFAULT marker route. "Default" is the
209
+ // pre-marked row when the target is unset (agent) or on the marker (search).
210
+ const isDefaultSelected = isSearch
211
+ ? (!currentRoute || isSearchDefaultRoute(currentRoute))
212
+ : !currentRoute;
213
+ const isUnset = isDefaultSelected;
214
+ const modelItems = models.map((m) => ({
215
+ value: `${m.provider}:${m.id}`,
216
+ label: m.display || m.id,
217
+ marker: routeMatchesModel(currentRoute, m) ? '✓' : '',
218
+ markerColor: theme.success,
219
+ description: modelDescription(m),
220
+ _model: m,
221
+ }));
222
+ const items = isLead
223
+ ? modelItems
224
+ : [
225
+ {
226
+ value: '__default__',
227
+ label: 'Default',
228
+ marker: isUnset ? '✓' : '',
229
+ markerColor: theme.success,
230
+ description: 'follows Main Model',
231
+ _default: true,
232
+ },
233
+ ...modelItems,
234
+ ];
235
+ const matchIdx = models.findIndex((m) => routeMatchesModel(currentRoute, m));
236
+ const initialIndex = isLead
237
+ ? Math.max(0, matchIdx)
238
+ : (isUnset || matchIdx < 0 ? 0 : matchIdx + 1);
239
+ const label = isLead
240
+ ? 'Main'
241
+ : isSearch
242
+ ? 'Search'
243
+ : (onboardingRef.current.agents || []).find((a) => a.id === target)?.label || target;
244
+ setPicker({
245
+ title: `First Run · ${label}`,
246
+ description: isLead
247
+ ? 'Pick the main model. Agents inherit this unless individually changed.'
248
+ : isSearch
249
+ ? 'Pick the native web-search model, or Default to follow the Main Model.'
250
+ : `Pick the model for ${label}, or Default to follow the Main Model.`,
251
+ initialIndex,
252
+ items,
253
+ onSelect: (_value, item) => {
254
+ // "Default" → clear the override so this target follows the Main Model.
255
+ if (item?._default) {
256
+ if (isSearch) {
257
+ // Store the SEARCH_DEFAULT marker so finish persists it and the
258
+ // runtime follows the Main Model (not a null that drops the field).
259
+ onboardingRef.current.searchRoute = { ...SEARCH_DEFAULT_ROUTE };
260
+ } else {
261
+ const nextOverrides = { ...(onboardingRef.current.agentRoutes || {}) };
262
+ delete nextOverrides[target];
263
+ onboardingRef.current.agentRoutes = nextOverrides;
264
+ }
265
+ void openOnboardingWorkflowStep();
266
+ return;
267
+ }
268
+ const next = item?._model ? routeFromModel(item._model) : null;
269
+ if (!next) {
270
+ store.pushNotice('select a provider model first', 'warn');
271
+ void openOnboardingWorkflowStep();
272
+ return;
273
+ }
274
+ if (isLead) {
275
+ onboardingRef.current.defaultRoute = next;
276
+ } else if (isSearch) {
277
+ onboardingRef.current.searchRoute = next;
278
+ } else {
279
+ onboardingRef.current.agentRoutes = {
280
+ ...(onboardingRef.current.agentRoutes || {}),
281
+ [target]: next,
282
+ };
283
+ }
284
+ void openOnboardingWorkflowStep();
285
+ },
286
+ onCancel: () => {
287
+ void openOnboardingWorkflowStep();
288
+ },
289
+ });
290
+ };
291
+
292
+ const openOnboardingWorkflowStep = async () => {
293
+ if (!Array.isArray(onboardingRef.current.providerModels) || onboardingRef.current.providerModels.length === 0) {
294
+ try {
295
+ onboardingRef.current.providerModels = await store.listProviderModels();
296
+ providerModelsCacheRef.current = { models: onboardingRef.current.providerModels, at: Date.now() };
297
+ } catch (e) {
298
+ onboardingRef.current.providerModels = [];
299
+ store.pushNotice(`could not list models: ${e?.message || e}`, 'warn');
300
+ }
301
+ }
302
+ const models = onboardingRef.current.providerModels || [];
303
+ if (models.length === 0) {
304
+ onboardingRef.current.defaultRoute = null;
305
+ onboardingRef.current.agentRoutes = {};
306
+ store.pushNotice('no provider models available; open /providers to sign in', 'warn');
307
+ openOnboardingAuthStep();
308
+ return;
309
+ }
310
+ // Main Model stays unset until the user picks one; no auto-recommendation.
311
+ // Load the real agent roster once (explore/maintainer/worker/heavy-worker/
312
+ // reviewer/debugger). Each agent defaults to the Main Model unless the user
313
+ // set an explicit override in agentRoutes.
314
+ if (!Array.isArray(onboardingRef.current.agents) || onboardingRef.current.agents.length === 0) {
315
+ try {
316
+ onboardingRef.current.agents = (store.listAgents?.() || []).map((a) => ({ id: a.id, label: a.label || a.id, description: a.description || '' }));
317
+ } catch (e) {
318
+ onboardingRef.current.agents = [];
319
+ store.pushNotice(`could not list agents: ${e?.message || e}`, 'warn');
320
+ }
321
+ }
322
+ const defaultRoute = onboardingRef.current.defaultRoute;
323
+ const searchRoute = onboardingRef.current.searchRoute || null;
324
+ const overrides = onboardingRef.current.agentRoutes || {};
325
+ const agents = onboardingRef.current.agents || [];
326
+ setProviderPrompt(null);
327
+ setChannelPrompt(null);
328
+ setHookPrompt(null);
329
+ setSettingsPrompt(null);
330
+ setPicker({
331
+ title: 'First Run · Step 2/4 · Models',
332
+ description: 'Set the Main Model; each agent inherits it unless changed.',
333
+ indexMode: 'always',
334
+ labelWidth: 18,
335
+ metaWidth: 33,
336
+ items: [
337
+ {
338
+ value: 'main-model',
339
+ label: 'Main',
340
+ metaParts: agentModelParts(defaultRoute),
341
+ description: 'main chat, planning, and agent default',
342
+ _action: 'slot',
343
+ _target: 'lead',
344
+ },
345
+ {
346
+ value: 'search-model',
347
+ label: 'Search',
348
+ // Marker route = follow Main Model → show a hint, not 'default/default'.
349
+ metaParts: isSearchDefaultRoute(searchRoute)
350
+ ? [{ text: '(follows main)', width: 17 }, { text: '', width: 6 }, { text: '', width: 4 }]
351
+ : agentModelParts(searchRoute),
352
+ description: 'native search model',
353
+ _action: 'slot',
354
+ _target: 'search',
355
+ },
356
+ ...agents.map((agent) => ({
357
+ value: `agent:${agent.id}`,
358
+ label: agent.label,
359
+ metaParts: agentModelParts(overrides[agent.id] || null),
360
+ description: agent.description || '',
361
+ _action: 'slot',
362
+ _target: agent.id,
363
+ })),
364
+ ],
365
+ confirmBar: {
366
+ buttons: [
367
+ { value: 'back', label: '◀ Back' },
368
+ { value: 'next', label: 'Next ▶' },
369
+ ],
370
+ onConfirm: (button) => {
371
+ // Both neighbors are async (Step 1 preloads provider setup; theme
372
+ // list is sync but keep symmetric) — leave Step 2 visible until the
373
+ // next picker replaces it to avoid a blank frame.
374
+ if (button.value === 'back') openOnboardingAuthStep();
375
+ else openOnboardingThemeStep();
376
+ },
377
+ },
378
+ onSelect: (_value, item) => {
379
+ if (item._action === 'slot') {
380
+ openOnboardingRoleModelPicker(item._target);
381
+ }
382
+ },
383
+ onCancel: () => {
384
+ setPicker(null);
385
+ onboardingWarnReopen();
386
+ },
387
+ });
388
+ };
389
+
390
+ return {
391
+ onboardingWarnReopen,
392
+ prefetchOnboardingStep2,
393
+ openOnboardingAuthStep,
394
+ openOnboardingThemeStep,
395
+ openOnboardingOutputStyleStep,
396
+ finishOnboarding,
397
+ openOnboardingRoleModelPicker,
398
+ openOnboardingWorkflowStep,
399
+ };
400
+ }
@@ -0,0 +1,247 @@
1
+ /**
2
+ * project-picker.mjs — the project selector / create / rename / enter cluster.
3
+ *
4
+ * Extracted from App.jsx behavior-preservingly. This cluster is ref/state
5
+ * coupled (it drives setPicker + a fan of prompt setters), so it's delivered as
6
+ * a dependency-injection factory rather than pure functions: App calls
7
+ * createProjectPicker({...}) once and destructures the returned builders. Every
8
+ * function body is the original App logic, verbatim, with closure identifiers
9
+ * (state, store, setPicker, the prompt setters, and the projects.mjs helpers)
10
+ * threaded in through the factory argument.
11
+ */
12
+ export function createProjectPicker({
13
+ state,
14
+ store,
15
+ setPicker,
16
+ setProviderPrompt,
17
+ setChannelPrompt,
18
+ setHookPrompt,
19
+ setSettingsPrompt,
20
+ setContextPanel,
21
+ closeUsagePanel,
22
+ listProjects,
23
+ addProject,
24
+ touchProjectSelected,
25
+ resolveProjectPath,
26
+ projectNameFromPath,
27
+ pickFolder,
28
+ }) {
29
+ const buildProjectPickerState = ({ initialEntry = false } = {}) => {
30
+ let projects = [];
31
+ try {
32
+ projects = listProjects() || [];
33
+ } catch {
34
+ projects = [];
35
+ }
36
+ const currentPath = String(state.cwd || process.cwd() || '');
37
+ const items = [];
38
+ // Registered projects (store order via listProjects).
39
+ for (const project of projects) {
40
+ if (!project?.path) continue;
41
+ items.push({
42
+ value: project.path,
43
+ label: project.name || project.path,
44
+ meta: project.path,
45
+ _project: project,
46
+ });
47
+ }
48
+ // Last row: implicit current-directory shortcut (not persisted).
49
+ items.push({
50
+ value: '__use_current__',
51
+ label: 'Current Path',
52
+ meta: currentPath,
53
+ _action: 'current',
54
+ });
55
+ return {
56
+ kind: 'project',
57
+ title: 'Project',
58
+ description: 'Choose a project.',
59
+ help: initialEntry
60
+ ? '↑/↓ Select · Enter Open · c Create · r Rename'
61
+ : '↑/↓ Select · Enter Open · c Create · r Rename · Esc Back',
62
+ indexMode: 'always',
63
+ labelWidth: 18,
64
+ metaWidth: 40,
65
+ items,
66
+ onSelect: (_value, item) => {
67
+ if (item?._action === 'new') {
68
+ beginNewProject();
69
+ return;
70
+ }
71
+ if (item?._action === 'current') {
72
+ setPicker(null);
73
+ try {
74
+ store.setCwd?.(currentPath, {
75
+ notice: !initialEntry,
76
+ message: `Project set: ${projectNameFromPath(currentPath)}`,
77
+ });
78
+ } catch (e) {
79
+ store.pushNotice(`project switch failed: ${e?.message || e}`, 'error');
80
+ }
81
+ return;
82
+ }
83
+ setPicker(null);
84
+ const project = item?._project;
85
+ if (project?.path) enterProject(project.path, { notice: !initialEntry });
86
+ },
87
+ onKey: (input, _key, item) => {
88
+ if (input === 'c' || input === 'C') {
89
+ beginNewProject();
90
+ return;
91
+ }
92
+ // 'r' renames the highlighted registered project (not the current-dir
93
+ // shortcut or the create row).
94
+ if ((input === 'r' || input === 'R') && item?._project?.path) {
95
+ beginRenameProject(item._project);
96
+ }
97
+ },
98
+ onCancel: () => {
99
+ setPicker(null);
100
+ },
101
+ };
102
+ };
103
+
104
+ // Open the manual path-entry flow. The user types a directory path; on submit
105
+ // we register it (and offer to create it if missing). Used as a
106
+ // fallback when no native folder dialog is available.
107
+ const beginNewProjectManual = () => {
108
+ setPicker(null);
109
+ setProviderPrompt(null);
110
+ setChannelPrompt(null);
111
+ setHookPrompt(null);
112
+ setContextPanel(null);
113
+ closeUsagePanel();
114
+ setSettingsPrompt({
115
+ kind: 'project-new',
116
+ label: 'New project · Path',
117
+ hint: 'Type a directory path. The folder name becomes the project name.',
118
+ });
119
+ };
120
+
121
+ // Begin "create project": open the OS-native folder picker. The project picker
122
+ // stays mounted (swapped to a non-interactive "Opening folder picker…" panel)
123
+ // while the native dialog is open, so the welcome banner/layout stay put and
124
+ // the prompt remains disabled (input is gated on `!!picker`). On a chosen
125
+ // folder we register; on cancel we return to the project picker;
126
+ // when no dialog tool exists we fall back to manual path typing.
127
+ const beginNewProject = () => {
128
+ setProviderPrompt(null);
129
+ setChannelPrompt(null);
130
+ setHookPrompt(null);
131
+ setContextPanel(null);
132
+ closeUsagePanel();
133
+ // Keep an overlay up (kind:'project' so the banner/height stay reserved) but
134
+ // make it inert: no selectable items, navigation is a no-op until resolve.
135
+ setPicker({
136
+ kind: 'project',
137
+ title: 'Project',
138
+ description: 'Opening folder picker… choose a folder in the dialog window.',
139
+ help: 'Waiting for the system folder dialog…',
140
+ indexMode: 'never',
141
+ items: [],
142
+ onSelect: () => {},
143
+ onCancel: () => {},
144
+ });
145
+ void pickFolder({
146
+ title: 'Select a project folder',
147
+ initialPath: String(state.cwd || process.cwd() || ''),
148
+ })
149
+ .then((result) => {
150
+ if (!result || result.available === false) {
151
+ // No native dialog on this system → manual typing.
152
+ beginNewProjectManual();
153
+ return;
154
+ }
155
+ if (!result.path) {
156
+ // User cancelled the dialog → back to the project list.
157
+ openProjectPicker();
158
+ return;
159
+ }
160
+ registerProject(result.path);
161
+ })
162
+ .catch(() => {
163
+ beginNewProjectManual();
164
+ });
165
+ };
166
+
167
+ // Register a project in the picker list without switching this session's cwd.
168
+ const registerProject = (rawPath) => {
169
+ const path = resolveProjectPath(rawPath);
170
+ if (!path) {
171
+ store.pushNotice('project path is required', 'warn');
172
+ return;
173
+ }
174
+ try {
175
+ const project = addProject(path);
176
+ if (project?.name) store.pushNotice(`project added: ${project.name}`, 'info');
177
+ openProjectPicker();
178
+ } catch (e) {
179
+ store.pushNotice(`project add failed: ${e?.message || e}`, 'error');
180
+ }
181
+ };
182
+
183
+ // Switch the active working directory to a registered/created project path.
184
+ const enterProject = (rawPath, options = {}) => {
185
+ const path = resolveProjectPath(rawPath);
186
+ if (!path) {
187
+ store.pushNotice('project path is required', 'warn');
188
+ return;
189
+ }
190
+ try {
191
+ // Switch cwd first; only persist the project once the runtime accepts it,
192
+ // so an invalid/missing path can never be written to projects.json.
193
+ store.setCwd?.(path, {
194
+ notice: options?.notice !== false,
195
+ message: `Project set: ${projectNameFromPath(path)}`,
196
+ });
197
+ addProject(path);
198
+ touchProjectSelected(path);
199
+ } catch (e) {
200
+ store.pushNotice(`project switch failed: ${e?.message || e}`, 'error');
201
+ }
202
+ };
203
+
204
+ // Begin renaming a registered project's display name. Opens a text prompt
205
+ // seeded with the current name; submitting persists via renameProject and
206
+ // returns to the project picker. The path is never changed.
207
+ const beginRenameProject = (project) => {
208
+ if (!project?.path) return;
209
+ setPicker(null);
210
+ setProviderPrompt(null);
211
+ setChannelPrompt(null);
212
+ setHookPrompt(null);
213
+ setContextPanel(null);
214
+ closeUsagePanel();
215
+ setSettingsPrompt({
216
+ kind: 'project-rename',
217
+ label: 'Rename project',
218
+ hint: 'Set a display name. Leave blank to reset to the folder name.',
219
+ projectPath: project.path,
220
+ initialValue: project.name || '',
221
+ });
222
+ };
223
+
224
+ // Open the project selector, styled like the Model picker: numbered rows with
225
+ // a Name column + Path column. The list always opens (even when empty) and
226
+ // lists registered projects first, then a trailing "Current Path" shortcut.
227
+ // Creating a new project is available via the picker-level c shortcut.
228
+ const openProjectPicker = () => {
229
+ setProviderPrompt(null);
230
+ setChannelPrompt(null);
231
+ setHookPrompt(null);
232
+ setSettingsPrompt(null);
233
+ setContextPanel(null);
234
+ closeUsagePanel();
235
+ setPicker(buildProjectPickerState());
236
+ };
237
+
238
+ return {
239
+ buildProjectPickerState,
240
+ beginNewProjectManual,
241
+ beginNewProject,
242
+ registerProject,
243
+ enterProject,
244
+ beginRenameProject,
245
+ openProjectPicker,
246
+ };
247
+ }