mixdog 0.9.151 → 0.9.153

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 (307) hide show
  1. package/LICENSES/browser-import-NOTICE.txt +11 -0
  2. package/NOTICE.md +11 -0
  3. package/README.md +108 -68
  4. package/package.json +17 -4
  5. package/scripts/computer-task-cost-report.mjs +144 -0
  6. package/scripts/run-office-live-tests.mjs +32 -0
  7. package/scripts/tool-stress.mjs +0 -2
  8. package/src/agents/front-worker/AGENT.md +13 -0
  9. package/src/agents/front-worker/agent.json +6 -0
  10. package/src/agents/heavy-worker/AGENT.md +10 -11
  11. package/src/agents/heavy-worker/agent.json +1 -1
  12. package/src/agents/maintainer/AGENT.md +0 -2
  13. package/src/agents/reviewer/AGENT.md +9 -13
  14. package/src/agents/reviewer/agent.json +1 -1
  15. package/src/agents/security/AGENT.md +14 -0
  16. package/src/agents/security/agent.json +6 -0
  17. package/src/agents/worker/AGENT.md +8 -8
  18. package/src/agents/worker/agent.json +1 -1
  19. package/src/agents/writer/AGENT.md +14 -0
  20. package/src/agents/writer/agent.json +6 -0
  21. package/src/defaults/skills/setup/SKILL.md +18 -27
  22. package/src/headless-exec.mjs +3 -10
  23. package/src/headless-exec.test.mjs +2 -7
  24. package/src/rules/agent/00-common.md +4 -7
  25. package/src/rules/lead/01-general.md +0 -3
  26. package/src/rules/shared/10-tool-workflow.md +10 -2
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +183 -26
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.test.mjs +378 -0
  29. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
  30. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +109 -0
  31. package/src/runtime/agent/orchestrator/cache-break-trace.test.mjs +115 -0
  32. package/src/runtime/agent/orchestrator/config.mjs +13 -68
  33. package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +153 -0
  34. package/src/runtime/agent/orchestrator/context/collect.mjs +49 -45
  35. package/src/runtime/agent/orchestrator/mcp/client.mjs +242 -15
  36. package/src/runtime/agent/orchestrator/mcp/features.test.mjs +66 -0
  37. package/src/runtime/agent/orchestrator/mcp/security.test.mjs +36 -20
  38. package/src/runtime/agent/orchestrator/providers/admission-scheduler-metrics.test.mjs +47 -0
  39. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +16 -3
  40. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +54 -33
  41. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +1 -0
  42. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  43. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +7 -0
  44. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -8
  45. package/src/runtime/agent/orchestrator/providers/gemini.mjs +7 -0
  46. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -3
  47. package/src/runtime/agent/orchestrator/providers/lib/provider-replay.mjs +50 -0
  48. package/src/runtime/agent/orchestrator/providers/lib/sse-framing.mjs +93 -0
  49. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +30 -9
  50. package/src/runtime/agent/orchestrator/providers/media-parity.test.mjs +59 -0
  51. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +3 -3
  52. package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +71 -0
  53. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +18 -26
  54. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +8 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -0
  56. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +25 -2
  57. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +35 -4
  58. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +47 -2
  59. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +103 -12
  60. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +13 -0
  61. package/src/runtime/agent/orchestrator/providers/openai-turn-state.mjs +68 -0
  62. package/src/runtime/agent/orchestrator/providers/openai-ws-delta-parity.test.mjs +122 -6
  63. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +36 -11
  64. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +144 -93
  65. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +18 -13
  66. package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +288 -0
  67. package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +719 -0
  68. package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +399 -39
  69. package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +26 -0
  70. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +86 -7
  71. package/src/runtime/agent/orchestrator/session/agent-loop.test.mjs +222 -0
  72. package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.mjs +61 -0
  73. package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.test.mjs +83 -0
  74. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +13 -0
  75. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +26 -2
  76. package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +112 -0
  77. package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +192 -0
  78. package/src/runtime/agent/orchestrator/session/context-usage-restart.test.mjs +72 -0
  79. package/src/runtime/agent/orchestrator/session/context-utils.mjs +100 -15
  80. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +17 -1
  81. package/src/runtime/agent/orchestrator/session/eager-dispatch.test.mjs +74 -0
  82. package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +12 -8
  83. package/src/runtime/agent/orchestrator/session/image-strip-recovery.test.mjs +20 -1
  84. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +110 -2
  85. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +3 -16
  86. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.test.mjs +28 -0
  87. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +2 -2
  88. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +6 -0
  89. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  90. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +6 -0
  91. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +111 -39
  92. package/src/runtime/agent/orchestrator/session/manager/ask-session.test.mjs +46 -0
  93. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -6
  94. package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +3 -0
  95. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +27 -3
  96. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +26 -54
  97. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.test.mjs +63 -1
  98. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +3 -0
  99. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -110
  100. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +580 -0
  101. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +593 -0
  102. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +119 -190
  103. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +115 -0
  104. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +43 -8
  105. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +129 -12
  106. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +9 -2
  107. package/src/runtime/agent/orchestrator/session/store/listing.mjs +8 -1
  108. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +15 -1
  109. package/src/runtime/agent/orchestrator/session/store-live-publication.test.mjs +83 -0
  110. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +36 -2
  111. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +157 -30
  112. package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +64 -0
  113. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +57 -0
  114. package/src/runtime/agent/orchestrator/session/store-summary-visibility.test.mjs +219 -0
  115. package/src/runtime/agent/orchestrator/session/store.mjs +26 -2
  116. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +22 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +14 -0
  118. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -10
  119. package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +30 -14
  120. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +15 -4
  121. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +73 -15
  122. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +76 -2
  123. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +24 -683
  124. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +64 -72
  125. package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +2 -5
  126. package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.mjs +75 -0
  127. package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +24 -0
  128. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +6 -6
  129. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +12 -0
  130. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +4 -1
  131. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +24 -5
  132. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +44 -0
  133. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +12 -2
  134. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +35 -21
  135. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +28 -13
  136. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +54 -0
  137. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
  138. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  139. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +12 -0
  140. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
  141. package/src/runtime/attachments/pdf-extract.mjs +3 -0
  142. package/src/runtime/attachments/pdfjs-runtime.mjs +13 -0
  143. package/src/runtime/bridge-clients.test.mjs +827 -0
  144. package/src/runtime/browser-bridge/action-schema.mjs +251 -0
  145. package/src/runtime/browser-bridge/client.mjs +152 -0
  146. package/src/runtime/browser-bridge/tool-defs.mjs +159 -0
  147. package/src/runtime/computer-bridge/action-schema.mjs +653 -0
  148. package/src/runtime/computer-bridge/client.mjs +231 -0
  149. package/src/runtime/computer-bridge/tool-defs.mjs +33 -0
  150. package/src/runtime/media/store.mjs +18 -8
  151. package/src/runtime/media/store.test.mjs +14 -0
  152. package/src/runtime/memory/index.mjs +1 -1
  153. package/src/runtime/memory/lib/transcript-ingest.mjs +8 -6
  154. package/src/runtime/memory/lib/transcript-ingest.test.mjs +52 -0
  155. package/src/runtime/office/assurance-benchmark.mjs +358 -0
  156. package/src/runtime/office/assurance.mjs +833 -0
  157. package/src/runtime/office/benchmark.mjs +496 -0
  158. package/src/runtime/office/capabilities.mjs +551 -0
  159. package/src/runtime/office/com-adapter.mjs +283 -0
  160. package/src/runtime/office/content-model.mjs +183 -0
  161. package/src/runtime/office/contract-benchmark.mjs +105 -0
  162. package/src/runtime/office/design-library.mjs +1562 -0
  163. package/src/runtime/office/design-system.mjs +1966 -0
  164. package/src/runtime/office/index.mjs +2413 -0
  165. package/src/runtime/office/journal.mjs +82 -0
  166. package/src/runtime/office/office-assurance.test.mjs +371 -0
  167. package/src/runtime/office/office-com-host.ps1 +3781 -0
  168. package/src/runtime/office/office-com-session-host.ps1 +855 -0
  169. package/src/runtime/office/office-live-runtime.test.mjs +819 -0
  170. package/src/runtime/office/office-runtime.test.mjs +2070 -0
  171. package/src/runtime/office/ooxml-validator.mjs +271 -0
  172. package/src/runtime/office/pagination.mjs +132 -0
  173. package/src/runtime/office/pdf-adapter.mjs +591 -0
  174. package/src/runtime/office/pdf-analysis.mjs +372 -0
  175. package/src/runtime/office/pdf-render.mjs +167 -0
  176. package/src/runtime/office/pdf-security.mjs +63 -0
  177. package/src/runtime/office/polish-benchmark.mjs +190 -0
  178. package/src/runtime/office/portable-ooxml.mjs +1867 -0
  179. package/src/runtime/office/quality-live-benchmark.mjs +486 -0
  180. package/src/runtime/office/quality-pipeline.mjs +136 -0
  181. package/src/runtime/office/tabular.mjs +302 -0
  182. package/src/runtime/office/templates/mixdog-executive.pptx +0 -0
  183. package/src/runtime/office/templates/mixdog-executive.pptx.mixdog.json +190 -0
  184. package/src/runtime/office/tool-defs.mjs +113 -0
  185. package/src/runtime/office/visual-diff.mjs +89 -0
  186. package/src/runtime/office/xlsx-assertions.mjs +172 -0
  187. package/src/runtime/office/xlsx-contract.mjs +147 -0
  188. package/src/runtime/shared/agent-route-config.mjs +9 -0
  189. package/src/runtime/shared/child-spawn-gate.mjs +17 -0
  190. package/src/runtime/shared/child-spawn-remote.mjs +69 -7
  191. package/src/runtime/shared/child-spawn-remote.test.mjs +124 -0
  192. package/src/runtime/shared/pristine-execution-contract.json +2 -1
  193. package/src/runtime/shared/provider-api-key.mjs +1 -0
  194. package/src/runtime/shared/session-runtime-health.mjs +91 -0
  195. package/src/runtime/shared/session-runtime-health.test.mjs +53 -0
  196. package/src/runtime/shared/skill-document.mjs +86 -0
  197. package/src/runtime/shared/skill-document.test.mjs +81 -0
  198. package/src/runtime/shared/tool-execution-contract.mjs +16 -0
  199. package/src/runtime/shared/tool-surface.mjs +27 -0
  200. package/src/runtime/shared/turn-snapshot-store.mjs +1 -0
  201. package/src/runtime/shared/turn-snapshot.mjs +32 -9
  202. package/src/runtime/shared/user-cwd.mjs +33 -14
  203. package/src/runtime/shared/user-cwd.test.mjs +43 -0
  204. package/src/session-runtime/agent-disable.test.mjs +20 -1
  205. package/src/session-runtime/context-status.mjs +9 -3
  206. package/src/session-runtime/cwd-plugins.mjs +26 -26
  207. package/src/session-runtime/cwd-plugins.test.mjs +75 -0
  208. package/src/session-runtime/cwd-tool-routing.test.mjs +5 -0
  209. package/src/session-runtime/deferred-tool-delta.mjs +104 -0
  210. package/src/session-runtime/deferred-tool-delta.test.mjs +40 -0
  211. package/src/session-runtime/global-extensions.mjs +26 -0
  212. package/src/session-runtime/global-extensions.test.mjs +28 -0
  213. package/src/session-runtime/global-settings-session-identity.test.mjs +76 -0
  214. package/src/session-runtime/goal-runtime.mjs +1141 -0
  215. package/src/session-runtime/goal-runtime.test.mjs +645 -0
  216. package/src/session-runtime/lifecycle-api.mjs +58 -11
  217. package/src/session-runtime/lifecycle-api.test.mjs +183 -0
  218. package/src/session-runtime/mcp-glue.mjs +49 -47
  219. package/src/session-runtime/plugin-mcp-standard.test.mjs +177 -0
  220. package/src/session-runtime/plugin-mcp.mjs +96 -22
  221. package/src/session-runtime/prewarm.mjs +1 -10
  222. package/src/session-runtime/provider-request-snapshot.mjs +33 -9
  223. package/src/session-runtime/provider-request-snapshot.test.mjs +27 -5
  224. package/src/session-runtime/resource-api-global.test.mjs +109 -0
  225. package/src/session-runtime/resource-api.mjs +168 -116
  226. package/src/session-runtime/runtime-core.mjs +208 -29
  227. package/src/session-runtime/runtime-tunables.mjs +6 -8
  228. package/src/session-runtime/session-lifecycle.mjs +34 -12
  229. package/src/session-runtime/session-title.mjs +2 -2
  230. package/src/session-runtime/session-turn-api.mjs +37 -25
  231. package/src/session-runtime/skills-api.mjs +79 -25
  232. package/src/session-runtime/skills-api.test.mjs +99 -0
  233. package/src/session-runtime/tool-catalog-data.mjs +1 -1
  234. package/src/session-runtime/tool-catalog-schema.mjs +0 -5
  235. package/src/session-runtime/tool-catalog.mjs +42 -112
  236. package/src/session-runtime/tool-defs.mjs +6 -1
  237. package/src/session-runtime/tool-policy-surface.test.mjs +93 -17
  238. package/src/session-runtime/tool-surface.mjs +30 -8
  239. package/src/session-runtime/workflow-agents-api.mjs +8 -25
  240. package/src/session-runtime/workflow.mjs +8 -6
  241. package/src/standalone/agent-dispatch-broker.mjs +5 -9
  242. package/src/standalone/agent-tool/helpers.mjs +2 -2
  243. package/src/standalone/agent-tool/job-views.mjs +3 -1
  244. package/src/standalone/agent-tool/notify.mjs +11 -9
  245. package/src/standalone/agent-tool/notify.test.mjs +37 -0
  246. package/src/standalone/agent-tool/render.mjs +3 -0
  247. package/src/standalone/agent-tool/spawn-flow.mjs +61 -16
  248. package/src/standalone/agent-tool/spawn-flow.test.mjs +41 -0
  249. package/src/standalone/agent-tool/tool-def.mjs +1 -1
  250. package/src/standalone/agent-tool/worker-index.mjs +123 -0
  251. package/src/standalone/agent-tool/worker-index.test.mjs +132 -0
  252. package/src/standalone/agent-tool/worker-rows.mjs +2 -1
  253. package/src/standalone/agent-tool.mjs +57 -10
  254. package/src/standalone/daemon.mjs +113 -15
  255. package/src/standalone/hook-bus/config.mjs +1 -0
  256. package/src/standalone/plugin-admin.mjs +17 -0
  257. package/src/standalone/plugin-admin.test.mjs +7 -0
  258. package/src/standalone/provider-admin.mjs +1 -0
  259. package/src/standalone/session-protocol.mjs +5 -0
  260. package/src/standalone/session-runtime-agent-control-client.mjs +133 -0
  261. package/src/standalone/session-runtime-agent-control-client.test.mjs +71 -0
  262. package/src/standalone/session-runtime-dispatch-cancel.test.mjs +139 -0
  263. package/src/standalone/session-runtime-host-factory.mjs +17 -0
  264. package/src/standalone/session-runtime-host-factory.test.mjs +31 -0
  265. package/src/standalone/session-runtime-host-health.test.mjs +5 -0
  266. package/src/standalone/session-runtime-host.mjs +709 -58
  267. package/src/standalone/session-runtime-inline-host.mjs +258 -0
  268. package/src/standalone/session-runtime-inline-host.test.mjs +385 -0
  269. package/src/standalone/session-runtime-provider-cooldown.mjs +78 -0
  270. package/src/standalone/session-runtime-provider-cooldown.test.mjs +80 -0
  271. package/src/standalone/session-runtime-shard-host.test.mjs +642 -0
  272. package/src/standalone/session-runtime-shard-router.mjs +149 -0
  273. package/src/standalone/session-runtime-shard-router.test.mjs +101 -0
  274. package/src/standalone/session-runtime-worker.mjs +302 -26
  275. package/src/standalone/session-service-agent-tree.test.mjs +984 -0
  276. package/src/standalone/session-service-goal-recovery.test.mjs +177 -0
  277. package/src/standalone/session-service.mjs +711 -11
  278. package/src/tui/app/app-view.jsx +1 -0
  279. package/src/tui/app/core-memory-picker.mjs +2 -0
  280. package/src/tui/app/extension-pickers.mjs +1 -1
  281. package/src/tui/app/maintenance-pickers.mjs +3 -3
  282. package/src/tui/app/model-options.mjs +2 -0
  283. package/src/tui/app/model-picker.mjs +14 -4
  284. package/src/tui/app/panel-handoff.test.mjs +311 -0
  285. package/src/tui/app/project-picker.mjs +1 -0
  286. package/src/tui/app/route-pickers.mjs +21 -6
  287. package/src/tui/app/settings-picker.mjs +26 -3
  288. package/src/tui/app/slash-commands.mjs +1 -0
  289. package/src/tui/app/slash-dispatch.mjs +73 -22
  290. package/src/tui/app/theme-effort-pickers.mjs +7 -2
  291. package/src/tui/components/Picker.jsx +10 -1
  292. package/src/tui/dist/index.mjs +187 -45
  293. package/src/tui/lib/voice-setup.mjs +4 -1
  294. package/src/tui/session/agent-message-source.test.mjs +37 -0
  295. package/src/tui/session/completion-card-restore.test.mjs +50 -0
  296. package/src/tui/session/context-state.mjs +3 -13
  297. package/src/tui/session/goal-continuation.mjs +141 -0
  298. package/src/tui/session/goal-continuation.test.mjs +203 -0
  299. package/src/tui/session/queue-helpers.mjs +5 -2
  300. package/src/tui/session/session-action-surface.test.mjs +190 -0
  301. package/src/tui/session/session-api-ext.mjs +48 -8
  302. package/src/tui/session/session-api.mjs +73 -1
  303. package/src/tui/session/session-flow.mjs +31 -1
  304. package/src/tui/session/turn.mjs +88 -15
  305. package/src/tui/session-local.mjs +39 -3
  306. package/src/vendor/statusline/src/gateway/route-meta.mjs +31 -0
  307. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -301
