mixdog 0.9.147 → 0.9.149

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 (265) hide show
  1. package/package.json +23 -3
  2. package/scripts/build-voice-runtime-unix.sh +101 -0
  3. package/scripts/embedding-quality-probe.mjs +96 -0
  4. package/scripts/generate-voice-runtime-manifest.mjs +300 -0
  5. package/scripts/generate-voice-runtime-manifest.test.mjs +135 -0
  6. package/scripts/native-binary-arch.mjs +30 -13
  7. package/scripts/native-binary-arch.test.mjs +40 -4
  8. package/scripts/plan-voice-runtime-release.mjs +73 -0
  9. package/scripts/prepare-native-assets.mjs +50 -9
  10. package/scripts/prune-embedding-runtime.mjs +3 -3
  11. package/scripts/sync-voice-runtime-manifest.mjs +46 -0
  12. package/scripts/verify-release-assets.mjs +106 -6
  13. package/scripts/verify-voice-runtime-release.mjs +89 -0
  14. package/scripts/voice-runtime-config.json +52 -0
  15. package/src/app.mjs +42 -5
  16. package/src/headless-exec.mjs +58 -32
  17. package/src/headless-exec.test.mjs +74 -13
  18. package/src/rules/lead/01-general.md +2 -0
  19. package/src/rules/shared/10-tool-workflow.md +7 -4
  20. package/src/rules/shared/30-exploration.md +5 -1
  21. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +28 -9
  22. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +6 -1
  23. package/src/runtime/agent/orchestrator/config.mjs +53 -18
  24. package/src/runtime/agent/orchestrator/mcp/cancellation.test.mjs +139 -0
  25. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +34 -5
  26. package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -10
  27. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +5 -0
  28. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +98 -0
  29. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.test.mjs +90 -0
  30. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +67 -16
  31. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +4 -82
  32. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +0 -5
  33. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +62 -121
  34. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +48 -0
  35. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.test.mjs +215 -0
  36. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +44 -48
  37. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +54 -31
  38. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +56 -2
  39. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +34 -2
  40. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +61 -1
  41. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +6 -1
  42. package/src/runtime/agent/orchestrator/providers/gemini.mjs +61 -15
  43. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +120 -1
  44. package/src/runtime/agent/orchestrator/providers/lib/env-utils.mjs +9 -0
  45. package/src/runtime/agent/orchestrator/providers/lib/stream-outcome.mjs +0 -3
  46. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +187 -84
  47. package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +32 -9
  48. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +78 -33
  49. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +21 -0
  50. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +36 -36
  51. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +16 -6
  52. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +50 -213
  53. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +135 -126
  54. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +20 -11
  55. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +112 -36
  56. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +220 -50
  57. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +112 -9
  58. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +11 -0
  59. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +130 -60
  60. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -2
  61. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +28 -10
  62. package/src/runtime/agent/orchestrator/providers/registry.mjs +122 -95
  63. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +69 -3
  64. package/src/runtime/agent/orchestrator/providers/tool-stream-state.mjs +4 -22
  65. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +7 -14
  66. package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +128 -0
  67. package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +5 -0
  68. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +27 -48
  69. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +19 -14
  70. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +9 -43
  71. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +5 -1
  72. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +43 -5
  73. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +15 -2
  74. package/src/runtime/agent/orchestrator/session/manager/pending-messages-persistence.test.mjs +325 -0
  75. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +599 -195
  76. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +22 -0
  77. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +9 -2
  78. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +2 -0
  79. package/src/runtime/agent/orchestrator/session/manager.mjs +3 -0
  80. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +26 -16
  81. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +23 -11
  82. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +118 -3
  83. package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +332 -0
  84. package/src/runtime/agent/orchestrator/session/store.mjs +38 -1
  85. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +27 -6
  86. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -20
  87. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +291 -128
  88. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -9
  89. package/src/runtime/agent/orchestrator/tools/builtin/edit-encoding-eol.test.mjs +224 -0
  90. package/src/runtime/agent/orchestrator/tools/builtin/edit-sequential-occupation.test.mjs +53 -3
  91. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +255 -25
  92. package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +87 -0
  93. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +1 -1
  94. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +132 -9
  95. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +10 -0
  96. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-pattern-fanout.mjs +29 -3
  97. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/tools/builtin/list-tool-integrity.test.mjs +14 -0
  99. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +180 -31
  100. package/src/runtime/agent/orchestrator/tools/builtin/local-search-telemetry.mjs +4 -1
  101. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +32 -5
  102. package/src/runtime/agent/orchestrator/tools/builtin/native-search-health.test.mjs +5 -2
  103. package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +10 -1
  104. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +38 -10
  105. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +12 -0
  106. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +31 -1
  107. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +11 -1
  108. package/src/runtime/agent/orchestrator/tools/builtin/read-windows.mjs +22 -4
  109. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +6 -8
  110. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +25 -24
  111. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +152 -133
  112. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +114 -42
  113. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +129 -15
  114. package/src/runtime/agent/orchestrator/tools/code-graph/loose-file-target.test.mjs +72 -0
  115. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +7 -8
  116. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +4 -4
  117. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +201 -19
  118. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +26 -3
  119. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +10 -1
  120. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-search-scope.test.mjs +55 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +115 -25
  122. package/src/runtime/agent/orchestrator/tools/code-graph/text-span-mask.test.mjs +268 -0
  123. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -3
  124. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +296 -46
  125. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.test.mjs +104 -0
  126. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +136 -0
  127. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +127 -38
  128. package/src/runtime/agent/orchestrator/tools/patch/engine-contract-gate.test.mjs +661 -0
  129. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +350 -9
  130. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +418 -44
  131. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +27 -3
  132. package/src/runtime/agent/orchestrator/tools/patch/patch-codec-eol.test.mjs +552 -0
  133. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +65 -15
  134. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  135. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +284 -109
  136. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +51 -21
  137. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +44 -27
  138. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +6 -1
  139. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
  140. package/src/runtime/attachments/store.mjs +11 -3
  141. package/src/runtime/channels/data/voice-runtime-manifest.json +46 -70
  142. package/src/runtime/channels/lib/event-queue.mjs +51 -41
  143. package/src/runtime/channels/lib/scheduler.mjs +23 -28
  144. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -17
  145. package/src/runtime/channels/lib/voice-runtime-fetcher.test.mjs +17 -0
  146. package/src/runtime/channels/lib/webhook.mjs +39 -20
  147. package/src/runtime/channels/lib/whisper-server.mjs +46 -1
  148. package/src/runtime/channels/lib/worker-main.mjs +3 -1
  149. package/src/runtime/media/adapters/gemini-video.mjs +20 -4
  150. package/src/runtime/media/jobs.mjs +13 -0
  151. package/src/runtime/media/renditions.mjs +7 -1
  152. package/src/runtime/media/store.mjs +42 -22
  153. package/src/runtime/memory/index.mjs +4 -9
  154. package/src/runtime/memory/lib/agent-ipc.mjs +1 -6
  155. package/src/runtime/memory/lib/archive-security.test.mjs +21 -1
  156. package/src/runtime/memory/lib/core-memory-store.mjs +28 -50
  157. package/src/runtime/memory/lib/embedding-model-config.mjs +37 -1
  158. package/src/runtime/memory/lib/embedding-model-config.test.mjs +33 -0
  159. package/src/runtime/memory/lib/embedding-provider.mjs +34 -12
  160. package/src/runtime/memory/lib/embedding-worker.mjs +37 -5
  161. package/src/runtime/memory/lib/ko-morph.mjs +46 -15
  162. package/src/runtime/memory/lib/memory-action-handlers.mjs +18 -1
  163. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -18
  164. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +1 -18
  165. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +98 -45
  166. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +71 -0
  167. package/src/runtime/memory/lib/memory-cycle2-shared.test.mjs +78 -0
  168. package/src/runtime/memory/lib/memory-cycle2.mjs +37 -3
  169. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  170. package/src/runtime/memory/lib/memory-embedding-migration.test.mjs +50 -0
  171. package/src/runtime/memory/lib/memory-recall-store.mjs +9 -24
  172. package/src/runtime/memory/lib/memory.mjs +50 -12
  173. package/src/runtime/memory/lib/pg/process.mjs +1 -10
  174. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -9
  175. package/src/runtime/memory/lib/query-handlers.mjs +2 -4
  176. package/src/runtime/memory/lib/session-ingest-runtime.mjs +2 -2
  177. package/src/runtime/shared/background-tasks.mjs +15 -0
  178. package/src/runtime/shared/background-tasks.test.mjs +78 -0
  179. package/src/runtime/shared/child-guardian.mjs +2 -10
  180. package/src/runtime/shared/pid-liveness.mjs +27 -0
  181. package/src/runtime/shared/pristine-execution-contract.json +0 -1
  182. package/src/runtime/shared/runtime-root.mjs +1 -11
  183. package/src/runtime/shared/service-discovery.mjs +1 -17
  184. package/src/runtime/shared/singleton-owner.mjs +1 -11
  185. package/src/runtime/shared/staged-update.mjs +2 -12
  186. package/src/runtime/shared/tool-status.mjs +5 -1
  187. package/src/runtime/shared/tool-surface.mjs +15 -7
  188. package/src/runtime/shared/turn-snapshot-store.mjs +168 -0
  189. package/src/runtime/shared/turn-snapshot.mjs +137 -13
  190. package/src/runtime/shared/turn-worktree-snapshot.mjs +110 -18
  191. package/src/runtime/shared/webhook-session-run.mjs +16 -2
  192. package/src/runtime/web-search/lib/http-fetch.mjs +34 -4
  193. package/src/runtime/web-search/lib/state.mjs +15 -15
  194. package/src/runtime/web-search/lib/web-tools.mjs +46 -5
  195. package/src/session-runtime/context-status.mjs +4 -12
  196. package/src/session-runtime/runtime-core.mjs +20 -1
  197. package/src/session-runtime/session-lifecycle.mjs +9 -0
  198. package/src/standalone/agent-dispatch-broker.mjs +33 -2
  199. package/src/standalone/agent-tool/job-task-reconcile.mjs +78 -0
  200. package/src/standalone/agent-tool/notify.mjs +3 -1
  201. package/src/standalone/agent-tool/spawn-flow.mjs +52 -70
  202. package/src/standalone/agent-tool/tag-registry.mjs +116 -33
  203. package/src/standalone/agent-tool/tool-def.mjs +2 -2
  204. package/src/standalone/agent-tool/worker-index.mjs +8 -11
  205. package/src/standalone/agent-tool/worker-rows.mjs +4 -1
  206. package/src/standalone/agent-tool.mjs +20 -55
  207. package/src/standalone/agent-watchdog-registry.mjs +28 -4
  208. package/src/standalone/channel-client.mjs +47 -15
  209. package/src/standalone/channel-transport.mjs +44 -25
  210. package/src/standalone/daemon.mjs +31 -2
  211. package/src/standalone/hook-bus/config.mjs +25 -9
  212. package/src/standalone/hook-bus/handlers.mjs +16 -52
  213. package/src/standalone/hook-bus.mjs +29 -23
  214. package/src/standalone/memory-runtime-proxy.mjs +1 -16
  215. package/src/standalone/session-client.mjs +86 -23
  216. package/src/standalone/session-protocol.mjs +0 -2
  217. package/src/standalone/session-runtime-host-health.test.mjs +5 -4
  218. package/src/standalone/session-runtime-host.mjs +101 -6
  219. package/src/standalone/session-runtime-record.mjs +1 -5
  220. package/src/standalone/session-runtime-worker.mjs +52 -57
  221. package/src/standalone/session-service.mjs +4 -1
  222. package/src/standalone/session-transport.mjs +56 -25
  223. package/src/tui/App.jsx +40 -49
  224. package/src/tui/app/app-view.jsx +14 -21
  225. package/src/tui/app/core-memory-picker.mjs +29 -16
  226. package/src/tui/app/create-app-pickers.mjs +15 -36
  227. package/src/tui/app/extension-pickers.mjs +60 -49
  228. package/src/tui/app/maintenance-pickers.mjs +100 -57
  229. package/src/tui/app/message-selector.mjs +6 -5
  230. package/src/tui/app/model-picker.mjs +30 -13
  231. package/src/tui/app/onboarding-steps.mjs +21 -12
  232. package/src/tui/app/panel-epoch.mjs +56 -0
  233. package/src/tui/app/panel-epoch.test.mjs +1009 -0
  234. package/src/tui/app/panel-surface.mjs +98 -0
  235. package/src/tui/app/project-picker.mjs +30 -29
  236. package/src/tui/app/prompt-submit.mjs +192 -97
  237. package/src/tui/app/prompt-submit.test.mjs +206 -0
  238. package/src/tui/app/provider-setup-picker.mjs +74 -43
  239. package/src/tui/app/resume-picker.mjs +11 -8
  240. package/src/tui/app/route-pickers.mjs +45 -28
  241. package/src/tui/app/settings-picker.mjs +69 -18
  242. package/src/tui/app/shell-layout.mjs +1 -3
  243. package/src/tui/app/slash-commands.mjs +0 -2
  244. package/src/tui/app/slash-dispatch.mjs +4 -2
  245. package/src/tui/app/text-entry-policy.mjs +25 -0
  246. package/src/tui/app/theme-effort-pickers.mjs +11 -16
  247. package/src/tui/app/usage-context-panels.mjs +21 -25
  248. package/src/tui/app/use-global-key-input.mjs +3 -2
  249. package/src/tui/app/use-prompt-draft-flow.mjs +6 -18
  250. package/src/tui/app/use-prompt-handlers.mjs +3 -2
  251. package/src/tui/app/use-welcome-prompt-hint.mjs +17 -3
  252. package/src/tui/components/Picker.jsx +1 -1
  253. package/src/tui/components/TextEntryPanel.jsx +7 -2
  254. package/src/tui/dist/index.mjs +652 -561
  255. package/src/tui/lib/voice-setup.mjs +1 -2
  256. package/src/tui/session/agent-job-feed.mjs +73 -11
  257. package/src/tui/session/session-api-ext.mjs +5 -6
  258. package/src/tui/session/session-api.mjs +1 -0
  259. package/src/tui/session/tool-approval.mjs +15 -5
  260. package/src/tui/session/tui-steering-persist.mjs +33 -19
  261. package/src/tui/session/tui-steering-persist.test.mjs +104 -0
  262. package/src/tui/session-local.mjs +0 -22
  263. package/scripts/.tmp-coverage-report.mjs +0 -96
  264. package/src/runtime/agent/orchestrator/tools/builtin/shell-monitor.mjs +0 -57
  265. package/src/runtime/agent/orchestrator/tools/builtin/shell-monitor.test.mjs +0 -57
