mixdog 1.1.1 → 1.2.0

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 (317) hide show
  1. package/README.md +1 -0
  2. package/package.json +2 -2
  3. package/scripts/lib/run-node-tests.mjs +9 -0
  4. package/scripts/prune-desktop-runtime.mjs +3 -0
  5. package/scripts/release-paths.mjs +39 -2
  6. package/scripts/test-direct.mjs +4 -2
  7. package/src/defaults/shipped-history.json +6 -0
  8. package/src/defaults/skills/setup/references/actions.md +5 -2
  9. package/src/defaults/skills/visualize/SKILL.md +157 -0
  10. package/src/defaults/skills/visualize/references/craft.md +88 -0
  11. package/src/headless-command.mjs +56 -3
  12. package/src/headless-exec.mjs +17 -1
  13. package/src/headless-role.mjs +2 -2
  14. package/src/help.mjs +2 -0
  15. package/src/lib/rules-builder.cjs +0 -23
  16. package/src/rules/lead/LEAD.md +2 -2
  17. package/src/rules/shared/70-delivery.md +2 -2
  18. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  19. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
  21. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  22. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
  23. package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
  24. package/src/runtime/agent/orchestrator/config.mjs +9 -1
  25. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +47 -7
  26. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +51 -8
  27. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +25 -5
  28. package/src/runtime/agent/orchestrator/mcp/client.mjs +348 -122
  29. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
  30. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
  31. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
  32. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +13 -1
  33. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
  34. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
  35. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
  36. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +12 -4
  37. package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
  38. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  40. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
  41. package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
  43. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
  44. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +67 -0
  45. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +521 -0
  46. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
  47. package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
  48. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
  49. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +3 -0
  50. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
  51. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
  52. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
  53. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
  54. package/src/runtime/agent/orchestrator/providers/registry.mjs +15 -1
  55. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
  56. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
  57. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +12 -11
  58. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
  59. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +11 -0
  60. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +11 -8
  61. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
  62. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
  63. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
  64. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
  65. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +18 -11
  66. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
  67. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
  68. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
  69. package/src/runtime/agent/orchestrator/session/context-utils.mjs +12 -14
  70. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
  71. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
  72. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
  73. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
  74. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -1
  75. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +1 -0
  76. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +17 -7
  77. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
  78. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -2
  79. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -0
  80. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +5 -3
  81. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
  82. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
  83. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
  84. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
  85. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
  86. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
  87. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
  88. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
  89. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
  90. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
  91. package/src/runtime/agent/orchestrator/session/manager.mjs +1 -5
  92. package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
  93. package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
  94. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +29 -19
  95. package/src/runtime/agent/orchestrator/session/read-dedup.mjs +0 -1
  96. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
  97. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
  98. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
  99. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +29 -0
  100. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +5 -1
  101. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +23 -4
  102. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
  103. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +28 -8
  104. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +140 -0
  105. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
  106. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
  107. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
  108. package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
  109. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +31 -12
  110. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
  111. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
  112. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
  113. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +80 -66
  114. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
  115. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -1
  116. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
  117. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
  118. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
  119. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
  120. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
  121. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
  122. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
  123. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
  124. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
  125. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
  126. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
  127. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +0 -10
  128. package/src/runtime/attachments/store.mjs +38 -0
  129. package/src/runtime/channels/index.mjs +1 -5
  130. package/src/runtime/channels/lib/runtime-paths.mjs +8 -23
  131. package/src/runtime/channels/lib/tool-dispatch.mjs +1 -13
  132. package/src/runtime/channels/lib/worker-ipc.mjs +2 -2
  133. package/src/runtime/channels/lib/worker-main.mjs +4 -34
  134. package/src/runtime/shared/atomic-file.mjs +8 -2
  135. package/src/runtime/shared/best-effort-stderr.mjs +8 -0
  136. package/src/runtime/shared/code-graph-state.mjs +12 -0
  137. package/src/runtime/shared/defaults-separation.mjs +2 -0
  138. package/src/runtime/shared/git-runner-worker.mjs +3 -2
  139. package/src/runtime/shared/git-runner.mjs +4 -3
  140. package/src/runtime/shared/llm/default-context-window.mjs +6 -0
  141. package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
  142. package/src/runtime/shared/llm/model-catalog.mjs +240 -118
  143. package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
  144. package/src/runtime/shared/llm/provider-catalog-registry.mjs +156 -0
  145. package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
  146. package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
  147. package/src/runtime/shared/model-proxy-mode.mjs +19 -0
  148. package/src/runtime/shared/provider-accounts.mjs +2 -2
  149. package/src/runtime/shared/provider-api-key.mjs +21 -1
  150. package/src/runtime/shared/schedule-model-ref.mjs +11 -2
  151. package/src/runtime/shared/semver.d.mts +1 -0
  152. package/src/runtime/shared/semver.mjs +57 -0
  153. package/src/runtime/shared/session-usage-totals.mjs +19 -0
  154. package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
  155. package/src/runtime/shared/singleton-owner.mjs +36 -6
  156. package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
  157. package/src/runtime/shared/tool-surface.mjs +9 -2
  158. package/src/runtime/shared/tool-work-units.mjs +14 -0
  159. package/src/runtime/shared/transcript-metadata.mjs +17 -1
  160. package/src/runtime/shared/turn-changes.mjs +791 -0
  161. package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
  162. package/src/runtime/shared/turn-snapshot.mjs +690 -81
  163. package/src/runtime/shared/turn-worktree-snapshot.mjs +161 -11
  164. package/src/runtime/shared/update-checker.mjs +2 -31
  165. package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
  166. package/src/runtime/shared/worktrees/contract.mjs +551 -0
  167. package/src/runtime/shared/worktrees/disk.mjs +57 -0
  168. package/src/runtime/shared/worktrees/git.mjs +247 -0
  169. package/src/runtime/shared/worktrees/include.mjs +480 -0
  170. package/src/runtime/shared/worktrees/lock.mjs +46 -0
  171. package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
  172. package/src/runtime/shared/worktrees/manager.mjs +1215 -0
  173. package/src/runtime/shared/worktrees/merge.mjs +182 -0
  174. package/src/runtime/shared/worktrees/owner.mjs +46 -0
  175. package/src/runtime/shared/worktrees/paths.mjs +62 -0
  176. package/src/runtime/shared/worktrees/registry.mjs +110 -0
  177. package/src/runtime/shared/worktrees/settings.mjs +84 -0
  178. package/src/runtime/shared/worktrees/slug.mjs +62 -0
  179. package/src/runtime/shared/worktrees/test-support.mjs +96 -0
  180. package/src/runtime/visualize-bridge/client.mjs +181 -0
  181. package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
  182. package/src/session-runtime/boot/apis.mjs +35 -0
  183. package/src/session-runtime/boot/begin.mjs +0 -2
  184. package/src/session-runtime/boot/config.mjs +2 -0
  185. package/src/session-runtime/boot/facade.mjs +20 -0
  186. package/src/session-runtime/boot/infrastructure.mjs +1 -0
  187. package/src/session-runtime/boot/session.mjs +1 -0
  188. package/src/session-runtime/boot/tools.mjs +9 -4
  189. package/src/session-runtime/config-lifecycle.mjs +10 -1
  190. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +11 -1
  191. package/src/session-runtime/global-extensions.mjs +2 -2
  192. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
  193. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
  194. package/src/session-runtime/internal-tool-executor.mjs +9 -1
  195. package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
  196. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
  197. package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
  198. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
  199. package/src/session-runtime/mcp-glue/connect.mjs +11 -6
  200. package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -3
  201. package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
  202. package/src/session-runtime/model-route/route-persist.mjs +3 -0
  203. package/src/session-runtime/model-route/set-route.mjs +15 -2
  204. package/src/session-runtime/notification-bus.mjs +9 -1
  205. package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
  206. package/src/session-runtime/resource-extension-sync.mjs +30 -7
  207. package/src/session-runtime/resource-mcp-api.mjs +27 -10
  208. package/src/session-runtime/route-resolution.mjs +10 -1
  209. package/src/session-runtime/route-state.mjs +16 -2
  210. package/src/session-runtime/runtime-bootstrap.mjs +0 -4
  211. package/src/session-runtime/runtime-core.mjs +5 -0
  212. package/src/session-runtime/runtime-facade.mjs +8 -0
  213. package/src/session-runtime/runtime-paths.mjs +0 -1
  214. package/src/session-runtime/runtime-review-api.mjs +9 -0
  215. package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
  216. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
  217. package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
  218. package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
  219. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
  220. package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
  221. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
  222. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
  223. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  224. package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
  225. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
  226. package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
  227. package/src/session-runtime/services/agent-tool.mjs +1 -0
  228. package/src/session-runtime/services/channel-worker.mjs +1 -1
  229. package/src/session-runtime/services/daemon-candidate.mjs +3 -2
  230. package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
  231. package/src/session-runtime/session-create/session-options.mjs +10 -1
  232. package/src/session-runtime/session-turn-api.mjs +1 -0
  233. package/src/session-runtime/settings-api.mjs +4 -2
  234. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
  235. package/src/session-runtime/settings-profile-api.mjs +27 -2
  236. package/src/session-runtime/settings-worktree-api.mjs +17 -0
  237. package/src/session-runtime/setup-tool/executor.mjs +11 -4
  238. package/src/session-runtime/setup-tool/settings-contract.mjs +3 -1
  239. package/src/session-runtime/setup-tool/tool-defs.mjs +31 -3
  240. package/src/session-runtime/starter-guide-state.mjs +62 -0
  241. package/src/session-runtime/turn/agent-ops.mjs +12 -1
  242. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +13 -3
  243. package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
  244. package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
  245. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
  246. package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
  247. package/src/session-runtime/turn/turn-run.mjs +6 -7
  248. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
  249. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -2
  250. package/src/session-runtime/workspace/admission-gate.mjs +197 -0
  251. package/src/session-runtime/workspace/project-branches.mjs +136 -0
  252. package/src/session-runtime/workspace/transition-steps.mjs +364 -0
  253. package/src/session-runtime/workspace/transitions.mjs +651 -0
  254. package/src/session-runtime/workspace/workspace-state.mjs +112 -0
  255. package/src/standalone/channel-binding.mjs +1 -2
  256. package/src/standalone/channel-transport/call-dispatch.mjs +4 -10
  257. package/src/standalone/channel-transport/http-routes.mjs +17 -0
  258. package/src/standalone/channel-transport.mjs +13 -1
  259. package/src/standalone/daemon-channels-loader.mjs +2 -3
  260. package/src/standalone/daemon-desktop-runtime.mjs +1 -0
  261. package/src/standalone/daemon-owner.mjs +372 -0
  262. package/src/standalone/daemon-stored-session-views.mjs +4 -1
  263. package/src/standalone/daemon.mjs +139 -5
  264. package/src/standalone/desktop-service-registry.mjs +20 -0
  265. package/src/standalone/local-session-runtime.mjs +13 -78
  266. package/src/standalone/session-client/attachment-calls.mjs +3 -1
  267. package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
  268. package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
  269. package/src/standalone/session-client.mjs +104 -13
  270. package/src/standalone/session-protocol.mjs +17 -0
  271. package/src/standalone/session-proxy.mjs +3 -0
  272. package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
  273. package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
  274. package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
  275. package/src/standalone/session-service/projection/revisions.mjs +2 -0
  276. package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
  277. package/src/standalone/session-service/projection/workspace.mjs +14 -0
  278. package/src/standalone/session-service/session-calls/session-action.mjs +11 -3
  279. package/src/standalone/session-transport/call-route.mjs +13 -0
  280. package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
  281. package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
  282. package/src/standalone/session-transport/client-registry.mjs +20 -3
  283. package/src/standalone/session-transport/routes.mjs +79 -2
  284. package/src/standalone/session-transport/stack.mjs +2 -0
  285. package/src/standalone/session-transport.mjs +24 -1
  286. package/src/tui/app/message-selector.mjs +2 -1
  287. package/src/tui/app/model-options.mjs +35 -5
  288. package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
  289. package/src/tui/app/model-picker.mjs +4 -2
  290. package/src/tui/dist/index.mjs +332 -36
  291. package/src/tui/index.jsx +10 -2
  292. package/src/tui/session/compaction-view.mjs +147 -0
  293. package/src/tui/session/context-state.mjs +6 -0
  294. package/src/tui/session/initial-state.mjs +3 -0
  295. package/src/tui/session/quota-resume.mjs +2 -1
  296. package/src/tui/session/session-api/commands.mjs +10 -2
  297. package/src/tui/session/session-api/controls.mjs +4 -0
  298. package/src/tui/session/session-api/extensions.mjs +12 -8
  299. package/src/tui/session/session-api/intake/rewind.mjs +2 -1
  300. package/src/tui/session/session-api/integrations.mjs +18 -0
  301. package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
  302. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
  303. package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
  304. package/src/tui/session/session-api/routes.mjs +10 -1
  305. package/src/tui/session/session-api/settings.mjs +2 -0
  306. package/src/tui/session/session-api/shared.mjs +10 -6
  307. package/src/tui/session/session-api/worktree.mjs +60 -0
  308. package/src/tui/session/session-api.mjs +2 -0
  309. package/src/tui/session/session-flow/auto-clear.mjs +31 -12
  310. package/src/tui/session/session-flow/drain.mjs +15 -1
  311. package/src/tui/session/session-flow/session-reset.mjs +4 -3
  312. package/src/tui/session/session-flow.mjs +2 -0
  313. package/src/tui/session/transcript-intake.mjs +14 -5
  314. package/src/tui/session/turn-stream.mjs +32 -4
  315. package/src/tui/session/turn-tool-cards.mjs +10 -2
  316. package/src/tui/session/turn.mjs +39 -10
  317. package/src/runtime/channels/tool-defs.mjs +0 -17
