mixdog 1.0.12 → 1.1.1

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 (356) hide show
  1. package/README.md +83 -182
  2. package/package.json +4 -2
  3. package/scripts/build-shipped-history.mjs +131 -0
  4. package/scripts/latest-guard.mjs +63 -0
  5. package/scripts/lib/run-node-tests.mjs +330 -32
  6. package/scripts/lib/test-failure-records.mjs +5 -0
  7. package/scripts/lib/test-failure-summary.mjs +14 -3
  8. package/scripts/lib/test-spawn-ledger.mjs +19 -0
  9. package/scripts/merge-mac-update-feed.mjs +107 -0
  10. package/scripts/mobile-release.mjs +210 -0
  11. package/scripts/post-release-check.mjs +353 -0
  12. package/scripts/prune-embedding-runtime.mjs +9 -5
  13. package/scripts/publish-asset-release.mjs +43 -0
  14. package/scripts/release-guard.mjs +63 -0
  15. package/scripts/test-direct.mjs +16 -1
  16. package/scripts/test.mjs +29 -7
  17. package/src/agents/advisor/AGENT.md +38 -0
  18. package/src/agents/advisor/agent.json +6 -0
  19. package/src/agents/reviewer/AGENT.md +5 -2
  20. package/src/agents/reviewer/agent.json +1 -1
  21. package/src/defaults/shipped-history.json +383 -0
  22. package/src/defaults/skills/computer-use/SKILL.md +1 -1
  23. package/src/defaults/skills/goal-management/SKILL.md +11 -5
  24. package/src/defaults/skills/setup/SKILL.md +5 -8
  25. package/src/defaults/skills/setup/references/actions.md +62 -31
  26. package/src/defaults/skills/setup/references/surfaces.md +12 -10
  27. package/src/lib/keychain-cjs.cjs +582 -17
  28. package/src/lib/rules-builder.cjs +1 -1
  29. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +3 -1
  30. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
  31. package/src/runtime/agent/orchestrator/config-presets.d.mts +1 -0
  32. package/src/runtime/agent/orchestrator/config-presets.mjs +44 -0
  33. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +9 -1
  34. package/src/runtime/agent/orchestrator/config-storage.mjs +32 -4
  35. package/src/runtime/agent/orchestrator/config.mjs +120 -21
  36. package/src/runtime/agent/orchestrator/context/instruction-files.mjs +130 -0
  37. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +4 -0
  38. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +77 -26
  39. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +44 -14
  40. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +2 -1
  41. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +11 -2
  42. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +2 -0
  43. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +3 -3
  44. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +388 -0
  45. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +92 -109
  46. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +107 -0
  47. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +51 -9
  48. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +2 -1
  49. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +11 -0
  50. package/src/runtime/agent/orchestrator/providers/lib/open-login-url.mjs +9 -0
  51. package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +254 -0
  52. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +218 -0
  53. package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +239 -0
  54. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +233 -0
  55. package/src/runtime/agent/orchestrator/providers/muse-code-oauth-login.mjs +247 -0
  56. package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +225 -0
  57. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +542 -0
  58. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +25 -0
  59. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +18 -10
  60. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +13 -0
  61. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +51 -11
  62. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +6 -1
  63. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +7 -5
  64. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +12 -0
  65. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +37 -9
  66. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +12 -2
  67. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
  68. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +99 -0
  69. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +4 -8
  70. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +3 -7
  71. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +8 -0
  72. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +70 -0
  73. package/src/runtime/agent/orchestrator/providers/registry.mjs +42 -3
  74. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +19 -35
  75. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +7 -0
  76. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +0 -17
  77. package/src/runtime/agent/orchestrator/runtime-core/goal-state.mjs +0 -3
  78. package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +0 -1
  79. package/src/runtime/agent/orchestrator/runtime-core/goal-text.mjs +1 -1
  80. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +16 -2
  81. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -2
  82. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +18 -4
  83. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +7 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/session-route-policy.mjs +16 -0
  85. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +2 -0
  86. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +2 -2
  87. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +7 -7
  88. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +6 -0
  89. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +11 -1
  90. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +28 -3
  91. package/src/runtime/agent/orchestrator/session/loop/tool-call-ids.mjs +90 -0
  92. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +19 -11
  93. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +1 -0
  94. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +24 -11
  95. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +2 -6
  96. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +28 -0
  97. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +63 -3
  98. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +8 -0
  99. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
  100. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +4 -4
  101. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +3 -0
  102. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +2 -0
  103. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +3 -0
  104. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +23 -0
  105. package/src/runtime/agent/orchestrator/session/turn-effort.mjs +18 -0
  106. package/src/runtime/agent/orchestrator/stall-policy.mjs +0 -1
  107. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +11 -8
  108. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +27 -12
  109. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +245 -0
  110. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +13 -1
  111. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +3 -1
  112. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
  113. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +49 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +3 -3
  115. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +9 -7
  116. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +19 -20
  117. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +67 -19
  118. package/src/runtime/agent/orchestrator/tools/builtin/file-version-test-helpers.mjs +11 -0
  119. package/src/runtime/agent/orchestrator/tools/builtin/file-version.mjs +13 -0
  120. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +28 -3
  121. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-single-file-rescue.mjs +8 -5
  122. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +25 -12
  123. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +73 -1
  124. package/src/runtime/agent/orchestrator/tools/builtin/raw-content-publication.mjs +17 -0
  125. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +136 -76
  126. package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +2 -2
  127. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +15 -17
  128. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -2
  129. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +11 -10
  130. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -3
  131. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +30 -16
  132. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +3 -1
  133. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +17 -6
  134. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +23 -5
  135. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +93 -43
  136. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +4 -11
  137. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +7 -3
  138. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-validation.mjs +16 -19
  139. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +32 -6
  140. package/src/runtime/agent/orchestrator/tools/builtin.mjs +9 -2
  141. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +3 -1
  142. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  143. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +12 -15
  144. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background.mjs +6 -3
  145. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +14 -4
  146. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +4 -2
  147. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +58 -8
  148. package/src/runtime/agent/orchestrator/tools/patch/content-guard.mjs +47 -0
  149. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +155 -0
  150. package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-proofs.mjs +6 -2
  151. package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-snapshots.mjs +6 -2
  152. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +19 -16
  153. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +13 -44
  154. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +2 -2
  155. package/src/runtime/agent/orchestrator/tools/patch/post-patch-excerpts.mjs +5 -2
  156. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +47 -7
  157. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +72 -8
  158. package/src/runtime/agent/orchestrator/tools/patch/section-coalesce.mjs +3 -2
  159. package/src/runtime/agent/orchestrator/tools/patch/sequence/run.mjs +9 -1
  160. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +9 -1
  161. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +6 -6
  162. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +16 -4
  163. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +35 -14
  164. package/src/runtime/agent/orchestrator/tools/patch/wave.mjs +42 -4
  165. package/src/runtime/agent/orchestrator/tools/shell-codepage.mjs +296 -0
  166. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +124 -81
  167. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +75 -24
  168. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +19 -4
  169. package/src/runtime/channels/lib/config.mjs +3 -8
  170. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +7 -3
  171. package/src/{tui → runtime/channels}/lib/voice-setup.mjs +38 -22
  172. package/src/runtime/channels/lib/webhook/request-handler.mjs +42 -8
  173. package/src/runtime/computer-bridge/actions.mjs +2 -0
  174. package/src/runtime/computer-bridge/client.mjs +25 -1
  175. package/src/runtime/computer-bridge/linux-action-capabilities.mjs +84 -0
  176. package/src/runtime/computer-bridge/linux-readiness.d.mts +52 -0
  177. package/src/runtime/computer-bridge/linux-readiness.mjs +411 -0
  178. package/src/runtime/computer-bridge/test-linux-display.mjs +5 -0
  179. package/src/runtime/effort-judge/compact-tokenizer.mjs +13 -8
  180. package/src/runtime/effort-judge/judge-client.mjs +12 -3
  181. package/src/runtime/effort-judge/judge-input.mjs +5 -2
  182. package/src/runtime/effort-judge/model-install.mjs +9 -3
  183. package/src/runtime/effort-judge/model-manifest.json +8 -8
  184. package/src/runtime/effort-judge/previous-human-request.mjs +162 -0
  185. package/src/runtime/effort-judge/verified-context-input.mjs +10 -0
  186. package/src/runtime/memory/lib/embedding-provider.mjs +12 -0
  187. package/src/runtime/memory/lib/embedding-worker.mjs +9 -3
  188. package/src/runtime/memory/lib/http-router/session-routes.mjs +2 -1
  189. package/src/runtime/memory/lib/memory-config-flags.mjs +3 -2
  190. package/src/runtime/memory/lib/memory-embed.mjs +5 -0
  191. package/src/runtime/memory/lib/pg/supervisor.mjs +3 -0
  192. package/src/runtime/shared/agent-route-config.mjs +50 -2
  193. package/src/runtime/shared/bounded-file-read.mjs +166 -0
  194. package/src/runtime/shared/builtin-grandfathering.mjs +37 -0
  195. package/src/runtime/shared/config.mjs +74 -6
  196. package/src/runtime/shared/defaults-separation.mjs +263 -0
  197. package/src/runtime/shared/developer-options.mjs +7 -0
  198. package/src/runtime/shared/instruction-files-mode.mjs +9 -0
  199. package/src/runtime/shared/llm/anthropic-effort.mjs +4 -0
  200. package/src/runtime/shared/llm/model-catalog.mjs +25 -1
  201. package/src/runtime/shared/llm/openai-daybreak.mjs +79 -0
  202. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +105 -1
  203. package/src/runtime/shared/llm/usage-ledger-worker.mjs +2 -1
  204. package/src/runtime/shared/llm/usage-ledger.mjs +72 -5
  205. package/src/runtime/shared/onnx-runtime-support.mjs +6 -0
  206. package/src/runtime/shared/orchestration.mjs +14 -6
  207. package/src/runtime/shared/pristine-execution-contract.json +9 -0
  208. package/src/runtime/shared/pristine-execution.mjs +1 -1
  209. package/src/runtime/shared/projects.mjs +65 -48
  210. package/src/runtime/shared/provider-accounts.mjs +49 -6
  211. package/src/runtime/shared/quota-resume-store.mjs +255 -0
  212. package/src/runtime/shared/resource-admission.mjs +10 -3
  213. package/src/runtime/shared/schedule-model-ref.mjs +11 -0
  214. package/src/runtime/shared/session-approval-wait.mjs +35 -0
  215. package/src/runtime/shared/shell-display.mjs +4 -2
  216. package/src/runtime/shared/shipped-definitions.mjs +275 -0
  217. package/src/runtime/shared/tool-card-model.golden.json +3 -3
  218. package/src/runtime/shared/tool-result-summary.mjs +86 -15
  219. package/src/runtime/shared/transcript-metadata.mjs +2 -0
  220. package/src/runtime/shared/turn-review-reply.mjs +2 -0
  221. package/src/runtime/shared/turn-snapshot.mjs +65 -6
  222. package/src/runtime/shared/turn-worktree-snapshot.mjs +208 -27
  223. package/src/session-runtime/boot/apis.mjs +24 -0
  224. package/src/session-runtime/boot/config.mjs +4 -0
  225. package/src/session-runtime/boot/facade.mjs +4 -1
  226. package/src/session-runtime/boot/infrastructure.mjs +21 -2
  227. package/src/session-runtime/boot/session.mjs +3 -0
  228. package/src/session-runtime/channel-config-api.mjs +5 -0
  229. package/src/session-runtime/config-lifecycle/config-writers.mjs +3 -0
  230. package/src/session-runtime/config-lifecycle.mjs +31 -6
  231. package/src/session-runtime/goal-facade-api.mjs +4 -0
  232. package/src/session-runtime/lifecycle/inheritance.mjs +28 -6
  233. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +3 -0
  234. package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -1
  235. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +6 -0
  236. package/src/session-runtime/lifecycle/teardown/session-close.mjs +14 -2
  237. package/src/session-runtime/media-api.mjs +32 -22
  238. package/src/session-runtime/model-recency.mjs +17 -0
  239. package/src/session-runtime/model-route/route-tuning.mjs +3 -5
  240. package/src/session-runtime/model-route/set-route.mjs +49 -21
  241. package/src/session-runtime/model-route-api.mjs +2 -1
  242. package/src/session-runtime/notification-bus.mjs +31 -6
  243. package/src/session-runtime/provider-auth/login.mjs +16 -10
  244. package/src/session-runtime/provider-auth/setup.mjs +22 -2
  245. package/src/session-runtime/provider-models/catalog-load.mjs +3 -1
  246. package/src/session-runtime/provider-models/row-hydration.mjs +26 -17
  247. package/src/session-runtime/provider-readiness.mjs +4 -0
  248. package/src/session-runtime/quick-model-rows/model-meta.mjs +26 -0
  249. package/src/session-runtime/quick-model-rows/provider-rows.mjs +40 -14
  250. package/src/session-runtime/resource-api.mjs +8 -1
  251. package/src/session-runtime/runtime-core.mjs +4 -0
  252. package/src/session-runtime/runtime-facade.mjs +25 -1
  253. package/src/session-runtime/runtime-review-api.mjs +12 -1
  254. package/src/session-runtime/schedule-session-run.mjs +3 -1
  255. package/src/session-runtime/services/agent-tool/helpers.mjs +4 -0
  256. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +63 -27
  257. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +15 -3
  258. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +2 -0
  259. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +11 -3
  260. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +2 -0
  261. package/src/session-runtime/services/agent-tool/worker-index/approval-wait.mjs +97 -0
  262. package/src/session-runtime/services/agent-tool/worker-index/mutation-batch.mjs +4 -2
  263. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -2
  264. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +3 -0
  265. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +58 -2
  266. package/src/session-runtime/services/agent-tool/worker-index.mjs +69 -15
  267. package/src/session-runtime/services/agent-tool/worker-rows.mjs +2 -0
  268. package/src/session-runtime/services/channel-admin.mjs +91 -15
  269. package/src/session-runtime/services/provider-admin.mjs +336 -80
  270. package/src/session-runtime/services/seeds.mjs +80 -152
  271. package/src/session-runtime/services/usage-dashboard-model.mjs +35 -0
  272. package/src/session-runtime/services/usage-dashboard.mjs +7 -0
  273. package/src/session-runtime/session-address.mjs +125 -0
  274. package/src/session-runtime/session-create.mjs +57 -2
  275. package/src/session-runtime/settings-builtin-tools-api.mjs +23 -0
  276. package/src/session-runtime/settings-compaction-api.mjs +13 -0
  277. package/src/session-runtime/setup-tool/executor.mjs +97 -40
  278. package/src/session-runtime/setup-tool/extended-actions.mjs +64 -5
  279. package/src/session-runtime/setup-tool/settings-contract.mjs +44 -1
  280. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -7
  281. package/src/session-runtime/tool-policy-refresh.mjs +3 -0
  282. package/src/session-runtime/tool-surface.mjs +16 -9
  283. package/src/session-runtime/turn/session-ops.mjs +3 -0
  284. package/src/session-runtime/turn/share-ops.mjs +4 -1
  285. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +6 -0
  286. package/src/session-runtime/usage-stats-api.mjs +15 -0
  287. package/src/session-runtime/webhook-session-run.mjs +3 -1
  288. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +31 -5
  289. package/src/session-runtime/workflow-agents-api/agent-route.mjs +4 -0
  290. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +27 -7
  291. package/src/standalone/daemon-boot-coordinator.mjs +10 -0
  292. package/src/standalone/daemon-drain-policy.mjs +9 -0
  293. package/src/standalone/daemon-stored-session-views.mjs +7 -8
  294. package/src/standalone/daemon.mjs +21 -5
  295. package/src/standalone/local-session-runtime.mjs +11 -0
  296. package/src/standalone/session-client.mjs +52 -7
  297. package/src/standalone/session-protocol.mjs +9 -0
  298. package/src/standalone/session-service/agent-tree/agent-child.mjs +2 -0
  299. package/src/standalone/session-service/entries/lifecycle.mjs +2 -0
  300. package/src/standalone/session-service/entries/session-loads.mjs +9 -0
  301. package/src/standalone/session-service/projection/revisions.mjs +28 -4
  302. package/src/standalone/session-service/projection.mjs +1 -0
  303. package/src/standalone/session-service/quota-resume.mjs +140 -0
  304. package/src/standalone/session-service/session-calls/session-action.mjs +2 -2
  305. package/src/standalone/session-service/session-calls.mjs +10 -2
  306. package/src/standalone/session-service/stored-reader.mjs +26 -1
  307. package/src/standalone/session-service.mjs +21 -0
  308. package/src/standalone/session-transport/routes.mjs +2 -0
  309. package/src/tui/app/doctor.mjs +7 -11
  310. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +3 -5
  311. package/src/tui/app/model-options.mjs +13 -1
  312. package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +10 -4
  313. package/src/tui/app/provider-setup/oauth-actions.mjs +5 -0
  314. package/src/tui/app/settings-picker/settings-rows.mjs +4 -6
  315. package/src/tui/app/settings-picker/settings-toggles/store-toggles.mjs +1 -1
  316. package/src/tui/app/slash-dispatch/route-commands.mjs +3 -2
  317. package/src/tui/components/ContextPanel.jsx +5 -1
  318. package/src/tui/components/Spinner.jsx +34 -319
  319. package/src/tui/components/SpinnerByline.jsx +256 -0
  320. package/src/tui/components/use-spinner-progress.mjs +113 -0
  321. package/src/tui/dist/index.mjs +15421 -14283
  322. package/src/tui/session/agent-job-feed/execution-delivery.mjs +13 -1
  323. package/src/tui/session/agent-job-feed/notification-router.mjs +3 -0
  324. package/src/tui/session/agent-job-feed/pending-resume.mjs +14 -1
  325. package/src/tui/session/agent-job-feed.mjs +4 -0
  326. package/src/tui/session/context-state.mjs +15 -1
  327. package/src/tui/session/initial-state.mjs +6 -0
  328. package/src/tui/session/live-share/owner.mjs +17 -0
  329. package/src/tui/session/oauth-flows.mjs +2 -0
  330. package/src/tui/session/quota-resume.mjs +263 -0
  331. package/src/tui/session/session-api/intake/abort.mjs +39 -35
  332. package/src/tui/session/session-api/intake/submission.mjs +13 -21
  333. package/src/tui/session/session-api/intake.mjs +3 -3
  334. package/src/tui/session/session-api/integrations.mjs +13 -9
  335. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  336. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -2
  337. package/src/tui/session/session-api/lifecycle/session-reset.mjs +4 -0
  338. package/src/tui/session/session-api/lifecycle.mjs +71 -2
  339. package/src/tui/session/session-api/media.mjs +2 -2
  340. package/src/tui/session/session-api/quota-resume.mjs +21 -0
  341. package/src/tui/session/session-api/routes.mjs +4 -2
  342. package/src/tui/session/session-api/settings.mjs +2 -0
  343. package/src/tui/session/session-api.mjs +2 -0
  344. package/src/tui/session/session-flow/auto-clear.mjs +14 -6
  345. package/src/tui/session/session-flow/drain.mjs +52 -5
  346. package/src/tui/session/session-flow/queue/take-entries.mjs +2 -3
  347. package/src/tui/session/session-flow/queue.mjs +3 -0
  348. package/src/tui/session/session-flow/steering.mjs +11 -0
  349. package/src/tui/session/session-flow.mjs +1 -0
  350. package/src/tui/session/tool-approval.mjs +7 -2
  351. package/src/tui/session/tool-card-results/grouped-result.mjs +11 -2
  352. package/src/tui/session/transcript-intake.mjs +1 -0
  353. package/src/tui/session/transcript-spill/spill-dir.mjs +22 -6
  354. package/src/tui/session/turn.mjs +40 -3
  355. package/src/tui/spinner-meta.mjs +26 -10
  356. package/src/vendor/statusline/bin/statusline-route.mjs +3 -2
