mixdog 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/README.md +1 -0
  2. package/package.json +2 -2
  3. package/scripts/lib/run-node-tests.mjs +9 -0
  4. package/scripts/prune-desktop-runtime.mjs +3 -0
  5. package/scripts/release-paths.mjs +39 -2
  6. package/scripts/test-direct.mjs +4 -2
  7. package/src/defaults/shipped-history.json +6 -0
  8. package/src/defaults/skills/setup/references/actions.md +5 -2
  9. package/src/defaults/skills/visualize/SKILL.md +157 -0
  10. package/src/defaults/skills/visualize/references/craft.md +88 -0
  11. package/src/headless-command.mjs +56 -3
  12. package/src/headless-exec.mjs +17 -1
  13. package/src/headless-role.mjs +2 -2
  14. package/src/help.mjs +2 -0
  15. package/src/lib/rules-builder.cjs +0 -23
  16. package/src/rules/lead/LEAD.md +2 -2
  17. package/src/rules/shared/70-delivery.md +2 -2
  18. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  19. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
  21. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  22. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
  23. package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
  24. package/src/runtime/agent/orchestrator/config.mjs +9 -1
  25. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +47 -7
  26. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +51 -8
  27. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +25 -5
  28. package/src/runtime/agent/orchestrator/mcp/client.mjs +348 -122
  29. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
  30. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
  31. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
  32. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +13 -1
  33. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
  34. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
  35. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
  36. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +12 -4
  37. package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
  38. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  40. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
  41. package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
  43. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
  44. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +67 -0
  45. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +521 -0
  46. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
  47. package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
  48. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
  49. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +3 -0
  50. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
  51. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
  52. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
  53. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
  54. package/src/runtime/agent/orchestrator/providers/registry.mjs +15 -1
  55. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
  56. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
  57. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +12 -11
  58. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
  59. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +11 -0
  60. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +11 -8
  61. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
  62. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
  63. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
  64. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
  65. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +18 -11
  66. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
  67. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
  68. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
  69. package/src/runtime/agent/orchestrator/session/context-utils.mjs +12 -14
  70. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
  71. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
  72. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
  73. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
  74. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -1
  75. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +1 -0
  76. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +17 -7
  77. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
  78. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -2
  79. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -0
  80. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +5 -3
  81. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
  82. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
  83. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
  84. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
  85. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
  86. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
  87. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
  88. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
  89. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
  90. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
  91. package/src/runtime/agent/orchestrator/session/manager.mjs +1 -5
  92. package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
  93. package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
  94. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +29 -19
  95. package/src/runtime/agent/orchestrator/session/read-dedup.mjs +0 -1
  96. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
  97. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
  98. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
  99. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +29 -0
  100. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +5 -1
  101. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +23 -4
  102. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
  103. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +28 -8
  104. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +140 -0
  105. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
  106. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
  107. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
  108. package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
  109. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +31 -12
  110. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
  111. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
  112. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
  113. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +80 -66
  114. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
  115. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -1
  116. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
  117. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
  118. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
  119. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
  120. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
  121. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
  122. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
  123. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
  124. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
  125. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
  126. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
  127. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +0 -10
  128. package/src/runtime/attachments/store.mjs +38 -0
  129. package/src/runtime/channels/index.mjs +1 -5
  130. package/src/runtime/channels/lib/runtime-paths.mjs +8 -23
  131. package/src/runtime/channels/lib/tool-dispatch.mjs +1 -13
  132. package/src/runtime/channels/lib/worker-ipc.mjs +2 -2
  133. package/src/runtime/channels/lib/worker-main.mjs +4 -34
  134. package/src/runtime/shared/atomic-file.mjs +8 -2
  135. package/src/runtime/shared/best-effort-stderr.mjs +8 -0
  136. package/src/runtime/shared/code-graph-state.mjs +12 -0
  137. package/src/runtime/shared/defaults-separation.mjs +2 -0
  138. package/src/runtime/shared/git-runner-worker.mjs +3 -2
  139. package/src/runtime/shared/git-runner.mjs +4 -3
  140. package/src/runtime/shared/llm/default-context-window.mjs +6 -0
  141. package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
  142. package/src/runtime/shared/llm/model-catalog.mjs +240 -118
  143. package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
  144. package/src/runtime/shared/llm/provider-catalog-registry.mjs +156 -0
  145. package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
  146. package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
  147. package/src/runtime/shared/model-proxy-mode.mjs +19 -0
  148. package/src/runtime/shared/provider-accounts.mjs +2 -2
  149. package/src/runtime/shared/provider-api-key.mjs +21 -1
  150. package/src/runtime/shared/schedule-model-ref.mjs +11 -2
  151. package/src/runtime/shared/semver.d.mts +1 -0
  152. package/src/runtime/shared/semver.mjs +57 -0
  153. package/src/runtime/shared/session-usage-totals.mjs +19 -0
  154. package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
  155. package/src/runtime/shared/singleton-owner.mjs +36 -6
  156. package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
  157. package/src/runtime/shared/tool-surface.mjs +9 -2
  158. package/src/runtime/shared/tool-work-units.mjs +14 -0
  159. package/src/runtime/shared/transcript-metadata.mjs +17 -1
  160. package/src/runtime/shared/turn-changes.mjs +791 -0
  161. package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
  162. package/src/runtime/shared/turn-snapshot.mjs +690 -81
  163. package/src/runtime/shared/turn-worktree-snapshot.mjs +161 -11
  164. package/src/runtime/shared/update-checker.mjs +2 -31
  165. package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
  166. package/src/runtime/shared/worktrees/contract.mjs +551 -0
  167. package/src/runtime/shared/worktrees/disk.mjs +57 -0
  168. package/src/runtime/shared/worktrees/git.mjs +247 -0
  169. package/src/runtime/shared/worktrees/include.mjs +480 -0
  170. package/src/runtime/shared/worktrees/lock.mjs +46 -0
  171. package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
  172. package/src/runtime/shared/worktrees/manager.mjs +1215 -0
  173. package/src/runtime/shared/worktrees/merge.mjs +182 -0
  174. package/src/runtime/shared/worktrees/owner.mjs +46 -0
  175. package/src/runtime/shared/worktrees/paths.mjs +62 -0
  176. package/src/runtime/shared/worktrees/registry.mjs +110 -0
  177. package/src/runtime/shared/worktrees/settings.mjs +84 -0
  178. package/src/runtime/shared/worktrees/slug.mjs +62 -0
  179. package/src/runtime/shared/worktrees/test-support.mjs +96 -0
  180. package/src/runtime/visualize-bridge/client.mjs +181 -0
  181. package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
  182. package/src/session-runtime/boot/apis.mjs +35 -0
  183. package/src/session-runtime/boot/begin.mjs +0 -2
  184. package/src/session-runtime/boot/config.mjs +2 -0
  185. package/src/session-runtime/boot/facade.mjs +20 -0
  186. package/src/session-runtime/boot/infrastructure.mjs +1 -0
  187. package/src/session-runtime/boot/session.mjs +1 -0
  188. package/src/session-runtime/boot/tools.mjs +9 -4
  189. package/src/session-runtime/config-lifecycle.mjs +10 -1
  190. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +11 -1
  191. package/src/session-runtime/global-extensions.mjs +2 -2
  192. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
  193. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
  194. package/src/session-runtime/internal-tool-executor.mjs +9 -1
  195. package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
  196. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
  197. package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
  198. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
  199. package/src/session-runtime/mcp-glue/connect.mjs +11 -6
  200. package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -3
  201. package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
  202. package/src/session-runtime/model-route/route-persist.mjs +3 -0
  203. package/src/session-runtime/model-route/set-route.mjs +15 -2
  204. package/src/session-runtime/notification-bus.mjs +9 -1
  205. package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
  206. package/src/session-runtime/resource-extension-sync.mjs +30 -7
  207. package/src/session-runtime/resource-mcp-api.mjs +27 -10
  208. package/src/session-runtime/route-resolution.mjs +10 -1
  209. package/src/session-runtime/route-state.mjs +16 -2
  210. package/src/session-runtime/runtime-bootstrap.mjs +0 -4
  211. package/src/session-runtime/runtime-core.mjs +5 -0
  212. package/src/session-runtime/runtime-facade.mjs +8 -0
  213. package/src/session-runtime/runtime-paths.mjs +0 -1
  214. package/src/session-runtime/runtime-review-api.mjs +9 -0
  215. package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
  216. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
  217. package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
  218. package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
  219. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
  220. package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
  221. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
  222. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
  223. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  224. package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
  225. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
  226. package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
  227. package/src/session-runtime/services/agent-tool.mjs +1 -0
  228. package/src/session-runtime/services/channel-worker.mjs +1 -1
  229. package/src/session-runtime/services/daemon-candidate.mjs +3 -2
  230. package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
  231. package/src/session-runtime/session-create/session-options.mjs +10 -1
  232. package/src/session-runtime/session-turn-api.mjs +1 -0
  233. package/src/session-runtime/settings-api.mjs +4 -2
  234. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
  235. package/src/session-runtime/settings-profile-api.mjs +27 -2
  236. package/src/session-runtime/settings-worktree-api.mjs +17 -0
  237. package/src/session-runtime/setup-tool/executor.mjs +11 -4
  238. package/src/session-runtime/setup-tool/settings-contract.mjs +3 -1
  239. package/src/session-runtime/setup-tool/tool-defs.mjs +31 -3
  240. package/src/session-runtime/starter-guide-state.mjs +62 -0
  241. package/src/session-runtime/turn/agent-ops.mjs +12 -1
  242. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +13 -3
  243. package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
  244. package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
  245. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
  246. package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
  247. package/src/session-runtime/turn/turn-run.mjs +6 -7
  248. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
  249. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -2
  250. package/src/session-runtime/workspace/admission-gate.mjs +197 -0
  251. package/src/session-runtime/workspace/project-branches.mjs +136 -0
  252. package/src/session-runtime/workspace/transition-steps.mjs +364 -0
  253. package/src/session-runtime/workspace/transitions.mjs +651 -0
  254. package/src/session-runtime/workspace/workspace-state.mjs +112 -0
  255. package/src/standalone/channel-binding.mjs +1 -2
  256. package/src/standalone/channel-transport/call-dispatch.mjs +4 -10
  257. package/src/standalone/channel-transport/http-routes.mjs +17 -0
  258. package/src/standalone/channel-transport.mjs +13 -1
  259. package/src/standalone/daemon-channels-loader.mjs +2 -3
  260. package/src/standalone/daemon-desktop-runtime.mjs +1 -0
  261. package/src/standalone/daemon-owner.mjs +372 -0
  262. package/src/standalone/daemon-stored-session-views.mjs +4 -1
  263. package/src/standalone/daemon.mjs +139 -5
  264. package/src/standalone/desktop-service-registry.mjs +20 -0
  265. package/src/standalone/local-session-runtime.mjs +13 -78
  266. package/src/standalone/session-client/attachment-calls.mjs +3 -1
  267. package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
  268. package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
  269. package/src/standalone/session-client.mjs +104 -13
  270. package/src/standalone/session-protocol.mjs +17 -0
  271. package/src/standalone/session-proxy.mjs +3 -0
  272. package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
  273. package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
  274. package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
  275. package/src/standalone/session-service/projection/revisions.mjs +2 -0
  276. package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
  277. package/src/standalone/session-service/projection/workspace.mjs +14 -0
  278. package/src/standalone/session-service/session-calls/session-action.mjs +11 -3
  279. package/src/standalone/session-transport/call-route.mjs +13 -0
  280. package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
  281. package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
  282. package/src/standalone/session-transport/client-registry.mjs +20 -3
  283. package/src/standalone/session-transport/routes.mjs +79 -2
  284. package/src/standalone/session-transport/stack.mjs +2 -0
  285. package/src/standalone/session-transport.mjs +24 -1
  286. package/src/tui/app/message-selector.mjs +2 -1
  287. package/src/tui/app/model-options.mjs +35 -5
  288. package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
  289. package/src/tui/app/model-picker.mjs +4 -2
  290. package/src/tui/dist/index.mjs +332 -36
  291. package/src/tui/index.jsx +10 -2
  292. package/src/tui/session/compaction-view.mjs +147 -0
  293. package/src/tui/session/context-state.mjs +6 -0
  294. package/src/tui/session/initial-state.mjs +3 -0
  295. package/src/tui/session/quota-resume.mjs +2 -1
  296. package/src/tui/session/session-api/commands.mjs +10 -2
  297. package/src/tui/session/session-api/controls.mjs +4 -0
  298. package/src/tui/session/session-api/extensions.mjs +12 -8
  299. package/src/tui/session/session-api/intake/rewind.mjs +2 -1
  300. package/src/tui/session/session-api/integrations.mjs +18 -0
  301. package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
  302. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
  303. package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
  304. package/src/tui/session/session-api/routes.mjs +10 -1
  305. package/src/tui/session/session-api/settings.mjs +2 -0
  306. package/src/tui/session/session-api/shared.mjs +10 -6
  307. package/src/tui/session/session-api/worktree.mjs +60 -0
  308. package/src/tui/session/session-api.mjs +2 -0
  309. package/src/tui/session/session-flow/auto-clear.mjs +31 -12
  310. package/src/tui/session/session-flow/drain.mjs +15 -1
  311. package/src/tui/session/session-flow/session-reset.mjs +4 -3
  312. package/src/tui/session/session-flow.mjs +2 -0
  313. package/src/tui/session/transcript-intake.mjs +14 -5
  314. package/src/tui/session/turn-stream.mjs +32 -4
  315. package/src/tui/session/turn-tool-cards.mjs +10 -2
  316. package/src/tui/session/turn.mjs +39 -10
  317. package/src/runtime/channels/tool-defs.mjs +0 -17
