mixdog 1.2.3 → 1.2.5

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 (469) hide show
  1. package/package.json +1 -1
  2. package/scripts/gate-local.mjs +6 -4
  3. package/scripts/lib/gate-scheduler.mjs +29 -11
  4. package/scripts/mobile-release.mjs +5 -4
  5. package/scripts/probe-provider-catalog.mjs +31 -0
  6. package/scripts/provider-pingpong-live.mjs +128 -38
  7. package/scripts/session-sweep.mjs +5 -25
  8. package/src/agents/maintainer/AGENT.md +1 -1
  9. package/src/agents/maintainer/agent.json +1 -1
  10. package/src/agents/reviewer/AGENT.md +2 -4
  11. package/src/agents/security/AGENT.md +1 -2
  12. package/src/agents/writer/AGENT.md +2 -3
  13. package/src/defaults/shipped-history.json +7 -1
  14. package/src/defaults/skills/image/SKILL.md +21 -23
  15. package/src/lib/keychain-cjs.cjs +13 -11
  16. package/src/output-styles/common.md +2 -1
  17. package/src/rules/agent/AGENT.md +7 -5
  18. package/src/rules/lead/LEAD.md +13 -13
  19. package/src/rules/shared/45-destructive.md +4 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  21. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +28 -7
  22. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -11
  23. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +101 -23
  24. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +2 -0
  25. package/src/runtime/agent/orchestrator/providers/anthropic-api-key-fetch.mjs +36 -0
  26. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +1 -1
  27. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -3
  28. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  29. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +26 -44
  30. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +2 -5
  31. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +4 -0
  32. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +41 -12
  33. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +13 -1
  34. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +81 -10
  35. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +109 -5
  36. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +2 -1
  37. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +107 -37
  38. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +4 -1
  39. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +3 -1
  40. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  41. package/src/runtime/agent/orchestrator/providers/cursor-native-context.mjs +48 -0
  42. package/src/runtime/agent/orchestrator/providers/cursor-wire-exec.mjs +144 -34
  43. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +28 -4
  44. package/src/runtime/agent/orchestrator/providers/cursor-wire-request.mjs +8 -4
  45. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +41 -11
  46. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +67 -5
  47. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +14 -4
  48. package/src/runtime/agent/orchestrator/providers/cursor.mjs +14 -1
  49. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +9 -6
  50. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -2
  51. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +26 -4
  52. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +12 -6
  53. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +14 -6
  54. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +4 -4
  55. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +1 -0
  56. package/src/runtime/agent/orchestrator/providers/gemini-rest-stream/watchdogs.mjs +16 -5
  57. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +6 -1
  58. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +22 -10
  59. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -2
  60. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +33 -7
  61. package/src/runtime/agent/orchestrator/providers/gemini.mjs +11 -2
  62. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  63. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +9 -2
  64. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +9 -5
  65. package/src/runtime/agent/orchestrator/providers/lib/request-compression.mjs +86 -0
  66. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +4 -2
  67. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -5
  68. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +7 -1
  69. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +60 -16
  70. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +11 -13
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +30 -19
  72. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  73. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +5 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +42 -5
  75. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +13 -9
  76. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +1 -3
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +18 -7
  78. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +36 -3
  79. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +51 -3
  80. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +11 -1
  81. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +64 -52
  82. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +3 -1
  83. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +7 -1
  85. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +1 -4
  86. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +0 -1
  87. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +7 -6
  88. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +13 -2
  89. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +4 -1
  90. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +1 -2
  91. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +5 -4
  92. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -1
  93. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +2 -1
  94. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +11 -4
  95. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +14 -4
  96. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +15 -3
  97. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +6 -2
  98. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +8 -6
  99. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +4 -1
  100. package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +30 -2
  101. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +3 -2
  102. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -3
  103. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +3 -1
  104. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge-contract.mjs +6 -0
  105. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +10 -10
  106. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +3 -1
  107. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +46 -8
  108. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -8
  109. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -2
  110. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +24 -0
  111. package/src/runtime/agent/orchestrator/session/cache/tool-result-reuse.mjs +10 -2
  112. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -3
  113. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -11
  114. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +6 -22
  115. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +2 -0
  116. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +16 -2
  117. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +28 -0
  118. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  119. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +0 -6
  120. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +0 -6
  121. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +1 -1
  122. package/src/runtime/agent/orchestrator/session/historical-session-closure.mjs +10 -0
  123. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +2 -5
  124. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +2 -4
  125. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +3 -3
  126. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +0 -27
  127. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +1 -1
  128. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -10
  129. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -9
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +4 -1
  131. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +4 -1
  132. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +1 -4
  133. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +76 -0
  134. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +5 -1
  135. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +8 -9
  136. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +6 -0
  137. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -2
  138. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +1 -4
  139. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -4
  140. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +33 -3
  141. package/src/runtime/agent/orchestrator/session/manager/ask-support.mjs +0 -4
  142. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +21 -1
  143. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +38 -13
  144. package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +33 -0
  145. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +5 -0
  146. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +19 -1
  147. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +57 -13
  148. package/src/runtime/agent/orchestrator/session/manager/failed-turn-rewind.mjs +16 -0
  149. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +6 -120
  150. package/src/runtime/agent/orchestrator/session/manager/pending-persist-queue.mjs +1 -1
  151. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +29 -8
  152. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +42 -49
  153. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +50 -0
  154. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +32 -14
  155. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +1 -1
  156. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +17 -33
  157. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +29 -13
  158. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -3
  159. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +45 -0
  160. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -9
  161. package/src/runtime/agent/orchestrator/session/send-instrumentation.mjs +2 -1
  162. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -2
  163. package/src/runtime/agent/orchestrator/session/side-question.mjs +12 -7
  164. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +5 -12
  165. package/src/runtime/agent/orchestrator/session/store/lifecycle-read.mjs +1 -1
  166. package/src/runtime/agent/orchestrator/session/store/listing.mjs +2 -6
  167. package/src/runtime/agent/orchestrator/session/store/live-cache-eviction.mjs +1 -1
  168. package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +46 -37
  169. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +1 -1
  170. package/src/runtime/agent/orchestrator/session/store/load-session.mjs +1 -1
  171. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +5 -11
  172. package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +59 -0
  173. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +1 -1
  174. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +1 -1
  175. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +27 -32
  176. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +0 -8
  177. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +6 -18
  178. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +33 -106
  179. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +1 -1
  180. package/src/runtime/agent/orchestrator/session/store-agent-worker-pool.mjs +5 -8
  181. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +160 -212
  182. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +50 -116
  183. package/src/runtime/agent/orchestrator/session/store-summary-locations.mjs +3 -29
  184. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +1 -1
  185. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +7 -8
  186. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +9 -20
  187. package/src/runtime/agent/orchestrator/session/store.mjs +3 -16
  188. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +11 -12
  189. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +37 -13
  190. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +15 -4
  191. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +4 -2
  192. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +72 -15
  193. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +45 -0
  194. package/src/runtime/agent/orchestrator/stall-policy.mjs +50 -0
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +5 -1
  196. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +6 -2
  197. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +204 -10
  198. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +160 -39
  199. package/src/runtime/agent/orchestrator/tools/builtin/git-partial-stage.mjs +7 -5
  200. package/src/runtime/agent/orchestrator/tools/builtin/grep-formatting.mjs +14 -5
  201. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +4 -3
  202. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-output.mjs +2 -1
  203. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-path-fanout.mjs +48 -44
  204. package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +10 -3
  205. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +200 -17
  206. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +5 -4
  207. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +6 -1
  208. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +20 -0
  209. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +101 -2
  210. package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +1 -1
  211. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +67 -0
  212. package/src/runtime/agent/orchestrator/tools/lib/process-table-fixture.mjs +50 -0
  213. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +303 -63
  214. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -0
  215. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +108 -18
  216. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +77 -11
  217. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +81 -5
  218. package/src/runtime/attachments/store.d.mts +9 -0
  219. package/src/runtime/attachments/store.mjs +80 -9
  220. package/src/runtime/browser-bridge/browser-action-contract.d.mts +0 -1
  221. package/src/runtime/browser-bridge/browser-action-contract.mjs +0 -2
  222. package/src/runtime/computer-bridge/action-schema.mjs +178 -178
  223. package/src/runtime/computer-bridge/linux-readiness.d.mts +0 -2
  224. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -16
  225. package/src/runtime/effort-judge/judge-client.mjs +21 -4
  226. package/src/runtime/effort-judge/judge-worker.mjs +4 -1
  227. package/src/runtime/github/tool.mjs +118 -17
  228. package/src/runtime/media/adapters/codex-image.mjs +140 -21
  229. package/src/runtime/media/catalog.mjs +4 -4
  230. package/src/runtime/media/chatgpt-usage.mjs +55 -0
  231. package/src/runtime/media/defaults.mjs +37 -0
  232. package/src/runtime/media/jobs.mjs +12 -2
  233. package/src/runtime/media/lanes.mjs +12 -0
  234. package/src/runtime/media/tool-defs.mjs +15 -4
  235. package/src/runtime/media/tool.mjs +41 -12
  236. package/src/runtime/media/upstream-error.mjs +72 -6
  237. package/src/runtime/memory/lib/ingest/transcript-rows.mjs +1 -0
  238. package/src/runtime/memory/lib/pg/process.mjs +5 -7
  239. package/src/runtime/memory/lib/pg/supervisor.mjs +4 -1
  240. package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
  241. package/src/runtime/office/authoring/html-browser.mjs +18 -8
  242. package/src/runtime/office/authoring/pdf-html-runner.mjs +1 -1
  243. package/src/runtime/office/authoring/pptx-html-measure.mjs +1 -1
  244. package/src/runtime/office/authoring/xlsx-html-runner.mjs +1 -1
  245. package/src/runtime/office/core/office-write-ledger.mjs +1 -5
  246. package/src/runtime/office/index.mjs +2 -3
  247. package/src/runtime/office/pdf/pdf-analysis.mjs +0 -68
  248. package/src/runtime/session-search/session-search-ingest.mjs +1 -1
  249. package/src/runtime/shared/agent-reap-state.mjs +13 -0
  250. package/src/runtime/shared/atomic-file.d.mts +1 -1
  251. package/src/runtime/shared/background-tasks.mjs +6 -1
  252. package/src/runtime/shared/bounded-line-diff.mjs +11 -2
  253. package/src/runtime/shared/browser-launch.mjs +15 -0
  254. package/src/runtime/shared/err-text.mjs +42 -4
  255. package/src/runtime/shared/file-lock.mjs +6 -5
  256. package/src/runtime/shared/llm/anthropic-betas.mjs +1 -2
  257. package/src/runtime/shared/llm/provider-model-identities.mjs +1 -0
  258. package/src/runtime/shared/model-route-chain.d.mts +8 -0
  259. package/src/runtime/shared/model-route-chain.mjs +48 -0
  260. package/src/runtime/shared/model-route-fallback.mjs +22 -0
  261. package/src/runtime/shared/pid-liveness.mjs +12 -0
  262. package/src/runtime/shared/provider-api-key.mjs +6 -0
  263. package/src/runtime/shared/provider-timeout-phase.mjs +17 -0
  264. package/src/runtime/shared/quiet-progress.d.mts +35 -0
  265. package/src/runtime/shared/quiet-progress.mjs +101 -0
  266. package/src/runtime/shared/quota-resume-store.mjs +32 -1
  267. package/src/runtime/shared/read-row-numbers.mjs +44 -0
  268. package/src/runtime/shared/remote-intent.mjs +2 -42
  269. package/src/runtime/shared/slow-span.mjs +8 -1
  270. package/src/runtime/shared/statusline/statusline-segments.mjs +24 -2
  271. package/src/runtime/shared/tool-card-model/card-header.mjs +2 -1
  272. package/src/runtime/shared/tool-output-budget.mjs +2 -0
  273. package/src/runtime/shared/tool-result-summary.mjs +16 -2
  274. package/src/runtime/shared/tool-status.mjs +3 -2
  275. package/src/runtime/shared/tool-surface.mjs +2 -3
  276. package/src/runtime/shared/tool-work-units.mjs +22 -15
  277. package/src/runtime/shared/transcript-metadata.mjs +49 -1
  278. package/src/runtime/shared/transcript-turn-keys.mjs +33 -0
  279. package/src/runtime/shared/transcript-writer/transcript-file.mjs +10 -1
  280. package/src/runtime/shared/transcript-writer.mjs +14 -4
  281. package/src/runtime/shared/turn-changes.mjs +2 -1
  282. package/src/runtime/shared/turn-snapshot.mjs +11 -3
  283. package/src/runtime/shared/worktrees/activity-gate.mjs +175 -0
  284. package/src/runtime/shared/worktrees/branch-management.mjs +179 -0
  285. package/src/runtime/shared/worktrees/carry.mjs +6 -4
  286. package/src/runtime/shared/worktrees/contract.mjs +27 -1
  287. package/src/runtime/shared/worktrees/dependencies.mjs +456 -0
  288. package/src/runtime/shared/worktrees/file-recovery.mjs +29 -13
  289. package/src/runtime/shared/worktrees/git-head.mjs +22 -13
  290. package/src/runtime/shared/worktrees/include.mjs +125 -7
  291. package/src/runtime/shared/worktrees/lock.mjs +1 -1
  292. package/src/runtime/shared/worktrees/maintenance.mjs +20 -2
  293. package/src/runtime/shared/worktrees/manager.mjs +378 -32
  294. package/src/runtime/shared/worktrees/owner.mjs +2 -1
  295. package/src/runtime/shared/worktrees/preparation-report.mjs +79 -0
  296. package/src/runtime/shared/worktrees/setup-commands.mjs +232 -0
  297. package/src/runtime/shared/worktrees/workspace-visibility.mjs +17 -0
  298. package/src/runtime/terminal-bridge/client.mjs +14 -10
  299. package/src/session-runtime/boot/apis.mjs +7 -1
  300. package/src/session-runtime/boot/config.mjs +2 -0
  301. package/src/session-runtime/boot/facade.mjs +3 -0
  302. package/src/session-runtime/boot/session.mjs +3 -2
  303. package/src/session-runtime/boot/tools.mjs +3 -0
  304. package/src/session-runtime/conversation-import/service.mjs +1 -1
  305. package/src/session-runtime/goal-facade-api.mjs +2 -2
  306. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +44 -18
  307. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +4 -1
  308. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +22 -8
  309. package/src/session-runtime/internal-tool-executor.mjs +2 -0
  310. package/src/session-runtime/lifecycle/cold-heir.mjs +0 -1
  311. package/src/session-runtime/lifecycle/session-catalog.mjs +14 -16
  312. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +18 -53
  313. package/src/session-runtime/lifecycle/session-switch.mjs +1 -1
  314. package/src/session-runtime/lifecycle/shared.mjs +5 -6
  315. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -5
  316. package/src/session-runtime/lifecycle/teardown.mjs +7 -16
  317. package/src/session-runtime/media-api.mjs +8 -0
  318. package/src/session-runtime/model-route/route-ownership.mjs +60 -0
  319. package/src/session-runtime/model-route/route-persist.mjs +3 -2
  320. package/src/session-runtime/model-route/set-route.mjs +114 -34
  321. package/src/session-runtime/model-route/web-search-route.mjs +7 -2
  322. package/src/session-runtime/model-route-api.mjs +4 -2
  323. package/src/session-runtime/native-web-search/routes.mjs +37 -24
  324. package/src/session-runtime/native-web-search.mjs +9 -8
  325. package/src/session-runtime/provider-auth/setup.mjs +4 -0
  326. package/src/session-runtime/provider-models/catalog-load.mjs +9 -0
  327. package/src/session-runtime/provider-models/web-search-catalog.mjs +23 -3
  328. package/src/session-runtime/provider-models.mjs +1 -0
  329. package/src/session-runtime/provider-usage.mjs +4 -0
  330. package/src/session-runtime/route-resolution.mjs +3 -1
  331. package/src/session-runtime/runtime-core.mjs +4 -0
  332. package/src/session-runtime/runtime-facade.mjs +6 -0
  333. package/src/session-runtime/services/agent-task-status.mjs +2 -0
  334. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +0 -3
  335. package/src/session-runtime/services/agent-tool/close-flow.mjs +6 -7
  336. package/src/session-runtime/services/agent-tool/execute-send.mjs +2 -2
  337. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +36 -3
  338. package/src/session-runtime/services/agent-tool/execute.mjs +9 -1
  339. package/src/session-runtime/services/agent-tool/helpers.mjs +30 -7
  340. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +7 -2
  341. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +6 -3
  342. package/src/session-runtime/services/agent-tool/lead-worker-index/lead-rows.mjs +3 -4
  343. package/src/session-runtime/services/agent-tool/lead-worker-index/reap-timers.mjs +9 -3
  344. package/src/session-runtime/services/agent-tool/lead-worker-index/row-settlement.mjs +7 -18
  345. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +9 -6
  346. package/src/session-runtime/services/agent-tool/model-select.mjs +82 -0
  347. package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -6
  348. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -1
  349. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -5
  350. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -4
  351. package/src/session-runtime/services/agent-tool/spawn-flow.mjs +2 -1
  352. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  353. package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +1 -3
  354. package/src/session-runtime/services/agent-tool/tag-registry/tag-refresh.mjs +1 -3
  355. package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -9
  356. package/src/session-runtime/services/agent-tool/terminal-reaper/scan-admission.mjs +8 -41
  357. package/src/session-runtime/services/agent-tool/terminal-reaper/stale-rows.mjs +3 -3
  358. package/src/session-runtime/services/agent-tool/terminal-reaper.mjs +6 -23
  359. package/src/session-runtime/services/agent-tool/terminal-trace.mjs +0 -1
  360. package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
  361. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +32 -20
  362. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -2
  363. package/src/session-runtime/services/agent-tool/worker-index.mjs +28 -8
  364. package/src/session-runtime/services/agent-tool.mjs +4 -0
  365. package/src/session-runtime/services/agent-watchdog-registry.mjs +2 -0
  366. package/src/session-runtime/services/provider-admin.mjs +77 -0
  367. package/src/session-runtime/session-create/session-options.mjs +7 -5
  368. package/src/session-runtime/session-create/session-start-hook.mjs +2 -1
  369. package/src/session-runtime/session-transcript.mjs +4 -5
  370. package/src/session-runtime/session-turn-api.mjs +3 -9
  371. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -6
  372. package/src/session-runtime/setup-tool/executor.mjs +23 -5
  373. package/src/session-runtime/setup-tool/extended-actions.mjs +6 -6
  374. package/src/session-runtime/setup-tool/settings-contract.mjs +7 -2
  375. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -1
  376. package/src/session-runtime/tool-policy-refresh.mjs +83 -32
  377. package/src/session-runtime/turn/agent-ops.mjs +49 -2
  378. package/src/session-runtime/turn/side-question-ops.mjs +1 -4
  379. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +2 -0
  380. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +4 -0
  381. package/src/session-runtime/turn/turn-run/turn-settle.mjs +8 -2
  382. package/src/session-runtime/turn/turn-run.mjs +1 -36
  383. package/src/session-runtime/usage-stats-api.mjs +4 -1
  384. package/src/session-runtime/workflow-agents-api/agent-route.mjs +16 -1
  385. package/src/session-runtime/workspace/admission-gate.mjs +44 -15
  386. package/src/session-runtime/workspace/project-branches.mjs +28 -36
  387. package/src/session-runtime/workspace/transition-steps.mjs +96 -95
  388. package/src/session-runtime/workspace/transitions.mjs +248 -68
  389. package/src/standalone/channel-transport.mjs +3 -4
  390. package/src/standalone/daemon-desktop-runtime.mjs +32 -1
  391. package/src/standalone/daemon-pg-stop.mjs +1 -3
  392. package/src/standalone/daemon-session-control.mjs +88 -0
  393. package/src/standalone/daemon-stored-session-views.mjs +23 -3
  394. package/src/standalone/daemon.mjs +2 -0
  395. package/src/standalone/desktop-service-registry.mjs +32 -5
  396. package/src/standalone/local-session-runtime.mjs +11 -12
  397. package/src/standalone/session-protocol.mjs +7 -0
  398. package/src/standalone/session-runtime-inline-host.mjs +2 -0
  399. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +91 -49
  400. package/src/standalone/session-service/agent-tree/agent-child.mjs +19 -1
  401. package/src/standalone/session-service/agent-tree/agent-registry.mjs +25 -0
  402. package/src/standalone/session-service/agent-tree/agent-turns.mjs +9 -1
  403. package/src/standalone/session-service/agent-tree.mjs +28 -2
  404. package/src/standalone/session-service/entries/session-loads.mjs +16 -5
  405. package/src/standalone/session-service/entries.mjs +1 -0
  406. package/src/standalone/session-service/projection/transcript-window.mjs +39 -0
  407. package/src/standalone/session-service/quota-resume.mjs +28 -13
  408. package/src/standalone/session-service/session-calls/session-turns.mjs +17 -6
  409. package/src/standalone/session-service/session-calls/session-views.mjs +6 -0
  410. package/src/standalone/session-tool-output.mjs +82 -0
  411. package/src/tui/app/app-view.jsx +2 -1
  412. package/src/tui/app/provider-setup/provider-items.mjs +8 -0
  413. package/src/tui/app/provider-setup-picker.mjs +15 -3
  414. package/src/tui/app/slash-commands.mjs +6 -0
  415. package/src/tui/app/slash-dispatch/session-commands.mjs +49 -0
  416. package/src/tui/components/Spinner.jsx +4 -0
  417. package/src/tui/components/SpinnerByline.jsx +21 -2
  418. package/src/tui/components/status-line/local-line.mjs +10 -2
  419. package/src/tui/dist/index.mjs +529 -270
  420. package/src/tui/session/active-tool-summary.mjs +1 -1
  421. package/src/tui/session/context-state.mjs +0 -3
  422. package/src/tui/session/goal-continuation/continuation-queue.mjs +2 -3
  423. package/src/tui/session/goal-continuation/deadline-delivery.mjs +4 -2
  424. package/src/tui/session/goal-continuation/session-boundary.mjs +21 -0
  425. package/src/tui/session/quota-resume.mjs +140 -61
  426. package/src/tui/session/session-api/commands.mjs +4 -10
  427. package/src/tui/session/session-api/controls.mjs +12 -0
  428. package/src/tui/session/session-api/lifecycle/dispose.mjs +2 -9
  429. package/src/tui/session/session-api/lifecycle/resume.mjs +2 -7
  430. package/src/tui/session/session-api/lifecycle.mjs +13 -11
  431. package/src/tui/session/session-api/media.mjs +2 -0
  432. package/src/tui/session/session-api/routes.mjs +81 -57
  433. package/src/tui/session/session-api/settings.mjs +3 -3
  434. package/src/tui/session/session-api/worktree.mjs +3 -2
  435. package/src/tui/session/session-flow/auto-clear.mjs +1 -3
  436. package/src/tui/session/session-flow/drain.mjs +6 -4
  437. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -0
  438. package/src/tui/session/session-flow/queue/take-entries.mjs +2 -1
  439. package/src/tui/session/session-flow/queue.mjs +1 -1
  440. package/src/tui/session/tool-approval.mjs +17 -8
  441. package/src/tui/session/transcript-store/streaming-tail.mjs +1 -1
  442. package/src/tui/session/tui-steering-persist.mjs +3 -5
  443. package/src/tui/session/turn-quiet.mjs +53 -0
  444. package/src/tui/session/turn-stream.mjs +5 -1
  445. package/src/tui/session/turn.mjs +38 -31
  446. package/src/tui/session/worktree-setup-notice.mjs +38 -0
  447. package/src/tui/spinner-meta.mjs +10 -0
  448. package/src/tui/spinner-verbs.mjs +3 -0
  449. package/src/ui/statusline.mjs +13 -3
  450. package/src/ui/streaming-markdown-heal.mjs +70 -18
  451. package/src/runtime/agent/orchestrator/providers/model-pricing-rates.mjs +0 -1
  452. package/src/runtime/agent/orchestrator/session/manager/foreign-pending-messages.mjs +0 -346
  453. package/src/runtime/agent/orchestrator/session/manager/session-owner-liveness.mjs +0 -117
  454. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +0 -302
  455. package/src/session-runtime/services/agent-tool/terminal-reaper/reap-timers.mjs +0 -142
  456. package/src/session-runtime/turn/share-ops.mjs +0 -77
  457. package/src/tui/session/cross-surface-share.mjs +0 -406
  458. package/src/tui/session/live-share/delta.mjs +0 -153
  459. package/src/tui/session/live-share/mirror.mjs +0 -106
  460. package/src/tui/session/live-share/owner.mjs +0 -254
  461. package/src/tui/session/live-share/retry.mjs +0 -54
  462. package/src/tui/session/live-share/viewer/frame-router.mjs +0 -32
  463. package/src/tui/session/live-share/viewer/pipe-client.mjs +0 -108
  464. package/src/tui/session/live-share/viewer/submit-acks.mjs +0 -65
  465. package/src/tui/session/live-share/viewer/sync-waiters.mjs +0 -53
  466. package/src/tui/session/live-share/viewer.mjs +0 -99
  467. package/src/tui/session/live-share/wire.mjs +0 -44
  468. package/src/tui/session/live-share.mjs +0 -154
  469. package/src/tui/session/shared-dir-watch.mjs +0 -69
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Managed git worktrees for Lead sessions.
3
3
  *