@@ -0,0 +1,251 @@
1
+ // Daemon-side stand-in for a provider in remote mode (MIXDOG_MODEL_PROXY=client).
2
+ // It holds no credentials and opens no model connection: every call is handed
3
+ // to the desktop client through the model proxy channel and executed by the
4
+ // PC's own provider of the same name. The registry still wraps it with
5
+ // admission scheduling and usage accounting, which then see the PC's result.
6
+ import {
7
+ getModelProxyChannel,
8
+ ModelProxyUnavailableError,
9
+ splitModelProxySendOptions,
10
+ stripModelProxySecrets,
11
+ } from './model-proxy-channel.mjs';
12
+ import {
13
+ isFinalizedProviderRequestTools,
14
+ providerNativeToolPrefixCount,
15
+ } from '../runtime-core/provider-request-tools.mjs';
16
+ import {
17
+ attachmentTextForPart,
18
+ isAttachmentReference,
19
+ isPermanentAttachmentError,
20
+ readAttachmentBuffer,
21
+ } from '../../../attachments/store.mjs';
22
+ import { MAX_ATTACHMENT_BLOB_BYTES } from '../../../attachments/limits.mjs';
23
+
24
+ /** Stored-attachment bytes one proxied call may carry inline: the store's own
25
+ * single-blob ceiling. Newer messages are inlined first; a reference past the
26
+ * budget stays a reference, which the PC lowers to its "unavailable" text. */
27
+ export const MODEL_PROXY_INLINE_MAX_BYTES = MAX_ATTACHMENT_BLOB_BYTES;
28
+
29
+ // A stored reference names a blob in THIS daemon's attachment store, which the
30
+ // PC running the call cannot read. Each one travels as the inline part it was
31
+ // stored from (image/file → base64 `data`, text → its text), so the PC lowers
32
+ // exactly what a local daemon would.
33
+ function inlineReference(part, budget) {
34
+ let buffer;
35
+ try {
36
+ buffer = readAttachmentBuffer(part);
37
+ } catch (error) {
38
+ if (isPermanentAttachmentError(error)) return part;
39
+ throw error;
40
+ }
41
+ if (buffer.length > budget.remaining) return part;
42
+ budget.remaining -= buffer.length;
43
+ const { attachmentRef: _ref, sizeBytes: _size, ...rest } = part;
44
+ if (part.type === 'text') {
45
+ const { textKind: _kind, filename: _name, mimeType: _mime, ...textRest } = rest;
46
+ return { ...textRest, type: 'text', text: attachmentTextForPart(part) };
47
+ }
48
+ return { ...rest, data: buffer.toString('base64') };
49
+ }
50
+
51
+ function inlinePart(part, budget) {
52
+ if (!part || typeof part !== 'object' || Array.isArray(part)) return part;
53
+ if (isAttachmentReference(part)) return inlineReference(part, budget);
54
+ if (Array.isArray(part.content)) {
55
+ const content = inlineParts(part.content, budget);
56
+ return content === part.content ? part : { ...part, content };
57
+ }
58
+ return part;
59
+ }
60
+
61
+ /** Newest last in a content list, so it is walked backwards for the budget. */
62
+ function inlineParts(parts, budget) {
63
+ let out = null;
64
+ for (let index = parts.length - 1; index >= 0; index -= 1) {
65
+ const next = inlinePart(parts[index], budget);
66
+ if (next === parts[index]) continue;
67
+ out ??= [...parts];
68
+ out[index] = next;
69
+ }
70
+ return out ?? parts;
71
+ }
72
+
73
+ /** `messages` with every stored attachment reference replaced by its bytes. */
74
+ export function inlineModelProxyAttachments(messages, { maxBytes = MODEL_PROXY_INLINE_MAX_BYTES } = {}) {
75
+ if (!Array.isArray(messages)) return messages;
76
+ const budget = { remaining: maxBytes };
77
+ let out = null;
78
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
79
+ const message = messages[index];
80
+ if (!message || typeof message !== 'object' || message.content == null || typeof message.content === 'string') {
81
+ continue;
82
+ }
83
+ const content = Array.isArray(message.content)
84
+ ? inlineParts(message.content, budget)
85
+ : inlinePart(message.content, budget);
86
+ if (content === message.content) continue;
87
+ out ??= [...messages];
88
+ out[index] = { ...message, content };
89
+ }
90
+ return out ?? messages;
91
+ }
92
+
93
+ export class ModelProxyProvider {
94
+ #channel;
95
+ #modelInfo = new Map();
96
+
97
+ constructor(name, config = {}, channel = getModelProxyChannel()) {
98
+ this.name = name;
99
+ this.config = config || {};
100
+ this.#channel = channel;
101
+ // Adapter traits are reported by the PC's real instance.
102
+ if (typeof this.config.modelProxy?.nativePdf === 'boolean') this.nativePdf = this.config.modelProxy.nativePdf;
103
+ }
104
+
105
+ #call(method, fields = {}, options = {}) {
106
+ return this.#channel.request({ provider: this.name, method, ...fields }, options);
107
+ }
108
+
109
+ async send(messages, model, tools, sendOpts) {
110
+ const { options, callbacks } = splitModelProxySendOptions(sendOpts);
111
+ const finalized = isFinalizedProviderRequestTools(tools);
112
+ return this.#call(
113
+ 'send',
114
+ {
115
+ model: model ?? null,
116
+ messages: inlineModelProxyAttachments(messages),
117
+ tools: Array.isArray(tools) ? tools : null,
118
+ toolsFinalized: finalized,
119
+ nativePrefixCount: finalized ? providerNativeToolPrefixCount(tools) : 0,
120
+ options,
121
+ },
122
+ { signal: sendOpts?.signal || null, callbacks }
123
+ );
124
+ }
125
+
126
+ #adoptCatalog(catalog) {
127
+ const models = Array.isArray(catalog?.models) ? catalog.models : [];
128
+ const info = catalog?.modelInfo && typeof catalog.modelInfo === 'object' ? catalog.modelInfo : {};
129
+ this.#modelInfo = new Map(Object.entries(info));
130
+ return models;
131
+ }
132
+
133
+ async listModels() {
134
+ return this.#adoptCatalog(await this.#call('listModels'));
135
+ }
136
+
137
+ async _refreshModelCache() {
138
+ return this.#adoptCatalog(await this.#call('refreshModels'));
139
+ }
140
+
141
+ async isAvailable() {
142
+ return (await this.#call('isAvailable')) === true;
143
+ }
144
+
145
+ getCachedModelInfo(model) {
146
+ return this.#modelInfo.get(String(model ?? '')) ?? null;
147
+ }
148
+ }
149
+
150
+ /** A proxy instance whose class carries the PC adapter's usage convention. */
151
+ export function createModelProxyProvider(name, config = {}, channel = getModelProxyChannel()) {
152
+ const inputExcludesCache = config?.modelProxy?.inputExcludesCache;
153
+ if (typeof inputExcludesCache !== 'boolean') return new ModelProxyProvider(name, config, channel);
154
+ class BoundModelProxyProvider extends ModelProxyProvider {
155
+ static inputExcludesCache = inputExcludesCache;
156
+ }
157
+ return new BoundModelProxyProvider(name, config, channel);
158
+ }
159
+
160
+ // The PC's provider set (availability + non-secret config) changes only on a
161
+ // PC-side sign-in or settings edit; a short cache keeps registry re-inits from
162
+ // paying a client round trip each. The cache belongs to one client generation:
163
+ // a reconnect (possibly from another PC) always asks again.
164
+ const PROVIDER_CONFIG_TTL_MS = 15_000;
165
+ /** How long a registry init waits for a client to connect and for its PC to
166
+ * answer with a loaded provider registry. */
167
+ export const MODEL_PROXY_CONFIG_WAIT_MS = 15_000;
168
+ const CONFIG_RETRY_FIRST_MS = 250;
169
+ const CONFIG_RETRY_MAX_MS = 2_000;
170
+ let _providerConfigCache = null;
171
+ let _providerConfigInFlight = null;
172
+
173
+ /** A reply the registry may act on: the PC says its registry is loaded, or it
174
+ * already names an enabled provider (a client that predates `ready`). An
175
+ * empty or unready answer is never read as "everything disabled". */
176
+ function providerConfigReady(reply, providers) {
177
+ // A loaded PC registry is authoritative, even when it has nothing enabled.
178
+ if (reply?.ready === true) return true;
179
+ if (reply?.ready === false) return false;
180
+ return Object.keys(providers).some((name) => providers[name]?.enabled === true);
181
+ }
182
+
183
+ const pause = (ms) =>
184
+ new Promise((resolve) => {
185
+ const timer = setTimeout(resolve, ms);
186
+ timer.unref?.();
187
+ });
188
+
189
+ export function loadModelProxyProviderConfig({
190
+ channel = getModelProxyChannel(),
191
+ now = Date.now,
192
+ waitMs = MODEL_PROXY_CONFIG_WAIT_MS,
193
+ } = {}) {
194
+ const generation = channel.generation;
195
+ const cached = _providerConfigCache;
196
+ if (
197
+ cached &&
198
+ cached.channel === channel &&
199
+ cached.generation === generation &&
200
+ now() - cached.at < PROVIDER_CONFIG_TTL_MS
201
+ ) {
202
+ return Promise.resolve(cached.value);
203
+ }
204
+ const inFlight = _providerConfigInFlight;
205
+ if (inFlight && inFlight.channel === channel && inFlight.generation === generation) return inFlight.promise;
206
+ const promise = (async () => {
207
+ const deadline = Date.now() + waitMs;
208
+ let delay = CONFIG_RETRY_FIRST_MS;
209
+ let lastError = null;
210
+ for (;;) {
211
+ if (!channel.connected) {
212
+ const remaining = deadline - Date.now();
213
+ if (remaining <= 0 || !(await channel.whenConnected(remaining))) {
214
+ throw lastError ?? new ModelProxyUnavailableError('no desktop client is connected to run model calls');
215
+ }
216
+ }
217
+ try {
218
+ const reply = await channel.request({ provider: '', method: 'providerConfig' });
219
+ const raw = reply?.providers && typeof reply.providers === 'object' ? reply.providers : {};
220
+ // Defense in depth: the PC already strips secrets before sending.
221
+ const value = stripModelProxySecrets(raw);
222
+ if (providerConfigReady(reply, value)) {
223
+ if (channel.generation === generation) {
224
+ _providerConfigCache = { at: now(), value, channel, generation };
225
+ }
226
+ return value;
227
+ }
228
+ lastError = new ModelProxyUnavailableError('the desktop client has not loaded its model providers yet');
229
+ } catch (error) {
230
+ // Only "no client / no answer yet" is worth waiting out.
231
+ if (error?.code !== 'MODEL_PROXY_UNAVAILABLE') throw error;
232
+ lastError = error;
233
+ }
234
+ if (Date.now() + delay > deadline) throw lastError;
235
+ await pause(delay);
236
+ delay = Math.min(delay * 2, CONFIG_RETRY_MAX_MS);
237
+ }
238
+ })();
239
+ const entry = { channel, generation, promise };
240
+ _providerConfigInFlight = entry;
241
+ const clear = () => {
242
+ if (_providerConfigInFlight === entry) _providerConfigInFlight = null;
243
+ };
244
+ promise.then(clear, clear);
245
+ return promise;
246
+ }
247
+
248
+ export function _resetModelProxyProviderConfigForTest() {
249
+ _providerConfigCache = null;
250
+ _providerConfigInFlight = null;
251
+ }
@@ -10,6 +10,7 @@ import { writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
10
10
  import { boundProviderAuthPath } from '../../../shared/provider-auth-binding.mjs';
11
11
  import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
12
12
  import { accountIdentityFields, scrubOAuthSecrets } from './lib/oauth-token-utils.mjs';
13
+ import { modelProxyClientMode } from '../../../shared/model-proxy-mode.mjs';
13
14
 
14
15
  export const CLIENT_ID = '1031625952748946';
15
16
  export const DEVICE_AUTH_URL = 'https://auth.meta.com/oidc/device/authorization/';
@@ -44,6 +45,7 @@ const nonEmptyString = (value) => typeof value === 'string' && value.trim() !==
44
45
  export const isValidMuseCredentialDocument = (doc) => nonEmptyString(doc?.accessToken) && nonEmptyString(doc?.apiKey);
45
46
 
46
47
  export function loadTokens() {
48
+ if (modelProxyClientMode()) return null;
47
49
  const path = getOwnTokenPath();
48
50
  if (!existsSync(path)) return null;
49
51
  try {
@@ -10,6 +10,15 @@ import { createHash } from 'node:crypto';
10
10
  import { noteErrorUsage } from './lib/note-error-usage.mjs';
11
11
  import { fileMtimeMs } from './lib/oauth-token-utils.mjs';
12
12
  import { OpenAICompatProvider, preloadOpenAICompatRuntime } from './openai-compat.mjs';
13
+ import { getModelMetadataSync } from './model-catalog.mjs';
14
+ import { makeModelCache } from './model-cache.mjs';
15
+ import { PRICING_RATE_KEYS } from '../../../shared/llm/model-pricing-rates.mjs';
16
+ import {
17
+ MUSE_DEFAULT_EFFORT_LEVELS as FIVE_LEVELS,
18
+ MUSE_SEED_IDS as SEED_MODELS,
19
+ MUSE_SPARK_MAX_OUTPUT as SPARK_MAX_OUTPUT,
20
+ museSeedRow,
21
+ } from '../../../shared/llm/provider-seed-rosters.mjs';
13
22
  import {
14
23
  API_VERSION,
15
24
  MODEL_API_BASE_URL,
@@ -27,108 +36,63 @@ export {
27
36
  export { beginOAuthLogin, loginOAuth } from './muse-code-oauth-login.mjs';
28
37
 
29
38
  export const DEFAULT_MODEL = 'muse-spark-1.3';
30
- const FIVE_LEVELS = Object.freeze(['minimal', 'low', 'medium', 'high', 'xhigh']);
31
- const SIX_LEVELS = Object.freeze([...FIVE_LEVELS, 'max']);
32
39
  const effortOptions = (levels) => [{ type: 'effort', values: [...levels] }];
33
40
  // Image and voice generation ids are served by the same catalog but are not chat models.
34
41
  const NON_CHAT_MODEL_RE = /^muse-(image|voice)-/i;
35
- const SPARK_CONTEXT = 1_048_576;
36
- const SPARK_MAX_OUTPUT = 131_072;
37
- const STANDARD_COST = { inputCostPerM: 1.25, outputCostPerM: 4.25, cacheReadCostPerM: 0.15, cacheWriteCostPerM: 0 };
38
- const CONTRIBUTOR_COST = { inputCostPerM: 0.1, outputCostPerM: 0.2, cacheReadCostPerM: 0.002, cacheWriteCostPerM: 0 };
39
-
40
- // Seeded roster: the discovery endpoint lists bare ids, so limits, pricing and
41
- // the effort ladder come from here. Only the 1.3 revisions accept `max`.
42
- const SEEDS = Object.freeze({
43
- 'muse-spark-1.1': { name: 'Muse Spark 1.1', levels: FIVE_LEVELS, cost: STANDARD_COST },
44
- 'muse-spark-1.2': { name: 'Muse Spark 1.2', levels: FIVE_LEVELS, cost: STANDARD_COST },
45
- 'muse-spark-1.2-contributor': { name: 'Muse Spark 1.2 (C)', levels: FIVE_LEVELS, cost: CONTRIBUTOR_COST },
46
- 'muse-spark-1.3': { name: 'Muse Spark 1.3', levels: SIX_LEVELS, cost: STANDARD_COST },
47
- 'muse-spark-1.3-contributor': { name: 'Muse Spark 1.3 (C)', levels: SIX_LEVELS, cost: CONTRIBUTOR_COST },
48
- });
49
- const SEED_MODELS = Object.freeze(Object.keys(SEEDS));
50
-
51
- function seedRow(id) {
52
- const seed = SEEDS[id];
53
- return {
54
- id,
55
- name: seed.name,
56
- provider: 'muse-code-oauth',
57
- family: 'muse-spark',
58
- contextWindow: SPARK_CONTEXT,
59
- outputTokens: SPARK_MAX_OUTPUT,
60
- supportsReasoning: true,
61
- supportsVision: true,
62
- supportsFunctionCalling: true,
63
- reasoningLevels: [...seed.levels],
64
- reasoningOptions: effortOptions(seed.levels),
65
- ...seed.cost,
66
- };
67
- }
68
-
69
- // Revisioned Spark ids the seed does not list inherit the newest seeded
70
- // revision of their tier (limits, vision, pricing) but take the five-level
71
- // ladder: only reviewed seeds advertise `max`.
72
- const SPARK_ID_RE = /^muse-spark-(\d+)\.(\d+)(?:\.(\d+))?(-contributor)?$/i;
73
42
 
74
- function revisionOf(id) {
75
- const m = SPARK_ID_RE.exec(id);
76
- return m ? [Number(m[1]), Number(m[2]), Number(m[3] || 0)] : null;
77
- }
43
+ // The discovered roster (fields /models returned, never seed values) is
44
+ // persisted so the shared sync lookup sees what listModels found.
45
+ const museModelCache = makeModelCache({
46
+ fileName: 'muse-code-oauth-models.json',
47
+ ttlMs: 24 * 60 * 60 * 1000,
48
+ version: 1,
49
+ });
78
50
 
79
- function compareRevision(a, b) {
80
- for (let i = 0; i < 3; i += 1) if (a[i] !== b[i]) return a[i] - b[i];
81
- return 0;
51
+ // Row fields the shared metadata lookup resolves: live roster, models.dev
52
+ // `meta`, verified corrections, then the offline seed (provider-seed-rosters).
53
+ const LOOKUP_FIELDS = Object.freeze([
54
+ 'contextWindow',
55
+ 'outputTokens',
56
+ ...PRICING_RATE_KEYS,
57
+ 'supportsReasoning',
58
+ 'supportsVision',
59
+ 'supportsFunctionCalling',
60
+ 'reasoningOptions',
61
+ ]);
62
+
63
+ // Labels compare without the trailing "(C)" tier marker, case or punctuation.
64
+ const nameKey = (name) =>
65
+ String(name || '')
66
+ .replace(/\s*\(c\)\s*$/i, '')
67
+ .toLowerCase()
68
+ .replace(/[^a-z0-9]+/g, '');
69
+
70
+ // A discovered or catalog label naming the seeded model keeps the seed's "(C)" marker.
71
+ function tierName(candidate, seedName) {
72
+ if (!candidate) return '';
73
+ return seedName && nameKey(candidate) === nameKey(seedName) ? seedName : candidate;
82
74
  }
83
75
 
84
- function lineageRow(id) {
85
- if (SEEDS[id]) return null;
86
- const match = SPARK_ID_RE.exec(id);
87
- if (!match) return null;
88
- const contributor = Boolean(match[4]);
89
- let template = null;
90
- let templateRevision = null;
91
- for (const seedId of SEED_MODELS) {
92
- if (SPARK_ID_RE.exec(seedId)[4] ? !contributor : contributor) continue;
93
- const candidate = revisionOf(seedId);
94
- if (!templateRevision || compareRevision(candidate, templateRevision) > 0) {
95
- template = seedId;
96
- templateRevision = candidate;
97
- }
76
+ // Discovered fields win, then the shared lookup (fetched catalogs before
77
+ // seeds). Rows that report no effort ladder gain the default five levels
78
+ // unless a source declares the model non-reasoning.
79
+ function museRow(id, discovered = null) {
80
+ const meta = getModelMetadataSync(id, 'muse-code-oauth');
81
+ if (!meta && !discovered) return null;
82
+ const row = { id, provider: 'muse-code-oauth', family: 'muse-spark' };
83
+ for (const key of LOOKUP_FIELDS) {
84
+ const value = meta?.[key];
85
+ if (value != null && !(Array.isArray(value) && !value.length)) row[key] = value;
98
86
  }
99
- if (!template) return null;
100
- const revision =
101
- match[3] && Number(match[3]) !== 0 ? `${match[1]}.${match[2]}.${match[3]}` : `${match[1]}.${match[2]}`;
102
- return {
103
- ...seedRow(template),
104
- id,
105
- name: `Muse Spark ${revision}${contributor ? ' (C)' : ''}`,
106
- reasoningLevels: [...FIVE_LEVELS],
107
- reasoningOptions: effortOptions(FIVE_LEVELS),
108
- };
109
- }
110
-
111
- // Seeds win for the ids they cover; lineage rows yield to discovered metadata.
112
- // A name supplied by discovery is always kept. Other discovered rows keep
113
- // their own metadata and only gain the default effort ladder when they report none.
114
- function museRow(id, discovered) {
87
+ Object.assign(row, discovered || {});
115
88
  const discoveredName =
116
89
  typeof discovered?.name === 'string' && discovered.name && discovered.name !== id ? discovered.name : '';
117
- if (SEEDS[id]) return { ...(discovered || {}), ...seedRow(id), ...(discoveredName ? { name: discoveredName } : {}) };
118
- const lineage = lineageRow(id);
119
- const row = {
120
- id,
121
- name: id,
122
- provider: 'muse-code-oauth',
123
- family: 'muse-spark',
124
- ...(lineage || {}),
125
- ...(discovered || {}),
126
- };
127
- if (discoveredName) row.name = discoveredName;
128
- else if (lineage) row.name = lineage.name;
129
- if (!row.reasoningOptions?.length) {
130
- row.reasoningOptions = effortOptions(FIVE_LEVELS);
131
- row.reasoningLevels = [...FIVE_LEVELS];
90
+ row.name = tierName(discoveredName || meta?.displayName, museSeedRow(id)?.name) || id;
91
+ // A model declared non-reasoning gets no default ladder.
92
+ if (!row.reasoningOptions?.length)
93
+ row.reasoningOptions = row.supportsReasoning === false ? [] : effortOptions(FIVE_LEVELS);
94
+ if (!row.reasoningLevels?.length) {
95
+ row.reasoningLevels = [...(row.reasoningOptions.find((o) => o?.type === 'effort')?.values || [])];
132
96
  }
133
97
  return row;
134
98
  }
@@ -414,12 +378,9 @@ export class MuseCodeOAuthProvider {
414
378
  });
415
379
  this._innerKey = apiKey;
416
380
  // The Responses builder reads limits and the effort ladder from the inner
417
- // provider's cached model row; seeds fill in what discovery omits.
381
+ // provider's cached model row; the shared lookup fills what discovery omits.
418
382
  const innerRow = this._inner.getCachedModelInfo.bind(this._inner);
419
- this._inner.getCachedModelInfo = (model) => {
420
- const discovered = innerRow(model);
421
- return discovered || SEEDS[model] || lineageRow(model) ? museRow(model, discovered) : null;
422
- };
383
+ this._inner.getCachedModelInfo = (model) => museRow(model, innerRow(model));
423
384
  return this._inner;
424
385
  }
425
386
 
@@ -452,6 +413,8 @@ export class MuseCodeOAuthProvider {
452
413
  async listModels() {
453
414
  const { apiKey } = await this.ensureAuth();
454
415
  const inner = this._ensureInner(apiKey);
416
+ // Only the latest request, fetched with the still-active key, publishes.
417
+ const publish = museModelCache.beginSave(() => loadTokens()?.apiKey === apiKey);
455
418
  let items;
456
419
  try {
457
420
  items = await inner._fetchModelItems();
@@ -460,25 +423,29 @@ export class MuseCodeOAuthProvider {
460
423
  return SEED_MODELS.map((id) => museRow(id));
461
424
  }
462
425
  // A successful roster is authoritative, even when it holds no chat models.
463
- const rows = (Array.isArray(items) ? items : [])
426
+ const discovered = (Array.isArray(items) ? items : [])
464
427
  .filter((m) => typeof m?.id === 'string' && m.id && !NON_CHAT_MODEL_RE.test(m.id))
465
428
  .map((m) => {
466
429
  const contextWindow = Number(m.context_window ?? m.max_context_window ?? m.context_length ?? 0);
467
430
  const outputTokens = Number(m.max_output_tokens ?? m.output_tokens ?? 0);
468
431
  const name = [m.name, m.display_name].find((v) => typeof v === 'string' && v.trim());
469
- return museRow(m.id, {
432
+ return {
433
+ id: m.id,
434
+ provider: 'muse-code-oauth',
470
435
  ...(name ? { name: name.trim() } : {}),
471
436
  ...(contextWindow > 0 ? { contextWindow } : {}),
472
437
  ...(outputTokens > 0 ? { outputTokens } : {}),
473
438
  ...(typeof m.created === 'number' ? { created: m.created } : {}),
474
- });
439
+ };
475
440
  });
476
- inner._enrichedModels = rows;
441
+ const rows = discovered.map((row) => museRow(row.id, row));
442
+ // A superseded or other-credential response never replaces the live roster either.
443
+ if (publish(discovered)) inner._enrichedModels = rows;
477
444
  return rows;
478
445
  }
479
446
 
480
447
  getCachedModelInfo(model) {
481
- return this._inner?.getCachedModelInfo?.(model) || (SEEDS[model] || lineageRow(model) ? museRow(model) : null);
448
+ return this._inner?.getCachedModelInfo?.(model) || museRow(model);
482
449
  }
483
450
 
484
451
  /** Subscription window/weekly usage, read from the key endpoint without onboarding. */
@@ -6,6 +6,7 @@ import { boundProviderAuthPath } from '../../../shared/provider-auth-binding.mjs
6
6
  import { cursorTokenExpiry } from './cursor-auth.mjs';
7
7
  import { isValidMuseCredentialDocument } from './muse-code-oauth-tokens.mjs';
8
8
  import { developerOptionEnabled, developerOptionForProvider } from '../../../shared/developer-options.mjs';
9
+ import { modelProxyClientMode } from '../../../shared/model-proxy-mode.mjs';
9
10
 
10
11
  const ANTHROPIC_DEFAULT_CREDENTIALS_PATH = join(resolvePluginData(), 'anthropic-oauth-credentials.json');
11
12
 
@@ -100,6 +101,8 @@ function credentialPathsSignature(paths, extra = '') {
100
101
  }
101
102
 
102
103
  function memoProbe(key, paths, compute, extraSignature = '') {
104
+ // Remote mode: no credential file on this server is ever read.
105
+ if (modelProxyClientMode()) return 'absent';
103
106
  const signature = credentialPathsSignature(paths, extraSignature);
104
107
  const hit = _probeCache.get(key);
105
108
  const now = Date.now();
@@ -12,6 +12,7 @@ import {
12
12
  useXaiResponsesWebSocket as preferXaiResponsesWebSocket,
13
13
  _shouldFallbackXaiWsToHttp,
14
14
  } from './openai-compat-xai.mjs';
15
+ import { withEffortRejectionFallback } from './effort-rejection.mjs';
15
16
  import { sendCompatResponses } from './openai-compat-responses.mjs';
16
17
  import { sendCompatChat, recoverCompatNonStreaming } from './openai-compat-chat-send.mjs';
17
18
  import { sendXaiResponses, sendXaiResponsesWebSocket } from './openai-compat-xai-send.mjs';
@@ -161,7 +162,11 @@ export class OpenAICompatProvider {
161
162
  }
162
163
  async _doSend(messages, model, tools, sendOpts) {
163
164
  const useModel = model || this.defaultModel;
164
- const opts = sendOpts || {};
165
+ return withEffortRejectionFallback(this.name, useModel, sendOpts || {}, this.config?.reasoningEffort, (opts) =>
166
+ this._sendWithOpts(messages, useModel, tools, opts)
167
+ );
168
+ }
169
+ async _sendWithOpts(messages, useModel, tools, opts) {
165
170
  // Re-warm a kept-alive socket to the provider origin before the turn so
166
171
  // the request hot path lands on a live socket instead of paying a cold
167
172
  // TLS handshake after an idle gap. Fire-and-forget; never awaited.
@@ -13,7 +13,7 @@ import { formatScheduleModelRef, parseScheduleModelRef } from '../../../shared/s
13
13
  export const CODEX_MODEL_CACHE_OPTIONS = Object.freeze({
14
14
  fileName: 'openai-oauth-models.json',
15
15
  ttlMs: 24 * 60 * 60_000,
16
- version: 6,
16
+ version: 7,
17
17
  });
18
18
 
19
19
  export const CODEX_PROVIDER_ID = 'openai-oauth';
@@ -13,7 +13,6 @@
13
13
  import { makeModelCache } from './model-cache.mjs';
14
14
  import { CODEX_MODEL_CACHE_OPTIONS } from './openai-oauth-catalog-store.mjs';
15
15
  import { enrichModels } from './model-catalog.mjs';
16
- import { sanitizeModelList } from './model-list-sanitize.mjs';
17
16
  import { modelSupportsServiceTier } from './model-service-tiers.mjs';
18
17
  import { codexClientVersionSync, warmCodexClientVersion } from './codex-client-meta.mjs';
19
18
  import { compareSemver, maxSemver } from './npm-cli-version.mjs';
@@ -131,7 +130,11 @@ async function fetchCodexCatalog(ensureAuth, label) {
131
130
  const items = Array.isArray(data?.models) ? data.models : [];
132
131
  const normalized = items.map((m) => _normalizeCodexModel(m));
133
132
  _markLatestCodex(normalized);
134
- const enriched = sanitizeModelList((await enrichModels(normalized)).filter(Boolean), { provider: 'openai-oauth' });
133
+ // The backend curates this list per account and client version and marks
134
+ // each row's picker visibility itself, so every row is kept: generic list
135
+ // pruning (external release dates, id patterns) dropped models it still
136
+ // serves, along with their access programs.
137
+ const enriched = (await enrichModels(normalized)).filter(Boolean);
135
138
  _modelCache.save(enriched, { clientVersion });
136
139
  return enriched;
137
140
  }
@@ -18,6 +18,7 @@ import { getPluginData } from '../config.mjs';
18
18
  import { writeJsonAtomicSync, withFileLock } from '../../../shared/atomic-file.mjs';
19
19
  import { boundProviderAuthPath } from '../../../shared/provider-auth-binding.mjs';
20
20
  import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
21
+ import { modelProxyClientMode, serverModelCredentialsDisabledError } from '../../../shared/model-proxy-mode.mjs';
21
22
  import {
22
23
  accountIdentityFields,
23
24
  decodeJwtPayload,
@@ -101,6 +102,7 @@ export function tokensFileMtimeMs() {
101
102
  }
102
103
 
103
104
  export function loadTokens() {
105
+ if (modelProxyClientMode()) return null;
104
106
  const ownPath = getOwnTokenPath();
105
107
  if (!existsSync(ownPath)) return null;
106
108
  try {
@@ -322,6 +324,7 @@ async function refreshUnderLock({ currentToken, force, reason, startingTokens })
322
324
  * the provider can hold off re-attempting until the expiry skew.
323
325
  */
324
326
  export async function refreshStoredTokens({ current = null, force = false, reason = 'preemptive' } = {}) {
327
+ if (modelProxyClientMode()) throw serverModelCredentialsDisabledError('OpenAI OAuth credentials');
325
328
  const currentToken = current?.access_token || null;
326
329
  const disk = loadTokens();
327
330
  const adopted = adoptableDiskTokens(currentToken, disk, force);
@@ -6,6 +6,7 @@ import { createAccountPoolProvider } from './account-pool.mjs';
6
6
  import { isInclusiveProvider } from '../../../shared/llm/cost.mjs';
7
7
  import { noEnabledProvidersError } from './registry-errors.mjs';
8
8
  import { providerTakesNativePdf } from '../../../attachments/store.mjs';
9
+ import { modelProxyClientMode } from '../../../shared/model-proxy-mode.mjs';
9
10
  // OpenAI-compat provider names are self-declared by openai-compat-presets.mjs via
10
11
  // OPENAI_COMPAT_PRESETS. No parallel list maintained here.
11
12
  const providers = new Map();
@@ -176,6 +177,12 @@ async function loadProviderCtor(name, signal = null, cfg = null) {
176
177
  }
177
178
 
178
179
  async function instantiateProvider(name, Ctor, cfg) {
180
+ // Remote mode: the client PC executes the call with its own credentials.
181
+ // Admission and usage accounting still wrap the daemon-side stand-in.
182
+ if (modelProxyClientMode()) {
183
+ const { createModelProxyProvider } = await import('./model-proxy-provider.mjs');
184
+ return wrapProviderAdmission(createModelProxyProvider(name, cfg), name);
185
+ }
179
186
  if (isCustomProviderConfig(name, cfg)) return wrapProviderAdmission(await Ctor(name, cfg), name);
180
187
  if (Object.hasOwn(OPENAI_COMPAT_PRESETS, name) && name !== 'opencode-go') {
181
188
  return wrapProviderAdmission(new Ctor(name, cfg), name);
@@ -185,6 +192,12 @@ async function instantiateProvider(name, Ctor, cfg) {
185
192
 
186
193
  export async function initProviders(config, { signal = null } = {}) {
187
194
  throwIfAborted(signal);
195
+ // Remote mode: provider availability and config are the PC's, never this
196
+ // server's credential probes. No connected client fails here, clearly.
197
+ if (modelProxyClientMode()) {
198
+ const { loadModelProxyProviderConfig } = await import('./model-proxy-provider.mjs');
199
+ config = await awaitWithAbort(loadModelProxyProviderConfig(), signal);
200
+ }
188
201
  const sig = configSignature(config);
189
202
  // Coalesce: an identical config is already mid-init — attach to it.
190
203
  if (sig !== null && _inFlightPromise && _inFlightSig === sig) {
@@ -254,7 +267,8 @@ async function _initProvidersUnsynchronized(config, signal = null) {
254
267
  return { name, inst: providers.get(name), sig };
255
268
  }
256
269
  try {
257
- const Ctor = await loadProviderCtor(name, signal, cfg);
270
+ // A proxied provider never loads the real adapter module.
271
+ const Ctor = modelProxyClientMode() ? null : await loadProviderCtor(name, signal, cfg);
258
272
  throwIfAborted(signal);
259
273
  const inst = await instantiateProvider(name, Ctor, cfg);
260
274
  return { name, inst, sig };
@@ -30,6 +30,7 @@ import { setBuiltinInstalledInConfig, withGrandfatheredBuiltins } from '../../..
30
30
  const BROWSER_BRIDGE_DISCOVERY_FILE = 'browser-bridge.json';
31
31
  const COMPUTER_BRIDGE_DISCOVERY_FILE = 'computer-bridge.json';
32
32
  const TERMINAL_BRIDGE_DISCOVERY_FILE = 'terminal-bridge.json';
33
+ const VISUALIZE_BRIDGE_DISCOVERY_FILE = 'visualize-bridge.json';
33
34
 
34
35
  function bridgePresent(file) {
35
36
  try {
@@ -116,6 +117,14 @@ export function builtinFeatureActive(configLike, id) {
116
117
  if (id === 'terminal') {
117
118
  return featureEnvOverride('MIXDOG_FEATURE_TERMINAL') ?? bridgePresent(TERMINAL_BRIDGE_DISCOVERY_FILE);
118
119
  }
120
+ // Visualize shows artifacts in the desktop side panel: no install step, on
121
+ // unless the user turns it off, and only while the desktop bridge is up.
122
+ if (id === 'visualize') {
123
+ return (
124
+ featureEnvOverride('MIXDOG_FEATURE_VISUALIZE') ??
125
+ (moduleEnabled(configLike, 'visualize', true) && bridgePresent(VISUALIZE_BRIDGE_DISCOVERY_FILE))
126
+ );
127
+ }
119
128
  return false;
120
129
  }
121
130
 
@@ -148,6 +157,7 @@ export function featureDisallowedToolsFor(
148
157
  ...(builtinFeatureActive(configLike, 'office') ? [] : ['office']),
149
158
  ...(builtinFeatureActive(configLike, 'media') ? [] : ['media']),
150
159
  ...(builtinFeatureActive(configLike, 'tidy') ? [] : ['tidy']),
160
+ ...(builtinFeatureActive(configLike, 'visualize') ? [] : ['visualize']),
151
161
  ];
152
162
  // Headless exec uses the Lead surface and excludes Skill/MCP tools. When
153
163
  // only its eager defaults remain, neither schemas nor loader guidance help.