@@ -44,6 +44,10 @@ export function createGoalFacadeApi({ agentStatusState, createCurrentSession, ge
44
44
  const sessionId = getSessionId();
45
45
  return sessionId ? goalRuntime.settleTurn(sessionId, detail) : null;
46
46
  },
47
+ reactivateUsageLimitedGoal(goalId) {
48
+ const sessionId = getSessionId();
49
+ return sessionId ? goalRuntime.reactivateUsageLimited(sessionId, goalId) : null;
50
+ },
47
51
  archiveCompletedGoalOnUserInput() {
48
52
  const sessionId = getSessionId();
49
53
  return sessionId ? goalRuntime.archiveCompletedOnUserInput(sessionId) : null;
@@ -23,15 +23,23 @@ export function createInheritance(deps, { compactConversation, saveSession }) {
23
23
  * budget, and no mutation of either session. Returns the conversation to
24
24
  * carry, or null when the route has no boundary to compact toward.
25
25
  */
26
- async function compactConversationForHeir(source, target) {
26
+ async function compactConversationForHeir(source, target, { summarize = false } = {}) {
27
27
  const plan = inheritanceCompactionPlan(target);
28
28
  if (!plan) return null;
29
29
  // The compactor sees the whole transcript — system blocks included — so it
30
30
  // preserves the protected head and the recent tail exactly as /compact
31
31
  // does. Only the conversation half of the result travels.
32
32
  const messages = structuredClone(Array.isArray(source.messages) ? source.messages : []);
33
+ // The compactor writes a model summary only once the conversation passes
34
+ // its size threshold; below it, it trims by rules and the transcript comes
35
+ // back nearly whole. An explicitly chosen compacted carry must summarize
36
+ // whatever its size, so the compactor gets a COPY of the source whose
37
+ // threshold is the floor — the source session itself is left untouched.
38
+ const compactSource = summarize
39
+ ? { ...source, compaction: { ...source.compaction, conversationThresholdTokens: 1 } }
40
+ : source;
33
41
  const result = await compactConversation({
34
- session: source,
42
+ session: compactSource,
35
43
  messages,
36
44
  ...plan,
37
45
  // Memory ingest belongs to the session that HELD the conversation; the
@@ -46,17 +54,26 @@ export function createInheritance(deps, { compactConversation, saveSession }) {
46
54
  // instead of sending the user away to run /compact by hand. The compaction
47
55
  // is sized for the HEIR and runs on a copy, so the source session keeps
48
56
  // every message it has.
49
- async function fitConversationForHeir(source, target, carried, fit) {
57
+ // `required`: the user explicitly chose a compacted carry. Then a compaction
58
+ // that cannot run (or yields nothing) is a refusal even when the original
59
+ // would fit — silently carrying the full conversation would answer a
60
+ // different request than the one made.
61
+ async function fitConversationForHeir(source, target, carried, fit, { required = false } = {}) {
50
62
  let compactionFault = null;
51
63
  let compacted = null;
52
64
  try {
53
- compacted = await compactConversationForHeir(source, target);
65
+ compacted = await compactConversationForHeir(source, target, { summarize: required });
54
66
  } catch (reason) {
55
67
  // A compaction that cannot run is a refusal, not a half-carry: the
56
68
  // user reads the same measured sentence they would have read without
57
69
  // the attempt, and the engine fault travels along as its cause.
58
70
  compactionFault = reason;
59
71
  }
72
+ if (required && !compacted) {
73
+ const refusal = new Error('inheritFrom: the conversation could not be compacted for the new session');
74
+ if (compactionFault) refusal.cause = compactionFault;
75
+ throw refusal;
76
+ }
60
77
  let next = carried;
61
78
  let nextFit = fit;
62
79
  if (compacted) {
@@ -138,7 +155,8 @@ export function createInheritance(deps, { compactConversation, saveSession }) {
138
155
  * The target must still be empty: interleaving two transcripts would
139
156
  * produce a conversation neither model ever had.
140
157
  */
141
- async function inheritFrom(sourceSessionId) {
158
+ async function inheritFrom(sourceSessionId, options = null) {
159
+ const compact = options?.compact === true;
142
160
  const id = clean(sourceSessionId);
143
161
  if (!id) throw new TypeError('inheritFrom: source session id is required');
144
162
  // A daemon-created heir is still only RESERVED when the desktop calls
@@ -168,7 +186,11 @@ export function createInheritance(deps, { compactConversation, saveSession }) {
168
186
  // offered the carry and the runtime that performs it cannot disagree —
169
187
  // and a refusal no longer has to unwind a half-filled transcript.
170
188
  const fit = inheritanceFit(carried, target);
171
- if (fit.known && !fit.fits) carried = await fitConversationForHeir(source, target, carried, fit);
189
+ // compact:true summarizes even a conversation that fits; otherwise only
190
+ // one that cannot fit is compacted.
191
+ if (compact || (fit.known && !fit.fits)) {
192
+ carried = await fitConversationForHeir(source, target, carried, fit, { required: compact });
193
+ }
172
194
  target.messages.push(
173
195
  ...stripEffortConfiguration(inheritedCompatReplayMessages(structuredClone(carried), source.provider))
174
196
  );
@@ -21,6 +21,8 @@ export function createContextSwitch(deps, { ingestSessionIntoMemory, closeSurfac
21
21
  } = deps;
22
22
 
23
23
  async function switchContext({ cwd, desktopSession: nextDesktopSession, forResume = false } = {}) {
24
+ // Leaving the open conversation would re-key an addressed runtime.
25
+ deps.assertNotAddressed?.('switchContext');
24
26
  clearRoutePreparation?.();
25
27
  const session = getSession();
26
28
  if (session?.id) {
@@ -55,6 +57,7 @@ export function createContextSwitch(deps, { ingestSessionIntoMemory, closeSurfac
55
57
  }
56
58
 
57
59
  async function newSession() {
60
+ deps.assertNotAddressed?.('newSession');
58
61
  const session = getSession();
59
62
  if (session?.id) {
60
63
  void ingestSessionIntoMemory(session);
@@ -5,7 +5,10 @@
5
5
  */
6
6
  import { writeStatuslineRoute } from '../../statusline-route.mjs';
7
7
  import { clean, hasOwn } from '../../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
8
- import { toolSpecForMode, deferredSurfaceModeForLead } from '../../../runtime/agent/orchestrator/runtime-core/effort.mjs';
8
+ import {
9
+ toolSpecForMode,
10
+ deferredSurfaceModeForLead,
11
+ } from '../../../runtime/agent/orchestrator/runtime-core/effort.mjs';
9
12
  import { isScratchConversation } from '../shared.mjs';
10
13
 
11
14
  export function resolveResumeCwd(session, currentCwd) {
@@ -97,6 +100,9 @@ export function createSessionResume(deps, { ingestSessionIntoMemory, closeSurfac
97
100
  }
98
101
 
99
102
  async function resume(id) {
103
+ // An addressed runtime may only reopen its own conversation; checked
104
+ // before anything is retired or closed.
105
+ deps.assertResumeTarget?.(id);
100
106
  clearRoutePreparation?.();
101
107
  const prev = getSession();
102
108
  // Captured before the manager reopens anything: the scratch decision for
@@ -88,6 +88,12 @@ export function createSessionDelete(deps, { cancelBackgroundTasks }) {
88
88
  setSession(null);
89
89
  invalidateContextStatusCache();
90
90
  invalidatePreSessionToolSurface();
91
+ // An addressed runtime never mints a replacement under the deleted
92
+ // address: it becomes terminal and a new conversation needs a new entry.
93
+ if (deps.getAddressedSessionId?.()) {
94
+ deps.markAddressTerminal?.();
95
+ return true;
96
+ }
91
97
  await createCurrentSession();
92
98
  return true;
93
99
  }
@@ -63,10 +63,22 @@ export function closeOwnSession(deps, closeSurfaceSession, reason) {
63
63
  /** Plant the ordinary manager/store tombstone barrier for a canonical
64
64
  * child session without running whole-process teardown. */
65
65
  export function closeCanonicalSession(deps, reason = 'canonical-session-close') {
66
- const { getSession, setSession, mgr, invalidateContextStatusCache } = deps;
66
+ const { getSession, setSession, mgr, invalidateContextStatusCache, beginCanonicalClose, finishCanonicalClose } = deps;
67
+ // Already terminal: closing is idempotent and keeps the address.
68
+ if (deps.isCanonicallyClosed?.() === true) return true;
67
69
  const session = getSession?.();
68
70
  if (!session?.id || session.remoteAttached === true) return false;
69
- const closed = mgr.closeSession(session.id, reason, { tombstone: true }) === true;
71
+ // Fence execution admission BEFORE the manager close: that close cancels the
72
+ // session's shells synchronously, and their completions must not be able to
73
+ // enqueue model work or create a conversation.
74
+ const fence = beginCanonicalClose?.(session.id);
75
+ let closed = false;
76
+ try {
77
+ closed = mgr.closeSession(session.id, reason, { tombstone: true }) === true;
78
+ } finally {
79
+ // Terminal after a durable close; admission restored when it failed.
80
+ finishCanonicalClose?.(fence, closed);
81
+ }
70
82
  if (!closed) return false;
71
83
  setSession?.(null);
72
84
  invalidateContextStatusCache?.();
@@ -4,33 +4,43 @@
4
4
  * The media graph (lane catalog, adapters, asset store) is imported on first
5
5
  * use: a session that never opens the Studio must not pay for it at boot.
6
6
  */
7
- let laneModule = null;
8
- let jobModule = null;
9
- let storeModule = null;
10
-
11
- async function lanes() {
12
- laneModule ??= await import('../runtime/media/lanes.mjs');
13
- return laneModule;
14
- }
15
-
16
- async function jobs() {
17
- jobModule ??= await import('../runtime/media/jobs.mjs');
18
- return jobModule;
19
- }
20
-
21
- async function store() {
22
- storeModule ??= await import('../runtime/media/store.mjs');
23
- return storeModule;
7
+ // Each loader keeps its literal import() so bundlers can trace it; only a
8
+ // resolved module is cached, so a failed import is retried on the next call.
9
+ function lazy(load) {
10
+ let module = null;
11
+ return async () => {
12
+ module ??= await load();
13
+ return module;
14
+ };
24
15
  }
25
16
 
26
- let defaultsModule = null;
27
- async function defaults() {
28
- defaultsModule ??= await import('../runtime/media/defaults.mjs');
29
- return defaultsModule;
30
- }
17
+ const lanes = lazy(() => import('../runtime/media/lanes.mjs'));
18
+ const jobs = lazy(() => import('../runtime/media/jobs.mjs'));
19
+ const store = lazy(() => import('../runtime/media/store.mjs'));
20
+ const defaults = lazy(() => import('../runtime/media/defaults.mjs'));
21
+ const voice = lazy(() => import('../runtime/channels/lib/voice-setup.mjs'));
31
22
 
32
23
  export function createMediaApi() {
33
24
  return {
25
+ /** Read-only voice state: `voice.enabled` plus the managed whisper/ffmpeg
26
+ * runtime on disk. */
27
+ async getVoiceStatus() {
28
+ return (await voice()).getVoiceStatus();
29
+ },
30
+ /** Same toggle as the session `toggleVoice` action: enabling installs any
31
+ * missing whisper/ffmpeg component first. `error` carries the last
32
+ * warning or failure the toggle reported. */
33
+ async setVoiceEnabled(enabled) {
34
+ const module = await voice();
35
+ let error = null;
36
+ await module.toggleVoice({
37
+ enabled,
38
+ pushNotice: (text, level) => {
39
+ if (level === 'warn' || level === 'error') error = text;
40
+ },
41
+ });
42
+ return { status: await module.getVoiceStatus(), error };
43
+ },
34
44
  async listMediaLanes() {
35
45
  return (await lanes()).listMediaLanes();
36
46
  },
@@ -3,6 +3,19 @@
3
3
  // injected route provider (for sort priority) and webSearchCapableFor predicate.
4
4
  import { clean } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
5
5
  import { contextWindowRange } from '../runtime/shared/llm/default-context-window.mjs';
6
+ import { isCustomProviderName } from '../runtime/agent/orchestrator/providers/custom-reasoning.mjs';
7
+
8
+ // A custom provider's effective row fields, carried when the row states them
9
+ // (explicit false included); first-party rows never carry them.
10
+ function customRowFields(name, m) {
11
+ if (!isCustomProviderName(name)) return {};
12
+ return {
13
+ ...(typeof m.name === 'string' && m.name ? { name: m.name } : {}),
14
+ ...(typeof m.hidden === 'boolean' ? { hidden: m.hidden } : {}),
15
+ ...(typeof m.manual === 'boolean' ? { manual: m.manual } : {}),
16
+ ...(m.customReasoning && typeof m.customReasoning === 'object' ? { customReasoning: m.customReasoning } : {}),
17
+ };
18
+ }
6
19
 
7
20
  function parsedProviderModelVersion(id) {
8
21
  const text = clean(id).toLowerCase();
@@ -138,6 +151,9 @@ export function providerModelCacheRow(name, m, webSearchCapableFor) {
138
151
  serviceTiers: Array.isArray(m.serviceTiers) ? m.serviceTiers : [],
139
152
  additionalSpeedTiers: Array.isArray(m.additionalSpeedTiers) ? m.additionalSpeedTiers : [],
140
153
  defaultServiceTier: m.defaultServiceTier || null,
154
+ ...(Array.isArray(m.accessPrograms) ? { accessPrograms: m.accessPrograms } : {}),
155
+ // A catalog-hidden model leaves the pickers; its metadata still resolves.
156
+ ...(m.hidden === true ? { hidden: true } : {}),
141
157
  fastCapable: m.fastCapable === true,
142
158
  fastEfforts: Array.isArray(m.fastEfforts) ? m.fastEfforts : undefined,
143
159
  modelParameterOptions: Array.isArray(m.modelParameterOptions) ? m.modelParameterOptions : [],
@@ -148,5 +164,6 @@ export function providerModelCacheRow(name, m, webSearchCapableFor) {
148
164
  defaultFast: m.defaultFast === true,
149
165
  supportsMaxMode: m.supportsMaxMode === true,
150
166
  mode: m.mode || null,
167
+ ...customRowFields(name, m),
151
168
  };
152
169
  }
@@ -29,11 +29,9 @@ export function createRouteTuning(deps, persist) {
29
29
  throw new Error(`ultrafast is not available for ${getRoute().provider}/${getRoute().model}`);
30
30
  }
31
31
  const { serviceTier: previousTier, ...otherParameters } = getRoute().modelParameters || {};
32
- const modelParameters = ultra
33
- ? { ...otherParameters, serviceTier: 'ultrafast' }
34
- : previousTier === 'ultrafast'
35
- ? otherParameters
36
- : getRoute().modelParameters;
32
+ let modelParameters = getRoute().modelParameters;
33
+ if (ultra) modelParameters = { ...otherParameters, serviceTier: 'ultrafast' };
34
+ else if (previousTier === 'ultrafast') modelParameters = otherParameters;
37
35
  const fastCapable = selectionFastCapable(
38
36
  getRoute().provider,
39
37
  modelMeta,
@@ -16,6 +16,7 @@ import { resolveRouteContextState, selectionFastCapable } from '../route-state.m
16
16
  import { writeStatuslineRoute } from '../statusline-route.mjs';
17
17
  import {
18
18
  sessionHasRouteHistory,
19
+ sessionRouteCommitted,
19
20
  shouldRecreateEmptySessionForRouteChange,
20
21
  } from '../../runtime/agent/orchestrator/runtime-core/session-route-policy.mjs';
21
22
  import { rebuildDeferredToolSurfaceForProvider } from '../../runtime/agent/orchestrator/runtime-core/tool-catalog.mjs';
@@ -84,7 +85,7 @@ async function recreateEmptySession(deps) {
84
85
  }
85
86
 
86
87
  /** Rewrite the addressed empty session to the route it was asked to run on. */
87
- function updateEmptySessionInPlace(deps, session) {
88
+ function updateSessionInPlace(deps, session, updateOptions) {
88
89
  const { getRoute, getSession, setSession, mgr, statusRoutes } = deps;
89
90
  const route = getRoute();
90
91
  const fields = {
@@ -96,56 +97,83 @@ function updateEmptySessionInPlace(deps, session) {
96
97
  contextPercent: route.contextPercent,
97
98
  selectedContextWindow: route.selectedContextWindow || null,
98
99
  };
99
- const updated = mgr.updateSessionRoute?.(session.id, fields);
100
+ const updated = mgr.updateSessionRoute?.(session.id, fields, updateOptions);
100
101
  if (updated) setSession(updated);
101
102
  else Object.assign(session, fields);
102
103
  rebuildDeferredToolSurfaceForProvider(getSession(), route.provider);
103
104
  writeStatuslineRoute(statusRoutes, getSession(), route);
104
105
  }
105
106
 
106
- export function createSetRoute(deps, persist) {
107
+ /** Validate, persist and adopt the requested route as the runtime's route. */
108
+ async function adoptRoute(deps, persist, next, options) {
107
109
  const {
108
110
  getConfig,
109
111
  getRoute,
110
112
  setRouteState,
111
- getSession,
112
113
  resolveRoute,
113
114
  refreshRouteEffort,
114
115
  refreshStatuslineUsageSnapshot,
115
116
  scheduleStatuslineUsageRefresh,
116
- invalidateContextStatusCache,
117
- createCurrentSession,
118
117
  } = deps;
118
+ const { selectedRoute, fastCapable, modelMeta } = await resolveSelectedRoute(deps, next);
119
+ persist.saveRouteModelSettings(selectedRoute, fastCapable);
120
+ // An heir opening on its source's model is not a main-model choice.
121
+ const leadRoute = persist.persistAdoptedModelSettings(selectedRoute, {
122
+ keepMainModel: options?.keepMainModel === true,
123
+ });
124
+ setRouteState(resolveRoute(getConfig(), leadRoute ? { model: workflowPresetId('lead') } : selectedRoute));
125
+ await refreshRouteEffort(modelMeta);
126
+ refreshStatuslineUsageSnapshot(getRoute());
127
+ scheduleStatuslineUsageRefresh();
128
+ }
129
+
130
+ /**
131
+ * Quota-exhaustion escape hatch: apply `next` to the addressed, ESTABLISHED
132
+ * session in place. The caller (session API) has already proven the latest
133
+ * settled turn failed on quota and that nothing is running. Starts no turn;
134
+ * the next turn's auto-compaction fits history to the new window.
135
+ */
136
+ export function createContinueSessionInModel(deps, persist) {
137
+ const { getSession, invalidateContextStatusCache } = deps;
138
+ return async function continueSessionInModel(sessionId, next) {
139
+ const session = getSession();
140
+ if (!sessionId || !session?.id || session.id !== sessionId) {
141
+ throw new Error(`session ${sessionId || ''} is not the active session`);
142
+ }
143
+ await adoptRoute(deps, persist, next, {});
144
+ updateSessionInPlace(deps, session, { crossProvider: true });
145
+ invalidateContextStatusCache();
146
+ return true;
147
+ };
148
+ }
149
+
150
+ export function createSetRoute(deps, persist) {
151
+ const { getRoute, getSession, invalidateContextStatusCache, createCurrentSession } = deps;
119
152
 
120
153
  return async function setRoute(next, options = {}) {
121
154
  // Selection is for a new/inherited session. An explicit durable address
122
155
  // permits initializing an EMPTY session, never rewriting an existing
123
156
  // conversation's model, context boundary or provider cache.
124
157
  const applyToCurrentSession = options?.applyToCurrentSession === true;
125
- const { selectedRoute, fastCapable, modelMeta } = await resolveSelectedRoute(deps, next);
126
- persist.saveRouteModelSettings(selectedRoute, fastCapable);
127
- // An heir opening on its source's model is not a main-model choice.
128
- const leadRoute = persist.persistAdoptedModelSettings(selectedRoute, {
129
- keepMainModel: options?.keepMainModel === true,
130
- });
131
- setRouteState(resolveRoute(getConfig(), leadRoute ? { model: workflowPresetId('lead') } : selectedRoute));
132
- await refreshRouteEffort(modelMeta);
133
- refreshStatuslineUsageSnapshot(getRoute());
134
- scheduleStatuslineUsageRefresh();
158
+ await adoptRoute(deps, persist, next, options);
135
159
  const session = getSession();
136
160
  // Model/provider changes are next-session-only for a session the user
137
161
  // has already talked in or compacted (provider-keyed prompt cache). But
138
- // an EMPTY current session — no committed route history and no in-flight
139
- // first-turn prompt — has no cache to protect, so /model before the first
140
- // chat takes effect live: route + statusline update immediately.
141
- const currentSessionEmpty = !!session && !sessionHasRouteHistory(session);
162
+ // a current session with no model reply, no compaction summary and no
163
+ // in-flight first turn — empty, or holding only refused prompts — has no
164
+ // cache to protect, so /model takes effect live: route + statusline
165
+ // update immediately. Only a truly empty session is recreated below.
166
+ const currentSessionEmpty = !!session && !sessionRouteCommitted(session);
142
167
  if (!currentSessionEmpty) {
143
168
  // Some desktop effort changes travel as a same-model route update.
144
169
  // Only effort/Fast may affect the source, starting with its next turn.
145
170
  if (applyToCurrentSession) persist.applySessionTuning();
146
171
  return getRoute();
147
172
  }
173
+ // An addressed runtime keeps its durable id: the empty session is updated
174
+ // in place, never closed and replaced by a freshly minted one.
148
175
  if (
176
+ !deps.getAddressedSessionId?.() &&
149
177
  shouldRecreateEmptySessionForRouteChange(session, applyToCurrentSession) &&
150
178
  session?.id &&
151
179
  typeof createCurrentSession === 'function'
@@ -159,7 +187,7 @@ export function createSetRoute(deps, persist) {
159
187
  // durable address. The caller asked to apply this route to THIS empty
160
188
  // session, so update it in place instead of closing A and silently
161
189
  // materializing B before the first prompt.
162
- updateEmptySessionInPlace(deps, session);
190
+ updateSessionInPlace(deps, session);
163
191
  invalidateContextStatusCache();
164
192
  }
165
193
  return getRoute();
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import { createRoutePersistence } from './model-route/route-persist.mjs';
14
14
  import { createWebSearchRouteApi } from './model-route/web-search-route.mjs';
15
- import { createSetRoute } from './model-route/set-route.mjs';
15
+ import { createSetRoute, createContinueSessionInModel } from './model-route/set-route.mjs';
16
16
  import { createRouteTuning } from './model-route/route-tuning.mjs';
17
17
 
18
18
  export { shouldRecreateEmptySessionForRouteChange } from '../runtime/agent/orchestrator/runtime-core/session-route-policy.mjs';
@@ -22,6 +22,7 @@ export function createModelRouteApi(deps) {
22
22
  return {
23
23
  ...createWebSearchRouteApi(deps),
24
24
  setRoute: createSetRoute(deps, persist),
25
+ continueSessionInModel: createContinueSessionInModel(deps, persist),
25
26
  ...createRouteTuning(deps, persist),
26
27
  };
27
28
  }
@@ -29,7 +29,13 @@ function cleanSessionId(value) {
29
29
  // the wake would leave its durable entry parked until the next user message.
30
30
  // A completion whose owner is no longer the live session is still dropped (the
31
31
  // durable queue carries it into that session's next turn).
32
- export function createCompletionWakeScheduler({ getCurrentSessionId, getTurnApi }) {
32
+ export function createCompletionWakeScheduler({ getCurrentSessionId, getTurnApi, getOwnerLifecycle }) {
33
+ // A closing/terminal owner never starts inference from a completion; the
34
+ // check runs at request time and again inside the delayed callback.
35
+ const ownerOpen = (sessionId) => {
36
+ const life = getOwnerLifecycle?.(sessionId);
37
+ return !life || life.state === 'open';
38
+ };
33
39
  // sessionId -> the oldest request suppressed while this session's wake is in
34
40
  // flight, or null when nothing was suppressed. A present key means in flight.
35
41
  const inFlight = new Map();
@@ -41,6 +47,7 @@ export function createCompletionWakeScheduler({ getCurrentSessionId, getTurnApi
41
47
  try {
42
48
  const turnApi = getTurnApi();
43
49
  if (cleanSessionId(getCurrentSessionId()) !== ownerSessionId || !turnApi) return;
50
+ if (!ownerOpen(ownerSessionId)) return;
44
51
  const delayMs = Math.max(0, Date.now() - queuedAt);
45
52
  if (delayMs >= WAKE_DELAY_REPORT_MS) {
46
53
  process.stderr.write(
@@ -70,7 +77,7 @@ export function createCompletionWakeScheduler({ getCurrentSessionId, getTurnApi
70
77
 
71
78
  return function wakeQueuedCompletion({ sessionId, executionId, enqueuedAt } = {}) {
72
79
  const ownerSessionId = cleanSessionId(sessionId);
73
- if (!ownerSessionId) return false;
80
+ if (!ownerSessionId || !ownerOpen(ownerSessionId)) return false;
74
81
  if (inFlight.has(ownerSessionId)) {
75
82
  const suppressed = inFlight.get(ownerSessionId);
76
83
  // Keep the oldest suppressed request so the replay reports the real wait.
@@ -99,10 +106,22 @@ function emitToListeners(source, content, meta = {}) {
99
106
  return { handled, modelVisibleDelivered: event.modelVisibleDelivered === true };
100
107
  }
101
108
 
102
- export function createNotificationBus({ listeners, mgr, onCompletionQueued }) {
109
+ export function createNotificationBus({ listeners, mgr, onCompletionQueued, getOwnerLifecycle }) {
103
110
  const boundSessionListeners = new Map();
104
111
  const queuedCompletionIds = new Map();
105
112
 
113
+ // Owner identity + lifecycle generation. A closing/terminal owner (or a
114
+ // callback captured under an earlier generation) keeps its result for
115
+ // UI/history only: it is flagged `owner_closed`, never queued for the model
116
+ // and never wakes inference.
117
+ function ownerOpen(sessionId, capturedGeneration) {
118
+ const life = getOwnerLifecycle?.(sessionId);
119
+ if (!life) return true;
120
+ if (life.state !== 'open') return false;
121
+ return capturedGeneration === undefined || capturedGeneration === life.generation;
122
+ }
123
+ const closedMeta = (meta) => ({ ...(meta && typeof meta === 'object' ? meta : {}), owner_closed: true });
124
+
106
125
  function emitRuntimeNotification(content, meta = {}) {
107
126
  return emitToListeners(listeners, content, meta);
108
127
  }
@@ -111,8 +130,10 @@ export function createNotificationBus({ listeners, mgr, onCompletionQueued }) {
111
130
  const targetSessionId = cleanSessionId(sessionId);
112
131
  if (!targetSessionId) return false;
113
132
  const targetListeners = sessionNotificationListeners.get(targetSessionId) || [];
114
- const { handled } = emitToListeners(targetListeners, content, meta);
133
+ const open = ownerOpen(targetSessionId);
134
+ const { handled } = emitToListeners(targetListeners, content, open ? meta : closedMeta(meta));
115
135
  if (handled) return true;
136
+ if (!open) return false;
116
137
  if (typeof mgr.enqueuePendingMessage !== 'function') return false;
117
138
  try {
118
139
  return mgr.enqueuePendingMessage(targetSessionId, content) > 0;
@@ -168,13 +189,15 @@ export function createNotificationBus({ listeners, mgr, onCompletionQueued }) {
168
189
  return true;
169
190
  }
170
191
 
171
- function notifySessionCompletion(sessionId, text, meta = {}) {
192
+ function notifySessionCompletion(sessionId, text, meta = {}, capturedGeneration = undefined) {
172
193
  const targetSessionId = cleanSessionId(sessionId);
173
194
  if (!targetSessionId) return false;
174
195
  const targetListeners = sessionNotificationListeners.get(targetSessionId) || [];
196
+ const open = ownerOpen(targetSessionId, capturedGeneration);
175
197
  const ownerMeta = {
176
198
  ...(meta && typeof meta === 'object' ? meta : {}),
177
199
  caller_session_id: targetSessionId,
200
+ ...(open ? {} : { owner_closed: true }),
178
201
  };
179
202
  delete ownerMeta.routing_session_id;
180
203
  const { handled, modelVisibleDelivered } = emitToListeners(targetListeners, text, ownerMeta);
@@ -187,6 +210,7 @@ export function createNotificationBus({ listeners, mgr, onCompletionQueued }) {
187
210
  handled,
188
211
  mvd: modelVisibleDelivered,
189
212
  });
213
+ if (!open) return handled;
190
214
  if (modelVisibleDelivered) {
191
215
  try {
192
216
  recordDeliveredCompletion({
@@ -219,7 +243,8 @@ export function createNotificationBus({ listeners, mgr, onCompletionQueued }) {
219
243
 
220
244
  function notifyFnForSession(callerSessionId) {
221
245
  const ownerSessionId = cleanSessionId(callerSessionId);
222
- return (text, meta = {}) => notifySessionCompletion(ownerSessionId, text, meta);
246
+ const generation = getOwnerLifecycle?.(ownerSessionId)?.generation;
247
+ return (text, meta = {}) => notifySessionCompletion(ownerSessionId, text, meta, generation);
223
248
  }
224
249
 
225
250
  function bindListenerToSession(sessionId, listener) {
@@ -10,6 +10,7 @@ import {
10
10
  saveProviderApiKey,
11
11
  saveCustomProvider,
12
12
  removeCustomProvider,
13
+ updateCustomProviderModel,
13
14
  testCustomProvider,
14
15
  discoverCustomProviderModels,
15
16
  } from '../services/provider-admin.mjs';
@@ -41,7 +42,7 @@ export function createLoginApi({ cfgMod, awaitKeychainPrewarm, reloadFullConfig,
41
42
  async authenticateProvider(providerId, secret) {
42
43
  await awaitKeychainPrewarm();
43
44
  const result = String(secret || '').trim()
44
- ? saveProviderApiKey(cfgMod, providerId, secret)
45
+ ? await saveProviderApiKey(cfgMod, providerId, secret)
45
46
  : await loginOAuthProvider(cfgMod, providerId);
46
47
  refresh.afterCredentialChange();
47
48
  return result;
@@ -58,18 +59,18 @@ export function createLoginApi({ cfgMod, awaitKeychainPrewarm, reloadFullConfig,
58
59
  reloadFullConfig();
59
60
  return completeOAuthLogin(result);
60
61
  },
61
- saveProviderApiKey(providerId, secret) {
62
- const result = saveProviderApiKey(cfgMod, providerId, secret);
62
+ async saveProviderApiKey(providerId, secret) {
63
+ const result = await saveProviderApiKey(cfgMod, providerId, secret);
63
64
  refresh.afterCredentialChange();
64
65
  return result;
65
66
  },
66
- saveOpenAIUsageSessionKey(secret) {
67
- const result = saveOpenAIUsageSessionKey(cfgMod, secret);
67
+ async saveOpenAIUsageSessionKey(secret) {
68
+ const result = await saveOpenAIUsageSessionKey(cfgMod, secret);
68
69
  refresh.afterUsageAuthChange();
69
70
  return result;
70
71
  },
71
- saveOpenCodeGoUsageAuth(opts) {
72
- const result = saveOpenCodeGoUsageAuth(cfgMod, opts);
72
+ async saveOpenCodeGoUsageAuth(opts) {
73
+ const result = await saveOpenCodeGoUsageAuth(cfgMod, opts);
73
74
  refresh.afterUsageAuthChange();
74
75
  return result;
75
76
  },
@@ -79,9 +80,14 @@ export function createLoginApi({ cfgMod, awaitKeychainPrewarm, reloadFullConfig,
79
80
  refresh.afterCredentialChange();
80
81
  return result;
81
82
  },
83
+ async updateCustomProviderModel(providerId, modelId, patch) {
84
+ const result = await updateCustomProviderModel(cfgMod, providerId, modelId, patch);
85
+ refresh.afterCredentialChange();
86
+ return result;
87
+ },
82
88
  async removeCustomProvider(providerId) {
83
89
  await awaitKeychainPrewarm();
84
- const result = removeCustomProvider(cfgMod, providerId);
90
+ const result = await removeCustomProvider(cfgMod, providerId);
85
91
  onCustomProviderRemoved?.(result.provider);
86
92
  refresh.afterCredentialChange();
87
93
  return result;
@@ -94,8 +100,8 @@ export function createLoginApi({ cfgMod, awaitKeychainPrewarm, reloadFullConfig,
94
100
  await awaitKeychainPrewarm();
95
101
  return await discoverCustomProviderModels(cfgMod, input);
96
102
  },
97
- forgetProviderAuth(providerId, accountId) {
98
- const result = forgetProviderAuth(cfgMod, providerId, accountId);
103
+ async forgetProviderAuth(providerId, accountId) {
104
+ const result = await forgetProviderAuth(cfgMod, providerId, accountId);
99
105
  refresh.afterCredentialChange();
100
106
  return result;
101
107
  },
@@ -1,6 +1,18 @@
1
1
  // provider-auth/setup.mjs — read-side provider surfaces: the setup snapshot
2
2
  // (quick until the keychain is ready), usage dashboard, Codex reset credit and
3
3
  // the model catalog.
4
+ import { refreshKeychainStatus, retryKeychain } from '../../runtime/shared/config.mjs';
5
+ import { keychainIssueFor } from '../services/provider-admin.mjs';
6
+
7
+ // The keyring alert must reflect the live state (it can lock or unlock while a
8
+ // cached setup is served), so overlay it on every ready read.
9
+ function withLiveKeychain(setup) {
10
+ if (!setup || typeof setup !== 'object') return setup;
11
+ const { keychain: _stale, ...rest } = setup;
12
+ const keychain = keychainIssueFor(true);
13
+ return keychain ? { ...rest, keychain } : rest;
14
+ }
15
+
4
16
  export function createSetupApi({
5
17
  awaitKeychainPrewarm,
6
18
  isKeychainPrewarmReady,
@@ -28,8 +40,16 @@ export function createSetupApi({
28
40
  return { ...quick, pendingSecrets: !isKeychainPrewarmReady() };
29
41
  }
30
42
  await awaitKeychainPrewarm();
31
- if (force) reloadFullConfig();
32
- return await cachedProviderSetup({ force });
43
+ // A keyring the user has since unlocked: rebuild the setup that was cached
44
+ // while every stored key read as absent.
45
+ const recovered = await retryKeychain({ interactive: options?.unlockKeychain === true });
46
+ if (force || recovered) reloadFullConfig();
47
+ // Passive: detects a re-locked keyring and re-snapshots a stale cache so
48
+ // external (CLI / other process) changes show up.
49
+ // `recheckKeychain` (window focus) skips the snapshot TTL.
50
+ const changed = await refreshKeychainStatus({ force: options?.recheckKeychain === true });
51
+ if (changed) reloadFullConfig();
52
+ return withLiveKeychain(await cachedProviderSetup({ force: force || recovered || changed }));
33
53
  },
34
54
  async getUsageDashboard(options = {}) {
35
55
  return await getUsageDashboard(options);