@@ -6,10 +6,14 @@ import { PATCH_EDIT_MAX_BYTES } from './bounded-file-read.mjs';
6
6
  import { cleanString as clean } from './clean.mjs';
7
7
  import { runGitOffThread } from './git-runner.mjs';
8
8
  import { resolvePluginData } from './plugin-paths.mjs';
9
+ import { normalizeChangeStatus, splitPatchSections } from './turn-changes.mjs';
9
10
 
10
11
  const COMMAND_TIMEOUT_MS = 30_000;
11
12
  const COMMAND_MAX_BYTES = 8 * 1024 * 1024;
12
13
  const PATCH_MAX_BYTES = 2 * 1024 * 1024;
14
+ // The complete (replay-safe) patch a sealing turn records; per-file and
15
+ // per-session bounds are applied on top of this by the change record.
16
+ const FREEZE_PATCH_MAX_BYTES = 64 * 1024 * 1024;
13
17
  const MAX_UNTRACKED_FILE_BYTES = 2 * 1024 * 1024;
14
18
  // Largest tool-supplied baseline kept for `hash-object`: the apply_patch edit
15
19
  // limit. (2–64 MiB explicit untracked edits keep their exact revert.)
@@ -70,9 +74,16 @@ function commandError(args, stderr, code) {
70
74
  // call that would otherwise stall the event loop each time.
71
75
  async function runGit(
72
76
  args,
73
- { cwd, input = '', timeoutMs = COMMAND_TIMEOUT_MS, maxBytes = COMMAND_MAX_BYTES, allowFailure = false } = {}
77
+ {
78
+ cwd,
79
+ input = '',
80
+ timeoutMs = COMMAND_TIMEOUT_MS,
81
+ maxBytes = COMMAND_MAX_BYTES,
82
+ allowFailure = false,
83
+ stdoutEncoding = 'utf8',
84
+ } = {}
74
85
  ) {
75
- const result = await runGitOffThread(args, { cwd, input, timeoutMs, maxBytes });
86
+ const result = await runGitOffThread(args, { cwd, input, timeoutMs, maxBytes, stdoutEncoding });
76
87
  if (result.code !== 0 && !allowFailure) throw commandError(args, result.stderr, result.code);
77
88
  return result;
78
89
  }
@@ -94,11 +105,14 @@ async function repositoryRoot(worktree) {
94
105
  return root ? resolve(root) : null;
95
106
  }
96
107
 
108
+ function shadowDirForKey(key) {
109
+ return join(snapshotRoot(), createHash('sha256').update(key).digest('hex').slice(0, 24));
110
+ }
111
+
97
112
  function stateForRoot(root) {
98
113
  const key = pathKey(root);
99
114
  let state = states.get(key);
100
- const hash = createHash('sha256').update(key).digest('hex').slice(0, 24);
101
- const gitDir = join(snapshotRoot(), hash);
115
+ const gitDir = shadowDirForKey(key);
102
116
  if (state && state.gitDir === gitDir) return state;
103
117
  state = {
104
118
  root,
@@ -112,6 +126,11 @@ function stateForRoot(root) {
112
126
  // Bumped by every reported write under `root`; a capture records the
113
127
  // value it started from.
114
128
  changeStamp: 0,
129
+ // Set by forgetTurnWorktreeRoot(): the shadow repository must never be
130
+ // recreated for this state. A later snapshot of the same path gets a new one.
131
+ forgotten: false,
132
+ gcTimer: null,
133
+ gcRun: null,
115
134
  };
116
135
  states.set(key, state);
117
136
  return state;
@@ -156,7 +175,19 @@ async function sourceLineEndingArgs(root) {
156
175
  return [...values].flatMap(([key, value]) => ['-c', `${key}=${value}`]);
157
176
  }
158
177
 
178
+ // A forgotten root's shadow repository is gone for good. Every git command and
179
+ // every file write on it goes through shadowArgs/ensureState, so refusing there
180
+ // keeps a snapshot object that outlived forgetTurnWorktreeRoot() from
181
+ // recreating the directory.
182
+ function assertStateLive(state) {
183
+ if (!state.forgotten) return;
184
+ const error = new Error(`turn worktree snapshot for ${state.root} was removed`);
185
+ error.code = 'TURN_WORKTREE_SNAPSHOT_REMOVED';
186
+ throw error;
187
+ }
188
+
159
189
  function shadowArgs(state, args) {
190
+ assertStateLive(state);
160
191
  return [
161
192
  ...state.lineEndingArgs,
162
193
  // A conversion warning must never fail the snapshot `add`.
@@ -239,6 +270,7 @@ async function refreshSourceTracked(state, { force = false } = {}) {
239
270
  }
240
271
 
241
272
  async function ensureState(state) {
273
+ assertStateLive(state);
242
274
  if (state.initialized && (await exists(join(state.gitDir, 'config')))) return;
243
275
  await mkdir(dirname(state.gitDir), { recursive: true });
244
276
  if (!(await exists(join(state.gitDir, 'config')))) {
@@ -643,24 +675,141 @@ async function diffTreesUnlocked(state, baselineTree, currentTree, paths = null,
643
675
  return { patch, files, patchTruncated, currentTree, ...(omitPatch ? { patchOmitted: true } : {}) };
644
676
  }
645
677
 
678
+ /**
679
+ * Freeze the snapshot's last captured net diff into replayable per-file
680
+ * patches: one complete `--binary --full-index` diff (independent of the
681
+ * bounded display patch), split back into its `diff --git` sections. `paths`
682
+ * scopes a contended worktree to the session-owned files. Callers refresh the
683
+ * snapshot first; this never captures. Files whose bytes cannot be replayed
684
+ * (an uncaptured baseline, a type change, an over-limit diff) stay in the
685
+ * record as non-revertable with a reason.
686
+ */
687
+ export async function freezeTurnWorktreeChanges(snapshot, paths = null) {
688
+ if (!snapshot?.state || !snapshot.baselineTree) return null;
689
+ const scope = Array.isArray(paths) ? [...new Set(paths.filter(Boolean))] : null;
690
+ if (scope && scope.length === 0) return { root: snapshot.root, files: [] };
691
+ return await withStateLock(snapshot.state, async () => {
692
+ const { state, baselineTree, currentTree } = snapshot;
693
+ let review = { files: [], patch: '', patchTruncated: false };
694
+ let sections = [];
695
+ let unavailable = '';
696
+ if (currentTree && currentTree !== baselineTree) {
697
+ review = await diffTreesUnlocked(state, baselineTree, currentTree, scope, { omitPatch: true });
698
+ const args = [
699
+ 'diff',
700
+ '--binary',
701
+ '--full-index',
702
+ '--no-color',
703
+ '--no-ext-diff',
704
+ '--no-textconv',
705
+ '--src-prefix=a/',
706
+ '--dst-prefix=b/',
707
+ '--find-renames',
708
+ baselineTree,
709
+ currentTree,
710
+ '--',
711
+ ...(scope || ['.']),
712
+ ];
713
+ try {
714
+ const result = await runGit(shadowArgs(state, args), {
715
+ cwd: state.root,
716
+ maxBytes: FREEZE_PATCH_MAX_BYTES,
717
+ stdoutEncoding: 'base64',
718
+ });
719
+ sections = splitPatchSections(Buffer.from(result.stdout, 'base64'));
720
+ } catch (error) {
721
+ if (error?.code !== 'EMAXBUFFER') throw error;
722
+ unavailable = 'the turn changed too much to keep its patches for undo';
723
+ }
724
+ }
725
+ const listed = withOmittedBaselines({ ...snapshot, scopePaths: scope }, { ...review, patchOmitted: true });
726
+ const byPath = new Map();
727
+ for (const section of sections) {
728
+ const key = pathKey(section.newPath ?? section.oldPath ?? '');
729
+ // A type change diffs as a delete plus an add of one path: not one patch.
730
+ byPath.set(key, byPath.has(key) ? null : section.patch);
731
+ }
732
+ const files = (listed.files || []).map((file) => {
733
+ const entry = {
734
+ path: file.path,
735
+ ...(file.oldPath ? { oldPath: file.oldPath } : {}),
736
+ status: normalizeChangeStatus(file.status),
737
+ additions: Number(file.additions) || 0,
738
+ deletions: Number(file.deletions) || 0,
739
+ ...(file.binary ? { binary: true } : {}),
740
+ };
741
+ if (entry.status !== 'R') delete entry.oldPath;
742
+ if (file.omitted === true) {
743
+ return { ...entry, revertable: false, reason: 'binary/large file content was not captured' };
744
+ }
745
+ if (file.status === 'T') return { ...entry, revertable: false, reason: 'the file type changed' };
746
+ const patch = byPath.get(pathKey(file.path));
747
+ if (!patch) return { ...entry, revertable: false, reason: unavailable || 'no replayable patch was recorded' };
748
+ return { ...entry, revertable: true, patch };
749
+ });
750
+ return { root: snapshot.root, files };
751
+ });
752
+ }
753
+
646
754
  // One maintenance pass per shadow repository per process, off the turn's
647
755
  // critical path. `--auto` does nothing until the repository actually needs it,
648
756
  // so the common case costs a process spawn and nothing else.
649
757
  const maintainedShadowRepositories = new Set();
650
758
 
651
759
  function scheduleShadowGc(state) {
652
- if (maintainedShadowRepositories.has(state.gitDir)) return;
760
+ if (state.forgotten || maintainedShadowRepositories.has(state.gitDir)) return;
653
761
  maintainedShadowRepositories.add(state.gitDir);
654
762
  const timer = setTimeout(() => {
763
+ state.gcTimer = null;
655
764
  // Deliberately outside the state lock: collection can run for minutes and
656
765
  // git serializes itself through gc.lock, so a clash just retries later.
657
- void runGit(shadowArgs(state, ['gc', '--auto', `--prune=${SHADOW_GC_PRUNE}`, '--quiet']), {
658
- cwd: state.root,
659
- allowFailure: true,
660
- timeoutMs: 300_000,
661
- }).catch(() => {});
766
+ // forgetTurnWorktreeRoot() waits for `gcRun` before deleting the repository.
767
+ state.gcRun = Promise.resolve()
768
+ .then(() =>
769
+ runGit(shadowArgs(state, ['gc', '--auto', `--prune=${SHADOW_GC_PRUNE}`, '--quiet']), {
770
+ cwd: state.root,
771
+ allowFailure: true,
772
+ timeoutMs: 300_000,
773
+ })
774
+ )
775
+ .catch(() => {});
662
776
  }, 60_000);
663
777
  timer.unref?.();
778
+ state.gcTimer = timer;
779
+ }
780
+
781
+ async function removeShadowDir(gitDir, removed, kept) {
782
+ if (!(await exists(gitDir))) return;
783
+ await rm(gitDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }).catch(() => {});
784
+ (await exists(gitDir) ? kept : removed).push(gitDir);
785
+ }
786
+
787
+ /** Drop every trace of one worktree root: serialize behind its pending capture
788
+ * work, remove its shadow repository and in-memory state, and refuse any write
789
+ * a snapshot object that still holds the old state attempts afterwards.
790
+ * Only the exact root is touched. Returns `{ removed, kept }` shadow paths;
791
+ * `kept` lists a shadow repository that could not be deleted. */
792
+ export async function forgetTurnWorktreeRoot(root) {
793
+ const text = clean(root);
794
+ const removed = [];
795
+ const kept = [];
796
+ if (!text) return { removed, kept };
797
+ const keys = new Set([pathKey(resolve(text)), pathKey(canonicalPath(text))]);
798
+ const gitDirs = new Set([...keys].map(shadowDirForKey));
799
+ for (const [key, state] of [...states]) {
800
+ if (!keys.has(key) && !keys.has(pathKey(state.root))) continue;
801
+ gitDirs.add(state.gitDir);
802
+ await withStateLock(state, async () => {
803
+ state.forgotten = true;
804
+ if (states.get(key) === state) states.delete(key);
805
+ if (state.gcTimer) clearTimeout(state.gcTimer);
806
+ state.gcTimer = null;
807
+ await state.gcRun;
808
+ maintainedShadowRepositories.delete(state.gitDir);
809
+ });
810
+ }
811
+ for (const gitDir of gitDirs) await removeShadowDir(gitDir, removed, kept);
812
+ return { removed, kept };
664
813
  }
665
814
 
666
815
  export async function createTurnWorktreeSnapshot(worktree) {
@@ -847,7 +996,8 @@ async function revertPathsUnlocked(snapshot, targets) {
847
996
  await diffTreesUnlocked(snapshot.state, snapshot.baselineTree, currentTree, snapshot.scopePaths)
848
997
  )
849
998
  );
850
- return snapshot;
999
+ // The absolute paths now back at their baseline bytes.
1000
+ return targets.map((target) => resolve(snapshot.root, target));
851
1001
  }
852
1002
 
853
1003
  export async function revertTurnWorktreeFile(snapshot, value) {
@@ -15,6 +15,7 @@ import { fileURLToPath } from 'node:url';
15
15
  import { writeJsonAtomicSync } from './atomic-file.mjs';
16
16
  import { readJsonSafe } from './json-file.mjs';
17
17
  import { resolvePluginData } from './plugin-paths.mjs';
18
+ import { compareSemver } from './semver.mjs';
18
19
 
19
20
  const PACKAGE_NAME = 'mixdog';
20
21
  const REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
@@ -84,37 +85,7 @@ function writeCache(dataDir, payload) {
84
85
  }
85
86
  }
86
87
 
87
- /** Parse a semver-ish string into { parts:[maj,min,patch], prerelease }. */
88
- function parseSemver(value) {
89
- const text = String(value || '')
90
- .trim()
91
- .replace(/^v/i, '');
92
- const [core, ...preParts] = text.split('-');
93
- const parts = core.split('.').map((n) => {
94
- const num = Number.parseInt(n, 10);
95
- return Number.isFinite(num) ? num : 0;
96
- });
97
- while (parts.length < 3) parts.push(0);
98
- return { parts, prerelease: preParts.join('-') };
99
- }
100
-
101
- /**
102
- * Compare two semver strings. Returns positive if `a` > `b`, negative if
103
- * `a` < `b`, 0 if equal. A version with a prerelease tag is considered
104
- * lower than the same core version without one (standard semver rule).
105
- */
106
- export function compareSemver(a, b) {
107
- const pa = parseSemver(a);
108
- const pb = parseSemver(b);
109
- for (let i = 0; i < 3; i++) {
110
- const diff = (pa.parts[i] || 0) - (pb.parts[i] || 0);
111
- if (diff !== 0) return diff;
112
- }
113
- if (pa.prerelease && !pb.prerelease) return -1;
114
- if (!pa.prerelease && pb.prerelease) return 1;
115
- if (pa.prerelease !== pb.prerelease) return pa.prerelease < pb.prerelease ? -1 : 1;
116
- return 0;
117
- }
88
+ export { compareSemver };
118
89
 
119
90
  export function isNewerVersion(latest, current) {
120
91
  if (!latest || !current) return false;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Root cleanups for Mixdog's path-keyed caches: when a managed worktree is
3
+ * removed these drop everything the runtime cached under that exact root, so
4
+ * the removal leaves no turn snapshots, snapshot-store scopes or code-graph
5
+ * state behind. Each returns `{ removed, kept }` and never touches other roots.
6
+ * Modules load lazily: registering costs nothing until a worktree is removed.
7
+ */
8
+ import { registerRootCleanup } from './contract.mjs';
9
+
10
+ const merge = (target, part) => {
11
+ target.removed.push(...part.removed);
12
+ target.kept.push(...part.kept);
13
+ };
14
+
15
+ /** @type {Record<string, (root: string) => Promise<{removed: string[], kept: string[]}>>} */
16
+ export const WORKTREE_CACHE_CLEANUPS = Object.freeze({
17
+ 'turn-worktree-snapshots': async (root) => {
18
+ const { forgetTurnWorktreeRoot } = await import('../turn-worktree-snapshot.mjs');
19
+ return await forgetTurnWorktreeRoot(root);
20
+ },
21
+ 'turn-snapshot-store': async (root) => {
22
+ const { forgetTurnSnapshotRoot } = await import('../turn-snapshot-store.mjs');
23
+ return await forgetTurnSnapshotRoot(root);
24
+ },
25
+ 'code-graph': async (root) => {
26
+ const [state, disk, trusted] = await Promise.all([
27
+ import('../code-graph-state.mjs'),
28
+ import('../../agent/orchestrator/tools/code-graph/disk-cache.mjs'),
29
+ import('../../agent/orchestrator/tools/code-graph/trusted-roots.mjs'),
30
+ ]);
31
+ const result = { removed: state.forgetCodeGraphStateRoot(root).map((name) => `${name}:${root}`), kept: [] };
32
+ merge(result, disk.forgetDiskCodeGraphRoot(root));
33
+ merge(result, trusted.forgetTrustedCodeGraphRoot(root));
34
+ return result;
35
+ },
36
+ });
37
+
38
+ const registrations = new WeakSet();
39
+
40
+ /**
41
+ * Register every cache cleanup once per registry function. Idempotent: calling
42
+ * it again (or from several hosts) neither throws nor registers duplicates.
43
+ * @param {typeof registerRootCleanup} [register] Defaults to the process registry.
44
+ * @returns {string[]} Cleanup names.
45
+ */
46
+ export function registerWorktreeCacheCleanups(register = registerRootCleanup) {
47
+ if (!registrations.has(register)) {
48
+ registrations.add(register);
49
+ for (const [name, cleanup] of Object.entries(WORKTREE_CACHE_CLEANUPS)) register(name, cleanup);
50
+ }
51
+ return Object.keys(WORKTREE_CACHE_CLEANUPS);
52
+ }