mixdog 1.1.2 → 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 (288) hide show
  1. package/README.md +1 -0
  2. package/package.json +2 -1
  3. package/scripts/lib/run-node-tests.mjs +9 -0
  4. package/scripts/prune-desktop-runtime.mjs +3 -0
  5. package/scripts/test-direct.mjs +4 -2
  6. package/src/defaults/shipped-history.json +6 -0
  7. package/src/defaults/skills/setup/references/actions.md +5 -2
  8. package/src/defaults/skills/visualize/SKILL.md +157 -0
  9. package/src/defaults/skills/visualize/references/craft.md +88 -0
  10. package/src/headless-command.mjs +56 -3
  11. package/src/headless-exec.mjs +17 -1
  12. package/src/help.mjs +2 -0
  13. package/src/rules/shared/70-delivery.md +2 -2
  14. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  15. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
  16. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
  17. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  18. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
  19. package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/config.mjs +9 -1
  21. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +47 -7
  22. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +51 -8
  23. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +25 -5
  24. package/src/runtime/agent/orchestrator/mcp/client.mjs +348 -122
  25. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
  26. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
  27. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
  28. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +13 -1
  29. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
  30. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
  31. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
  32. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +12 -4
  33. package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
  34. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  35. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  36. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
  37. package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
  38. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
  39. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
  40. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +67 -0
  41. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +521 -0
  42. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
  43. package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
  44. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
  45. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +3 -0
  46. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
  47. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
  49. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
  50. package/src/runtime/agent/orchestrator/providers/registry.mjs +15 -1
  51. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
  52. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
  53. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +12 -11
  54. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
  55. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +11 -0
  56. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -3
  57. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
  58. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
  59. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
  60. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
  61. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +18 -11
  62. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
  63. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
  64. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
  65. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
  66. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
  67. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
  69. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -1
  70. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +1 -0
  71. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +17 -7
  72. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
  73. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -2
  74. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -0
  75. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +5 -3
  76. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
  77. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
  78. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
  79. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
  80. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
  81. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
  82. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
  83. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
  84. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
  85. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
  86. package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
  87. package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
  88. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +29 -19
  89. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
  90. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
  91. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
  92. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +29 -0
  93. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +5 -1
  94. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +23 -4
  95. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
  96. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +28 -8
  97. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +140 -0
  98. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
  99. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
  100. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
  101. package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
  102. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +31 -12
  103. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
  104. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
  105. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
  106. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +80 -66
  107. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
  108. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -1
  109. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
  110. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
  111. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
  113. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
  114. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
  115. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
  116. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
  117. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
  118. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
  119. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
  120. package/src/runtime/attachments/store.mjs +38 -0
  121. package/src/runtime/shared/atomic-file.mjs +8 -2
  122. package/src/runtime/shared/best-effort-stderr.mjs +8 -0
  123. package/src/runtime/shared/code-graph-state.mjs +12 -0
  124. package/src/runtime/shared/defaults-separation.mjs +2 -0
  125. package/src/runtime/shared/git-runner-worker.mjs +3 -2
  126. package/src/runtime/shared/git-runner.mjs +4 -3
  127. package/src/runtime/shared/llm/default-context-window.mjs +6 -0
  128. package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
  129. package/src/runtime/shared/llm/model-catalog.mjs +240 -118
  130. package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
  131. package/src/runtime/shared/llm/provider-catalog-registry.mjs +156 -0
  132. package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
  133. package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
  134. package/src/runtime/shared/model-proxy-mode.mjs +19 -0
  135. package/src/runtime/shared/provider-accounts.mjs +2 -2
  136. package/src/runtime/shared/provider-api-key.mjs +21 -1
  137. package/src/runtime/shared/schedule-model-ref.mjs +11 -2
  138. package/src/runtime/shared/session-usage-totals.mjs +19 -0
  139. package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
  140. package/src/runtime/shared/singleton-owner.mjs +36 -6
  141. package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
  142. package/src/runtime/shared/tool-surface.mjs +7 -0
  143. package/src/runtime/shared/tool-work-units.mjs +14 -0
  144. package/src/runtime/shared/transcript-metadata.mjs +17 -1
  145. package/src/runtime/shared/turn-changes.mjs +791 -0
  146. package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
  147. package/src/runtime/shared/turn-snapshot.mjs +690 -81
  148. package/src/runtime/shared/turn-worktree-snapshot.mjs +161 -11
  149. package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
  150. package/src/runtime/shared/worktrees/contract.mjs +551 -0
  151. package/src/runtime/shared/worktrees/disk.mjs +57 -0
  152. package/src/runtime/shared/worktrees/git.mjs +247 -0
  153. package/src/runtime/shared/worktrees/include.mjs +480 -0
  154. package/src/runtime/shared/worktrees/lock.mjs +46 -0
  155. package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
  156. package/src/runtime/shared/worktrees/manager.mjs +1215 -0
  157. package/src/runtime/shared/worktrees/merge.mjs +182 -0
  158. package/src/runtime/shared/worktrees/owner.mjs +46 -0
  159. package/src/runtime/shared/worktrees/paths.mjs +62 -0
  160. package/src/runtime/shared/worktrees/registry.mjs +110 -0
  161. package/src/runtime/shared/worktrees/settings.mjs +84 -0
  162. package/src/runtime/shared/worktrees/slug.mjs +62 -0
  163. package/src/runtime/shared/worktrees/test-support.mjs +96 -0
  164. package/src/runtime/visualize-bridge/client.mjs +181 -0
  165. package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
  166. package/src/session-runtime/boot/apis.mjs +35 -0
  167. package/src/session-runtime/boot/config.mjs +2 -0
  168. package/src/session-runtime/boot/facade.mjs +20 -0
  169. package/src/session-runtime/boot/infrastructure.mjs +1 -0
  170. package/src/session-runtime/boot/session.mjs +1 -0
  171. package/src/session-runtime/boot/tools.mjs +4 -0
  172. package/src/session-runtime/config-lifecycle.mjs +10 -1
  173. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +11 -1
  174. package/src/session-runtime/global-extensions.mjs +2 -2
  175. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
  176. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
  177. package/src/session-runtime/internal-tool-executor.mjs +9 -1
  178. package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
  179. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
  180. package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
  181. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
  182. package/src/session-runtime/mcp-glue/connect.mjs +11 -6
  183. package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -3
  184. package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
  185. package/src/session-runtime/model-route/route-persist.mjs +3 -0
  186. package/src/session-runtime/model-route/set-route.mjs +15 -2
  187. package/src/session-runtime/notification-bus.mjs +9 -1
  188. package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
  189. package/src/session-runtime/resource-extension-sync.mjs +30 -7
  190. package/src/session-runtime/resource-mcp-api.mjs +20 -7
  191. package/src/session-runtime/route-resolution.mjs +10 -1
  192. package/src/session-runtime/route-state.mjs +16 -2
  193. package/src/session-runtime/runtime-core.mjs +5 -0
  194. package/src/session-runtime/runtime-facade.mjs +8 -0
  195. package/src/session-runtime/runtime-review-api.mjs +9 -0
  196. package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
  197. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
  198. package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
  199. package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
  200. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
  201. package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
  202. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
  203. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
  204. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  205. package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
  206. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
  207. package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
  208. package/src/session-runtime/services/agent-tool.mjs +1 -0
  209. package/src/session-runtime/services/daemon-candidate.mjs +3 -2
  210. package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
  211. package/src/session-runtime/session-create/session-options.mjs +10 -1
  212. package/src/session-runtime/session-turn-api.mjs +1 -0
  213. package/src/session-runtime/settings-api.mjs +4 -2
  214. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
  215. package/src/session-runtime/settings-worktree-api.mjs +17 -0
  216. package/src/session-runtime/setup-tool/executor.mjs +11 -4
  217. package/src/session-runtime/setup-tool/settings-contract.mjs +3 -1
  218. package/src/session-runtime/setup-tool/tool-defs.mjs +31 -3
  219. package/src/session-runtime/turn/agent-ops.mjs +12 -1
  220. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +13 -3
  221. package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
  222. package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
  223. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
  224. package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
  225. package/src/session-runtime/turn/turn-run.mjs +6 -7
  226. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
  227. package/src/session-runtime/workspace/admission-gate.mjs +197 -0
  228. package/src/session-runtime/workspace/project-branches.mjs +136 -0
  229. package/src/session-runtime/workspace/transition-steps.mjs +364 -0
  230. package/src/session-runtime/workspace/transitions.mjs +651 -0
  231. package/src/session-runtime/workspace/workspace-state.mjs +112 -0
  232. package/src/standalone/channel-transport/http-routes.mjs +17 -0
  233. package/src/standalone/channel-transport.mjs +13 -1
  234. package/src/standalone/daemon-desktop-runtime.mjs +1 -0
  235. package/src/standalone/daemon-owner.mjs +372 -0
  236. package/src/standalone/daemon-stored-session-views.mjs +4 -1
  237. package/src/standalone/daemon.mjs +139 -5
  238. package/src/standalone/desktop-service-registry.mjs +20 -0
  239. package/src/standalone/local-session-runtime.mjs +11 -0
  240. package/src/standalone/session-client/attachment-calls.mjs +3 -1
  241. package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
  242. package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
  243. package/src/standalone/session-client.mjs +104 -13
  244. package/src/standalone/session-protocol.mjs +16 -0
  245. package/src/standalone/session-proxy.mjs +3 -0
  246. package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
  247. package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
  248. package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
  249. package/src/standalone/session-service/projection/revisions.mjs +2 -0
  250. package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
  251. package/src/standalone/session-service/projection/workspace.mjs +14 -0
  252. package/src/standalone/session-service/session-calls/session-action.mjs +11 -3
  253. package/src/standalone/session-transport/call-route.mjs +13 -0
  254. package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
  255. package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
  256. package/src/standalone/session-transport/client-registry.mjs +20 -3
  257. package/src/standalone/session-transport/routes.mjs +79 -2
  258. package/src/standalone/session-transport/stack.mjs +2 -0
  259. package/src/standalone/session-transport.mjs +24 -1
  260. package/src/tui/app/message-selector.mjs +2 -1
  261. package/src/tui/app/model-options.mjs +35 -5
  262. package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
  263. package/src/tui/app/model-picker.mjs +4 -2
  264. package/src/tui/dist/index.mjs +332 -36
  265. package/src/tui/index.jsx +10 -2
  266. package/src/tui/session/compaction-view.mjs +147 -0
  267. package/src/tui/session/context-state.mjs +6 -0
  268. package/src/tui/session/initial-state.mjs +3 -0
  269. package/src/tui/session/quota-resume.mjs +2 -1
  270. package/src/tui/session/session-api/commands.mjs +10 -2
  271. package/src/tui/session/session-api/controls.mjs +4 -0
  272. package/src/tui/session/session-api/intake/rewind.mjs +2 -1
  273. package/src/tui/session/session-api/integrations.mjs +17 -0
  274. package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
  275. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
  276. package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
  277. package/src/tui/session/session-api/routes.mjs +10 -1
  278. package/src/tui/session/session-api/settings.mjs +2 -0
  279. package/src/tui/session/session-api/worktree.mjs +60 -0
  280. package/src/tui/session/session-api.mjs +2 -0
  281. package/src/tui/session/session-flow/auto-clear.mjs +31 -12
  282. package/src/tui/session/session-flow/drain.mjs +15 -1
  283. package/src/tui/session/session-flow/session-reset.mjs +4 -3
  284. package/src/tui/session/session-flow.mjs +2 -0
  285. package/src/tui/session/transcript-intake.mjs +14 -5
  286. package/src/tui/session/turn-stream.mjs +32 -4
  287. package/src/tui/session/turn-tool-cards.mjs +10 -2
  288. package/src/tui/session/turn.mjs +39 -10
