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
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Loopback client for the desktop app's visualize bridge. The desktop app
3
+ * advertises { port, token } through `visualize-bridge.json`; this runtime
4
+ * half is the sync availability probe and the executor behind tool calls.
5
+ * Request: POST /command { sessionId, cwd, action, input }.
6
+ * Response: { ok:true, text, tabs?, path? } | { ok:false, error }.
7
+ */
8
+ import { bridgeDiscoveryChanged, readBridgeDiscovery, readBridgeDiscoveryDetail } from '../bridge-discovery.mjs';
9
+
10
+ const DISCOVERY_FILE = 'visualize-bridge.json';
11
+ const REQUEST_TIMEOUT_MS = 30_000;
12
+ /** `export` renders a page and writes a file; the desktop host's own deadline
13
+ * for it (45 s) is shorter, so a timeout is reported by the host, which then
14
+ * writes nothing, rather than raced by this client. */
15
+ const EXPORT_REQUEST_TIMEOUT_MS = 60_000;
16
+ const MAX_RESPONSE_BYTES = 1024 * 1024;
17
+ const ACTIONS = new Set(['open', 'close', 'annotate', 'capture', 'export', 'check', 'inspect']);
18
+ const INLINE_KINDS = ['mermaid', 'svg', 'chart', 'html', 'markdown'];
19
+ const MAX_INLINE_BYTES = 512 * 1024;
20
+ const BRIDGE_UNAVAILABLE_MESSAGE = 'visualize is unavailable; the Mixdog desktop app must be running';
21
+
22
+ export function visualizeBridgeAvailableSync() {
23
+ return readBridgeDiscovery(DISCOVERY_FILE) !== null;
24
+ }
25
+
26
+ function toolError(message) {
27
+ return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
28
+ }
29
+
30
+ function unavailableMessage() {
31
+ const { reason } = readBridgeDiscoveryDetail(DISCOVERY_FILE);
32
+ return reason && reason !== 'missing'
33
+ ? `${BRIDGE_UNAVAILABLE_MESSAGE} (discovery ${reason})`
34
+ : BRIDGE_UNAVAILABLE_MESSAGE;
35
+ }
36
+
37
+ export function validateVisualizeArgs(args) {
38
+ if (!args || typeof args !== 'object' || Array.isArray(args)) {
39
+ return { ok: false, error: 'visualize arguments must be an object' };
40
+ }
41
+ if (!ACTIONS.has(args.action)) return { ok: false, error: 'action must be open, close, annotate, capture, export, check, or inspect' };
42
+ const input = args.input ?? {};
43
+ if (typeof input !== 'object' || Array.isArray(input)) return { ok: false, error: 'input must be an object' };
44
+ if (args.action === 'open' && input.content != null) {
45
+ const content = input.content;
46
+ if (input.targets != null) return { ok: false, error: 'open takes either input.targets or input.content, not both' };
47
+ if (typeof content !== 'object' || Array.isArray(content)) {
48
+ return { ok: false, error: 'open input.content must be { kind, source, title? }' };
49
+ }
50
+ if (!INLINE_KINDS.includes(content.kind)) {
51
+ return { ok: false, error: `open input.content.kind must be one of ${INLINE_KINDS.join(', ')}` };
52
+ }
53
+ if (typeof content.source !== 'string' || !content.source.trim()) {
54
+ return { ok: false, error: 'open input.content.source must be a non-empty string' };
55
+ }
56
+ if (Buffer.byteLength(content.source) > MAX_INLINE_BYTES) {
57
+ return { ok: false, error: `open input.content.source must be at most ${MAX_INLINE_BYTES / 1024} KiB` };
58
+ }
59
+ if (content.title != null && typeof content.title !== 'string') {
60
+ return { ok: false, error: 'open input.content.title must be a string' };
61
+ }
62
+ if (input.version != null && !(Number.isInteger(input.version) && input.version >= 1)) {
63
+ return { ok: false, error: 'open input.version must be a positive integer (a kept version number)' };
64
+ }
65
+ } else if (args.action === 'open') {
66
+ const targets = input.targets;
67
+ if (
68
+ !Array.isArray(targets) ||
69
+ targets.length < 1 ||
70
+ targets.length > 3 ||
71
+ targets.some((target) => typeof target !== 'string' || !target.trim())
72
+ ) {
73
+ return { ok: false, error: 'open requires input.targets: 1-3 non-empty strings' };
74
+ }
75
+ if (targets.some((target) => /^https?:\/\//i.test(target.trim())) && targets.length > 1) {
76
+ return { ok: false, error: 'a web URL must be the only target; do not mix it with files or other URLs' };
77
+ }
78
+ if (input.version != null && !(Number.isInteger(input.version) && input.version >= 1)) {
79
+ return { ok: false, error: 'open input.version must be a positive integer (a kept version number)' };
80
+ }
81
+ }
82
+ if (['annotate', 'capture', 'export', 'check', 'inspect'].includes(args.action) && (typeof input.tab !== 'string' || !input.tab)) {
83
+ return { ok: false, error: `${args.action} requires input.tab` };
84
+ }
85
+ if (args.action === 'inspect') {
86
+ const { viewport, selector } = input;
87
+ if (viewport != null) {
88
+ const sized = (value, min, max) => Number.isInteger(value) && value >= min && value <= max;
89
+ if (
90
+ typeof viewport !== 'object' ||
91
+ Array.isArray(viewport) ||
92
+ !sized(viewport.width, 200, 3000) ||
93
+ (viewport.height != null && !sized(viewport.height, 200, 4000))
94
+ ) {
95
+ return { ok: false, error: 'inspect input.viewport must be { width: 200-3000, height?: 200-4000 } in whole pixels' };
96
+ }
97
+ }
98
+ if (selector != null && (typeof selector !== 'string' || !selector.trim() || selector.length > 500)) {
99
+ return { ok: false, error: 'inspect input.selector must be a non-empty CSS selector of at most 500 characters' };
100
+ }
101
+ }
102
+ if (args.action === 'capture' && !(typeof input.output === 'string' && /\.png$/i.test(input.output.trim()))) {
103
+ return { ok: false, error: 'capture requires input.output ending in .png' };
104
+ }
105
+ if (args.action === 'export') {
106
+ if (input.format !== 'png' && input.format !== 'pdf') return { ok: false, error: 'export requires input.format png or pdf' };
107
+ if (typeof input.output !== 'string' || !new RegExp('\\.' + input.format + '$', 'i').test(input.output.trim())) {
108
+ return { ok: false, error: `export requires input.output ending in .${input.format}` };
109
+ }
110
+ if (input.version != null && !(Number.isInteger(input.version) && input.version >= 1)) {
111
+ return { ok: false, error: 'export input.version must be a positive integer (a kept version number)' };
112
+ }
113
+ if (input.overwrite != null && typeof input.overwrite !== 'boolean') {
114
+ return { ok: false, error: 'export input.overwrite must be a boolean' };
115
+ }
116
+ }
117
+ return { ok: true, action: args.action, input };
118
+ }
119
+
120
+ async function requestBridge(discovery, payload, signal) {
121
+ const response = await fetch(`http://127.0.0.1:${discovery.port}/command`, {
122
+ method: 'POST',
123
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${discovery.token}` },
124
+ body: JSON.stringify(payload),
125
+ signal,
126
+ });
127
+ try {
128
+ const text = await response.text();
129
+ if (text.length > MAX_RESPONSE_BYTES) throw new Error('too large');
130
+ return { body: JSON.parse(text), status: response.status };
131
+ } catch {
132
+ throw new Error(`visualize bridge returned an invalid response (HTTP ${response.status})`);
133
+ }
134
+ }
135
+
136
+ function formatSuccess(body) {
137
+ const lines = [String(body.text ?? '')];
138
+ if (Array.isArray(body.tabs) && body.tabs.length) {
139
+ lines.push(...body.tabs.map((tab) => `${tab.id} ${tab.kind} ${tab.label} → ${tab.target}`));
140
+ }
141
+ if (typeof body.path === 'string' && body.path) lines.push(`path: ${body.path}`);
142
+ return lines.filter(Boolean).join('\n');
143
+ }
144
+
145
+ /** Execute one `visualize` tool call. */
146
+ export async function executeVisualizeTool(args, options = {}) {
147
+ const validated = validateVisualizeArgs(args);
148
+ if (!validated.ok) return toolError(validated.error);
149
+ const sessionId = String(options.sessionId || '').trim();
150
+ if (!sessionId) return toolError('visualize session context is unavailable');
151
+ const payload = {
152
+ sessionId,
153
+ cwd: options.cwd || process.cwd(),
154
+ action: validated.action,
155
+ input: validated.input,
156
+ };
157
+ let discovery = readBridgeDiscovery(DISCOVERY_FILE);
158
+ if (!discovery) return toolError(unavailableMessage());
159
+ let result;
160
+ for (let attempt = 0; attempt < 2; attempt += 1) {
161
+ const timeout = AbortSignal.timeout(validated.action === 'export' ? EXPORT_REQUEST_TIMEOUT_MS : REQUEST_TIMEOUT_MS);
162
+ try {
163
+ result = await requestBridge(discovery, payload, options.signal ? AbortSignal.any([timeout, options.signal]) : timeout);
164
+ break;
165
+ } catch (error) {
166
+ if (error?.name === 'TimeoutError') return toolError('visualize bridge timed out');
167
+ if (options.signal?.aborted) return toolError('visualize command cancelled');
168
+ const replacement = readBridgeDiscovery(DISCOVERY_FILE);
169
+ if (attempt === 0 && bridgeDiscoveryChanged(discovery, replacement)) {
170
+ discovery = replacement;
171
+ continue;
172
+ }
173
+ return toolError(error?.message?.startsWith('visualize bridge') ? error.message : unavailableMessage());
174
+ }
175
+ }
176
+ const { body, status } = result;
177
+ if (!body?.ok) {
178
+ return toolError(String(body?.error || `visualize bridge request failed (HTTP ${status})`).replace(/^Error:\s*/, ''));
179
+ }
180
+ return { content: [{ type: 'text', text: formatSuccess(body) }] };
181
+ }
@@ -0,0 +1,97 @@
1
+ import { TOOL_SYNC_EXECUTION_CONTRACT } from '../shared/tool-execution-contract.mjs';
2
+
3
+ /**
4
+ * `visualize` shows produced artifacts in the Mixdog desktop app's side panel
5
+ * over a loopback bridge. Deferred, and present only while the desktop
6
+ * Visualize bridge is up and the feature is on.
7
+ */
8
+ export const TOOL_DEFS = [
9
+ {
10
+ name: 'visualize',
11
+ title: 'Mixdog Visualize',
12
+ description:
13
+ "Show the user any artifact you produced or changed (mockups, docs, decks, sheets, PDFs, images, video, local dev pages) in the desktop side panel beside the chat. Panel tabs auto-reload when a file changes, so re-opening after edits is unnecessary. " +
14
+ 'open: input.targets (1-3 file paths, 2-3 = side-by-side comparison; or a single http(s) URL alone, never mixed with files or other URLs), optional label, optional version (show that kept version of a file tab read-only; each open/reload that changes the files keeps a version). close: optional tab (omit = all). ' +
15
+ 'annotate: tab + add [{selector,label?}] | remove [selector] | clear; HTML pages only, never edits the file. capture: tab + output (.png path). ' +
16
+ 'export: tab + format (png|pdf) + output (project-relative path ending .png/.pdf) + optional version (a kept version of the tab) + optional overwrite (true replaces an existing file, only under design/; otherwise an existing file is refused). Writes a record of the approved page inside the project and returns its project-relative path, size/dimensions and the page problems seen while rendering. A single HTML page (file, or inline html/chart) is rendered whole in a hidden window, so the tab need not be on screen (PDF: HTML pages only; PNG has a pixel cap, over it export pdf or shorten the page); any other tab exports as capture does (the displayed tab, PNG only). Same project containment as capture: links and paths outside the working directory are refused. ' +
17
+ 'check: tab; HTML pages only; waits briefly for the page to settle and returns only its runtime problems (uncaught errors, unhandled rejections, CSP violations, console errors/warnings, missing local assets). ' +
18
+ 'inspect: tab + optional viewport {width,height} (e.g. 375x812 to look at a mobile layout) + optional selector (scope); HTML pages with scripts/styles only (not static mermaid/svg/markdown). Returns a compact text read-out of the rendered page without a screenshot: viewport, landmarks and headings, interactive elements (role, accessible name, selector, x/y/w/h), and layout findings (horizontal overflow, clipped text, overlapping or tiny tap targets, low text contrast, images without alt, unlabeled inputs). Run it (desktop width and, for responsive designs, a mobile width) and fix the findings before reporting; `check` also says when findings exist. Secret field values are never included. ' +
19
+ 'Any result about a tab ends with a "Page problems" section when the page has problems: fix them in the file and re-check before reporting; never report a page with unresolved problems as done. ' +
20
+ 'HTML pages run their scripts, but outside network (CDNs, web fonts, APIs, any other http(s)/ws address) is blocked unless the user allows it for the tab, so mockups must be self-contained; a blocked request appears as a "blocked-network" page problem on open/check. ' +
21
+ 'Direct renderers: instead of targets, open takes input.content {kind: mermaid|svg|chart|html|markdown, source, title?} (max 512 KiB; chart = an ECharts option as JSON) and shows it with no file written. ' +
22
+ 'Prefer inline content for diagrams, charts and quick sketches; write a file for real mockups that need iteration, scripts or versioning on disk. ' +
23
+ 'mermaid, svg and markdown render as static, sanitized output (no scripts, no network). chart (ECharts option JSON, interactive: tooltips, zoom) and html (scripts run) open as in-memory pages with the same protections as HTML file pages (outside network blocked, page problems captured); html content must be self-contained. Re-open with the same title to add a version. A render error (Mermaid parse error, bad chart JSON) is returned as a page problem: fix the source and open again. ' +
24
+ 'Tweaks: an HTML page (file or inline html/chart) can declare adjustable parameters in <script type="application/json" id="mx-tweaks">[{id,label,type:color|range|select|toggle,var:"--name" or attr:"data-name",min,max,step,unit,options:[{value,label}],default}] (max 16, keep it to a few meaningful ones). The panel shows a Tweaks drawer and applies changes live to the page root (CSS variables / data attributes your CSS and script read); the chosen values come back with their next message under tweaks: to bake into the file. A malformed block is a "tweaks: <reason>" page problem. ' +
25
+ 'Does not replace browser for clicking/typing. ' +
26
+ TOOL_SYNC_EXECUTION_CONTRACT,
27
+ inputSchema: {
28
+ type: 'object',
29
+ properties: {
30
+ action: { type: 'string', enum: ['open', 'close', 'annotate', 'capture', 'export', 'check', 'inspect'] },
31
+ input: {
32
+ type: 'object',
33
+ properties: {
34
+ targets: {
35
+ type: 'array',
36
+ items: { type: 'string' },
37
+ minItems: 1,
38
+ maxItems: 3,
39
+ description: 'open: 1-3 project-relative/absolute file paths (side by side), or exactly one http(s) URL as the only target.',
40
+ },
41
+ content: {
42
+ type: 'object',
43
+ properties: {
44
+ kind: { type: 'string', enum: ['mermaid', 'svg', 'chart', 'html', 'markdown'] },
45
+ source: {
46
+ type: 'string',
47
+ maxLength: 524288,
48
+ description: 'Diagram text, SVG/HTML/Markdown source, or an ECharts option as JSON (chart).',
49
+ },
50
+ title: { type: 'string', description: 'Tab title; re-opening the same title adds a version.' },
51
+ },
52
+ required: ['kind', 'source'],
53
+ additionalProperties: false,
54
+ description: 'open: render this directly instead of files. Exclusive with targets.',
55
+ },
56
+ label: { type: 'string', description: 'open: tab label.' },
57
+ version: {
58
+ type: 'integer',
59
+ minimum: 1,
60
+ description: 'open (file tabs): kept version number to show read-only instead of the live files. export: kept version number to render instead of the live page.',
61
+ },
62
+ tab: { type: 'string', description: 'Tab id from open (close: omit for all).' },
63
+ add: {
64
+ type: 'array',
65
+ items: {
66
+ type: 'object',
67
+ properties: { selector: { type: 'string' }, label: { type: 'string' } },
68
+ required: ['selector'],
69
+ additionalProperties: false,
70
+ },
71
+ description: 'annotate: CSS selectors to outline.',
72
+ },
73
+ remove: { type: 'array', items: { type: 'string' }, description: 'annotate: selectors to un-outline.' },
74
+ clear: { type: 'boolean', description: 'annotate: remove all outlines.' },
75
+ output: { type: 'string', description: 'capture: project path ending .png. export: project path ending .png or .pdf (matching format).' },
76
+ format: { type: 'string', enum: ['png', 'pdf'], description: 'export: file format.' },
77
+ overwrite: { type: 'boolean', description: 'export: replace an existing output file; honored only for paths under design/.' },
78
+ viewport: {
79
+ type: 'object',
80
+ properties: {
81
+ width: { type: 'integer', minimum: 200, maximum: 3000 },
82
+ height: { type: 'integer', minimum: 200, maximum: 4000 },
83
+ },
84
+ required: ['width'],
85
+ additionalProperties: false,
86
+ description: 'inspect: size to lay the page out at (height defaults to 800); omit for the panel\'s current size.',
87
+ },
88
+ selector: { type: 'string', maxLength: 500, description: 'inspect: CSS selector scoping the read-out to one subtree.' },
89
+ },
90
+ additionalProperties: false,
91
+ },
92
+ },
93
+ required: ['action'],
94
+ additionalProperties: false,
95
+ },
96
+ },
97
+ ];
@@ -58,6 +58,8 @@ import { loadInstructionFilesContext } from '../../runtime/agent/orchestrator/co
58
58
  import { createSessionTurnApi } from '../session-turn-api.mjs';
59
59
  import { createGoalFacadeApi } from '../goal-facade-api.mjs';
60
60
  import { createRuntimeReviewApi } from '../runtime-review-api.mjs';
61
+ import { createWorkspaceTransitions, refreshWorkspacePrompt } from '../workspace/transitions.mjs';
62
+ import { saveSession } from '../../runtime/agent/orchestrator/session/store.mjs';
61
63
  import { bootProfile } from '../boot-profile.mjs';
62
64
  import { STANDALONE_DATA_DIR } from '../runtime-paths.mjs';
63
65
  import {
@@ -98,6 +100,7 @@ export function wireApis(boot) {
98
100
  };
99
101
  boot.modelRouteApi = modelRouteApiFor(boot);
100
102
  boot.workflowAgentsApi = workflowAgentsApiFor(boot);
103
+ boot.workspaceApi = workspaceApiFor(boot);
101
104
  boot.sessionTurnApi = sessionTurnApiFor(boot);
102
105
  const getFacadeSessionId = () => boot.rt.session?.id || boot.rt.reservedSessionId || null;
103
106
  boot.runtimeReviewApi = createRuntimeReviewApi({
@@ -364,6 +367,8 @@ function lifecycleApiFor(boot) {
364
367
  invalidateContextStatusCache: boot.invalidateContextStatusCache,
365
368
  invalidatePreSessionToolSurface: boot.invalidatePreSessionToolSurface,
366
369
  applyResolvedCwd: boot.applyResolvedCwd,
370
+ // Created after the lifecycle API, so resolved at call time.
371
+ cleanupDeletedSessionWorktree: (sessionId) => boot.workspaceApi?.cleanupDeletedSession?.(sessionId),
367
372
  resolveRoute,
368
373
  applyDeferredToolSurface,
369
374
  beginRoutePreparation: (task) => routePreparation.start(task),
@@ -486,9 +491,39 @@ function workflowAgentsApiFor(boot) {
486
491
  });
487
492
  }
488
493
 
494
+ // The Lead workspace transition authority (worktree / project branch / remove)
495
+ // and the turn-start workspace gate. Facade-facing methods are spread by
496
+ // buildFacade; prepareTurnWorkspace feeds the turn runner.
497
+ function workspaceApiFor(boot) {
498
+ const { rt } = boot;
499
+ return createWorkspaceTransitions({
500
+ getSession: () => rt.session,
501
+ getReservedSessionId: () => rt.reservedSessionId,
502
+ getCurrentCwd: () => rt.currentCwd,
503
+ getDesktopSession: () => rt.desktopSession,
504
+ getConfig: () => rt.config,
505
+ createCurrentSession: (reason) => boot.createCurrentSession(reason),
506
+ applyResolvedCwd: (cwd) => boot.applyResolvedCwd(cwd),
507
+ persistSession: (session) => saveSession(session, { immediate: true }),
508
+ refreshWorkspaceContext: (session, cwd) =>
509
+ refreshWorkspacePrompt(session, cwd, { loadCoreMemoryContext: boot.loadCoreMemoryContext }),
510
+ invalidateCaches: () => {
511
+ boot.invalidateContextStatusCache();
512
+ boot.invalidatePreSessionToolSurface();
513
+ },
514
+ getActiveTurnCount: () => rt.activeTurnCount || 0,
515
+ agentStatusState: boot.agentStatusState,
516
+ updateLeadWorkerCwd: (sessionId, cwd) => boot.agentTool?.updateLeadWorkerCwd?.(sessionId, cwd),
517
+ sessionManager: boot.mgr,
518
+ initialIntent: boot.params.worktree,
519
+ });
520
+ }
521
+
489
522
  function sessionTurnApiFor(boot) {
490
523
  const { rt, tunables, remoteTranscript, routePreparation } = boot;
491
524
  return createSessionTurnApi({
525
+ prepareTurnWorkspace: boot.workspaceApi.prepareTurnWorkspace,
526
+ admitTurnWorkspace: boot.workspaceApi.admitTurnWorkspace,
492
527
  getSession: () => rt.session,
493
528
  setSession: boot.adoptSession,
494
529
  getCurrentCwd: () => rt.currentCwd,
@@ -30,8 +30,10 @@ export async function resolveConfig(boot) {
30
30
  model: params.model,
31
31
  effort: params.effort,
32
32
  fast: params.fast,
33
+ autoEffort: params.autoEffort,
33
34
  modelParameters: params.modelParameters,
34
35
  contextPercent: params.contextPercent,
36
+ contextTokens: params.contextTokens,
35
37
  selectedContextWindow: params.selectedContextWindow,
36
38
  }));
37
39
  configureLocalProviderIdleTtl(rt.config.providers?.[LOCAL_PROVIDER_ID]?.idleTtlSeconds);
@@ -27,6 +27,26 @@ export function buildFacade(boot) {
27
27
  ...boot.modelRouteApi,
28
28
  ...boot.workflowAgentsApi,
29
29
  ...boot.sessionTurnApi,
30
+ // A surface registers its idle auto-clear here; returns an unregister.
31
+ setCompletionWakeGate: (gate) => {
32
+ const next = typeof gate === 'function' ? gate : null;
33
+ boot.completionWakeGate = next;
34
+ return () => {
35
+ if (boot.completionWakeGate === next) boot.completionWakeGate = null;
36
+ };
37
+ },
38
+ applyWorkspaceTransition: boot.workspaceApi.applyWorkspaceTransition,
39
+ getWorkspace: boot.workspaceApi.getWorkspace,
40
+ getBranchMenu: boot.workspaceApi.getBranchMenu,
41
+ getProjectBranchMenu: boot.workspaceApi.getProjectBranchMenu,
42
+ selectBranch: boot.workspaceApi.selectBranch,
43
+ selectProjectBranch: boot.workspaceApi.selectProjectBranch,
44
+ mergeProjectBranch: boot.workspaceApi.mergeProjectBranch,
45
+ requestWorktree: boot.workspaceApi.requestWorktree,
46
+ mergeWorkspace: boot.workspaceApi.mergeWorkspace,
47
+ removeWorktree: boot.workspaceApi.removeWorktree,
48
+ listWorktrees: boot.workspaceApi.listWorktrees,
49
+ pruneWorktrees: boot.workspaceApi.pruneWorktrees,
30
50
  claimSetupRequest: setupTool.claimSetupRequest,
31
51
  isSetupRequestActive: setupTool.isSetupRequestActive,
32
52
  completeSetupRequest: setupTool.completeSetupRequest,
@@ -119,6 +119,7 @@ function wireNotifications(boot) {
119
119
  getCurrentSessionId: () => rt.session?.id || rt.reservedSessionId || null,
120
120
  getTurnApi: () => boot.sessionTurnApi,
121
121
  getOwnerLifecycle: (sessionId) => ownerLifecycle(rt, sessionId),
122
+ beforeWake: (sessionId) => boot.completionWakeGate?.(sessionId),
122
123
  });
123
124
  const {
124
125
  emitRuntimeNotification,
@@ -20,6 +20,7 @@ export function wireSessionLifecycle(boot) {
20
20
  params.model !== undefined ||
21
21
  params.effort !== undefined ||
22
22
  params.fast !== undefined ||
23
+ params.autoEffort !== undefined ||
23
24
  params.modelParameters !== undefined,
24
25
  invalidatePreSessionToolSurface: boot.invalidatePreSessionToolSurface,
25
26
  invalidateOutputStyleStatusCache: boot.invalidateOutputStyleStatusCache,
@@ -20,6 +20,7 @@ import { TOOL_DEFS as TERMINAL_BRIDGE_TOOL_DEFS } from '../../runtime/terminal-b
20
20
  import { TOOL_DEFS as OFFICE_TOOL_DEFS } from '../../runtime/office/tool-defs.mjs';
21
21
  import { TOOL_DEFS as MEDIA_TOOL_DEFS } from '../../runtime/media/tool-defs.mjs';
22
22
  import { TOOL_DEFS as TIDY_TOOL_DEFS } from '../../runtime/tidy/tool-defs.mjs';
23
+ import { TOOL_DEFS as VISUALIZE_BRIDGE_TOOL_DEFS } from '../../runtime/visualize-bridge/tool-defs.mjs';
23
24
  import { SETUP_TOOL_DEFS } from '../setup-tool/tool-defs.mjs';
24
25
  import { createSetupToolExecutor } from '../setup-tool/executor.mjs';
25
26
  import { bootProfile } from '../boot-profile.mjs';
@@ -156,6 +157,7 @@ function wireToolSurface(boot) {
156
157
  officeToolDefs: OFFICE_TOOL_DEFS,
157
158
  mediaToolDefs: MEDIA_TOOL_DEFS,
158
159
  tidyToolDefs: TIDY_TOOL_DEFS,
160
+ visualizeToolDefs: VISUALIZE_BRIDGE_TOOL_DEFS,
159
161
  setupToolDefs: SETUP_TOOL_DEFS,
160
162
  goalTools: goalRuntime.tools,
161
163
  agentTools: agentTool.tools,
@@ -246,6 +248,8 @@ function wireInternalTools(boot) {
246
248
  mcpStatus: boot.mcpStatus,
247
249
  applyResolvedCwd: boot.applyResolvedCwd,
248
250
  skillToolContent: boot.skillToolContent,
251
+ // Late-bound: the workspace authority is built with the API slices.
252
+ getWorkspaceApi: () => boot.workspaceApi || null,
249
253
  }),
250
254
  });
251
255
  Object.assign(boot, { setupTool, disposeInternalTools });
@@ -10,6 +10,7 @@
10
10
  import { withGrandfatheredBuiltins } from '../runtime/agent/orchestrator/runtime-core/builtin-features.mjs';
11
11
  import { webSearchRouteOrDefault } from '../runtime/agent/orchestrator/runtime-core/workflow.mjs';
12
12
  import { applyConfigPatch } from '../runtime/shared/config-patch.mjs';
13
+ import { normalizeContextTokens } from '../runtime/shared/llm/default-context-window.mjs';
13
14
  import { withEffectiveDisabledAgents, withStoredDisabledAgents } from '../runtime/shared/agent-route-config.mjs';
14
15
  import {
15
16
  DEFAULT_MAINTENANCE,
@@ -40,19 +41,27 @@ export function resolveInitialConfigState({
40
41
  model,
41
42
  effort,
42
43
  fast,
44
+ autoEffort,
43
45
  modelParameters,
44
46
  contextPercent,
47
+ contextTokens,
45
48
  selectedContextWindow,
46
49
  }) {
47
50
  const config = withGrandfatheredBuiltins(
48
51
  initialConfig && typeof initialConfig === 'object' ? initialConfig : loadConfig()
49
52
  );
53
+ // A requested percent replaces a saved token window; requested tokens
54
+ // replace both (resolveRouteContextState lets tokens win).
55
+ const { contextTokens: savedTokens, ...resolved } = resolveRoute(config, { provider, model });
56
+ const tokens = normalizeContextTokens(contextTokens) || (contextPercent > 0 ? undefined : savedTokens);
50
57
  return {
51
58
  config,
52
59
  route: {
53
- ...resolveRoute(config, { provider, model }),
60
+ ...resolved,
61
+ ...(tokens ? { contextTokens: tokens } : {}),
54
62
  ...(effort === undefined ? {} : { effort: effort || null }),
55
63
  ...(fast === true || fast === false ? { fast } : {}),
64
+ ...(autoEffort === true || autoEffort === false ? { autoEffort } : {}),
56
65
  ...(modelParameters && typeof modelParameters === 'object' ? { modelParameters: { ...modelParameters } } : {}),
57
66
  ...(contextPercent > 0 ? { contextPercent } : {}),
58
67
  ...(selectedContextWindow > 0 ? { selectedContextWindow } : {}),
@@ -1,6 +1,9 @@
1
1
  // cwd-plugins/cwd-apply.mjs — resolving a requested cwd and retargeting the
2
2
  // live session to it (project selection persistence, code-graph prewarm,
3
3
  // CwdChanged hook) without recreating the session.
4
+ import { setWorkspaceCwdLock } from '../workspace/admission-gate.mjs';
5
+ import { workspaceForProjectSelection } from '../workspace/workspace-state.mjs';
6
+
4
7
  export function createCwdApply({
5
8
  getCurrentCwd,
6
9
  setCurrentCwd,
@@ -31,9 +34,16 @@ export function createCwdApply({
31
34
  }
32
35
 
33
36
  function persistProjectSelection(session, currentCwd) {
37
+ // A Lead in its worktree keeps the registered Project as projectPath; a
38
+ // switch elsewhere leaves the worktree at a new workspace revision.
39
+ const { workspace, projectPath } = workspaceForProjectSelection(session, currentCwd);
40
+ if (session && workspace !== (session.workspace ?? null)) {
41
+ session.workspace = workspace;
42
+ setWorkspaceCwdLock(session.id, null);
43
+ }
34
44
  const desktop = getDesktopSession?.() || session?.desktopSession;
35
45
  if (desktop && typeof desktop === 'object') {
36
- const nextDesktop = { classification: 'project', projectPath: currentCwd };
46
+ const nextDesktop = { classification: 'project', projectPath };
37
47
  setDesktopSession?.(nextDesktop);
38
48
  if (session) session.desktopSession = nextDesktop;
39
49
  }
@@ -16,11 +16,11 @@ export function subscribeGlobalExtensionChanges(listener) {
16
16
  };
17
17
  }
18
18
 
19
- export async function publishGlobalExtensionChange(kind, originId = null) {
19
+ export async function publishGlobalExtensionChange(kind, originId = null, detail = null) {
20
20
  const jobs = [];
21
21
  for (const [id, listener] of subscribers) {
22
22
  if (id === originId) continue;
23
- jobs.push(Promise.resolve().then(() => listener(kind)));
23
+ jobs.push(Promise.resolve().then(() => listener(kind, detail)));
24
24
  }
25
25
  await Promise.allSettled(jobs);
26
26
  }
@@ -3,7 +3,8 @@
3
3
  import { executeBrowserTool } from '../../runtime/browser-bridge/client.mjs';
4
4
  import { executeComputerTool } from '../../runtime/computer-bridge/client.mjs';
5
5
  import { executeTerminalTool } from '../../runtime/terminal-bridge/client.mjs';
6
- import { featureEnvOverride } from '../../runtime/agent/orchestrator/runtime-core/config-helpers.mjs';
6
+ import { executeVisualizeTool } from '../../runtime/visualize-bridge/client.mjs';
7
+ import { featureEnvOverride, moduleEnabled } from '../../runtime/agent/orchestrator/runtime-core/config-helpers.mjs';
7
8
  import { createCallerContextResolvers } from './caller-context.mjs';
8
9
 
9
10
  export function createBridgeToolHandlers({ rt }) {
@@ -50,5 +51,29 @@ export function createBridgeToolHandlers({ rt }) {
50
51
  });
51
52
  };
52
53
 
53
- return { browser, browser_devtools: browser, computer, terminal };
54
+ const visualize = async (args, { callerCtx, callerCwd }) => {
55
+ if (environmentDisabled(callerCtx, 'MIXDOG_FEATURE_VISUALIZE')) {
56
+ throw new Error('the visualize tool is disabled in this environment');
57
+ }
58
+ // A conversation keeps its frozen tool list, so honor the Settings toggle
59
+ // at execution time too (an env override wins, as in builtinFeatureActive).
60
+ if (!(featureEnvOverride('MIXDOG_FEATURE_VISUALIZE') ?? moduleEnabled(rt.config, 'visualize', true))) {
61
+ return {
62
+ content: [
63
+ {
64
+ type: 'text',
65
+ text: 'Error: Visualize is turned off in Settings by the user. Do not turn it back on unless the user asks; give the file path instead.',
66
+ },
67
+ ],
68
+ isError: true,
69
+ };
70
+ }
71
+ return await executeVisualizeTool(args, {
72
+ sessionId: sessionIdFor(callerCtx),
73
+ cwd: callerCwd,
74
+ signal: signalFor(callerCtx),
75
+ });
76
+ };
77
+
78
+ return { browser, browser_devtools: browser, computer, terminal, visualize };
54
79
  }
@@ -5,9 +5,40 @@ import { resolve } from 'node:path';
5
5
  import { listProjects } from '../../runtime/shared/projects.mjs';
6
6
  import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
7
7
 
8
- export function createWorkspaceToolHandlers({ rt, goalRuntime, agentTool, notifyFnForSession, applyResolvedCwd }) {
8
+ export function createWorkspaceToolHandlers({
9
+ rt,
10
+ goalRuntime,
11
+ agentTool,
12
+ notifyFnForSession,
13
+ applyResolvedCwd,
14
+ getWorkspaceApi = () => null,
15
+ }) {
16
+ // The Lead's own worktree, on the user's request: the same authority the
17
+ // branch chip menu drives, tolerating the calling turn but nothing else
18
+ // running (workers, background shells).
19
+ const worktreeAction = async (action, args, callerCtx) => {
20
+ const workspace = getWorkspaceApi();
21
+ if (!workspace) throw new Error(`cwd: ${action} is unavailable in this runtime`);
22
+ const callerSessionId = callerCtx?.callerSessionId || rt.session?.id || null;
23
+ if (!rt.session?.id || callerSessionId !== rt.session.id) {
24
+ throw new Error(`cwd: ${action} belongs to the Lead session`);
25
+ }
26
+ const result =
27
+ action === 'merge-worktree'
28
+ ? await workspace.mergeWorkspace({ confirmed: true }, { ownTurn: true })
29
+ : action === 'remove-worktree'
30
+ ? await workspace.removeWorktree({ force: args?.force === true }, { ownTurn: true })
31
+ : action === 'list-worktrees'
32
+ ? await workspace.listWorktrees()
33
+ : await workspace.pruneWorktrees();
34
+ return JSON.stringify({ action, cwd: rt.currentCwd, result }, null, 2);
35
+ };
36
+
37
+ const WORKTREE_ACTIONS = new Set(['merge-worktree', 'remove-worktree', 'list-worktrees', 'prune-worktrees']);
38
+
9
39
  const cwd = async (args, { callerCtx, callerCwd }) => {
10
40
  const action = clean(args?.action || (args?.path ? 'set' : 'get')).toLowerCase();
41
+ if (WORKTREE_ACTIONS.has(action)) return worktreeAction(action, args, callerCtx);
11
42
  let currentCwd = callerCwd;
12
43
  if (action === 'list') {
13
44
  return JSON.stringify(
@@ -31,6 +31,7 @@ export function createInternalToolExecutor({
31
31
  mcpStatus,
32
32
  applyResolvedCwd,
33
33
  skillToolContent,
34
+ getWorkspaceApi,
34
35
  }) {
35
36
  const handlers = {
36
37
  ...createBridgeToolHandlers({ rt }),
@@ -46,7 +47,14 @@ export function createInternalToolExecutor({
46
47
  mcpStatus,
47
48
  skillToolContent,
48
49
  }),
49
- ...createWorkspaceToolHandlers({ rt, goalRuntime, agentTool, notifyFnForSession, applyResolvedCwd }),
50
+ ...createWorkspaceToolHandlers({
51
+ rt,
52
+ goalRuntime,
53
+ agentTool,
54
+ notifyFnForSession,
55
+ applyResolvedCwd,
56
+ getWorkspaceApi,
57
+ }),
50
58
  };
51
59
 
52
60
  // Settings-disabled features are refused for model-initiated calls before
@@ -8,7 +8,12 @@ import {
8
8
  isRootLeadSession,
9
9
  } from '../../runtime/agent/orchestrator/session/store-summary-visibility.mjs';
10
10
  import { listSessionHeartbeatMtimes } from '../../runtime/agent/orchestrator/session/store/paths-heartbeat.mjs';
11
- import { sessionMessageText, isSessionPreviewNoise, cleanSessionPreview, clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
11
+ import {
12
+ sessionMessageText,
13
+ isSessionPreviewNoise,
14
+ cleanSessionPreview,
15
+ clean,
16
+ } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
12
17
 
13
18
  const VISIBLE_OWNERS = ['cli', 'user', 'mixdog'];
14
19
 
@@ -58,6 +63,8 @@ function leadSessionRow(s, heartbeatMtimes) {
58
63
  // it remains an ordinary resumable session, not a child dispatch.
59
64
  if (!isRootLeadSession(s) && owner && !VISIBLE_OWNERS.includes(owner)) return null;
60
65
  const sourceType = clean(s.sourceType || '').toLowerCase();
66
+ // One-shot `mixdog exec` runs are never resumable conversations.
67
+ if (sourceType === 'exec') return null;
61
68
  if (!isLeadShaped(s, owner, sourceType)) return null;
62
69
  const { preview, messageCount } = previewOf(s);
63
70
  if (!preview && messageCount === 0) return null;