@@ -301,6 +301,7 @@ export function renderAppView(ctx) {
301
301
  indexMode={picker.indexMode}
302
302
  visibleCount={pickerVisibleRows}
303
303
  fillHeight={expandedOptionPanel}
304
+ loading={picker.loading === true}
304
305
  themeEpoch={state.themeEpoch || 0}
305
306
  confirmBar={picker.confirmBar}
306
307
  />
@@ -39,6 +39,7 @@ export function createCoreMemoryPicker({
39
39
  // Loading state lives in the header description row, not as a fake
40
40
  // selectable menu item.
41
41
  description: 'Loading memories…',
42
+ loading: true,
42
43
  items: [],
43
44
  onSelect: () => {},
44
45
  onCancel: closeMemoryCorePicker,
@@ -113,6 +114,7 @@ export function createCoreMemoryPicker({
113
114
  paintPanel({
114
115
  title: 'Memory · List',
115
116
  description: 'Loading memories…',
117
+ loading: true,
116
118
  items: [],
117
119
  onSelect: () => {},
118
120
  onCancel: () => openMemoryCorePicker(),
@@ -121,7 +121,7 @@ export function createExtensionPickers({
121
121
  };
122
122
 
123
123
  const openMcpPicker = () => {
124
- openMcpServersPicker();
124
+ return openMcpServersPicker();
125
125
  };
126
126
 
127
127
  const skillsStatus = async () => {
@@ -158,7 +158,7 @@ export function createMaintenancePickers({
158
158
  // binds it to the previous owner's epoch, which the open transition (a
159
159
  // panel identity change) supersedes — leaving "Latest version" stuck on
160
160
  // "checking…". Esc after the paint still closes the panel for good.
161
- void Promise.resolve(render({ checking: true }))
161
+ return Promise.resolve(render({ checking: true }))
162
162
  .catch((e) => {
163
163
  store.pushNotice(`update panel failed: ${e?.message || e}`, 'error');
164
164
  return false;
@@ -308,8 +308,8 @@ export function createMaintenancePickers({
308
308
  setSettingsPrompt(null);
309
309
  own.context(null);
310
310
  closeUsagePanel();
311
- if (options.advanced === true) renderAdvanced();
312
- else render();
311
+ return Promise.resolve(options.advanced === true ? renderAdvanced() : render())
312
+ .catch((e) => store.pushNotice(`auto-clear panel failed: ${e?.message || e}`, 'error'));
313
313
  };
314
314
 
315
315
  const openProfilePicker = async (options = {}) => {
@@ -166,6 +166,7 @@ export const providerDisplayName = (provider) => {
166
166
  if (key === 'xai' || key === 'xai-api') return 'xAI API';
167
167
  if (key === 'deepseek' || key === 'deepseek-api') return 'DeepSeek API';
168
168
  if (key === 'opencode-go') return 'OpenCode Go API';
169
+ if (key === 'openrouter') return 'OpenRouter';
169
170
  if (key === 'ollama') return 'Ollama';
170
171
  if (key === 'lmstudio') return 'LM Studio';
171
172
  if (key === 'default') return 'Default';
@@ -182,6 +183,7 @@ export const providerDisplayRank = (provider) => {
182
183
  'antigravity-oauth': 31,
183
184
  'cursor-oauth': 32,
184
185
  'opencode-go': 35,
186
+ openrouter: 36,
185
187
  openai: 40,
186
188
  'openai-api': 40,
187
189
  anthropic: 50,
@@ -59,6 +59,9 @@ export function createModelPicker({
59
59
  const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
60
60
  const returnLabel = String(options.returnLabel || 'Agents');
61
61
  const returnOnNestedCancel = options.returnOnNestedCancel === true;
62
+ const handoffPanel = options.handoffPanel && typeof options.handoffPanel === 'object'
63
+ ? options.handoffPanel
64
+ : null;
62
65
  const cancelModelPicker = () => {
63
66
  modelPickerClosed = true;
64
67
  if (returnTo) returnTo();
@@ -77,6 +80,7 @@ export function createModelPicker({
77
80
  title: options.title || 'Model',
78
81
  description: options.loadingDescription || 'Loading models...',
79
82
  help: returnTo ? `↑/↓ Select · Enter Open · Esc ${returnLabel}` : '↑/↓ Select · Enter Open · Esc Back',
83
+ loading: true,
80
84
  items: [],
81
85
  onCancel: cancelModelPicker,
82
86
  });
@@ -382,7 +386,7 @@ export function createModelPicker({
382
386
  if (typeof options.onImmediateSelect === 'function') {
383
387
  options.onImmediateSelect(routeInput, selected, effort);
384
388
  } else {
385
- own.paint(null);
389
+ own.paint(handoffPanel);
386
390
  }
387
391
  // Post-ack delegation (return to the caller's panel), bound to the
388
392
  // claim AFTER this keypress's own navigation: an Esc between the
@@ -397,10 +401,13 @@ export function createModelPicker({
397
401
  afterSelect();
398
402
  return result;
399
403
  })
400
- .catch((e) => store.pushNotice(`Couldn’t save model: ${e?.message || e}`, 'error'));
404
+ .catch((e) => {
405
+ store.pushNotice(`Couldn’t save model: ${e?.message || e}`, 'error');
406
+ if (handoffPanel) afterSelect();
407
+ });
401
408
  return;
402
409
  }
403
- own.paint(null);
410
+ own.paint(handoffPanel);
404
411
  const afterSelect = own.defer(() => {
405
412
  if (typeof options.onAfterSelect === 'function') options.onAfterSelect();
406
413
  });
@@ -415,7 +422,10 @@ export function createModelPicker({
415
422
  );
416
423
  if (ok) afterSelect();
417
424
  })
418
- .catch((e) => store.pushNotice(`Couldn’t switch model: ${e?.message || e}`, 'error'));
425
+ .catch((e) => {
426
+ store.pushNotice(`Couldn’t switch model: ${e?.message || e}`, 'error');
427
+ if (handoffPanel) afterSelect();
428
+ });
419
429
  };
420
430
  const renderProviderModels = () => {
421
431
  const providerModelItems = buildProviderModelItems(models, provider, activeRoute);
@@ -0,0 +1,311 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+
4
+ import { createModelPicker } from './model-picker.mjs';
5
+ import { shouldSupersedePanelEpoch, supersedePanelEpoch } from './panel-epoch.mjs';
6
+ import { createPanelSurface } from './panel-surface.mjs';
7
+ import { createRoutePickers } from './route-pickers.mjs';
8
+ import { createSettingsPicker } from './settings-picker.mjs';
9
+ import { createSlashDispatch } from './slash-dispatch.mjs';
10
+ import { createThemeEffortPickers } from './theme-effort-pickers.mjs';
11
+
12
+ function deferred() {
13
+ let resolve;
14
+ let reject;
15
+ const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
16
+ return { promise, resolve, reject };
17
+ }
18
+
19
+ const flush = async () => {
20
+ await new Promise((resolve) => setTimeout(resolve, 0));
21
+ await new Promise((resolve) => setImmediate(resolve));
22
+ };
23
+
24
+ function createPanelHost() {
25
+ let current = null;
26
+ const painted = [];
27
+ const setPicker = (next) => {
28
+ const previous = current;
29
+ current = typeof next === 'function' ? next(previous) : next;
30
+ if (shouldSupersedePanelEpoch(previous, current)) supersedePanelEpoch();
31
+ painted.push(current);
32
+ };
33
+ const surface = createPanelSurface({
34
+ setPicker,
35
+ setContextPanel: () => {},
36
+ setUsagePanel: () => {},
37
+ });
38
+ return { current: () => current, painted, setPicker, surface };
39
+ }
40
+
41
+ const HANDOFF = {
42
+ title: 'Settings',
43
+ description: 'Applying selection...',
44
+ help: 'Esc Close',
45
+ indexMode: 'never',
46
+ pickerKey: 'settings-handoff',
47
+ loading: true,
48
+ items: [],
49
+ };
50
+
51
+ function settingsDeps(host, store, opened = {}) {
52
+ const noop = () => {};
53
+ return {
54
+ store: { pushNotice: noop, ...store },
55
+ state: { model: 'gpt-5', provider: 'openai', workflow: { id: 'default' } },
56
+ surface: host.surface,
57
+ setProviderPrompt: noop,
58
+ setSettingsPrompt: noop,
59
+ settingsHeavyCacheRef: { current: null },
60
+ settingsRequestRef: { current: 0 },
61
+ formatDuration: (ms) => `${ms}ms`,
62
+ displayModelName: () => 'GPT-5',
63
+ routeModelLabel: () => '(unset)',
64
+ workflowDisplayName: () => 'Default',
65
+ workflowSwitchNotice: () => 'workflow switched',
66
+ themeNotice: () => 'theme set',
67
+ openModelPicker: (options) => { opened.model = options; },
68
+ openWebSearchPicker: noop,
69
+ openAgentsPicker: noop,
70
+ openWorkflowPicker: (options) => { opened.workflow = options; },
71
+ openOutputStylePicker: (options) => { opened.outputStyle = options; },
72
+ openProviderSetupPicker: noop,
73
+ openThemePicker: (options) => { opened.theme = options; },
74
+ openAutoClearPicker: noop,
75
+ openProfilePicker: noop,
76
+ openMcpPicker: noop,
77
+ openPluginsPicker: noop,
78
+ openHooksPicker: noop,
79
+ openSkillsPicker: noop,
80
+ openMemoryCorePicker: noop,
81
+ openUpdatePicker: noop,
82
+ };
83
+ }
84
+
85
+ test('Settings paints its loading destination before the daemon snapshot settles', async () => {
86
+ supersedePanelEpoch();
87
+ const host = createPanelHost();
88
+ host.setPicker({ title: 'Theme' });
89
+ const snapshotGate = deferred();
90
+ const opened = {};
91
+ const { openSettingsPicker } = createSettingsPicker(settingsDeps(
92
+ host,
93
+ { getSettingsSnapshot: () => snapshotGate.promise },
94
+ opened,
95
+ ));
96
+
97
+ const opening = openSettingsPicker();
98
+ assert.equal(host.current()?.title, 'Settings');
99
+ assert.match(host.current()?.description || '', /Loading settings/i);
100
+ assert.notEqual(host.current(), null);
101
+
102
+ snapshotGate.resolve({});
103
+ await opening;
104
+ const settings = host.current();
105
+ assert.equal(settings?.title, 'Settings');
106
+ assert.ok(settings.items.length > 0);
107
+
108
+ for (const action of ['model', 'workflow', 'output-style', 'theme']) {
109
+ const item = settings.items.find((entry) => entry._action === action);
110
+ settings.onSelect(item.value, item);
111
+ }
112
+ for (const options of [opened.model, opened.workflow, opened.outputStyle, opened.theme]) {
113
+ assert.equal(options?.handoffPanel?.title, 'Settings');
114
+ assert.equal(options?.handoffPanel?.pickerKey, 'settings-handoff');
115
+ assert.equal(options?.handoffPanel?.loading, true);
116
+ }
117
+ });
118
+
119
+ test('Model save keeps a Settings handoff panel visible until the write settles', async () => {
120
+ supersedePanelEpoch();
121
+ const host = createPanelHost();
122
+ const saveGate = deferred();
123
+ const { openModelPicker } = createModelPicker({
124
+ store: {
125
+ pushNotice: () => {},
126
+ listProviderModels: async () => [],
127
+ setRoute: () => saveGate.promise,
128
+ },
129
+ getState: () => ({ provider: 'openai', model: 'gpt-5', effort: null, fast: false }),
130
+ surface: host.surface,
131
+ setProviderPrompt: () => {},
132
+ setSettingsPrompt: () => {},
133
+ providerModelsCacheRef: {
134
+ current: {
135
+ at: Date.now(),
136
+ models: [{ provider: 'openai', id: 'gpt-5', display: 'GPT-5' }],
137
+ },
138
+ },
139
+ webSearchModelsCacheRef: { current: { models: [], at: 0 } },
140
+ modelPickerRequestRef: { current: 0 },
141
+ clearModelCaches: () => {},
142
+ modelSwitchNotice: () => 'switched',
143
+ openProviderSetupPicker: () => {},
144
+ });
145
+
146
+ await openModelPicker({
147
+ handoffPanel: HANDOFF,
148
+ onAfterSelect: () => host.setPicker({ title: 'Settings', items: [{ value: 'done' }] }),
149
+ });
150
+ const providers = host.current();
151
+ providers.onSelect(providers.items[0].value, providers.items[0]);
152
+ const models = host.current();
153
+ models.onSelect(models.items[0].value, models.items[0]);
154
+
155
+ assert.equal(host.current(), HANDOFF);
156
+ assert.equal(host.painted.at(-1), HANDOFF);
157
+
158
+ saveGate.resolve(true);
159
+ await flush();
160
+ assert.equal(host.current()?.title, 'Settings');
161
+ assert.notEqual(host.current(), HANDOFF);
162
+ });
163
+
164
+ function routePicker(host, store) {
165
+ return createRoutePickers({
166
+ store: { pushNotice: () => {}, ...store },
167
+ state: {},
168
+ surface: host.surface,
169
+ setProviderPrompt: () => {},
170
+ setSettingsPrompt: () => {},
171
+ closeUsagePanel: () => {},
172
+ clean: (value) => value,
173
+ routeLabel: () => 'route',
174
+ agentModelParts: () => [],
175
+ agentModelProfile: () => 'profile',
176
+ workflowSwitchNotice: () => 'workflow switched',
177
+ openModelPicker: () => {},
178
+ });
179
+ }
180
+
181
+ test('Workflow and output-style saves keep the Settings handoff visible until their writes settle', async () => {
182
+ supersedePanelEpoch();
183
+ const workflowHost = createPanelHost();
184
+ const workflowGate = deferred();
185
+ const workflow = routePicker(workflowHost, {
186
+ listWorkflows: async () => [{ id: 'solo', name: 'Solo', active: true }],
187
+ setWorkflow: () => workflowGate.promise,
188
+ });
189
+ await workflow.openWorkflowPicker({
190
+ returnTo: () => workflowHost.setPicker({ title: 'Settings', items: [{ value: 'done' }] }),
191
+ handoffPanel: HANDOFF,
192
+ });
193
+ const workflowPanel = workflowHost.current();
194
+ workflowPanel.onSelect(workflowPanel.items[0].value, workflowPanel.items[0]);
195
+ assert.equal(workflowHost.current(), HANDOFF);
196
+
197
+ workflowGate.resolve({ id: 'solo' });
198
+ await flush();
199
+ assert.equal(workflowHost.current()?.title, 'Settings');
200
+
201
+ const styleHost = createPanelHost();
202
+ const styleGate = deferred();
203
+ const outputStyle = routePicker(styleHost, {
204
+ listOutputStyles: async () => ({
205
+ current: { id: 'simple' },
206
+ styles: [{ id: 'simple', label: 'Simple' }],
207
+ }),
208
+ setOutputStyle: () => styleGate.promise,
209
+ });
210
+ await outputStyle.openOutputStylePicker({
211
+ returnTo: () => styleHost.setPicker({ title: 'Settings', items: [{ value: 'done' }] }),
212
+ handoffPanel: HANDOFF,
213
+ });
214
+ const stylePanel = styleHost.current();
215
+ stylePanel.onSelect(stylePanel.items[0].value, stylePanel.items[0]);
216
+ assert.equal(styleHost.current(), HANDOFF);
217
+
218
+ styleGate.resolve({ current: { id: 'simple', label: 'Simple' } });
219
+ await flush();
220
+ assert.equal(styleHost.current()?.title, 'Settings');
221
+ });
222
+
223
+ test('Theme selection hands directly to Settings without a null panel', () => {
224
+ supersedePanelEpoch();
225
+ const host = createPanelHost();
226
+ const { openThemePicker } = createThemeEffortPickers({
227
+ state: {},
228
+ store: {
229
+ listThemes: () => [{ id: 'basic', label: 'Basic' }],
230
+ getTheme: () => 'basic',
231
+ setTheme: (id) => ({ id, label: 'Basic' }),
232
+ pushNotice: () => {},
233
+ },
234
+ surface: host.surface,
235
+ setProviderPrompt: () => {},
236
+ setSettingsPrompt: () => {},
237
+ closeUsagePanel: () => {},
238
+ clean: (value) => value,
239
+ });
240
+
241
+ openThemePicker({
242
+ returnTo: () => host.setPicker({ title: 'Settings', items: [{ value: 'done' }] }),
243
+ handoffPanel: HANDOFF,
244
+ });
245
+ const theme = host.current();
246
+ const paintedBefore = host.painted.length;
247
+ theme.onSelect(theme.items[0].value, theme.items[0]);
248
+
249
+ const transitionPaints = host.painted.slice(paintedBefore);
250
+ assert.deepEqual(transitionPaints.map((panel) => panel?.title), ['Settings', 'Settings']);
251
+ assert.ok(transitionPaints.every(Boolean));
252
+ });
253
+
254
+ function workflowSlashDispatch(host, openWorkflowPicker, notices = []) {
255
+ return createSlashDispatch({
256
+ state: { busy: false, commandBusy: false },
257
+ store: { pushNotice: (message, tone) => notices.push([message, tone]) },
258
+ normalizeSlashCommandName: (value) => value,
259
+ surface: host.surface,
260
+ closeUsagePanel: () => {},
261
+ openWorkflowPicker,
262
+ });
263
+ }
264
+
265
+ test('slash option entry keeps a loading panel until the async picker paints', async () => {
266
+ supersedePanelEpoch();
267
+ const host = createPanelHost();
268
+ const openGate = deferred();
269
+ const { runSlashCommand } = workflowSlashDispatch(host, () => {
270
+ const own = host.surface.claim();
271
+ return openGate.promise.then(() => own.paint({
272
+ title: 'Workflow',
273
+ description: 'Select active workflow.',
274
+ items: [{ value: 'solo', label: 'Solo' }],
275
+ }));
276
+ });
277
+
278
+ assert.equal(runSlashCommand('workflow'), true);
279
+ assert.equal(host.current()?._kind, 'slash-loading:workflow');
280
+ assert.equal(host.current()?.title, 'Workflow');
281
+ assert.equal(host.current()?.loading, true);
282
+
283
+ openGate.resolve();
284
+ await flush();
285
+ assert.equal(host.current()?.title, 'Workflow');
286
+ assert.equal(host.current()?._kind, undefined);
287
+ assert.ok(host.current()?.items.length > 0);
288
+ });
289
+
290
+ test('Esc on slash loading prevents the pending picker from appearing later', async () => {
291
+ supersedePanelEpoch();
292
+ const host = createPanelHost();
293
+ const openGate = deferred();
294
+ const { runSlashCommand } = workflowSlashDispatch(host, () => {
295
+ const own = host.surface.claim();
296
+ return openGate.promise.then(() => own.paint({
297
+ title: 'Workflow',
298
+ items: [{ value: 'solo', label: 'Solo' }],
299
+ }));
300
+ });
301
+
302
+ runSlashCommand('workflow');
303
+ const loading = host.current();
304
+ assert.equal(loading?._kind, 'slash-loading:workflow');
305
+ loading.onCancel();
306
+ assert.equal(host.current(), null);
307
+
308
+ openGate.resolve();
309
+ await flush();
310
+ assert.equal(host.current(), null);
311
+ });
@@ -134,6 +134,7 @@ export function createProjectPicker({
134
134
  description: 'Opening folder picker… choose a folder in the dialog window.',
135
135
  help: 'Waiting for the system folder dialog…',
136
136
  indexMode: 'never',
137
+ loading: true,
137
138
  items: [],
138
139
  onSelect: () => {},
139
140
  onCancel: () => {},
@@ -188,6 +188,9 @@ export function createRoutePickers({
188
188
 
189
189
  const openWorkflowPicker = async (options = {}) => {
190
190
  const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
191
+ const handoffPanel = options.handoffPanel && typeof options.handoffPanel === 'object'
192
+ ? options.handoffPanel
193
+ : null;
191
194
  const own = surface.claim();
192
195
  let workflows = [];
193
196
  try {
@@ -225,21 +228,26 @@ export function createRoutePickers({
225
228
  // Clear-and-continue: the flow keeps the surface it just emptied, so
226
229
  // the post-ack hop below is bound to THIS keypress. An Esc before the
227
230
  // switch acks cancels the hop instead of re-opening Settings.
228
- own.paint(null);
231
+ own.paint(handoffPanel);
229
232
  const returnAfterSwitch = own.defer(() => { if (returnTo) returnTo(); });
230
233
  void store.setWorkflow?.(workflow.id)
231
234
  .then((result) => {
232
235
  if (!result) {
233
236
  store.pushNotice('Workflow switch is already running.', 'warn');
237
+ if (handoffPanel) returnAfterSwitch();
234
238
  return;
235
239
  }
236
240
  store.pushNotice(workflowSwitchNotice(result), 'info');
237
241
  returnAfterSwitch();
238
242
  })
239
- .catch((e) => store.pushNotice(`Couldn’t switch workflow: ${e?.message || e}`, 'error'));
243
+ .catch((e) => {
244
+ store.pushNotice(`Couldn’t switch workflow: ${e?.message || e}`, 'error');
245
+ if (handoffPanel) returnAfterSwitch();
246
+ });
240
247
  },
241
248
  onCancel: () => {
242
- own.close();
249
+ if (handoffPanel) own.paint(handoffPanel);
250
+ else own.close();
243
251
  if (returnTo) returnTo();
244
252
  },
245
253
  });
@@ -247,6 +255,9 @@ export function createRoutePickers({
247
255
 
248
256
  const openOutputStylePicker = async (options = {}) => {
249
257
  const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
258
+ const handoffPanel = options.handoffPanel && typeof options.handoffPanel === 'object'
259
+ ? options.handoffPanel
260
+ : null;
250
261
  // Onboarding mode: Enter (row select) and ConfirmBar Next must both persist
251
262
  // the chosen style, then advance. `onboarding.onAdvance/onBack` drive the
252
263
  // wizard; the confirm bar is built here so both paths share `saveStyle`.
@@ -284,7 +295,7 @@ export function createRoutePickers({
284
295
  // Onboarding advance: keep the current picker visible during the async
285
296
  // style switch so the screen never flashes empty between steps; the next
286
297
  // step (or finishOnboarding) replaces/clears the picker itself.
287
- if (!(advance && onboarding)) own.paint(null);
298
+ if (!(advance && onboarding)) own.paint(handoffPanel);
288
299
  // Post-ack delegation (next onboarding step, or back to the caller):
289
300
  // bound after this keypress's own navigation so an Esc while the switch
290
301
  // is in flight cannot paint a panel over the new surface.
@@ -301,7 +312,10 @@ export function createRoutePickers({
301
312
  }
302
313
  advanceAfterSave();
303
314
  })
304
- .catch((e) => store.pushNotice(`Couldn’t switch output style: ${e?.message || e}`, 'error'));
315
+ .catch((e) => {
316
+ store.pushNotice(`Couldn’t switch output style: ${e?.message || e}`, 'error');
317
+ if (handoffPanel) advanceAfterSave();
318
+ });
305
319
  };
306
320
  own.paint({
307
321
  title: 'Output Style',
@@ -334,7 +348,8 @@ export function createRoutePickers({
334
348
  saveStyle(style.id, { advance: Boolean(onboarding) });
335
349
  },
336
350
  onCancel: () => {
337
- own.close();
351
+ if (handoffPanel) own.paint(handoffPanel);
352
+ else own.close();
338
353
  if (onboarding) onboarding.onCancel?.();
339
354
  else if (returnTo) returnTo();
340
355
  },
@@ -41,6 +41,15 @@ export function createSettingsPicker({
41
41
  openMemoryCorePicker,
42
42
  openUpdatePicker,
43
43
  }) {
44
+ const settingsHandoffPanel = (description = 'Loading settings...') => ({
45
+ title: 'Settings',
46
+ description,
47
+ help: 'Esc Close',
48
+ indexMode: 'never',
49
+ pickerKey: 'settings-handoff',
50
+ loading: true,
51
+ items: [],
52
+ });
44
53
  // Build generation guard. Every open/refresh takes a ticket and Esc bumps it,
45
54
  // so a slow snapshot from a superseded (or already closed) build can never
46
55
  // re-open Settings. The ref is owned by App so it survives this per-render
@@ -60,6 +69,10 @@ export function createSettingsPicker({
60
69
  // caught here instead.
61
70
  const own = surface.claim();
62
71
  const light = opts.light === true;
72
+ // A full Settings open usually follows another option panel. Paint the
73
+ // destination synchronously before the daemon snapshot so the empty-chat
74
+ // welcome logo cannot appear between the old panel and Settings.
75
+ if (!light && !own.paint(settingsHandoffPanel())) return;
63
76
  const overrides = opts.overrides || null;
64
77
  const heavyCache = light ? settingsHeavyCacheRef.current : null;
65
78
  // ONE round-trip for the whole panel: on a daemon-backed store each getter
@@ -448,14 +461,24 @@ export function createSettingsPicker({
448
461
  else if (item._action === 'memory-enabled') toggleMemory();
449
462
  else if (item._action === 'memory-cycles') toggleMemoryCycles();
450
463
  else if (item._action === 'voice') applyVoice();
451
- else if (item._action === 'output-style') openOutputStylePicker({ returnTo: openSettingsPicker });
452
- else if (item._action === 'theme') openThemePicker({ returnTo: openSettingsPicker });
453
- else if (item._action === 'workflow') openWorkflowPicker({ returnTo: openSettingsPicker });
464
+ else if (item._action === 'output-style') openOutputStylePicker({
465
+ returnTo: openSettingsPicker,
466
+ handoffPanel: settingsHandoffPanel('Applying output style...'),
467
+ });
468
+ else if (item._action === 'theme') openThemePicker({
469
+ returnTo: openSettingsPicker,
470
+ handoffPanel: settingsHandoffPanel('Applying theme...'),
471
+ });
472
+ else if (item._action === 'workflow') openWorkflowPicker({
473
+ returnTo: openSettingsPicker,
474
+ handoffPanel: settingsHandoffPanel('Switching workflow...'),
475
+ });
454
476
  else if (item._action === 'model') openModelPicker({
455
477
  returnTo: openSettingsPicker,
456
478
  returnLabel: 'Settings',
457
479
  returnOnNestedCancel: true,
458
480
  onAfterSelect: openSettingsPicker,
481
+ handoffPanel: settingsHandoffPanel('Switching model...'),
459
482
  });
460
483
  else if (item._action === 'websearch') openWebSearchPicker({
461
484
  returnTo: openSettingsPicker,
@@ -7,6 +7,7 @@ export const SLASH_COMMANDS = [
7
7
  { name: 'clear', usage: '/clear', aliases: ['new'], aliasUsage: ['new'], description: 'Start a fresh chat' },
8
8
  { name: 'project', usage: '/project', aliases: ['projects'], aliasUsage: ['projects'], showAliasUsage: false, params: '[path]', description: 'Switch working directory (project)' },
9
9
  { name: 'compact', usage: '/compact', description: 'Compact older conversation context' },
10
+ { name: 'goal', usage: '/goal', params: '[objective --time 1h|status|pause|resume|edit|complete|clear]', description: 'Run and manage a durable session Goal' },
10
11
  { name: 'autoclear', usage: '/autoclear', params: '[on|off|duration]', description: 'Reduce cache-miss cost after long idle gaps' },
11
12
  { name: 'resume', usage: '/resume', params: '[id]', description: 'Resume a saved chat' },
12
13
  { name: 'context', usage: '/context', description: 'Show current context surface' },