@@ -3,6 +3,7 @@
3
3
  * new desktop context (cwd + desktop session) or a fresh session.
4
4
  */
5
5
  import { isScratchSession } from '../shared.mjs';
6
+ import { replacementSessionCwd } from '../../workspace/workspace-state.mjs';
6
7
 
7
8
  export function createContextSwitch(deps, { ingestSessionIntoMemory, closeSurfaceSession, releaseSessionWork }) {
8
9
  const {
@@ -59,11 +60,15 @@ export function createContextSwitch(deps, { ingestSessionIntoMemory, closeSurfac
59
60
  async function newSession() {
60
61
  deps.assertNotAddressed?.('newSession');
61
62
  const session = getSession();
63
+ // Leaving a worktree-bound Lead: the new session starts in the Project
64
+ // folder, never in the worktree the old session retains.
65
+ const projectFolder = replacementSessionCwd(session);
62
66
  if (session?.id) {
63
67
  void ingestSessionIntoMemory(session);
64
68
  closeSurfaceSession(session, 'cli-new', { tombstone: isScratchSession(session) });
65
69
  setSession(null);
66
70
  }
71
+ if (projectFolder) await applyResolvedCwd(projectFolder);
67
72
  invalidateContextStatusCache();
68
73
  await createCurrentSession();
69
74
  return getSession().id;
@@ -10,8 +10,16 @@ import {
10
10
  deferredSurfaceModeForLead,
11
11
  } from '../../../runtime/agent/orchestrator/runtime-core/effort.mjs';
12
12
  import { isScratchConversation } from '../shared.mjs';
13
+ import { compactionArchiveRange } from '../../../runtime/agent/orchestrator/session/compaction-archive.mjs';
14
+ import { stampCompactionMessageIds } from '../../../runtime/agent/orchestrator/session/compaction-archive-capture.mjs';
15
+ import { boundWorkspaceCwd, restoreWorkspaceAfterResume } from '../../workspace/workspace-state.mjs';
13
16
 
14
17
  export function resolveResumeCwd(session, currentCwd) {
18
+ // A worktree-bound Lead resumes in its worktree whatever its desktop class;
19
+ // a vanished worktree resumes in the Project folder in `recovery`, where
20
+ // turns are refused (restoreWorkspaceAfterResume) rather than silently run.
21
+ const bound = boundWorkspaceCwd(session);
22
+ if (bound) return bound.cwd;
15
23
  const desktop = session?.desktopSession;
16
24
  if (desktop?.classification === 'project') {
17
25
  // `session.cwd` is the execution source of truth. Older builds updated it
@@ -60,9 +68,9 @@ export function createSessionResume(deps, { ingestSessionIntoMemory, closeSurfac
60
68
  }
61
69
 
62
70
  // The resumed session's OWN effort wins. resolveRoute always returns an
63
- // effort key, so this used to reinstate the effort of whichever session
64
- // happened to be open before — a schedule/webhook session opened with a
65
- // different effort than the one it actually ran with.
71
+ // effort key, so the route's effort applies only when the session has none —
72
+ // otherwise a schedule/webhook session would reopen with the effort of
73
+ // whichever session happened to be open before.
66
74
  function resumedRouteFor(resumed) {
67
75
  const route = getRoute();
68
76
  const resumeEffort = resumed.effort || (hasOwn(route, 'effort') ? route.effort : undefined);
@@ -71,6 +79,7 @@ export function createSessionResume(deps, { ingestSessionIntoMemory, closeSurfac
71
79
  model: resumed.model,
72
80
  effort: resumeEffort,
73
81
  fast: resumed.fast === true,
82
+ autoEffort: resumed.autoEffort !== false,
74
83
  modelParameters: resumed.modelParameters || {},
75
84
  });
76
85
  }
@@ -92,6 +101,8 @@ export function createSessionResume(deps, { ingestSessionIntoMemory, closeSurfac
92
101
  const activeSession = getSession();
93
102
  activeSession.effort = getRoute().effectiveEffort || null;
94
103
  activeSession.fast = getRoute().fast === true;
104
+ if (getRoute().autoEffort === false) activeSession.autoEffort = false;
105
+ else delete activeSession.autoEffort;
95
106
  activeSession.cwd = getCurrentCwd();
96
107
  restoreResumedToolSurface(activeSession, getRoute());
97
108
  writeStatuslineRoute(statusRoutes, activeSession, getRoute());
@@ -124,11 +135,16 @@ export function createSessionResume(deps, { ingestSessionIntoMemory, closeSurfac
124
135
  };
125
136
  const resumed = await mgr.resumeSession(id, toolSpecForMode(getMode()), resumeOptions);
126
137
  if (!resumed) return null;
138
+ // Restored rows carry their messages' stable ids (legacy transcripts are
139
+ // stamped here and persisted), so a live compaction can grey exactly the
140
+ // rows of what it drops.
141
+ stampCompactionMessageIds(resumed.messages, resumed.id || id);
127
142
  retirePrevious(previous, resumed);
128
143
  setSession(resumed);
129
144
  // Resume is not work: the Lead pool row is written only by turn
130
145
  // start/settle, so opening an old conversation never re-lists it in the
131
146
  // Agent window for another reap window (user report).
147
+ restoreWorkspaceAfterResume(resumed);
132
148
  applyResolvedCwd(resolveResumeCwd(resumed, getCurrentCwd()));
133
149
  // Commit the applied cwd before returning the resume transcript.
134
150
  resumed.cwd = getCurrentCwd();
@@ -154,6 +170,8 @@ export function createSessionResume(deps, { ingestSessionIntoMemory, closeSurfac
154
170
  cwd: getCurrentCwd(),
155
171
  provider: resumed.provider,
156
172
  model: resumed.model,
173
+ // The display-only compaction archive above the restored rows.
174
+ transcriptArchive: compactionArchiveRange(resumed),
157
175
  };
158
176
  }
159
177
 
@@ -7,6 +7,7 @@ import { clean } from '../../../runtime/agent/orchestrator/runtime-core/session-
7
7
  import { forgetSessionSearch } from '../../../runtime/session-search/session-search-ingest.mjs';
8
8
  import { listLeadSessions } from '../session-catalog.mjs';
9
9
  import { SESSION_ID_PATTERN } from '../shared.mjs';
10
+ import { replacementSessionCwd } from '../../workspace/workspace-state.mjs';
10
11
 
11
12
  export function createSessionDelete(deps, { cancelBackgroundTasks }) {
12
13
  const {
@@ -49,6 +50,14 @@ export function createSessionDelete(deps, { cancelBackgroundTasks }) {
49
50
  }
50
51
  }
51
52
 
53
+ // The deleted task's worktree: the autoCleanup setting decides (clean and
54
+ // merged → removed; otherwise kept). Off the deletion path; never fails it.
55
+ function releaseDeletedWorktree(sessionId) {
56
+ return Promise.resolve()
57
+ .then(() => deps.cleanupDeletedSessionWorktree?.(sessionId))
58
+ .catch(() => {});
59
+ }
60
+
52
61
  async function deleteSession(id) {
53
62
  const sessionId = clean(id);
54
63
  if (!sessionId || !SESSION_ID_PATTERN.test(sessionId)) return false;
@@ -68,8 +77,12 @@ export function createSessionDelete(deps, { cancelBackgroundTasks }) {
68
77
  mgr.deleteSession(childId);
69
78
  } catch {}
70
79
  }
80
+ void releaseDeletedWorktree(sessionId);
71
81
  return true;
72
82
  }
83
+ // Leaving a worktree-bound Lead: the replacement starts in the Project
84
+ // folder, and the worktree can go once nothing runs in it.
85
+ const projectFolder = replacementSessionCwd(current);
73
86
  const cleanupReason = 'desktop-session-delete';
74
87
  releaseSessionWork(sessionId, cleanupReason);
75
88
  // Active sessions retain a tombstone until the normal sweep. Unlinking
@@ -86,15 +99,18 @@ export function createSessionDelete(deps, { cancelBackgroundTasks }) {
86
99
  } catch {}
87
100
  }
88
101
  setSession(null);
102
+ if (projectFolder) await deps.applyResolvedCwd?.(projectFolder);
89
103
  invalidateContextStatusCache();
90
104
  invalidatePreSessionToolSurface();
91
105
  // An addressed runtime never mints a replacement under the deleted
92
106
  // address: it becomes terminal and a new conversation needs a new entry.
93
107
  if (deps.getAddressedSessionId?.()) {
94
108
  deps.markAddressTerminal?.();
109
+ void releaseDeletedWorktree(sessionId);
95
110
  return true;
96
111
  }
97
112
  await createCurrentSession();
113
+ void releaseDeletedWorktree(sessionId);
98
114
  return true;
99
115
  }
100
116
 
@@ -24,11 +24,10 @@ export function createMcpConnect({ mcpClient, getMcpScopeId, state, resolveEffec
24
24
  }
25
25
  const cfg = servers[target];
26
26
  if (!cfg) return;
27
- // Drop any existing live entry first so connectMcpServers doesn't overwrite
28
- // the registry Map entry and leak the old transport/process.
29
- await mcpClient.disconnectMcpServer?.(target, scopeOptions());
27
+ // Close-then-connect as one generation-guarded op: a shutdown landing
28
+ // between the close and the connect never resurrects the server.
30
29
  try {
31
- await mcpClient.connectMcpServers({ [target]: cfg }, scopeOptions());
30
+ await mcpClient.reconnectSingleMcpServer(getMcpScopeId(), target, cfg);
32
31
  } catch (error) {
33
32
  const failures = Array.isArray(error?.failures)
34
33
  ? error.failures
@@ -126,8 +125,14 @@ export function createMcpConnect({ mcpClient, getMcpScopeId, state, resolveEffec
126
125
  if (!inFlight) return;
127
126
  let budgetMs = 10000;
128
127
  try {
129
- const resolved = mcpClient.resolveMcpStartupTimeoutMs?.({});
130
- if (Number.isFinite(resolved)) budgetMs = resolved;
128
+ // The gate must outlast the slowest enabled server's own startup budget;
129
+ // a server with its budget disabled (0) means the connect may never
130
+ // settle, so the whole gate is disabled (handled below).
131
+ const configs = Object.values(resolveEffectiveMcpServers().servers).filter((cfg) => cfg?.enabled !== false);
132
+ const budgets = (configs.length > 0 ? configs : [{}])
133
+ .map((cfg) => mcpClient.resolveMcpStartupTimeoutMs?.(cfg))
134
+ .filter((value) => Number.isFinite(value));
135
+ if (budgets.length > 0) budgetMs = budgets.includes(0) ? 0 : Math.max(...budgets);
131
136
  } catch {
132
137
  /* fall back to default budget */
133
138
  }
@@ -68,13 +68,17 @@ export function createMcpServerCatalog({ mcpClient, getConfig, getMcpScopeId, st
68
68
  const servers = [];
69
69
  for (const [name, cfg] of Object.entries(configured)) {
70
70
  const live = connected.get(name);
71
- const fail = failures.get(name);
71
+ // A registered server whose client reports it not alive (runtime
72
+ // failure after connect) is surfaced as failed, not connected.
73
+ const dead = live && (live.alive === false || live.connected === false);
74
+ const fail =
75
+ failures.get(name) || (dead ? { msg: live.error || live.lastError || 'MCP server is not alive' } : undefined);
72
76
  servers.push({
73
77
  name,
74
78
  configured: true,
75
79
  enabled: cfg?.enabled !== false,
76
- connected: Boolean(live),
77
- status: mcpServerStatus(cfg, live, fail),
80
+ connected: Boolean(live) && !dead,
81
+ status: mcpServerStatus(cfg, live && !dead, fail),
78
82
  transport: mcpTransportLabel(cfg),
79
83
  toolCount: live?.toolCount || 0,
80
84
  tools: live?.tools || [],
@@ -26,8 +26,37 @@ function coerceStringArray(value) {
26
26
  return Array.isArray(value) ? value.map((entry) => clean(entry)).filter(Boolean) : [];
27
27
  }
28
28
 
29
+ function positiveNumber(value, label) {
30
+ const n = Number(value);
31
+ if (!Number.isFinite(n) || n <= 0) throw new Error(`${label} must be a positive number`);
32
+ return n;
33
+ }
34
+
35
+ // Per-server timeouts, stored under the keys client-config.mjs already reads:
36
+ // startupTimeoutSec/startupTimeoutMs and timeoutMs. A stored value (save
37
+ // merges the existing entry into the input) round-trips unchanged unless the
38
+ // setup-facing startupTimeoutSec/toolTimeoutSec overrides it.
39
+ function timeoutFields(input) {
40
+ const out = {};
41
+ const startupSec = input.startupTimeoutSec ?? input.startup_timeout_sec;
42
+ if (startupSec != null && startupSec !== '') out.startupTimeoutSec = positiveNumber(startupSec, 'startupTimeoutSec');
43
+ else if (input.startupTimeoutMs != null && input.startupTimeoutMs !== '')
44
+ out.startupTimeoutMs = positiveNumber(input.startupTimeoutMs, 'startupTimeoutMs');
45
+ if (input.toolTimeoutSec != null && input.toolTimeoutSec !== '')
46
+ out.timeoutMs = Math.round(positiveNumber(input.toolTimeoutSec, 'toolTimeoutSec') * 1000);
47
+ else if (input.timeoutMs != null && input.timeoutMs !== '')
48
+ out.timeoutMs = Math.round(positiveNumber(input.timeoutMs, 'timeoutMs'));
49
+ return out;
50
+ }
51
+
29
52
  export function createMcpServerInput({ mcpClient, getCurrentCwd }) {
30
53
  function normalizeMcpServerInput(input = {}) {
54
+ const result = normalizeTransport(input);
55
+ Object.assign(result.config, timeoutFields(input));
56
+ return result;
57
+ }
58
+
59
+ function normalizeTransport(input) {
31
60
  const currentCwd = getCurrentCwd();
32
61
  const name = clean(input.name)
33
62
  .toLowerCase()
@@ -52,6 +52,9 @@ export function createRoutePersistence(deps) {
52
52
  // A selection for the heir must not leak its tuning into the source model.
53
53
  if (!sessionUsesRoute(session, route)) return;
54
54
  session.fast = route.fast === true;
55
+ // Absent means on; only the off state is stored on the session.
56
+ if (route.autoEffort === false) session.autoEffort = false;
57
+ else delete session.autoEffort;
55
58
  session.effort = route.effectiveEffort || null;
56
59
  // The request path reads session.modelParameters; keep only serviceTier in step.
57
60
  const { serviceTier: _previous, ...otherParameters } = session.modelParameters || {};
@@ -55,12 +55,23 @@ async function resolveSelectedRoute(deps, next) {
55
55
  selectedRoute.effort,
56
56
  selectedRoute.modelParameters
57
57
  );
58
+ const contextState = resolveRouteContextState(selectedRoute, modelMeta);
59
+ // An exact selection is saved clamped, and not at all when it is the
60
+ // default window; an unknown window keeps it as requested.
61
+ const contextTokens =
62
+ contextState.contextDefaultPercent === undefined ? selectedRoute.contextTokens : contextState.contextTokens;
63
+ const { contextTokens: requestedTokens, ...routeFields } = selectedRoute;
64
+ // Tokens that land on the default window select the default, whatever
65
+ // percent travelled with them.
66
+ const tokensAtDefault = requestedTokens && !contextTokens && contextState.contextDefaultPercent !== undefined;
58
67
  return {
59
68
  selectedRoute: {
60
- ...selectedRoute,
69
+ ...routeFields,
70
+ ...(tokensAtDefault ? { contextPercent: contextState.contextDefaultPercent } : {}),
61
71
  fast: fastCapable ? selectedRoute.fast === true : false,
62
72
  // Lets the saved model settings tell a chosen window from the default.
63
- contextDefaultPercent: resolveRouteContextState(selectedRoute, modelMeta).contextDefaultPercent,
73
+ contextDefaultPercent: contextState.contextDefaultPercent,
74
+ ...(contextTokens ? { contextTokens } : {}),
64
75
  },
65
76
  fastCapable,
66
77
  modelMeta,
@@ -92,9 +103,11 @@ function updateSessionInPlace(deps, session, updateOptions) {
92
103
  provider: route.provider,
93
104
  model: route.model,
94
105
  fast: route.fast === true,
106
+ autoEffort: route.autoEffort !== false,
95
107
  effort: route.effectiveEffort || null,
96
108
  modelParameters: route.modelParameters || {},
97
109
  contextPercent: route.contextPercent,
110
+ contextTokens: route.contextTokens || null,
98
111
  selectedContextWindow: route.selectedContextWindow || null,
99
112
  };
100
113
  const updated = mgr.updateSessionRoute?.(session.id, fields, updateOptions);
@@ -29,7 +29,7 @@ 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, getOwnerLifecycle }) {
32
+ export function createCompletionWakeScheduler({ getCurrentSessionId, getTurnApi, getOwnerLifecycle, beforeWake }) {
33
33
  // A closing/terminal owner never starts inference from a completion; the
34
34
  // check runs at request time and again inside the delayed callback.
35
35
  const ownerOpen = (sessionId) => {
@@ -55,6 +55,14 @@ export function createCompletionWakeScheduler({ getCurrentSessionId, getTurnApi,
55
55
  ` executionId=${executionId || 'unknown'} queuedMs=${delayMs}\n`
56
56
  );
57
57
  }
58
+ // Surface hook (idle auto-clear): compacts a cold-cache session before
59
+ // the wake sends it. A failing hook never blocks the wake.
60
+ if (typeof beforeWake === 'function') {
61
+ try {
62
+ await beforeWake(ownerSessionId);
63
+ } catch {}
64
+ if (cleanSessionId(getCurrentSessionId()) !== ownerSessionId || !ownerOpen(ownerSessionId)) return;
65
+ }
58
66
  // The wake continues the user turn that launched the worker, so its
59
67
  // review keeps that turn's Lead edits and worker diffs.
60
68
  await turnApi.ask('', { submittedAt: queuedAt, continueTurn: true });
@@ -65,8 +65,10 @@ export function hydrateProviderModelRow(cfg, row) {
65
65
  ) || customAutoEffortCapable(row.provider, cfg?.providers?.[row.provider]?.protocol, row),
66
66
  savedEffort: saved.effort || null,
67
67
  savedFast: Object.hasOwn(saved, 'fast') ? saved.fast === true : undefined,
68
+ savedAutoEffort: Object.hasOwn(saved, 'autoEffort') ? saved.autoEffort !== false : undefined,
68
69
  savedModelParameters: saved.modelParameters || {},
69
70
  savedContextPercent: Number(saved.contextPercent) || undefined,
71
+ savedContextTokens: Number(saved.contextTokens) || undefined,
70
72
  };
71
73
  }
72
74
 
@@ -28,17 +28,27 @@ export function createExtensionSync({
28
28
  }
29
29
  }
30
30
 
31
- async function refreshState(kind) {
31
+ // Apply per-server MCP changes in order; every other server stays untouched.
32
+ async function applyMcpChanges(changes) {
33
+ let status;
34
+ for (const { name, enabled } of changes) status = await connectConfiguredMcp({ only: name, enabled });
35
+ return status;
36
+ }
37
+
38
+ async function refreshState(kind, detail = null) {
32
39
  reloadFullConfig?.();
33
40
  if (kind === 'skills' || kind === 'plugins') invalidateSkills?.();
34
- if (kind === 'mcp' || kind === 'plugins') {
41
+ const mcpChanges = kind === 'mcp' ? detail?.mcpChanges : null;
42
+ if (Array.isArray(mcpChanges)) {
43
+ await applyMcpChanges(mcpChanges);
44
+ } else if (kind === 'mcp' || kind === 'plugins') {
35
45
  await connectConfiguredMcp({ reset: true });
36
46
  }
37
47
  await refreshSurface(kind);
38
48
  }
39
49
 
40
50
  const subscription = subscribeGlobalExtensionChanges(refreshState);
41
- const publish = (kind) => publishGlobalExtensionChange(kind, subscription.id);
51
+ const publish = (kind, detail = null) => publishGlobalExtensionChange(kind, subscription.id, detail);
42
52
 
43
53
  function scheduleMcpToggle(serverName, enabled) {
44
54
  const chain = mcpToggleChains.get(serverName) || { desired: enabled, running: null };
@@ -65,14 +75,27 @@ export function createExtensionSync({
65
75
 
66
76
  /** Skills/plugins invalidate the skill cache; every kind rebuilds the
67
77
  * pre-session surface and notifies the other runtimes. */
68
- async function announce(kind, { invalidate = kind !== 'mcp' } = {}) {
78
+ async function announce(kind, { invalidate = kind !== 'mcp', detail = null } = {}) {
69
79
  if (invalidate) invalidateSkills?.();
70
80
  await refreshSurface(kind);
71
- await publish(kind);
81
+ await publish(kind, detail);
72
82
  }
73
83
 
74
- /** Reconnect every configured MCP server after a config write, then announce. */
75
- async function reconnectMcpAndAnnounce() {
84
+ /** Reconnect MCP after a config write, then announce. With `only`, just that
85
+ * server is (re)connected (or disconnected when `enabled` is false) and
86
+ * `drop` names (e.g. a renamed/removed server) are disconnected; every other
87
+ * server stays untouched. Without `only`, every configured server reconnects. */
88
+ async function reconnectMcpAndAnnounce({ only = null, enabled = true, drop = [] } = {}) {
89
+ if (only || drop.length > 0) {
90
+ // Other runtimes receive the same scoped change list, not a full reset.
91
+ const mcpChanges = [
92
+ ...drop.map((name) => ({ name, enabled: false })),
93
+ ...(only ? [{ name: only, enabled }] : []),
94
+ ];
95
+ const status = await applyMcpChanges(mcpChanges);
96
+ await announce('mcp', { detail: { mcpChanges } });
97
+ return status;
98
+ }
76
99
  const status = await connectConfiguredMcp({ reset: true });
77
100
  await announce('mcp');
78
101
  return status;
@@ -11,6 +11,7 @@ import {
11
11
  pluginServerMatcher,
12
12
  resolveContainedPluginPath,
13
13
  mergeMcpServerConfig,
14
+ MCP_TIMEOUT_FIELD_GROUPS,
14
15
  } from '../runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs';
15
16
 
16
17
  /** The `mcpServers` map of a config as a fresh object (never the live one). */
@@ -63,8 +64,13 @@ function mergedServerInput(input, existing, originalName) {
63
64
  if (Object.hasOwn(input, key)) maps[key] = { ...(existing[key] || {}), ...input[key] };
64
65
  }
65
66
  }
67
+ // Existing timeouts are kept by mergeMcpServerConfig; the normalizer must only
68
+ // see (and validate) newly supplied ones, so a stored 0/'off' never blocks
69
+ // an unrelated save.
70
+ const { ...base } = transportChanged ? {} : existing;
71
+ for (const field of MCP_TIMEOUT_FIELD_GROUPS.flat()) delete base[field];
66
72
  return {
67
- ...(transportChanged ? {} : existing),
73
+ ...base,
68
74
  ...input,
69
75
  ...maps,
70
76
  name: clean(input.name) || originalName,
@@ -98,17 +104,20 @@ export function createMcpResourceApi({ deps, sync, decorate }) {
98
104
  getMcpServerConfig(name) {
99
105
  return getMcpServerConfig(name);
100
106
  },
101
- async reconnectMcp() {
107
+ async reconnectMcp(name) {
102
108
  await awaitKeychainPrewarm();
103
109
  reloadFullConfig();
104
- return sync.reconnectMcpAndAnnounce();
110
+ const serverName = clean(name);
111
+ if (!serverName) return sync.reconnectMcpAndAnnounce();
112
+ const { enabled } = getMcpServerConfig(serverName);
113
+ return sync.reconnectMcpAndAnnounce({ only: serverName, enabled });
105
114
  },
106
115
  async addMcpServer(input = {}) {
107
116
  const { name, config: serverConfig } = normalizeMcpServerInput(input);
108
117
  const servers = mcpServersOf(getConfig());
109
118
  if (Object.hasOwn(servers, name)) throw new Error(`MCP server already exists: ${name}`);
110
119
  saveServers({ ...servers, [name]: serverConfig });
111
- const status = await sync.reconnectMcpAndAnnounce();
120
+ const status = await sync.reconnectMcpAndAnnounce({ only: name, enabled: serverConfig.enabled !== false });
112
121
  return { name, status };
113
122
  },
114
123
  async saveMcpServer(input = {}) {
@@ -130,7 +139,11 @@ export function createMcpResourceApi({ deps, sync, decorate }) {
130
139
  if (originalName && originalName !== name) delete servers[originalName];
131
140
  servers[name] = mergeMcpServerConfig(existing, normalizedInput.config);
132
141
  saveServers(servers);
133
- const status = await sync.reconnectMcpAndAnnounce();
142
+ const status = await sync.reconnectMcpAndAnnounce({
143
+ only: name,
144
+ enabled: servers[name].enabled !== false,
145
+ drop: originalName && originalName !== name ? [originalName] : [],
146
+ });
134
147
  return { name, source: 'config', status };
135
148
  },
136
149
  async removeMcpServer(name) {
@@ -142,7 +155,7 @@ export function createMcpResourceApi({ deps, sync, decorate }) {
142
155
  }
143
156
  delete servers[serverName];
144
157
  saveServers(servers);
145
- return sync.reconnectMcpAndAnnounce();
158
+ return sync.reconnectMcpAndAnnounce({ drop: [serverName] });
146
159
  },
147
160
  async setMcpServerEnabled(name, enabled) {
148
161
  const serverName = clean(name);
@@ -158,7 +171,7 @@ export function createMcpResourceApi({ deps, sync, decorate }) {
158
171
  servers[serverName] = { ...current, enabled: want };
159
172
  saveServers(servers);
160
173
  const status = await sync.scheduleMcpToggle(serverName, want);
161
- await sync.publish('mcp');
174
+ await sync.publish('mcp', { mcpChanges: [{ name: serverName, enabled: want }] });
162
175
  return status;
163
176
  },
164
177
  async enablePluginMcp(plugin = {}) {
@@ -5,6 +5,7 @@ import { ensureProviderEnabled } from '../runtime/agent/orchestrator/runtime-cor
5
5
  import { clean } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
6
6
  import { effortItemsFor } from '../runtime/agent/orchestrator/runtime-core/effort.mjs';
7
7
  import { runAbortable, throwIfAborted } from '../runtime/shared/abort-race.mjs';
8
+ import { normalizeContextTokens } from '../runtime/shared/llm/default-context-window.mjs';
8
9
  import { resolveRouteContextState, resolveRouteEffortState, routeModelDisplay } from './route-state.mjs';
9
10
 
10
11
  export function createRouteResolution({ rt, collectProviderModels, ensureProvidersReady, lookupModelMeta }) {
@@ -53,14 +54,22 @@ export function createRouteResolution({ rt, collectProviderModels, ensureProvide
53
54
  const selectedContextWindow = [contextState.selectedContextWindow, contextOption?.contextWindow]
54
55
  .map(Number)
55
56
  .find((value) => value > 0);
57
+ // Without a known window the exact selection is kept as requested; with
58
+ // one it is the clamped value, absent when it is the default window.
59
+ const contextTokens =
60
+ contextState.contextDefaultPercent === undefined
61
+ ? normalizeContextTokens(targetRoute.contextTokens)
62
+ : contextState.contextTokens;
63
+ const { contextTokens: _requestedTokens, ...routeFields } = targetRoute;
56
64
  rt.route = {
57
- ...targetRoute,
65
+ ...routeFields,
58
66
  fast: fastCapable ? targetRoute.fast === true : false,
59
67
  fastCapable,
60
68
  effectiveEffort,
61
69
  effortOptions: effortItemsFor(rt.route.provider, modelMeta, effectiveEffort),
62
70
  contextPercent: contextState.contextPercent,
63
71
  contextDefaultPercent: contextState.contextDefaultPercent,
72
+ ...(contextTokens ? { contextTokens } : {}),
64
73
  ...(selectedContextWindow ? { selectedContextWindow } : {}),
65
74
  ...(modelDisplay ? { modelDisplay } : {}),
66
75
  };
@@ -1,11 +1,14 @@
1
1
  // Pure route derivations: the effective effort / Fast capability of a route
2
2
  // against provider model metadata, and its context-window percentage.
3
- import { modelMetaLooksResolved, modelSettingsFor } from '../runtime/agent/orchestrator/runtime-core/config-helpers.mjs';
3
+ import {
4
+ modelMetaLooksResolved,
5
+ modelSettingsFor,
6
+ } from '../runtime/agent/orchestrator/runtime-core/config-helpers.mjs';
4
7
  import { clean, hasOwn } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
5
8
  import { coerceEffortFor } from '../runtime/agent/orchestrator/runtime-core/effort.mjs';
6
9
  import { fastCapableFor } from '../runtime/agent/orchestrator/runtime-core/model-capabilities.mjs';
7
10
  import { providerCachedModelMetadataSync } from '../runtime/agent/orchestrator/providers/provider-catalog-cache.mjs';
8
- import { contextWindowRange } from '../runtime/shared/llm/default-context-window.mjs';
11
+ import { contextWindowRange, normalizeContextTokens } from '../runtime/shared/llm/default-context-window.mjs';
9
12
 
10
13
  export function resolveRouteEffortState(targetRoute = {}, modelMeta = null) {
11
14
  const requested = hasOwn(targetRoute, 'effort') ? targetRoute.effort : targetRoute.preset?.effort || null;
@@ -68,6 +71,17 @@ export function resolveRouteContextState(targetRoute = {}, modelMeta = null, win
68
71
  return { contextPercent: undefined, contextDefaultPercent: undefined, selectedContextWindow: undefined };
69
72
  }
70
73
  const contextDefaultPercent = Math.max(10, Math.min(100, Math.round((defaultWindow / maxWindow) * 10) * 10));
74
+ // An exact token selection overrides the 10% stops. It is clamped to the
75
+ // selectable range; one that lands on the default window is the default.
76
+ const requestedTokens = normalizeContextTokens(targetRoute?.contextTokens);
77
+ if (requestedTokens) {
78
+ const contextTokens = Math.max(Math.ceil(maxWindow * 0.1), Math.min(maxWindow, requestedTokens));
79
+ if (contextTokens === defaultWindow) {
80
+ return { contextPercent: contextDefaultPercent, contextDefaultPercent, selectedContextWindow: defaultWindow };
81
+ }
82
+ const contextPercent = Math.max(10, Math.min(100, Math.round((contextTokens / maxWindow) * 10) * 10));
83
+ return { contextPercent, contextDefaultPercent, selectedContextWindow: contextTokens, contextTokens };
84
+ }
71
85
  const requested = Number(targetRoute?.contextPercent);
72
86
  const contextPercent =
73
87
  Number.isFinite(requested) && requested > 0
@@ -20,6 +20,7 @@ function bootParams({
20
20
  fast,
21
21
  modelParameters,
22
22
  contextPercent,
23
+ contextTokens,
23
24
  selectedContextWindow,
24
25
  cwd = process.cwd(),
25
26
  toolMode = 'full',
@@ -30,6 +31,7 @@ function bootParams({
30
31
  initialConfig = null,
31
32
  desktopSession = null,
32
33
  sessionProfile = null,
34
+ worktree = null,
33
35
  executeAgentControl = null,
34
36
  } = {}) {
35
37
  return {
@@ -39,6 +41,7 @@ function bootParams({
39
41
  fast,
40
42
  modelParameters,
41
43
  contextPercent,
44
+ contextTokens,
42
45
  selectedContextWindow,
43
46
  cwd,
44
47
  toolMode,
@@ -49,6 +52,8 @@ function bootParams({
49
52
  initialConfig,
50
53
  desktopSession,
51
54
  sessionProfile,
55
+ // Creation-time worktree intent (workspace/transitions.mjs creationIntent).
56
+ worktree,
52
57
  executeAgentControl,
53
58
  };
54
59
  }
@@ -74,6 +74,10 @@ export function createRuntimeFacade({
74
74
  get fast() {
75
75
  return state.route.fast === true;
76
76
  },
77
+ // The route's Auto effort switch; absent means on.
78
+ get autoEffort() {
79
+ return state.route.autoEffort !== false;
80
+ },
77
81
  get fastCapable() {
78
82
  return state.route.fastCapable === true;
79
83
  },
@@ -94,6 +98,10 @@ export function createRuntimeFacade({
94
98
  // Picker preference for a new/inherited session, not the live gauge.
95
99
  return state.route?.contextPercent ?? state.session?.contextPercent ?? null;
96
100
  },
101
+ get contextTokens() {
102
+ // Exact window preference in tokens; null when the route uses a percent stop.
103
+ return state.route ? state.route.contextTokens || null : state.session?.contextTokens || null;
104
+ },
97
105
  get rawContextWindow() {
98
106
  return state.session?.rawContextWindow || state.session?.contextWindow || null;
99
107
  },
@@ -2,7 +2,10 @@ import { registerCacheInvalidationListener } from '../runtime/agent/orchestrator
2
2
  import { turnReviewReply } from '../runtime/shared/turn-review-reply.mjs';
3
3
  import {
4
4
  getSessionReviewDiff,
5
+ getTurnChanges,
5
6
  getTurnReviewDiff,
7
+ reapplyTurnChanges,
8
+ revertTurnChanges,
6
9
  revertTurnReview,
7
10
  revertTurnReviewFile,
8
11
  } from '../runtime/shared/turn-snapshot.mjs';
@@ -22,5 +25,11 @@ export function createRuntimeReviewApi({ getCwd, getSessionId }) {
22
25
  getSessionReviewDiff: () => getSessionReviewDiff(getCwd(), getSessionId()),
23
26
  revertTurnReview: (checkpointId) => revertTurnReview(getCwd(), getSessionId(), checkpointId),
24
27
  revertTurnReviewFile: (file, checkpointId) => revertTurnReviewFile(getCwd(), getSessionId(), file, checkpointId),
28
+ // Historical per-turn changes, addressed by checkpointId (TurnChangesReply).
29
+ getTurnChanges: (options = {}) => getTurnChanges(getSessionId(), options),
30
+ revertTurnChanges: (checkpointId, file, revision) =>
31
+ revertTurnChanges(getSessionId(), checkpointId, file, revision),
32
+ reapplyTurnChanges: (checkpointId, file, revision) =>
33
+ reapplyTurnChanges(getSessionId(), checkpointId, file, revision),
25
34
  };
26
35
  }
@@ -12,6 +12,7 @@ import {
12
12
  import { PRESET_ALIASES } from './tool-def.mjs';
13
13
  import { positiveInt } from '../../../runtime/shared/numbers.mjs';
14
14
  import { clean } from '../../../runtime/shared/clean.mjs';
15
+ import { normalizeContextTokens } from '../../../runtime/shared/llm/default-context-window.mjs';
15
16
 
16
17
  export { positiveInt };
17
18
 
@@ -220,10 +221,14 @@ export function normalizeAgentRoute(routeLike) {
220
221
  model,
221
222
  effort: clean(routeLike?.effort) || undefined,
222
223
  fast: routeLike?.fast === true,
224
+ ...(routeLike?.autoEffort === false ? { autoEffort: false } : {}),
223
225
  ...(routeLike?.modelParameters && typeof routeLike.modelParameters === 'object'
224
226
  ? { modelParameters: { ...routeLike.modelParameters } }
225
227
  : {}),
226
228
  ...(Number(routeLike?.contextPercent) > 0 ? { contextPercent: Number(routeLike.contextPercent) } : {}),
229
+ ...(normalizeContextTokens(routeLike?.contextTokens)
230
+ ? { contextTokens: normalizeContextTokens(routeLike.contextTokens) }
231
+ : {}),
227
232
  };
228
233
  }
229
234