@@ -19,12 +19,9 @@ import { providerDisplayRank } from './model-options.mjs';
19
19
 
20
20
  export function createProviderSetupPicker({
21
21
  store,
22
- setPicker,
22
+ surface,
23
23
  setProviderPrompt,
24
- setChannelPrompt,
25
- setHookPrompt,
26
24
  setSettingsPrompt,
27
- setContextPanel,
28
25
  closeUsagePanel,
29
26
  oauthSubmitRef,
30
27
  clearModelCaches,
@@ -33,25 +30,34 @@ export function createProviderSetupPicker({
33
30
  const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
34
31
  const onContinue = typeof options.onContinue === 'function' ? options.onContinue : returnTo;
35
32
  const onCancel = typeof options.onCancel === 'function' ? options.onCancel : null;
33
+ // ONE claim (panel-surface.mjs) per open, shared by the nested action /
34
+ // progress / result panels: every paint re-validates and re-arms it, so a
35
+ // daemon ack (key removal, OAuth callback, usage login) landing after Esc
36
+ // can neither paint nor navigate.
37
+ const own = surface.claim();
36
38
  setProviderPrompt(null);
37
- setChannelPrompt(null);
38
- setHookPrompt(null);
39
39
  setSettingsPrompt(null);
40
40
  // Close full-panel overlays too: they render ahead of providerPrompt in
41
41
  // the floating-panel chain, so a lingering usage/context panel would mask
42
42
  // any text-entry prompt opened from the provider actions (e.g. the
43
43
  // OpenCode Go cookie prompt appeared to do nothing).
44
- setContextPanel(null);
44
+ own.context(null);
45
45
  closeUsagePanel();
46
46
  // Onboarding (and any caller) can pass a preloaded provider setup so we skip
47
47
  // the "Checking Providers" placeholder frame that otherwise flashes before
48
48
  // the real list — that swap is what looked like a jump on Step 1 entry.
49
+ const ownsSurface = () => own.owns();
50
+ const paintProviders = (panel) => own.paint(panel);
51
+ // The flow itself handing the picker surface to a prompt (Enter on a row):
52
+ // still ours, so clear-and-continue instead of going stale. Esc paths use
53
+ // own.close() — leaving IS the handover.
54
+ const releaseSurface = () => own.paint(null);
49
55
  let setup = options.preloadedSetup && typeof options.preloadedSetup === 'object'
50
56
  ? options.preloadedSetup
51
57
  : null;
52
58
  options.preloadedSetup = null;
53
59
  if (!setup) {
54
- setPicker({
60
+ paintProviders({
55
61
  title: options.title || 'Providers',
56
62
  description: options.description || 'Choose a provider to configure.',
57
63
  labelWidth: 18,
@@ -67,7 +73,7 @@ export function createProviderSetupPicker({
67
73
  }],
68
74
  onSelect: () => {},
69
75
  onCancel: () => {
70
- setPicker(null);
76
+ own.close();
71
77
  if (onCancel) onCancel();
72
78
  },
73
79
  });
@@ -170,7 +176,11 @@ export function createProviderSetupPicker({
170
176
  };
171
177
 
172
178
  const reopenProviders = () => {
173
- void openProviderSetupPicker(options);
179
+ // Reached from acks as well as key presses: a stale ack must not restart
180
+ // the whole cluster over the user's surface.
181
+ if (!ownsSurface()) return;
182
+ void Promise.resolve(openProviderSetupPicker(options))
183
+ .catch((e) => store.pushNotice(`providers failed: ${e?.message || e}`, 'error'));
174
184
  };
175
185
  const providerActionFooter = (provider) => provider ? [{
176
186
  glyph: providerIsActive(provider) ? '●' : '○',
@@ -178,6 +188,7 @@ export function createProviderSetupPicker({
178
188
  text: [providerKindLabel(provider), providerStatusLabel(provider), providerDetailText(provider)].filter(Boolean).join(' · '),
179
189
  }] : '';
180
190
  const setApiKeyPrompt = (providerItem) => {
191
+ if (!ownsSurface()) return;
181
192
  setProviderPrompt({
182
193
  kind: 'api-key',
183
194
  providerId: providerItem._providerId,
@@ -189,6 +200,9 @@ export function createProviderSetupPicker({
189
200
  });
190
201
  };
191
202
  const openApiProviderActions = (providerItem) => {
203
+ // Reached from acks (forget-key failure, usage-login back-out) as well as
204
+ // key presses: prove ownership at the sink so every caller is covered.
205
+ if (!ownsSurface()) return;
192
206
  rememberProviderSelection(providerItem);
193
207
  const provider = providerItem._provider || {};
194
208
  const hasAuth = providerItem._authenticated || provider.authenticated;
@@ -216,7 +230,7 @@ export function createProviderSetupPicker({
216
230
  _action: 'usage-login-browser',
217
231
  });
218
232
  }
219
- setPicker({
233
+ paintProviders({
220
234
  title: `Provider · ${providerItem._providerName}`,
221
235
  description: 'Choose an API-key action.',
222
236
  footer: () => providerActionFooter(provider),
@@ -227,19 +241,23 @@ export function createProviderSetupPicker({
227
241
  initialIndex: 0,
228
242
  items: apiActions,
229
243
  onSelect: (_detailValue, detail) => {
230
- setPicker(null);
244
+ releaseSurface();
231
245
  if (detail._action === 'set-key') {
232
246
  setApiKeyPrompt(providerItem);
233
247
  return;
234
248
  }
235
249
  if (detail._action === 'forget-key') {
236
- try {
237
- store.forgetProviderAuth(providerItem._providerId);
238
- clearModelCaches('all');
239
- reopenProviders();
240
- } catch (e) {
241
- store.pushNotice(`auth-forget failed: ${e?.message || e}`, 'error');
242
- }
250
+ // Daemon RPC: only navigate once the removal is acknowledged, and
251
+ // return to these actions (not an empty panel) when it fails.
252
+ void Promise.resolve(store.forgetProviderAuth?.(providerItem._providerId))
253
+ .then(() => {
254
+ clearModelCaches('all');
255
+ reopenProviders();
256
+ })
257
+ .catch((e) => {
258
+ store.pushNotice(`auth-forget failed: ${e?.message || e}`, 'error');
259
+ openApiProviderActions(providerItem);
260
+ });
243
261
  }
244
262
  if (detail._action === 'usage-login-browser') {
245
263
  let backedOut = false;
@@ -259,7 +277,7 @@ export function createProviderSetupPicker({
259
277
  _action: 'back',
260
278
  },
261
279
  ];
262
- setPicker({
280
+ paintProviders({
263
281
  title: `Provider · ${providerItem._providerName}`,
264
282
  description: 'Opening browser. Sign in at opencode.ai/auth; the auth cookie is captured automatically.',
265
283
  footer: () => providerActionFooter(provider),
@@ -317,7 +335,7 @@ export function createProviderSetupPicker({
317
335
  _action: 'back',
318
336
  },
319
337
  ];
320
- setPicker({
338
+ paintProviders({
321
339
  title: `Provider · ${providerItem._providerName}`,
322
340
  description: message,
323
341
  footer: () => providerActionFooter(provider),
@@ -336,7 +354,7 @@ export function createProviderSetupPicker({
336
354
  };
337
355
  const showOAuthResult = (ok, message = '') => {
338
356
  setProviderPrompt(null);
339
- setPicker({
357
+ paintProviders({
340
358
  title: `Provider · ${providerItem._providerName}`,
341
359
  description: message || (ok ? 'Login complete.' : 'Login did not complete.'),
342
360
  footer: () => providerActionFooter(provider),
@@ -386,7 +404,10 @@ export function createProviderSetupPicker({
386
404
  void store.beginOAuthProviderLogin(providerItem._providerId)
387
405
  .then((login) => {
388
406
  if (typeof login?.completeCode === 'function') {
389
- setPicker(null);
407
+ // Post-await handover to the OAuth code prompt: only if this flow
408
+ // still owns the surface (Esc during beginOAuthProviderLogin).
409
+ if (!ownsSurface()) return;
410
+ releaseSurface();
390
411
  const manualUrl = login?.manualUrl || '';
391
412
  setProviderPrompt({
392
413
  kind: 'oauth-code',
@@ -445,6 +466,7 @@ export function createProviderSetupPicker({
445
466
  });
446
467
  };
447
468
  const openOAuthProviderActions = (providerItem) => {
469
+ if (!ownsSurface()) return;
448
470
  rememberProviderSelection(providerItem);
449
471
  const provider = providerItem._provider || {};
450
472
  const hasAuth = providerItem._authenticated || provider.authenticated || provider.reauthRequired === true;
@@ -463,7 +485,7 @@ export function createProviderSetupPicker({
463
485
  _action: 'forget-oauth',
464
486
  });
465
487
  }
466
- setPicker({
488
+ paintProviders({
467
489
  title: `Provider · ${providerItem._providerName}`,
468
490
  description: 'Choose an OAuth login action.',
469
491
  footer: () => providerActionFooter(provider),
@@ -479,13 +501,15 @@ export function createProviderSetupPicker({
479
501
  return;
480
502
  }
481
503
  if (detail._action === 'forget-oauth') {
482
- try {
483
- store.forgetProviderAuth(providerItem._providerId);
484
- clearModelCaches('all');
485
- reopenProviders();
486
- } catch (e) {
487
- store.pushNotice(`auth-forget failed: ${e?.message || e}`, 'error');
488
- }
504
+ void Promise.resolve(store.forgetProviderAuth?.(providerItem._providerId))
505
+ .then(() => {
506
+ clearModelCaches('all');
507
+ reopenProviders();
508
+ })
509
+ .catch((e) => {
510
+ store.pushNotice(`auth-forget failed: ${e?.message || e}`, 'error');
511
+ openOAuthProviderActions(providerItem);
512
+ });
489
513
  }
490
514
  },
491
515
  onCancel: reopenProviders,
@@ -493,6 +517,7 @@ export function createProviderSetupPicker({
493
517
  };
494
518
 
495
519
  const openLocalProviderActions = (providerItem) => {
520
+ if (!ownsSurface()) return;
496
521
  rememberProviderSelection(providerItem);
497
522
  const provider = providerItem._provider || {};
498
523
  const localActions = [
@@ -511,7 +536,7 @@ export function createProviderSetupPicker({
511
536
  _action: 'disable-local',
512
537
  });
513
538
  }
514
- setPicker({
539
+ paintProviders({
515
540
  title: `Provider · ${providerItem._providerName}`,
516
541
  description: 'Choose a local endpoint action.',
517
542
  footer: () => providerActionFooter(provider),
@@ -522,7 +547,7 @@ export function createProviderSetupPicker({
522
547
  initialIndex: 0,
523
548
  items: localActions,
524
549
  onSelect: (_detailValue, detail) => {
525
- setPicker(null);
550
+ releaseSurface();
526
551
  if (detail._action === 'set-local-url') {
527
552
  setProviderPrompt({
528
553
  kind: 'local-url',
@@ -534,20 +559,22 @@ export function createProviderSetupPicker({
534
559
  return;
535
560
  }
536
561
  if (detail._action === 'disable-local') {
537
- try {
538
- store.setLocalProvider(providerItem._providerId, { enabled: false, baseURL: providerItem._baseURL });
539
- clearModelCaches('all');
540
- reopenProviders();
541
- } catch (e) {
542
- store.pushNotice(`local provider update failed: ${e?.message || e}`, 'error');
543
- }
562
+ void Promise.resolve(store.setLocalProvider?.(providerItem._providerId, { enabled: false, baseURL: providerItem._baseURL }))
563
+ .then(() => {
564
+ clearModelCaches('all');
565
+ reopenProviders();
566
+ })
567
+ .catch((e) => {
568
+ store.pushNotice(`local provider update failed: ${e?.message || e}`, 'error');
569
+ openLocalProviderActions(providerItem);
570
+ });
544
571
  }
545
572
  },
546
573
  onCancel: reopenProviders,
547
574
  });
548
575
  };
549
576
 
550
- setPicker({
577
+ paintProviders({
551
578
  title: options.title || 'Providers',
552
579
  description: options.description || 'Choose a provider. Enter opens provider actions.',
553
580
  footer: providerFooter,
@@ -564,7 +591,11 @@ export function createProviderSetupPicker({
564
591
  if (item?._providerId) rememberProviderSelection(item);
565
592
  },
566
593
  onSelect: (_value, item) => {
567
- setPicker(null);
594
+ // In-flow navigation (Enter on a provider row): the flow keeps the
595
+ // surface, so clear-and-continue — own.close() would supersede this
596
+ // very flow and paintProviders would then reject the action panel the
597
+ // user just asked for. Esc (onCancel below) does close.
598
+ releaseSurface();
568
599
  if (item._type === 'continue') {
569
600
  onContinue?.();
570
601
  return;
@@ -582,7 +613,7 @@ export function createProviderSetupPicker({
582
613
  }
583
614
  },
584
615
  onCancel: () => {
585
- setPicker(null);
616
+ own.close();
586
617
  if (onCancel) onCancel();
587
618
  else if (returnTo) returnTo();
588
619
  },
@@ -2,18 +2,21 @@
2
2
  * resume-picker.mjs — the /resume saved-chat session picker.
3
3
  *
4
4
  * Extracted from App.jsx behavior-preservingly as a dependency-injection
5
- * factory: openResumePicker drives setPicker and reads live store state, so it
6
- * can't be pure. The function body is the original App logic verbatim, with
7
- * closure identifiers (store, setPicker, and the two session formatters from
8
- * projects.mjs) threaded in through the factory argument.
5
+ * factory: openResumePicker drives the panel surface and reads live store
6
+ * state, so it can't be pure. The function body is the original App logic
7
+ * verbatim, with closure identifiers (store, surface, and the two session
8
+ * formatters from projects.mjs) threaded in through the factory argument.
9
9
  */
10
10
  export function createResumePicker({
11
11
  store,
12
- setPicker,
12
+ surface,
13
13
  formatSessionUpdatedAt,
14
14
  formatSessionMessageCount,
15
15
  }) {
16
16
  const openResumePicker = async () => {
17
+ // Surface claim (panel-surface.mjs): the storage rescan below is slow, so
18
+ // Esc can easily land before this panel ever paints.
19
+ const own = surface.claim();
17
20
  let sessions;
18
21
  try {
19
22
  // Terminal ↔ desktop interop: the summary cache is per-process, so a
@@ -39,13 +42,13 @@ export function createResumePicker({
39
42
  description: title || preview || '(no message)',
40
43
  };
41
44
  });
42
- setPicker({
45
+ own.paint({
43
46
  title: 'Resume',
44
47
  description: 'Restore a saved chat session.',
45
48
  items,
46
49
  labelWidth: 21,
47
50
  onSelect: (value) => {
48
- setPicker(null);
51
+ own.close();
49
52
  const selected = sessions.find((session) => session.id === value);
50
53
  const resumedName = String(selected?.title || selected?.preview || value)
51
54
  .replace(/\s+/g, ' ')
@@ -55,7 +58,7 @@ export function createResumePicker({
55
58
  .catch((e) => store.pushNotice(`Couldn’t resume chat: ${e?.message || e}`, 'error'));
56
59
  },
57
60
  onCancel: () => {
58
- setPicker(null);
61
+ own.close();
59
62
  },
60
63
  });
61
64
  };
@@ -19,12 +19,9 @@ export const outputStyleNotice = (result) => {
19
19
  export function createRoutePickers({
20
20
  store,
21
21
  state,
22
- setPicker,
22
+ surface,
23
23
  setProviderPrompt,
24
- setChannelPrompt,
25
- setHookPrompt,
26
24
  setSettingsPrompt,
27
- setContextPanel,
28
25
  closeUsagePanel,
29
26
  clean,
30
27
  routeLabel,
@@ -38,7 +35,11 @@ export function createRoutePickers({
38
35
  const openWebSearchPicker = async (options = {}) => {
39
36
  const routeOverride = options.routeOverride || null;
40
37
  const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
38
+ // Surface claim (panel-surface.mjs) taken on this keypress: the route read
39
+ // below is a daemon round-trip, so Esc can land before anything paints.
40
+ const own = surface.claim();
41
41
  const currentWebSearchRoute = routeOverride || (await store.getWebSearchRoute?.()) || null;
42
+ if (!own.owns()) return;
42
43
  void openModelPicker({
43
44
  title: 'Web Search Model',
44
45
  loadingDescription: 'Loading web-search-capable models...',
@@ -53,7 +54,8 @@ export function createRoutePickers({
53
54
  returnOnNestedCancel: options.returnOnNestedCancel === true,
54
55
  onImmediateSelect: () => {
55
56
  if (returnTo) returnTo();
56
- else setPicker(null);
57
+ // Enter inside the nested picker: that keypress owns what it clears.
58
+ else surface.claim().close();
57
59
  },
58
60
  onSelectRoute: async (routeInput) => {
59
61
  const result = await store.setWebSearchRoute?.(routeInput);
@@ -69,6 +71,7 @@ export function createRoutePickers({
69
71
  };
70
72
 
71
73
  const openAgentsPicker = async (options = {}) => {
74
+ const own = surface.claim();
72
75
  let agents = [];
73
76
  try {
74
77
  // Await: on a daemon-backed store this is a remote call, and the old sync
@@ -92,13 +95,12 @@ export function createRoutePickers({
92
95
  description: agent.description || agent.definition?.description || '',
93
96
  _agent: agent,
94
97
  }));
98
+ if (!own.owns()) return;
95
99
  setProviderPrompt(null);
96
- setChannelPrompt(null);
97
- setHookPrompt(null);
98
100
  setSettingsPrompt(null);
99
- setContextPanel(null);
101
+ own.context(null);
100
102
  closeUsagePanel();
101
- setPicker({
103
+ own.paint({
102
104
  title: 'Agents',
103
105
  description: 'Workflow agents available for agent tasks.',
104
106
  help: '↑/↓ Select · Enter Set Model · Esc Back',
@@ -130,7 +132,9 @@ export function createRoutePickers({
130
132
  store.pushNotice(`${agent.label} model set to ${agentModelProfile(result)}`, 'info');
131
133
  },
132
134
  });
133
- setPicker({
135
+ // Nested panel for an Enter on an agent row: the same claim that
136
+ // painted the Agents list, re-armed by that paint.
137
+ own.paint({
134
138
  title: agent.label,
135
139
  description: 'Pick a model for this agent, or turn it off.',
136
140
  help: '↑/↓ Select · Enter Choose · Esc Back',
@@ -156,6 +160,9 @@ export function createRoutePickers({
156
160
  openAgentModelPicker();
157
161
  return;
158
162
  }
163
+ // Post-ack handover back to Agents, bound to THIS keypress: a late
164
+ // ack must not overwrite whatever replaced this panel meanwhile.
165
+ const reopenAgents = own.defer(() => { void openAgentsPicker(); });
159
166
  try {
160
167
  const result = await store.setAgentRoute?.(agent.id, { disabled: true });
161
168
  if (!result) {
@@ -166,7 +173,7 @@ export function createRoutePickers({
166
173
  } catch (e) {
167
174
  store.pushNotice(`could not turn off ${agent.label}: ${e?.message || e}`, 'error');
168
175
  }
169
- void openAgentsPicker();
176
+ reopenAgents();
170
177
  },
171
178
  onCancel: () => {
172
179
  void openAgentsPicker();
@@ -174,13 +181,14 @@ export function createRoutePickers({
174
181
  });
175
182
  },
176
183
  onCancel: () => {
177
- setPicker(null);
184
+ own.close();
178
185
  },
179
186
  });
180
187
  };
181
188
 
182
189
  const openWorkflowPicker = async (options = {}) => {
183
190
  const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
191
+ const own = surface.claim();
184
192
  let workflows = [];
185
193
  try {
186
194
  workflows = (await store.listWorkflows?.()) || [];
@@ -200,13 +208,12 @@ export function createRoutePickers({
200
208
  description: workflow.description || `${workflow.source || 'workflow'} workflow`,
201
209
  _workflow: workflow,
202
210
  }));
211
+ if (!own.owns()) return;
203
212
  setProviderPrompt(null);
204
- setChannelPrompt(null);
205
- setHookPrompt(null);
206
213
  setSettingsPrompt(null);
207
- setContextPanel(null);
214
+ own.context(null);
208
215
  closeUsagePanel();
209
- setPicker({
216
+ own.paint({
210
217
  title: 'Workflow',
211
218
  description: 'Select active workflow.',
212
219
  help: returnTo ? '↑/↓ Select · Enter Choose · Esc Settings' : '↑/↓ Select · Enter Choose · Esc Back',
@@ -215,7 +222,11 @@ export function createRoutePickers({
215
222
  onSelect: (_value, item) => {
216
223
  const workflow = item?._workflow;
217
224
  if (!workflow) return;
218
- setPicker(null);
225
+ // Clear-and-continue: the flow keeps the surface it just emptied, so
226
+ // the post-ack hop below is bound to THIS keypress. An Esc before the
227
+ // switch acks cancels the hop instead of re-opening Settings.
228
+ own.paint(null);
229
+ const returnAfterSwitch = own.defer(() => { if (returnTo) returnTo(); });
219
230
  void store.setWorkflow?.(workflow.id)
220
231
  .then((result) => {
221
232
  if (!result) {
@@ -223,12 +234,12 @@ export function createRoutePickers({
223
234
  return;
224
235
  }
225
236
  store.pushNotice(workflowSwitchNotice(result), 'info');
226
- if (returnTo) returnTo();
237
+ returnAfterSwitch();
227
238
  })
228
239
  .catch((e) => store.pushNotice(`Couldn’t switch workflow: ${e?.message || e}`, 'error'));
229
240
  },
230
241
  onCancel: () => {
231
- setPicker(null);
242
+ own.close();
232
243
  if (returnTo) returnTo();
233
244
  },
234
245
  });
@@ -240,6 +251,7 @@ export function createRoutePickers({
240
251
  // the chosen style, then advance. `onboarding.onAdvance/onBack` drive the
241
252
  // wizard; the confirm bar is built here so both paths share `saveStyle`.
242
253
  const onboarding = options.onboarding || null;
254
+ const own = surface.claim();
243
255
  let status = null;
244
256
  try {
245
257
  status = (await store.listOutputStyles?.()) || null;
@@ -262,18 +274,24 @@ export function createRoutePickers({
262
274
  description: style.description || style.source || 'output style',
263
275
  _style: style,
264
276
  }));
277
+ if (!own.owns()) return;
265
278
  setProviderPrompt(null);
266
- setChannelPrompt(null);
267
- setHookPrompt(null);
268
279
  setSettingsPrompt(null);
269
- setContextPanel(null);
280
+ own.context(null);
270
281
  closeUsagePanel();
271
282
  const saveStyle = (styleId, { advance = false } = {}) => {
272
283
  if (!styleId) return;
273
284
  // Onboarding advance: keep the current picker visible during the async
274
285
  // style switch so the screen never flashes empty between steps; the next
275
286
  // step (or finishOnboarding) replaces/clears the picker itself.
276
- if (!(advance && onboarding)) setPicker(null);
287
+ if (!(advance && onboarding)) own.paint(null);
288
+ // Post-ack delegation (next onboarding step, or back to the caller):
289
+ // bound after this keypress's own navigation so an Esc while the switch
290
+ // is in flight cannot paint a panel over the new surface.
291
+ const advanceAfterSave = own.defer(() => {
292
+ if (advance && onboarding) onboarding.onAdvance?.();
293
+ else if (returnTo) returnTo();
294
+ });
277
295
  void store.setOutputStyle?.(styleId)
278
296
  .then((result) => {
279
297
  if (!result) {
@@ -281,12 +299,11 @@ export function createRoutePickers({
281
299
  } else {
282
300
  store.pushNotice(outputStyleNotice(result), 'info');
283
301
  }
284
- if (advance && onboarding) onboarding.onAdvance?.();
285
- else if (returnTo) returnTo();
302
+ advanceAfterSave();
286
303
  })
287
304
  .catch((e) => store.pushNotice(`Couldn’t switch output style: ${e?.message || e}`, 'error'));
288
305
  };
289
- setPicker({
306
+ own.paint({
290
307
  title: 'Output Style',
291
308
  description: 'Select response style.',
292
309
  // Onboarding uses a ConfirmBar (←/→ = Back/Next); let the Picker supply
@@ -301,7 +318,7 @@ export function createRoutePickers({
301
318
  ],
302
319
  onConfirm: (button) => {
303
320
  if (button.value === 'back') {
304
- setPicker(null);
321
+ own.close();
305
322
  onboarding.onBack?.();
306
323
  return;
307
324
  }
@@ -317,7 +334,7 @@ export function createRoutePickers({
317
334
  saveStyle(style.id, { advance: Boolean(onboarding) });
318
335
  },
319
336
  onCancel: () => {
320
- setPicker(null);
337
+ own.close();
321
338
  if (onboarding) onboarding.onCancel?.();
322
339
  else if (returnTo) returnTo();
323
340
  },