4
- * One retained worktree per Lead session, created lazily from the project's
5
- * current branch on "<branchPrefix><slug>" at managedWorktreePath(). Every
4
+ * Automatic worktrees are created lazily from the project's current branch
5
+ * on "<branchPrefix><slug>" at managedWorktreePath(). Explicit branch selection
6
+ * reuses its checkout or creates one for that exact branch. Creator retention
7
+ * and additional sessions' durable references protect shared checkouts. Every
6
8
  * mutation runs under withRepoLock (in-process git lock keyed by the common
7
9
  * git dir + cross-process file lock) and is recorded in the durable registry
8
10
  * (registry.mjs) before it touches anything:
@@ -26,8 +28,13 @@
26
28
  * -d` forgets it).
27
29
  */
28
30
  import { existsSync } from 'node:fs';
29
- import { assertRecoveryStoresClear, recoveryScopes, retainedRecoveryScopes, validateRecoveryScope } from './file-recovery.mjs';
30
- import { lstat, mkdir, readdir, readFile, rm, rmdir } from 'node:fs/promises';
31
+ import {
32
+ assertRecoveryStoresClear,
33
+ recoveryScopes,
34
+ retainedRecoveryScopes,
35
+ validateRecoveryScope,
36
+ } from './file-recovery.mjs';
37
+ import { lstat, mkdir, readdir, readFile, rm, rmdir, stat } from 'node:fs/promises';
31
38
  import { dirname, join, relative, resolve, sep } from 'node:path';
32
39
  import {
33
40
  managedWorktreePath,
@@ -64,21 +71,34 @@ import {
64
71
  applyIncludes,
65
72
  coveredByIncludes,
66
73
  modifiedIncludeCopies,
74
+ untrackedEnvFiles,
67
75
  planIncludes,
68
76
  readIncludeEntries,
69
77
  removeIncludeLinks,
78
+ restoreIncludeLinks,
70
79
  } from './include.mjs';
80
+ import { linkWorktreeDependencies } from './dependencies.mjs';
71
81
  import { withRepoLock as withRepoMutationLock } from './lock.mjs';
72
- import { mergeBranchIntoCheckout } from './merge.mjs';
82
+ import { mergeBranchIntoCheckout, operationInProgress } from './merge.mjs';
73
83
  import { readOwnerLease, removeOwnerLease, sameOwner, writeOwnerLease } from './owner.mjs';
74
84
  import { canonicalPath, checkoutFolder, isWithin, pathKey, projectSubdirOf, samePath } from './paths.mjs';
75
- import { deleteRecord, entryView, getRecord, insertRecord, putRecord, readRegistry, registryPath } from './registry.mjs';
85
+ import {
86
+ deleteRecord,
87
+ entryView,
88
+ getRecord,
89
+ insertRecord,
90
+ putRecord,
91
+ readRegistry,
92
+ registryPath,
93
+ } from './registry.mjs';
76
94
  import { normalizeWorktreeSettings } from './settings.mjs';
77
95
  import { slugFromHint, uniqueSlug } from './slug.mjs';
78
96
 
79
97
  const WORKTREE_ADD_TIMEOUT_MS = 30 * 60_000;
80
98
  const REMOVE_RETRIES = 6;
81
99
  const REMOVE_RETRY_DELAY_MS = 150;
100
+ // Entries a folder-only removal checks for unrecorded directory links.
101
+ const LINK_SCAN_LIMIT = 250_000;
82
102
  // The session copies the project identity into each managed worktree; it is
83
103
  // Mixdog metadata, not user data, so it alone never makes a worktree dirty.
84
104
  const SESSION_PROJECT_ID_PATH = '.mixdog/project.id';
@@ -324,6 +344,7 @@ async function dirtyPaths(record) {
324
344
  }
325
345
 
326
346
  async function deleteBranchIfFree(record) {
347
+ if (record.existingBranch === true) return { deleted: false, reason: 'pre-existing' };
327
348
  const cwd = repoCwd(record);
328
349
  if (!(await branchCommit(cwd, record.branch))) return { deleted: false, reason: 'missing' };
329
350
  const holder = (await listWorktrees(cwd)).find((worktree) => worktree.branch === record.branch);
@@ -339,6 +360,10 @@ async function deleteBranchIfFree(record) {
339
360
  async function removeCheckout(record) {
340
361
  await assertRecoveryStoresClear(record);
341
362
  await assertManagedCheckoutPath(record);
363
+ // `git worktree remove --force` can descend through a junction on Windows
364
+ // (fs.rm below does not: it unlinks links), so an unknown directory link
365
+ // refuses this removal as it does the folder-only one.
366
+ await assertNoUnrecordedDirectoryLinks(record);
342
367
  const remaining = [];
343
368
  for (const path of await removeIncludeLinks(record.resources?.includes)) remaining.push({ kind: 'link', path });
344
369
  if (remaining.length) return remaining;
@@ -365,6 +390,128 @@ async function removeCheckout(record) {
365
390
  return remaining;
366
391
  }
367
392
 
393
+ /**
394
+ * Refuse a checkout that holds a directory link the ledger does not know (a
395
+ * junction or directory symlink made by the user or a build tool): `git
396
+ * worktree remove` can descend through it on Windows and delete the target's
397
+ * files. Refusing is chosen over unlinking: nothing the manager did not make
398
+ * is ever touched, and the user removes the link (never its target) and
399
+ * retries. The walk never follows a link, skips `.git` entries (the back link
400
+ * file, nested repositories' internals) and is bounded: a checkout too large
401
+ * to verify is refused as well. Recorded links are skipped (unlinked next).
402
+ */
403
+ async function assertNoUnrecordedDirectoryLinks(record) {
404
+ const key = (path) => (process.platform === 'win32' ? resolve(path).toLowerCase() : resolve(path));
405
+ const recorded = new Set(
406
+ (record.resources?.includes || []).filter((item) => item.mode === 'link').map((item) => key(item.path))
407
+ );
408
+ const refuse = (message, details) =>
409
+ worktreeError('WORKTREE_PATH_UNSAFE', message, { entryId: record.id, path: record.path, ...details });
410
+ const found = [];
411
+ const pending = [record.path];
412
+ let scanned = 0;
413
+ while (pending.length) {
414
+ const dir = pending.pop();
415
+ let entries;
416
+ try {
417
+ entries = await readdir(dir, { withFileTypes: true });
418
+ } catch (error) {
419
+ if (error?.code === 'ENOENT') continue;
420
+ throw refuse(
421
+ `The worktree folder could not be checked for links (${error?.code || error?.message}); nothing was deleted.`,
422
+ { reason: 'unreadable' }
423
+ );
424
+ }
425
+ for (const entry of entries) {
426
+ if (++scanned > LINK_SCAN_LIMIT) {
427
+ throw refuse(
428
+ `The worktree folder has more than ${LINK_SCAN_LIMIT} entries to check for links; nothing was deleted.`,
429
+ { reason: 'too-many-entries' }
430
+ );
431
+ }
432
+ if (entry.name.toLowerCase() === '.git') continue;
433
+ const path = join(dir, entry.name);
434
+ // A junction is a link here too (a reparse point), so it is never descended.
435
+ if (entry.isDirectory()) pending.push(path);
436
+ else if (entry.isSymbolicLink() && !recorded.has(key(path)) && (await linksToDirectory(path))) {
437
+ found.push(relative(record.path, path).split(sep).join('/'));
438
+ }
439
+ }
440
+ }
441
+ if (found.length) {
442
+ throw refuse(
443
+ `The worktree contains linked folders it did not create (${found.slice(0, 10).join(', ')}${found.length > 10 ? ', …' : ''}). ` +
444
+ 'Removing the folder could delete their targets: remove those links (not their targets) first; nothing was deleted.',
445
+ { reason: 'unrecorded-links', links: found.slice(0, 50), count: found.length }
446
+ );
447
+ }
448
+ }
449
+
450
+ /** True when the link at `path` resolves to a directory (or cannot be ruled out). */
451
+ async function linksToDirectory(path) {
452
+ try {
453
+ return (await stat(path)).isDirectory();
454
+ } catch (error) {
455
+ // Dangling: nothing to descend into.
456
+ return error?.code !== 'ENOENT' && error?.code !== 'ENOTDIR';
457
+ }
458
+ }
459
+
460
+ /** Explicit folder-only cleanup never force-removes a checkout or user files. */
461
+ async function removeCheckoutSafely(record) {
462
+ const registration = await registrationFor(record);
463
+ if (!registration && !existsSync(record.path)) return [];
464
+ await assertManagedCheckoutPath(record);
465
+ await assertRecoveryStoresClear(record);
466
+ await assertRemovable(record);
467
+ if (await operationInProgress(gitStatusRunner, record.path)) {
468
+ throw worktreeError('WORKTREE_DIRTY', 'Finish the Git operation before removing this worktree.');
469
+ }
470
+ const ownedLock = `mixdog session=${record.ownerSessionId}`;
471
+ if (registration?.locked !== null && registration?.locked !== ownedLock) {
472
+ throw worktreeError('WORKTREE_BUSY_JOBS', 'The worktree has an external Git lock.');
473
+ }
474
+ // `git worktree remove` descends through a directory junction on Windows
475
+ // and would empty its target (a linked node_modules in the main checkout):
476
+ // an unknown link refuses the removal, the recorded links go first, by unlink only.
477
+ await assertNoUnrecordedDirectoryLinks(record);
478
+ const includes = record.resources?.includes;
479
+ if ((await removeIncludeLinks(includes)).length) {
480
+ const failures = await restoreIncludeLinks(includes);
481
+ throw withRollbackFailures(
482
+ worktreeError('WORKTREE_BUSY_JOBS', 'A linked folder in the worktree could not be unlinked.'),
483
+ failures.map((failure) => `${failure.path} (${failure.error})`)
484
+ );
485
+ }
486
+ let unlocked = false;
487
+ try {
488
+ if (registration?.locked === ownedLock) {
489
+ await runGit(repoCwd(record), ['worktree', 'unlock', record.path]);
490
+ unlocked = true;
491
+ }
492
+ await runGit(repoCwd(record), ['worktree', 'remove', record.path]);
493
+ } catch (error) {
494
+ const failures = (await restoreIncludeLinks(includes)).map((failure) => `${failure.path} (${failure.error})`);
495
+ if (unlocked && (await registrationFor(record))) {
496
+ try {
497
+ await runGit(repoCwd(record), ['worktree', 'lock', '--reason', ownedLock, record.path]);
498
+ } catch (lockError) {
499
+ failures.push(`worktree lock (${firstLine(lockError?.message) || lockError})`);
500
+ }
501
+ }
502
+ throw withRollbackFailures(error, failures);
503
+ }
504
+ return [];
505
+ }
506
+
507
+ /** Report what a failed safe removal could not put back on its error (never swallowed). */
508
+ function withRollbackFailures(error, failures) {
509
+ if (!failures.length) return error;
510
+ error.rollbackFailures = failures;
511
+ error.message = `${error.message} Not restored: ${failures.join(', ')}.`;
512
+ return error;
513
+ }
514
+
368
515
  /**
369
516
  * After the last worktree of a repository is gone, drop its now-empty store
370
517
  * folder (<dataDir>/worktrees-v1/<repoHash>/ and its .owners/). Runs under the
@@ -410,7 +557,11 @@ async function rollbackCreation(record, cause) {
410
557
  if (resources.worktree) {
411
558
  const left = await removeCheckout(record);
412
559
  remaining.push(...left);
413
- if (!left.length) {
560
+ if (
561
+ !left.length &&
562
+ record.existingBranch !== true &&
563
+ !(record.exactBranch === true && resources.worktree !== 'created')
564
+ ) {
414
565
  // The slug was free when this transaction reserved it, so a branch
415
566
  // still at the base commit is the one `worktree add -b` made. A
416
567
  // branch that moved holds someone's commits and is kept.
@@ -460,7 +611,7 @@ async function finishDeletion(record) {
460
611
  for (const result of results) {
461
612
  if (!result.ok) remaining.push({ kind: 'cleanup', name: result.name, error: result.error.message });
462
613
  }
463
- remaining.push(...(await removeCheckout(record)));
614
+ remaining.push(...(await (record.deleting?.safeOnly ? removeCheckoutSafely(record) : removeCheckout(record))));
464
615
  } catch (error) {
465
616
  remaining.push({ kind: 'error', error: String(error?.message || error) });
466
617
  }
@@ -472,7 +623,7 @@ async function finishDeletion(record) {
472
623
  let branch = { deleted: false, reason: 'kept' };
473
624
  let kept = { tip: null, recoveryRef: null };
474
625
  const merged = (await unmergedCommitCount(record)) === 0;
475
- if (record.deleting?.force || merged) {
626
+ if (!record.deleting?.preserveBranch && (record.deleting?.force || merged)) {
476
627
  // Forced past unmerged commits: the tip stays reachable before `branch -D`.
477
628
  if (!merged) kept = await keepRemovedTip(record);
478
629
  else kept.tip = await branchCommit(repoCwd(record), record.branch);
@@ -521,6 +672,100 @@ export async function getWorktreeByPath(path) {
521
672
  return record ? entryView(record) : null;
522
673
  }
523
674
 
675
+ /** Resolve a branch to its checkout without switching any existing folder. */
676
+ export async function resolveBranchWorkspace({
677
+ projectPath,
678
+ branch,
679
+ create = false,
680
+ sessionId,
681
+ runtimeToken,
682
+ txId,
683
+ confirmLarge = false,
684
+ signal,
685
+ } = {}) {
686
+ requireText(branch, 'branch');
687
+ const repo = await inspectRepo(projectPath, { signal });
688
+ return withRepoLock(repo.repoCommonDir, async () => {
689
+ if (!(await validBranchName(repo.projectPath, branch))) throw new TypeError('Invalid branch name.');
690
+ const records = Object.values((await readRegistry()).entries);
691
+ const transaction = records.find((record) => record.txId === txId);
692
+ if (
693
+ transaction &&
694
+ (transaction.ownerSessionId !== sessionId ||
695
+ transaction.branch !== branch ||
696
+ !samePath(transaction.repoCommonDir, repo.repoCommonDir))
697
+ ) {
698
+ throw worktreeError('WORKTREE_REVISION_CONFLICT', 'This transaction belongs to another workspace.');
699
+ }
700
+ const retry =
701
+ transaction ??
702
+ (create
703
+ ? records.find(
704
+ (record) =>
705
+ record.ownerSessionId === sessionId &&
706
+ record.branch === branch &&
707
+ record.exactBranch === true &&
708
+ samePath(record.repoCommonDir, repo.repoCommonDir)
709
+ )
710
+ : null);
711
+ if (retry?.state === 'ready') {
712
+ if (!existsSync(retry.path)) throw worktreeError('WORKTREE_MISSING');
713
+ await claimWorktreeOwner(retry.id, { sessionId, runtimeToken, pid: process.pid });
714
+ return { path: retry.path, branch, entry: entryView(retry), repoCommonDir: repo.repoCommonDir };
715
+ }
716
+ const branches = await localBranches(repo.projectPath, { signal });
717
+ if (create && branches.includes(branch))
718
+ throw worktreeError('WORKTREE_REVISION_CONFLICT', 'Branch already exists.');
719
+ if (!create && !branches.includes(branch)) throw worktreeError('WORKTREE_MISSING', 'Branch does not exist.');
720
+ const holder = (await listWorktrees(repo.projectPath)).find((item) => item.branch === branch && !item.prunable);
721
+ if (holder) {
722
+ if (!existsSync(holder.path)) throw worktreeError('WORKTREE_MISSING');
723
+ const entry = await getWorktreeByPath(holder.path);
724
+ const claim = entry ? await claimWorktreeOwner(entry.id, { sessionId, runtimeToken, pid: process.pid }) : null;
725
+ return {
726
+ path: holder.path,
727
+ branch,
728
+ entry,
729
+ referenceAdded: claim?.referenceAdded === true,
730
+ repoCommonDir: repo.repoCommonDir,
731
+ };
732
+ }
733
+ const entry = await createManagedWorktree({
734
+ projectPath,
735
+ sessionId,
736
+ runtimeToken,
737
+ txId,
738
+ branch,
739
+ existingBranch: !create,
740
+ baseBranch: create ? repo.currentBranch : branch,
741
+ nameHint: branch,
742
+ confirmLarge,
743
+ signal,
744
+ });
745
+ const record = await getRecord(entry.id);
746
+ record.mergeTarget = repo.currentBranch;
747
+ await putRecord(record);
748
+ entry.mergeTarget = repo.currentBranch;
749
+ return { path: entry.path, branch, entry, created: true, repoCommonDir: repo.repoCommonDir };
750
+ });
751
+ }
752
+
753
+ /** Remove only a deleted session's shared references, never its neighbours'. */
754
+ export async function forgetSharedWorktreeSession(sessionId) {
755
+ for (const record of Object.values((await readRegistry()).entries)) {
756
+ if (!record.sharedSessionIds?.includes(sessionId)) continue;
757
+ await withRepoLock(record.repoCommonDir, async () => {
758
+ const current = await getRecord(record.id);
759
+ if (!current) return;
760
+ current.sharedSessionIds = (current.sharedSessionIds ?? []).filter((id) => id !== sessionId);
761
+ const leases = { ...current.sharedLeases };
762
+ delete leases[sessionId];
763
+ current.sharedLeases = leases;
764
+ await putRecord(current);
765
+ });
766
+ }
767
+ }
768
+
524
769
  /** The managed worktree whose checkout is `path` or contains it (a subfolder Project's session root), or null. */
525
770
  export async function findWorktreeContaining(path) {
526
771
  if (typeof path !== 'string' || !path.trim()) return null;
@@ -545,10 +790,14 @@ export async function withWorktreeRecoveryStore(root, directory, action, run) {
545
790
  // lookup. Its reserved namespace must never become an ordinary project.
546
791
  // Retain a lexical check as well, so a replacement link cannot escape it.
547
792
  const store = dirname(registryPath());
548
- const lexicalKey = (path) => process.platform === 'win32' ? resolve(path).toLowerCase() : resolve(path);
793
+ const lexicalKey = (path) => (process.platform === 'win32' ? resolve(path).toLowerCase() : resolve(path));
549
794
  const requested = lexicalKey(root);
550
- if (isWithin(store, root) || [store, canonicalPath(store)].some((path) =>
551
- requested === lexicalKey(path) || requested.startsWith(lexicalKey(path) + sep)))
795
+ if (
796
+ isWithin(store, root) ||
797
+ [store, canonicalPath(store)].some(
798
+ (path) => requested === lexicalKey(path) || requested.startsWith(lexicalKey(path) + sep)
799
+ )
800
+ )
552
801
  throw worktreeError('WORKTREE_MISSING', undefined, { path: root });
553
802
  return run();
554
803
  }
@@ -588,7 +837,7 @@ export async function markWorktreeDisposable(entryId, disposable = true) {
588
837
  * WORKTREE_REVISION_CONFLICT for another session's transaction, a ready
589
838
  * worktree of another repository, or an attempt that is still being cleaned up.
590
839
  */
591
- async function settleSessionAttempts({ txId, owner, repoCommonDir }) {
840
+ async function settleSessionAttempts({ txId, owner, repoCommonDir, multiple = false }) {
592
841
  const doc = await readRegistry();
593
842
  const sameRepo = (record) => samePath(record.repoCommonDir, repoCommonDir);
594
843
 
@@ -602,6 +851,7 @@ async function settleSessionAttempts({ txId, owner, repoCommonDir }) {
602
851
  }
603
852
 
604
853
  for (const record of Object.values(doc.entries).filter((item) => item.ownerSessionId === owner.sessionId)) {
854
+ if (multiple && record.txId !== txId) continue;
605
855
  if (record.state === 'ready') {
606
856
  if (!sameRepo(record)) {
607
857
  throw worktreeError(
@@ -677,6 +927,8 @@ export async function createManagedWorktree({
677
927
  txId,
678
928
  signal,
679
929
  carryChanges = null,
930
+ branch: requestedBranch = null,
931
+ existingBranch = false,
680
932
  } = {}) {
681
933
  requireText(projectPath, 'projectPath');
682
934
  requireText(txId, 'txId');
@@ -687,7 +939,12 @@ export async function createManagedWorktree({
687
939
  throwIfAborted(signal);
688
940
  const repo = await inspectRepo(projectPath, { signal });
689
941
  return withRepoLock(repo.repoCommonDir, async () => {
690
- const existing = await settleSessionAttempts({ txId, owner, repoCommonDir: repo.repoCommonDir });
942
+ const existing = await settleSessionAttempts({
943
+ txId,
944
+ owner,
945
+ repoCommonDir: repo.repoCommonDir,
946
+ multiple: requestedBranch !== null,
947
+ });
691
948
  if (existing) return existing;
692
949
  throwIfAborted(signal);
693
950
 
@@ -714,7 +971,11 @@ export async function createManagedWorktree({
714
971
  mergeTarget = baseBranch;
715
972
  }
716
973
 
717
- const includePlan = await planIncludes(repo.topLevel, await readIncludeEntries(repo.topLevel));
974
+ // The main checkout's local env files come along on their own;
975
+ // `.worktreeinclude` applies on top.
976
+ const includePlan = await planIncludes(repo.topLevel, await readIncludeEntries(repo.topLevel), {
977
+ envFiles: await untrackedEnvFiles(repo.topLevel),
978
+ });
718
979
  const tracked = await trackedCheckoutBytes(repo.topLevel, baseCommit, { signal });
719
980
  const contentBytes = tracked.bytes + includePlan.copyBytes;
720
981
  const root = managedWorktreeRoot(repo.repoCommonDir);
@@ -750,10 +1011,16 @@ export async function createManagedWorktree({
750
1011
  return reserved;
751
1012
  };
752
1013
  const addCheckout = (target) =>
753
- runGit(repo.projectPath, ['worktree', 'add', '-b', target.branch, target.path, baseCommit], {
754
- signal,
755
- timeoutMs: WORKTREE_ADD_TIMEOUT_MS,
756
- });
1014
+ runGit(
1015
+ repo.projectPath,
1016
+ existingBranch
1017
+ ? ['worktree', 'add', target.path, target.branch]
1018
+ : ['worktree', 'add', '-b', target.branch, target.path, baseCommit],
1019
+ {
1020
+ signal,
1021
+ timeoutMs: WORKTREE_ADD_TIMEOUT_MS,
1022
+ }
1023
+ );
757
1024
  let record = await reserve();
758
1025
  let carried = null;
759
1026
 
@@ -763,6 +1030,7 @@ export async function createManagedWorktree({
763
1030
  try {
764
1031
  await addCheckout(record);
765
1032
  } catch (error) {
1033
+ if (requestedBranch) throw error;
766
1034
  // Another process took the branch name (or folder) after the
767
1035
  // reservation: git refused before making anything, so reserve the
768
1036
  // next free name once and try again.
@@ -807,10 +1075,29 @@ export async function createManagedWorktree({
807
1075
  }
808
1076
  const applied = await applyIncludes(includePlan, record.path, (ledger) => recordInclude(record, ledger));
809
1077
  throwIfAborted(signal);
1078
+ // After the includes: one that already brought a node_modules wins.
1079
+ const linkedDependencies = await linkWorktreeDependencies({
1080
+ mainDir: repo.projectPath,
1081
+ worktreeDir: projectSubdir ? join(record.path, ...projectSubdir.split('/')) : record.path,
1082
+ worktreeRel: projectSubdir,
1083
+ signal,
1084
+ record: (ledger) => recordInclude(record, ledger),
1085
+ });
1086
+ throwIfAborted(signal);
810
1087
  record.warnings = [...record.warnings, ...applied.warnings];
811
1088
  record.state = 'ready';
812
1089
  await putRecord(record);
813
- return { ...entryView(record), ...(changes ? { changes } : {}) };
1090
+ const made = (record.resources.includes || []).filter((item) => item.status === 'created');
1091
+ const rels = (predicate) => made.filter(predicate).map((item) => item.rel);
1092
+ // What creation did, for the agent's note at the first turn (transitions.mjs).
1093
+ const prepared = {
1094
+ root: repo.topLevel,
1095
+ copied: rels((item) => item.mode === 'copy'),
1096
+ linked: rels((item) => item.mode === 'link' && !item.dependency),
1097
+ dependencies: linkedDependencies,
1098
+ skipped: [...includePlan.warnings, ...applied.warnings],
1099
+ };
1100
+ return { ...entryView(record), prepared, ...(changes ? { changes } : {}) };
814
1101
  } catch (error) {
815
1102
  // Carried already: the changes go back to the project folder before the
816
1103
  // worktree that holds them is rolled back (the recovery ref keeps them
@@ -841,7 +1128,7 @@ export async function createManagedWorktree({
841
1128
  branches.has(`${settings.branchPrefix}${candidate}`.toLowerCase()) ||
842
1129
  existsSync(join(root, candidate))
843
1130
  );
844
- const branch = worktreeBranchName(slug, settings.branchPrefix);
1131
+ const branch = requestedBranch ?? worktreeBranchName(slug, settings.branchPrefix);
845
1132
  if (!(await validBranchName(repo.projectPath, branch))) {
846
1133
  throw worktreeError('WORKTREE_UNSUPPORTED_REPO', `Branch name ${branch} is not valid.`, { branch });
847
1134
  }
@@ -857,6 +1144,8 @@ export async function createManagedWorktree({
857
1144
  mergeTarget,
858
1145
  createdAt: Date.now(),
859
1146
  ownerSessionId: owner.sessionId,
1147
+ existingBranch,
1148
+ exactBranch: requestedBranch !== null,
860
1149
  state: 'creating',
861
1150
  disposable: false,
862
1151
  warnings: [...(tracked.submodules ? ['submodules'] : []), ...includePlan.warnings],
@@ -906,10 +1195,21 @@ export async function claimWorktreeOwner(entryId, owner) {
906
1195
  const current = await getRecord(entryId);
907
1196
  if (current?.state !== 'ready') throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
908
1197
  if (current.ownerSessionId && current.ownerSessionId !== claimant.sessionId) {
909
- throw worktreeError('WORKTREE_BRANCH_OCCUPIED', 'The worktree is retained by another session.', {
910
- entryId,
911
- sessionId: current.ownerSessionId,
912
- });
1198
+ const references = new Set(current.sharedSessionIds ?? []);
1199
+ const referenceAdded = !references.has(claimant.sessionId);
1200
+ references.add(claimant.sessionId);
1201
+ current.sharedSessionIds = [...references];
1202
+ const previous = current.sharedLeases?.[claimant.sessionId] ?? null;
1203
+ const lease = { ...claimant, entryId, claimedAt: Date.now() };
1204
+ current.sharedLeases = { ...current.sharedLeases, [claimant.sessionId]: lease };
1205
+ current.disposable = false;
1206
+ await putRecord(current);
1207
+ return {
1208
+ entry: entryView(current),
1209
+ lease,
1210
+ referenceAdded,
1211
+ replaced: previous && !sameOwner(previous, claimant) ? previous : null,
1212
+ };
913
1213
  }
914
1214
  if (!current.ownerSessionId) {
915
1215
  const other = findRecord(
@@ -934,13 +1234,23 @@ export async function claimWorktreeOwner(entryId, owner) {
934
1234
  * Release the live lease when (and only when) it is held by exactly `owner`.
935
1235
  * Durable ownership (the session retaining the worktree) is unchanged.
936
1236
  */
937
- export async function releaseWorktreeOwner(entryId, owner) {
1237
+ export async function releaseWorktreeOwner(entryId, owner, { forgetReference = false } = {}) {
938
1238
  const claimant = normalizeWorktreeOwner(owner);
939
1239
  const record = await getRecord(entryId);
940
1240
  if (!record) return { released: false };
941
1241
  return withRepoLock(record.repoCommonDir, async () => {
942
1242
  const current = await getRecord(entryId);
943
1243
  if (!current) return { released: false };
1244
+ const shared = current.sharedLeases?.[claimant.sessionId];
1245
+ if (sameOwner(shared, claimant)) {
1246
+ const leases = { ...current.sharedLeases };
1247
+ delete leases[claimant.sessionId];
1248
+ current.sharedLeases = leases;
1249
+ if (forgetReference)
1250
+ current.sharedSessionIds = (current.sharedSessionIds ?? []).filter((id) => id !== claimant.sessionId);
1251
+ await putRecord(current);
1252
+ return { released: true };
1253
+ }
944
1254
  const lease = await readOwnerLease(current);
945
1255
  if (!sameOwner(lease, claimant)) return { released: false };
946
1256
  await removeOwnerLease(current);
@@ -1186,6 +1496,27 @@ async function assertRemovable(record) {
1186
1496
  }
1187
1497
  }
1188
1498
 
1499
+ /** User changes, excluding the manager's own project identity and unchanged include copies. */
1500
+ export async function managedWorktreeDirtyPaths(entryId) {
1501
+ const record = await getRecord(entryId);
1502
+ if (!record) throw worktreeError('WORKTREE_MISSING');
1503
+ return dirtyPaths(record);
1504
+ }
1505
+
1506
+ // Durable references protect sleeping sessions as well as running runtimes.
1507
+ // Force may discard files, never another session's workspace.
1508
+ function assertNotShared(record, sessionId = record.ownerSessionId) {
1509
+ const others = new Set(record.sharedSessionIds ?? []);
1510
+ if (record.ownerSessionId) others.add(record.ownerSessionId);
1511
+ if (sessionId) others.delete(sessionId);
1512
+ if (others.size) {
1513
+ throw worktreeError('WORKTREE_BUSY_JOBS', 'Other sessions retain this worktree; its folder must be kept.', {
1514
+ reason: 'workspace-shared',
1515
+ sessions: [...others],
1516
+ });
1517
+ }
1518
+ }
1519
+
1189
1520
  /**
1190
1521
  * The refusal removeManagedWorktree would raise, checked before anything
1191
1522
  * moves (a session leaves its worktree only for a removal that can proceed).
@@ -1193,9 +1524,10 @@ async function assertRemovable(record) {
1193
1524
  * and non-ready entries; ordinary dirty/unmerged checks apply only to ready ones.
1194
1525
  * @param {{ entryId: string, force?: boolean }} input
1195
1526
  */
1196
- export async function assertWorktreeRemovable({ entryId, force = false } = {}) {
1527
+ export async function assertWorktreeRemovable({ entryId, force = false, sessionId } = {}) {
1197
1528
  const record = await getRecord(entryId);
1198
1529
  if (!record) throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
1530
+ assertNotShared(record, sessionId);
1199
1531
  await assertRecoveryStoresClear(record);
1200
1532
  if (record.state !== 'ready') return;
1201
1533
  await assertManagedCheckoutPath(record);
@@ -1209,12 +1541,19 @@ export async function assertWorktreeRemovable({ entryId, force = false } = {}) {
1209
1541
  * @param {{ entryId: string, force?: boolean }} input
1210
1542
  * @returns {Promise<{ removed: boolean, retryable?: boolean, remaining?: object[], branchDeleted?: boolean }>}
1211
1543
  */
1212
- export async function removeManagedWorktree({ entryId, force = false } = {}) {
1544
+ export async function removeManagedWorktree({
1545
+ entryId,
1546
+ force = false,
1547
+ sessionId,
1548
+ preserveBranch = false,
1549
+ safeOnly = false,
1550
+ } = {}) {
1213
1551
  const record = await getRecord(entryId);
1214
1552
  if (!record) throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
1215
1553
  return withRepoLock(record.repoCommonDir, async () => {
1216
1554
  const current = await getRecord(entryId);
1217
1555
  if (!current) throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
1556
+ assertNotShared(current, sessionId);
1218
1557
  if (current.state === 'creating' || current.state === 'recovery') {
1219
1558
  const rollback = await rollbackCreation(current);
1220
1559
  return rollback.complete ? { removed: true } : { removed: false, retryable: true, remaining: rollback.remaining };
@@ -1224,7 +1563,7 @@ export async function removeManagedWorktree({ entryId, force = false } = {}) {
1224
1563
  await assertManagedCheckoutPath(current);
1225
1564
  if (!force) await assertRemovable(current);
1226
1565
  current.state = 'deleting';
1227
- current.deleting = { force: force === true, startedAt: Date.now(), remaining: [] };
1566
+ current.deleting = { force: force === true, preserveBranch, safeOnly, startedAt: Date.now(), remaining: [] };
1228
1567
  await putRecord(current);
1229
1568
  }
1230
1569
  return finishDeletion(current);
@@ -1251,10 +1590,13 @@ export async function listManagedWorktrees({ liveSessionIds = [], retainedSessio
1251
1590
  const ownerLive =
1252
1591
  (!!record.ownerSessionId && live.has(record.ownerSessionId)) || leaseProcessAlive(await readOwnerLease(record));
1253
1592
  const isRetained = !!record.ownerSessionId && retained.has(record.ownerSessionId);
1254
- let removable = record.state !== 'ready' || (!ownerLive && (record.disposable === true || !isRetained));
1593
+ let removable =
1594
+ !record.sharedSessionIds?.length &&
1595
+ (record.state !== 'ready' || (!ownerLive && (record.disposable === true || !isRetained)));
1255
1596
  if (removable) {
1256
- try { await assertRecoveryStoresClear(record); }
1257
- catch (error) {
1597
+ try {
1598
+ await assertRecoveryStoresClear(record);
1599
+ } catch (error) {
1258
1600
  if (error?.code !== 'WORKTREE_FILE_RECOVERY') throw error;
1259
1601
  removable = false;
1260
1602
  }
@@ -1311,6 +1653,10 @@ export async function reconcileManagedWorktrees({
1311
1653
  for (const record of records) {
1312
1654
  const isRetained = !!record.ownerSessionId && retained.has(record.ownerSessionId);
1313
1655
  try {
1656
+ if (record.sharedSessionIds?.length) {
1657
+ report.kept.push({ id: record.id, reason: 'shared' });
1658
+ continue;
1659
+ }
1314
1660
  if (record.state === 'creating' || record.state === 'recovery') {
1315
1661
  if (isRetained) {
1316
1662
  report.kept.push({ id: record.id, reason: 'retained' });
@@ -17,7 +17,7 @@ function ownerLeasePath(record) {
17
17
  return join(managedWorktreeRoot(record.repoCommonDir), '.owners', `${record.id}.json`);
18
18
  }
19
19
 
20
- export async function readOwnerLease(record) {
20
+ export async function readOwnerLease(record, { strict = false } = {}) {
21
21
  try {
22
22
  const value = parseJsonText(await readFile(ownerLeasePath(record), 'utf8'));
23
23
  return {
@@ -26,6 +26,7 @@ export async function readOwnerLease(record) {
26
26
  claimedAt: value.claimedAt ?? null,
27
27
  };
28
28
  } catch (error) {
29
+ if (strict && error?.code !== 'ENOENT') throw error;
29
30
  if (error?.code === 'ENOENT' || error instanceof SyntaxError || error instanceof TypeError) return null;
30
31
  throw error;
31
32
  }