mixdog 1.2.2 → 1.2.4

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 (692) hide show
  1. package/README.md +18 -0
  2. package/package.json +2 -1
  3. package/scripts/gate-local.mjs +6 -4
  4. package/scripts/lib/gate-scheduler.mjs +29 -11
  5. package/scripts/probe-provider-catalog.mjs +31 -0
  6. package/scripts/provider-pingpong-live.mjs +599 -0
  7. package/scripts/session-sweep.mjs +5 -25
  8. package/src/agents/advisor/AGENT.md +1 -1
  9. package/src/agents/advisor/agent.json +1 -1
  10. package/src/agents/maintainer/AGENT.md +1 -1
  11. package/src/agents/maintainer/agent.json +1 -1
  12. package/src/agents/reviewer/AGENT.md +2 -4
  13. package/src/agents/security/AGENT.md +1 -2
  14. package/src/agents/writer/AGENT.md +2 -3
  15. package/src/defaults/shipped-history.json +11 -1
  16. package/src/defaults/skills/image/SKILL.md +21 -23
  17. package/src/defaults/skills/setup/SKILL.md +6 -1
  18. package/src/defaults/skills/setup/references/actions.md +44 -22
  19. package/src/defaults/skills/setup/references/surfaces.md +10 -8
  20. package/src/headless-exec.mjs +15 -8
  21. package/src/lib/keychain-cjs.cjs +20 -15
  22. package/src/output-styles/common.md +11 -10
  23. package/src/rules/agent/AGENT.md +6 -4
  24. package/src/rules/lead/LEAD.md +7 -9
  25. package/src/rules/shared/45-destructive.md +4 -0
  26. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +28 -7
  28. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -11
  29. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
  30. package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
  31. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
  32. package/src/runtime/agent/orchestrator/config.mjs +5 -0
  33. package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
  34. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
  35. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
  36. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +101 -23
  37. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +2 -0
  38. package/src/runtime/agent/orchestrator/providers/anthropic-api-key-fetch.mjs +36 -0
  39. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
  40. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +3 -1
  41. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +24 -17
  42. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +9 -9
  43. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +26 -44
  45. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +31 -15
  46. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
  47. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
  48. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
  49. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
  50. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
  51. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
  52. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
  53. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -0
  54. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
  55. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +10 -0
  56. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +41 -12
  57. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +13 -1
  58. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +131 -9
  59. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +29 -10
  60. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +125 -8
  61. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +12 -62
  62. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +117 -41
  63. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  64. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +117 -72
  65. package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
  66. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +39 -5
  67. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/providers/cursor-native-context.mjs +48 -0
  69. package/src/runtime/agent/orchestrator/providers/cursor-wire-exec.mjs +144 -34
  70. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +28 -4
  71. package/src/runtime/agent/orchestrator/providers/cursor-wire-request.mjs +8 -4
  72. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +42 -11
  73. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +109 -6
  74. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +17 -4
  75. package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -2
  76. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +73 -7
  77. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +75 -6
  78. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
  79. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
  80. package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
  81. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +29 -5
  82. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
  83. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +36 -13
  84. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +101 -11
  85. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
  86. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +1 -0
  87. package/src/runtime/agent/orchestrator/providers/gemini-rest-stream/watchdogs.mjs +16 -5
  88. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +19 -14
  89. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +371 -41
  90. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -2
  91. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +97 -49
  92. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +81 -10
  93. package/src/runtime/agent/orchestrator/providers/gemini.mjs +35 -8
  94. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  95. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +1 -0
  96. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
  97. package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
  98. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +253 -42
  99. package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
  100. package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
  101. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +392 -0
  102. package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
  103. package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
  104. package/src/runtime/agent/orchestrator/providers/lib/request-compression.mjs +86 -0
  105. package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
  106. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
  107. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +50 -22
  108. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +133 -21
  109. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
  110. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +7 -1
  111. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
  112. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  113. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +92 -16
  114. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +13 -13
  115. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +133 -62
  117. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
  118. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +5 -2
  119. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +39 -7
  120. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
  121. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +5 -0
  122. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +1 -1
  123. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +84 -2
  124. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
  125. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
  126. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +155 -0
  127. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +40 -24
  128. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +63 -25
  129. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
  130. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +183 -0
  131. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +87 -6
  132. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +51 -3
  133. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -1
  134. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +103 -26
  136. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +23 -4
  137. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +11 -6
  138. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +17 -2
  139. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +15 -23
  140. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +64 -52
  141. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
  142. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +28 -5
  143. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
  144. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
  145. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +4 -0
  146. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +5 -0
  147. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
  148. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
  149. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +3 -1
  150. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +7 -0
  151. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +7 -1
  152. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +72 -24
  153. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
  154. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +50 -16
  155. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -19
  156. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
  157. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -36
  158. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
  159. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
  160. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
  161. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -9
  162. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +181 -0
  163. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +78 -70
  164. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +74 -0
  165. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +150 -10
  166. package/src/runtime/agent/orchestrator/providers/registry.mjs +13 -2
  167. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +7 -1
  168. package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
  169. package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
  170. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +164 -0
  171. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +522 -0
  172. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
  173. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +195 -0
  174. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +104 -0
  175. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +8 -6
  176. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +24 -0
  177. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
  178. package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +30 -2
  179. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +3 -2
  180. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -3
  181. package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
  182. package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
  183. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +16 -3
  184. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge-contract.mjs +6 -0
  185. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
  186. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  187. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
  188. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +178 -71
  189. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +102 -12
  190. package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
  191. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -8
  192. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -2
  193. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +24 -0
  194. package/src/runtime/agent/orchestrator/session/cache/tool-result-reuse.mjs +10 -2
  195. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -3
  196. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +5 -11
  197. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +6 -22
  198. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
  199. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +16 -93
  200. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +16 -2
  201. package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
  202. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +148 -0
  203. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  204. package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
  205. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
  206. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +0 -6
  207. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +0 -6
  208. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +3 -2
  209. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
  210. package/src/runtime/agent/orchestrator/session/historical-session-closure.mjs +10 -0
  211. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +2 -5
  212. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +89 -1
  213. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
  214. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
  215. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
  216. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -27
  217. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +1 -1
  218. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -10
  219. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -9
  220. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +24 -10
  221. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +135 -0
  222. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +31 -4
  223. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +76 -0
  224. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +67 -20
  225. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +10 -10
  226. package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
  227. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +6 -0
  228. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
  229. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +29 -5
  230. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  231. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +147 -0
  232. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +7 -5
  233. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +33 -3
  234. package/src/runtime/agent/orchestrator/session/manager/ask-support.mjs +0 -4
  235. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +23 -2
  236. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
  237. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +54 -13
  238. package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +33 -0
  239. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
  240. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +11 -0
  241. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +22 -0
  242. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +57 -13
  243. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
  244. package/src/runtime/agent/orchestrator/session/manager/failed-turn-rewind.mjs +16 -0
  245. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +6 -120
  246. package/src/runtime/agent/orchestrator/session/manager/pending-persist-queue.mjs +1 -1
  247. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +231 -16
  248. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +42 -49
  249. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +50 -0
  250. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +47 -17
  251. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +26 -4
  252. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
  253. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +36 -35
  254. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +29 -13
  255. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +6 -3
  256. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
  257. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +45 -0
  258. package/src/runtime/agent/orchestrator/session/manager.mjs +4 -10
  259. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
  260. package/src/runtime/agent/orchestrator/session/send-instrumentation.mjs +2 -1
  261. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +17 -1
  262. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
  263. package/src/runtime/agent/orchestrator/session/side-question.mjs +197 -0
  264. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +22 -15
  265. package/src/runtime/agent/orchestrator/session/store/lifecycle-read.mjs +1 -1
  266. package/src/runtime/agent/orchestrator/session/store/listing.mjs +2 -6
  267. package/src/runtime/agent/orchestrator/session/store/live-cache-eviction.mjs +1 -1
  268. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
  269. package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +46 -37
  270. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +1 -1
  271. package/src/runtime/agent/orchestrator/session/store/load-session.mjs +1 -1
  272. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +44 -37
  273. package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
  274. package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +59 -0
  275. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +1 -1
  276. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +1 -1
  277. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +27 -21
  278. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +0 -8
  279. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +6 -18
  280. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +33 -106
  281. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +4 -2
  282. package/src/runtime/agent/orchestrator/session/store-agent-worker-pool.mjs +5 -8
  283. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +136 -196
  284. package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
  285. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +53 -115
  286. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +8 -0
  287. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +10 -1
  288. package/src/runtime/agent/orchestrator/session/store-summary-locations.mjs +3 -29
  289. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +1 -1
  290. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +17 -10
  291. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +9 -20
  292. package/src/runtime/agent/orchestrator/session/store.mjs +22 -19
  293. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +12 -12
  294. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +38 -14
  295. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +15 -4
  296. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
  297. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
  298. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
  299. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +79 -0
  300. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +72 -15
  301. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +45 -0
  302. package/src/runtime/agent/orchestrator/stall-policy.mjs +50 -0
  303. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
  304. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
  305. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +5 -1
  306. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +6 -2
  307. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
  308. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +218 -20
  309. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +160 -39
  310. package/src/runtime/agent/orchestrator/tools/builtin/git-partial-stage.mjs +7 -5
  311. package/src/runtime/agent/orchestrator/tools/builtin/grep-formatting.mjs +14 -5
  312. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +4 -3
  313. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-output.mjs +2 -1
  314. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-path-fanout.mjs +48 -44
  315. package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +10 -3
  316. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +247 -49
  317. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +5 -4
  318. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +6 -1
  319. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
  320. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +20 -0
  321. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +101 -2
  322. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
  323. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
  324. package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +1 -1
  325. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +67 -0
  326. package/src/runtime/agent/orchestrator/tools/lib/process-table-fixture.mjs +50 -0
  327. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +303 -63
  328. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -0
  329. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +108 -18
  330. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +77 -11
  331. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +81 -5
  332. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
  333. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
  334. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
  335. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
  336. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
  337. package/src/runtime/attachments/store.d.mts +9 -0
  338. package/src/runtime/attachments/store.mjs +80 -9
  339. package/src/runtime/browser-bridge/browser-action-contract.d.mts +0 -1
  340. package/src/runtime/browser-bridge/browser-action-contract.mjs +0 -2
  341. package/src/runtime/computer-bridge/action-schema.mjs +219 -196
  342. package/src/runtime/computer-bridge/linux-readiness.d.mts +0 -2
  343. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -16
  344. package/src/runtime/effort-judge/judge-client.mjs +21 -4
  345. package/src/runtime/effort-judge/judge-worker.mjs +4 -1
  346. package/src/runtime/github/tool.mjs +208 -17
  347. package/src/runtime/media/adapters/codex-image.mjs +140 -21
  348. package/src/runtime/media/catalog.mjs +4 -4
  349. package/src/runtime/media/chatgpt-usage.mjs +55 -0
  350. package/src/runtime/media/defaults.mjs +37 -0
  351. package/src/runtime/media/jobs.mjs +12 -2
  352. package/src/runtime/media/lanes.mjs +12 -0
  353. package/src/runtime/media/tool-defs.mjs +15 -4
  354. package/src/runtime/media/tool.mjs +41 -12
  355. package/src/runtime/media/upstream-error.mjs +72 -6
  356. package/src/runtime/memory/index.mjs +1 -0
  357. package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
  358. package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
  359. package/src/runtime/memory/lib/ingest/transcript-rows.mjs +1 -0
  360. package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
  361. package/src/runtime/memory/lib/pg/process.mjs +36 -33
  362. package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
  363. package/src/runtime/memory/lib/pg/supervisor.mjs +112 -4
  364. package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
  365. package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
  366. package/src/runtime/office/README.md +1 -1
  367. package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
  368. package/src/runtime/office/authoring/html-browser.mjs +18 -8
  369. package/src/runtime/office/authoring/html-document-author.mjs +2 -0
  370. package/src/runtime/office/authoring/pdf-html-runner.mjs +1 -1
  371. package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
  372. package/src/runtime/office/authoring/pptx-html-measure.mjs +3 -1
  373. package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
  374. package/src/runtime/office/authoring/xlsx-html-runner.mjs +3 -1
  375. package/src/runtime/office/core/office-actions-read.mjs +3 -0
  376. package/src/runtime/office/core/office-render-preview.mjs +2 -0
  377. package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
  378. package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
  379. package/src/runtime/office/core/office-sessions.mjs +5 -0
  380. package/src/runtime/office/core/office-transactions.mjs +3 -0
  381. package/src/runtime/office/core/office-write-ledger.mjs +94 -0
  382. package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
  383. package/src/runtime/office/index.mjs +29 -0
  384. package/src/runtime/office/pdf/pdf-analysis.mjs +0 -68
  385. package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
  386. package/src/runtime/office/pdf/pdf-render.mjs +23 -7
  387. package/src/runtime/office/pdf/pdf-security.mjs +2 -0
  388. package/src/runtime/office/portable/portable-opc.mjs +4 -1
  389. package/src/runtime/office/quality/visual-diff.mjs +2 -0
  390. package/src/runtime/session-search/session-search-ingest.mjs +1 -1
  391. package/src/runtime/shared/agent-reap-state.mjs +13 -0
  392. package/src/runtime/shared/atomic-file.d.mts +1 -1
  393. package/src/runtime/shared/background-tasks.mjs +6 -1
  394. package/src/runtime/shared/bounded-file-read.mjs +13 -0
  395. package/src/runtime/shared/bounded-line-diff.mjs +191 -0
  396. package/src/runtime/shared/browser-launch.mjs +15 -0
  397. package/src/runtime/shared/buffered-appender.mjs +18 -0
  398. package/src/runtime/shared/config.mjs +78 -18
  399. package/src/runtime/shared/err-text.mjs +48 -4
  400. package/src/runtime/shared/file-lock.mjs +6 -5
  401. package/src/runtime/shared/file-permissions.d.mts +6 -1
  402. package/src/runtime/shared/file-permissions.mjs +54 -6
  403. package/src/runtime/shared/llm/anthropic-betas.mjs +1 -2
  404. package/src/runtime/shared/llm/anthropic-effort.mjs +6 -0
  405. package/src/runtime/shared/llm/provider-model-identities.mjs +1 -0
  406. package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
  407. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
  408. package/src/runtime/shared/loopback-listener.mjs +9 -0
  409. package/src/runtime/shared/model-route-chain.d.mts +8 -0
  410. package/src/runtime/shared/model-route-chain.mjs +48 -0
  411. package/src/runtime/shared/model-route-fallback.mjs +22 -0
  412. package/src/runtime/shared/pid-liveness.mjs +12 -0
  413. package/src/runtime/shared/provider-api-key.mjs +8 -0
  414. package/src/runtime/shared/provider-timeout-phase.mjs +17 -0
  415. package/src/runtime/shared/quiet-progress.d.mts +35 -0
  416. package/src/runtime/shared/quiet-progress.mjs +101 -0
  417. package/src/runtime/shared/quota-resume-store.mjs +32 -1
  418. package/src/runtime/shared/read-row-numbers.mjs +44 -0
  419. package/src/runtime/shared/remote-intent.mjs +2 -42
  420. package/src/runtime/shared/review-diff.mjs +52 -0
  421. package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
  422. package/src/runtime/shared/session-sidebar-host.mjs +38 -0
  423. package/src/runtime/shared/slow-span.mjs +121 -0
  424. package/src/runtime/shared/statusline/statusline-segments.mjs +52 -6
  425. package/src/runtime/shared/tool-card-model/card-header.mjs +7 -1
  426. package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
  427. package/src/runtime/shared/tool-card-model.mjs +5 -2
  428. package/src/runtime/shared/tool-output-budget.mjs +2 -0
  429. package/src/runtime/shared/tool-output-limit.mjs +4 -0
  430. package/src/runtime/shared/tool-primitives.mjs +13 -0
  431. package/src/runtime/shared/tool-result-summary.mjs +22 -1
  432. package/src/runtime/shared/tool-status.mjs +3 -2
  433. package/src/runtime/shared/tool-surface.mjs +20 -6
  434. package/src/runtime/shared/tool-work-units.mjs +54 -11
  435. package/src/runtime/shared/transcript-metadata.mjs +49 -1
  436. package/src/runtime/shared/transcript-turn-keys.mjs +33 -0
  437. package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
  438. package/src/runtime/shared/transcript-writer/transcript-file.mjs +10 -1
  439. package/src/runtime/shared/transcript-writer.mjs +24 -6
  440. package/src/runtime/shared/turn-changes.mjs +269 -100
  441. package/src/runtime/shared/turn-review-reply.mjs +8 -39
  442. package/src/runtime/shared/turn-snapshot-store.mjs +19 -108
  443. package/src/runtime/shared/turn-snapshot.mjs +865 -683
  444. package/src/runtime/shared/turn-worktree-snapshot.mjs +183 -255
  445. package/src/runtime/shared/user-data-guard.mjs +262 -49
  446. package/src/runtime/shared/worktrees/activity-gate.mjs +175 -0
  447. package/src/runtime/shared/worktrees/branch-management.mjs +179 -0
  448. package/src/runtime/shared/worktrees/carry.mjs +533 -0
  449. package/src/runtime/shared/worktrees/contract.mjs +60 -0
  450. package/src/runtime/shared/worktrees/dependencies.mjs +456 -0
  451. package/src/runtime/shared/worktrees/file-recovery.mjs +84 -0
  452. package/src/runtime/shared/worktrees/git-head.mjs +214 -0
  453. package/src/runtime/shared/worktrees/git.mjs +8 -3
  454. package/src/runtime/shared/worktrees/include.mjs +125 -7
  455. package/src/runtime/shared/worktrees/lock.mjs +1 -1
  456. package/src/runtime/shared/worktrees/maintenance.mjs +20 -2
  457. package/src/runtime/shared/worktrees/manager.mjs +525 -31
  458. package/src/runtime/shared/worktrees/merge.mjs +31 -21
  459. package/src/runtime/shared/worktrees/owner.mjs +2 -1
  460. package/src/runtime/shared/worktrees/paths.d.mts +6 -0
  461. package/src/runtime/shared/worktrees/preparation-report.mjs +79 -0
  462. package/src/runtime/shared/worktrees/setup-commands.mjs +232 -0
  463. package/src/runtime/shared/worktrees/workspace-visibility.mjs +17 -0
  464. package/src/runtime/terminal-bridge/client.mjs +57 -20
  465. package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
  466. package/src/runtime/tidy/apply.mjs +47 -2
  467. package/src/runtime/tidy/run-engines.mjs +11 -5
  468. package/src/session-runtime/agent-tool-routing.mjs +19 -13
  469. package/src/session-runtime/boot/apis.mjs +20 -6
  470. package/src/session-runtime/boot/config.mjs +2 -0
  471. package/src/session-runtime/boot/facade.mjs +4 -0
  472. package/src/session-runtime/boot/infrastructure.mjs +6 -1
  473. package/src/session-runtime/boot/session.mjs +3 -3
  474. package/src/session-runtime/boot/tools.mjs +7 -1
  475. package/src/session-runtime/conversation-import/service.mjs +1 -1
  476. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
  477. package/src/session-runtime/goal-facade-api.mjs +2 -2
  478. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +91 -0
  479. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
  480. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +15 -5
  481. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +137 -26
  482. package/src/session-runtime/internal-tool-executor.mjs +8 -2
  483. package/src/session-runtime/lifecycle/cold-heir.mjs +187 -0
  484. package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
  485. package/src/session-runtime/lifecycle/session-catalog.mjs +25 -18
  486. package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
  487. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +36 -48
  488. package/src/session-runtime/lifecycle/session-switch.mjs +1 -1
  489. package/src/session-runtime/lifecycle/shared.mjs +5 -6
  490. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -5
  491. package/src/session-runtime/lifecycle/teardown.mjs +70 -14
  492. package/src/session-runtime/lifecycle-api.mjs +1 -0
  493. package/src/session-runtime/mcp-glue/connect.mjs +17 -5
  494. package/src/session-runtime/media-api.mjs +8 -0
  495. package/src/session-runtime/model-route/route-ownership.mjs +60 -0
  496. package/src/session-runtime/model-route/route-persist.mjs +3 -2
  497. package/src/session-runtime/model-route/set-route.mjs +116 -36
  498. package/src/session-runtime/model-route/web-search-route.mjs +7 -2
  499. package/src/session-runtime/model-route-api.mjs +4 -2
  500. package/src/session-runtime/native-web-search/routes.mjs +37 -24
  501. package/src/session-runtime/native-web-search-usage.mjs +51 -0
  502. package/src/session-runtime/native-web-search.mjs +19 -9
  503. package/src/session-runtime/new-session-config.mjs +4 -2
  504. package/src/session-runtime/provider-auth/login.mjs +4 -0
  505. package/src/session-runtime/provider-auth/setup.mjs +4 -0
  506. package/src/session-runtime/provider-models/catalog-load.mjs +9 -0
  507. package/src/session-runtime/provider-models/web-search-catalog.mjs +23 -3
  508. package/src/session-runtime/provider-models.mjs +1 -0
  509. package/src/session-runtime/provider-usage.mjs +4 -0
  510. package/src/session-runtime/resource-scope-decorate.mjs +10 -3
  511. package/src/session-runtime/route-resolution.mjs +3 -1
  512. package/src/session-runtime/runtime-core.mjs +4 -0
  513. package/src/session-runtime/runtime-facade.mjs +6 -0
  514. package/src/session-runtime/runtime-review-api.mjs +8 -2
  515. package/src/session-runtime/services/agent-task-status.mjs +2 -0
  516. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +0 -3
  517. package/src/session-runtime/services/agent-tool/close-flow.mjs +6 -7
  518. package/src/session-runtime/services/agent-tool/execute-send.mjs +2 -2
  519. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +36 -3
  520. package/src/session-runtime/services/agent-tool/execute.mjs +9 -1
  521. package/src/session-runtime/services/agent-tool/helpers.mjs +30 -7
  522. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
  523. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +7 -2
  524. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +6 -3
  525. package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
  526. package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
  527. package/src/session-runtime/services/agent-tool/lead-worker-index/lead-rows.mjs +3 -4
  528. package/src/session-runtime/services/agent-tool/lead-worker-index/reap-timers.mjs +9 -3
  529. package/src/session-runtime/services/agent-tool/lead-worker-index/row-settlement.mjs +7 -18
  530. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +82 -13
  531. package/src/session-runtime/services/agent-tool/model-select.mjs +82 -0
  532. package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -6
  533. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -1
  534. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -5
  535. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -4
  536. package/src/session-runtime/services/agent-tool/spawn-flow.mjs +2 -1
  537. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  538. package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +1 -3
  539. package/src/session-runtime/services/agent-tool/tag-registry/tag-refresh.mjs +1 -3
  540. package/src/session-runtime/services/agent-tool/tag-registry.mjs +18 -10
  541. package/src/session-runtime/services/agent-tool/terminal-reaper/scan-admission.mjs +8 -41
  542. package/src/session-runtime/services/agent-tool/terminal-reaper/stale-rows.mjs +3 -3
  543. package/src/session-runtime/services/agent-tool/terminal-reaper.mjs +6 -23
  544. package/src/session-runtime/services/agent-tool/terminal-trace.mjs +0 -1
  545. package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
  546. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +32 -20
  547. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -2
  548. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
  549. package/src/session-runtime/services/agent-tool/worker-index.mjs +74 -15
  550. package/src/session-runtime/services/agent-tool.mjs +7 -0
  551. package/src/session-runtime/services/agent-watchdog-registry.mjs +2 -0
  552. package/src/session-runtime/services/channel-admin.mjs +19 -3
  553. package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
  554. package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
  555. package/src/session-runtime/services/provider-admin.mjs +158 -15
  556. package/src/session-runtime/session-create/session-options.mjs +7 -5
  557. package/src/session-runtime/session-create/session-start-hook.mjs +2 -1
  558. package/src/session-runtime/session-transcript-purge.mjs +32 -0
  559. package/src/session-runtime/session-transcript.mjs +4 -5
  560. package/src/session-runtime/session-turn-api.mjs +17 -14
  561. package/src/session-runtime/settings-builtin-tools-api.mjs +18 -6
  562. package/src/session-runtime/setup-tool/executor.mjs +57 -9
  563. package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
  564. package/src/session-runtime/setup-tool/settings-contract.mjs +18 -3
  565. package/src/session-runtime/setup-tool/tool-defs.mjs +25 -4
  566. package/src/session-runtime/skills-api.mjs +3 -5
  567. package/src/session-runtime/tool-policy-refresh.mjs +83 -32
  568. package/src/session-runtime/tool-surface.mjs +5 -1
  569. package/src/session-runtime/turn/agent-ops.mjs +49 -2
  570. package/src/session-runtime/turn/compaction-gate.mjs +25 -0
  571. package/src/session-runtime/turn/session-ops.mjs +7 -5
  572. package/src/session-runtime/turn/side-question-ops.mjs +19 -0
  573. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +7 -1
  574. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +12 -3
  575. package/src/session-runtime/turn/turn-run/turn-settle.mjs +18 -2
  576. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  577. package/src/session-runtime/turn/turn-run.mjs +26 -45
  578. package/src/session-runtime/usage-stats-api.mjs +46 -11
  579. package/src/session-runtime/workflow-agents-api/agent-route.mjs +16 -1
  580. package/src/session-runtime/workspace/admission-gate.mjs +44 -15
  581. package/src/session-runtime/workspace/project-branches.mjs +35 -41
  582. package/src/session-runtime/workspace/transition-steps.mjs +215 -116
  583. package/src/session-runtime/workspace/transitions.mjs +428 -73
  584. package/src/standalone/channel-transport.mjs +3 -4
  585. package/src/standalone/daemon-agent-control.mjs +9 -1
  586. package/src/standalone/daemon-desktop-runtime.mjs +32 -1
  587. package/src/standalone/daemon-local-session-bridge.mjs +14 -1
  588. package/src/standalone/daemon-owner.mjs +17 -4
  589. package/src/standalone/daemon-pg-stop.mjs +49 -0
  590. package/src/standalone/daemon-session-control.mjs +88 -0
  591. package/src/standalone/daemon-stop-budget.d.mts +2 -0
  592. package/src/standalone/daemon-stop-budget.mjs +18 -0
  593. package/src/standalone/daemon-stored-session-views.mjs +25 -3
  594. package/src/standalone/daemon-telemetry.mjs +19 -0
  595. package/src/standalone/daemon.mjs +63 -1
  596. package/src/standalone/desktop-service-registry.mjs +5 -0
  597. package/src/standalone/local-session-runtime.mjs +80 -30
  598. package/src/standalone/session-client.mjs +42 -0
  599. package/src/standalone/session-protocol.mjs +14 -0
  600. package/src/standalone/session-proxy/projection.mjs +41 -4
  601. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
  602. package/src/standalone/session-runtime-inline-host.mjs +3 -0
  603. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +91 -49
  604. package/src/standalone/session-service/agent-tree/agent-child.mjs +19 -1
  605. package/src/standalone/session-service/agent-tree/agent-registry.mjs +25 -0
  606. package/src/standalone/session-service/agent-tree/agent-turns.mjs +9 -1
  607. package/src/standalone/session-service/agent-tree.mjs +28 -2
  608. package/src/standalone/session-service/entries/session-loads.mjs +16 -5
  609. package/src/standalone/session-service/entries.mjs +1 -0
  610. package/src/standalone/session-service/projection/transcript-window.mjs +39 -0
  611. package/src/standalone/session-service/quota-resume.mjs +28 -13
  612. package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
  613. package/src/standalone/session-service/session-calls/session-turns.mjs +17 -6
  614. package/src/standalone/session-service/session-calls/session-views.mjs +6 -0
  615. package/src/standalone/session-service-api.mjs +6 -1
  616. package/src/standalone/session-state-patch.d.mts +2 -1
  617. package/src/standalone/session-state-patch.mjs +41 -1
  618. package/src/standalone/session-tool-output.mjs +82 -0
  619. package/src/tui/App.jsx +25 -0
  620. package/src/tui/app/app-view/floating-panel.jsx +26 -1
  621. package/src/tui/app/app-view.jsx +2 -1
  622. package/src/tui/app/btw-controller.mjs +118 -0
  623. package/src/tui/app/create-app-pickers.mjs +2 -0
  624. package/src/tui/app/provider-setup/provider-items.mjs +8 -0
  625. package/src/tui/app/provider-setup-picker.mjs +15 -3
  626. package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
  627. package/src/tui/app/shell-layout/surfaces.mjs +3 -1
  628. package/src/tui/app/slash-commands.mjs +19 -3
  629. package/src/tui/app/slash-dispatch/session-commands.mjs +68 -6
  630. package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
  631. package/src/tui/app/use-global-key-input.mjs +5 -12
  632. package/src/tui/app/use-prompt-handlers.mjs +11 -0
  633. package/src/tui/components/BtwPanel.jsx +53 -0
  634. package/src/tui/components/QuestionPanel.jsx +275 -0
  635. package/src/tui/components/Spinner.jsx +4 -0
  636. package/src/tui/components/SpinnerByline.jsx +21 -2
  637. package/src/tui/components/question-panel-model.mjs +156 -0
  638. package/src/tui/components/status-line/local-line.mjs +10 -2
  639. package/src/tui/dist/index.mjs +2897 -1722
  640. package/src/tui/session/active-tool-summary.mjs +1 -1
  641. package/src/tui/session/context-state.mjs +4 -5
  642. package/src/tui/session/goal-continuation/continuation-queue.mjs +2 -3
  643. package/src/tui/session/goal-continuation/deadline-delivery.mjs +4 -2
  644. package/src/tui/session/goal-continuation/session-boundary.mjs +21 -0
  645. package/src/tui/session/quota-resume.mjs +140 -61
  646. package/src/tui/session/session-api/commands.mjs +14 -10
  647. package/src/tui/session/session-api/controls.mjs +14 -2
  648. package/src/tui/session/session-api/integrations.mjs +9 -2
  649. package/src/tui/session/session-api/lifecycle/dispose.mjs +3 -9
  650. package/src/tui/session/session-api/lifecycle/resume.mjs +2 -7
  651. package/src/tui/session/session-api/lifecycle.mjs +14 -11
  652. package/src/tui/session/session-api/media.mjs +2 -0
  653. package/src/tui/session/session-api/routes.mjs +81 -57
  654. package/src/tui/session/session-api/settings.mjs +9 -3
  655. package/src/tui/session/session-api/worktree.mjs +3 -2
  656. package/src/tui/session/session-api.mjs +1 -0
  657. package/src/tui/session/session-flow/auto-clear.mjs +1 -3
  658. package/src/tui/session/session-flow/drain.mjs +6 -4
  659. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -0
  660. package/src/tui/session/session-flow/queue/take-entries.mjs +2 -1
  661. package/src/tui/session/session-flow/queue.mjs +1 -1
  662. package/src/tui/session/tool-approval.mjs +20 -8
  663. package/src/tui/session/transcript-spill.mjs +14 -7
  664. package/src/tui/session/transcript-store/streaming-tail.mjs +1 -1
  665. package/src/tui/session/tui-steering-persist.mjs +3 -5
  666. package/src/tui/session/turn-quiet.mjs +53 -0
  667. package/src/tui/session/turn-stream.mjs +179 -32
  668. package/src/tui/session/turn.mjs +50 -36
  669. package/src/tui/session/worktree-setup-notice.mjs +38 -0
  670. package/src/tui/spinner-meta.mjs +10 -0
  671. package/src/tui/spinner-verbs.mjs +3 -0
  672. package/src/ui/statusline.mjs +13 -3
  673. package/src/workflows/default/WORKFLOW.md +3 -1
  674. package/src/runtime/agent/orchestrator/providers/model-pricing-rates.mjs +0 -1
  675. package/src/runtime/agent/orchestrator/session/manager/foreign-pending-messages.mjs +0 -346
  676. package/src/runtime/agent/orchestrator/session/manager/session-owner-liveness.mjs +0 -117
  677. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +0 -246
  678. package/src/session-runtime/services/agent-tool/terminal-reaper/reap-timers.mjs +0 -142
  679. package/src/session-runtime/turn/share-ops.mjs +0 -77
  680. package/src/tui/session/cross-surface-share.mjs +0 -406
  681. package/src/tui/session/live-share/delta.mjs +0 -153
  682. package/src/tui/session/live-share/mirror.mjs +0 -106
  683. package/src/tui/session/live-share/owner.mjs +0 -254
  684. package/src/tui/session/live-share/retry.mjs +0 -54
  685. package/src/tui/session/live-share/viewer/frame-router.mjs +0 -32
  686. package/src/tui/session/live-share/viewer/pipe-client.mjs +0 -108
  687. package/src/tui/session/live-share/viewer/submit-acks.mjs +0 -65
  688. package/src/tui/session/live-share/viewer/sync-waiters.mjs +0 -53
  689. package/src/tui/session/live-share/viewer.mjs +0 -99
  690. package/src/tui/session/live-share/wire.mjs +0 -44
  691. package/src/tui/session/live-share.mjs +0 -154
  692. 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,14 @@
26
28
  * -d` forgets it).
27
29
  */
28
30
  import { existsSync } from 'node:fs';
29
- import { lstat, mkdir, readdir, readFile, rm, rmdir } from 'node:fs/promises';
30
- import { dirname, join, relative, sep } from 'node:path';
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';
38
+ import { dirname, join, relative, resolve, sep } from 'node:path';
31
39
  import {
32
40
  managedWorktreePath,
33
41
  managedWorktreeRoot,
@@ -37,6 +45,13 @@ import {
37
45
  worktreeBranchName,
38
46
  worktreeError,
39
47
  } from './contract.mjs';
48
+ import {
49
+ assertCarryable,
50
+ carryUncommittedChanges,
51
+ dropRecoveryRef,
52
+ restoreFromRecoveryRef,
53
+ uncommittedChanges,
54
+ } from './carry.mjs';
40
55
  import { freeBytesAt, requiredBytes, trackedCheckoutBytes } from './disk.mjs';
41
56
  import {
42
57
  branchCommit,
@@ -56,24 +71,39 @@ import {
56
71
  applyIncludes,
57
72
  coveredByIncludes,
58
73
  modifiedIncludeCopies,
74
+ untrackedEnvFiles,
59
75
  planIncludes,
60
76
  readIncludeEntries,
61
77
  removeIncludeLinks,
78
+ restoreIncludeLinks,
62
79
  } from './include.mjs';
80
+ import { linkWorktreeDependencies } from './dependencies.mjs';
63
81
  import { withRepoLock as withRepoMutationLock } from './lock.mjs';
64
- import { mergeBranchIntoCheckout } from './merge.mjs';
82
+ import { mergeBranchIntoCheckout, operationInProgress } from './merge.mjs';
65
83
  import { readOwnerLease, removeOwnerLease, sameOwner, writeOwnerLease } from './owner.mjs';
66
84
  import { canonicalPath, checkoutFolder, isWithin, pathKey, projectSubdirOf, samePath } from './paths.mjs';
67
- import { deleteRecord, entryView, getRecord, insertRecord, putRecord, readRegistry } from './registry.mjs';
85
+ import {
86
+ deleteRecord,
87
+ entryView,
88
+ getRecord,
89
+ insertRecord,
90
+ putRecord,
91
+ readRegistry,
92
+ registryPath,
93
+ } from './registry.mjs';
68
94
  import { normalizeWorktreeSettings } from './settings.mjs';
69
95
  import { slugFromHint, uniqueSlug } from './slug.mjs';
70
96
 
71
97
  const WORKTREE_ADD_TIMEOUT_MS = 30 * 60_000;
72
98
  const REMOVE_RETRIES = 6;
73
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;
74
102
  // The session copies the project identity into each managed worktree; it is
75
103
  // Mixdog metadata, not user data, so it alone never makes a worktree dirty.
76
104
  const SESSION_PROJECT_ID_PATH = '.mixdog/project.id';
105
+ // Changed paths WORKTREE_DIRTY_CHOICE lists for the dialog's preview (the count is exact).
106
+ const DIRTY_CHOICE_PREVIEW = 50;
77
107
 
78
108
  export { registerRootReleaser } from './contract.mjs';
79
109
 
@@ -314,6 +344,7 @@ async function dirtyPaths(record) {
314
344
  }
315
345
 
316
346
  async function deleteBranchIfFree(record) {
347
+ if (record.existingBranch === true) return { deleted: false, reason: 'pre-existing' };
317
348
  const cwd = repoCwd(record);
318
349
  if (!(await branchCommit(cwd, record.branch))) return { deleted: false, reason: 'missing' };
319
350
  const holder = (await listWorktrees(cwd)).find((worktree) => worktree.branch === record.branch);
@@ -327,7 +358,12 @@ async function deleteBranchIfFree(record) {
327
358
  * still present (empty when everything is gone).
328
359
  */
329
360
  async function removeCheckout(record) {
361
+ await assertRecoveryStoresClear(record);
330
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);
331
367
  const remaining = [];
332
368
  for (const path of await removeIncludeLinks(record.resources?.includes)) remaining.push({ kind: 'link', path });
333
369
  if (remaining.length) return remaining;
@@ -354,6 +390,128 @@ async function removeCheckout(record) {
354
390
  return remaining;
355
391
  }
356
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
+
357
515
  /**
358
516
  * After the last worktree of a repository is gone, drop its now-empty store
359
517
  * folder (<dataDir>/worktrees-v1/<repoHash>/ and its .owners/). Runs under the
@@ -399,7 +557,11 @@ async function rollbackCreation(record, cause) {
399
557
  if (resources.worktree) {
400
558
  const left = await removeCheckout(record);
401
559
  remaining.push(...left);
402
- if (!left.length) {
560
+ if (
561
+ !left.length &&
562
+ record.existingBranch !== true &&
563
+ !(record.exactBranch === true && resources.worktree !== 'created')
564
+ ) {
403
565
  // The slug was free when this transaction reserved it, so a branch
404
566
  // still at the base commit is the one `worktree add -b` made. A
405
567
  // branch that moved holds someone's commits and is kept.
@@ -437,6 +599,7 @@ async function rollbackCreation(record, cause) {
437
599
  * and retried with the deletion; it never keeps the checkout.
438
600
  */
439
601
  async function finishDeletion(record) {
602
+ await assertRecoveryStoresClear(record);
440
603
  const remaining = [];
441
604
  try {
442
605
  await removeOwnerLease(record);
@@ -448,7 +611,7 @@ async function finishDeletion(record) {
448
611
  for (const result of results) {
449
612
  if (!result.ok) remaining.push({ kind: 'cleanup', name: result.name, error: result.error.message });
450
613
  }
451
- remaining.push(...(await removeCheckout(record)));
614
+ remaining.push(...(await (record.deleting?.safeOnly ? removeCheckoutSafely(record) : removeCheckout(record))));
452
615
  } catch (error) {
453
616
  remaining.push({ kind: 'error', error: String(error?.message || error) });
454
617
  }
@@ -460,7 +623,7 @@ async function finishDeletion(record) {
460
623
  let branch = { deleted: false, reason: 'kept' };
461
624
  let kept = { tip: null, recoveryRef: null };
462
625
  const merged = (await unmergedCommitCount(record)) === 0;
463
- if (record.deleting?.force || merged) {
626
+ if (!record.deleting?.preserveBranch && (record.deleting?.force || merged)) {
464
627
  // Forced past unmerged commits: the tip stays reachable before `branch -D`.
465
628
  if (!merged) kept = await keepRemovedTip(record);
466
629
  else kept.tip = await branchCommit(repoCwd(record), record.branch);
@@ -509,6 +672,100 @@ export async function getWorktreeByPath(path) {
509
672
  return record ? entryView(record) : null;
510
673
  }
511
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
+
512
769
  /** The managed worktree whose checkout is `path` or contains it (a subfolder Project's session root), or null. */
513
770
  export async function findWorktreeContaining(path) {
514
771
  if (typeof path !== 'string' || !path.trim()) return null;
@@ -516,6 +773,50 @@ export async function findWorktreeContaining(path) {
516
773
  return record ? entryView(record) : null;
517
774
  }
518
775
 
776
+ /** File-host seam, not a user capability. Persist the host-owned scope before
777
+ * preparing a byte journal, and hold the same repository lock as every removal
778
+ * through the operation. The descriptor survives daemon exit/startup reconcile.
779
+ * @template T
780
+ * @param {string} root
781
+ * @param {string} directory
782
+ * @param {'write'|'restore'|'inspect'|'discard'} action
783
+ * @param {() => Promise<T>} run
784
+ * @returns {Promise<T>}
785
+ */
786
+ export async function withWorktreeRecoveryStore(root, directory, action, run) {
787
+ const entry = await findWorktreeContaining(root);
788
+ if (!entry) {
789
+ // A managed checkout can disappear between host authorization and this
790
+ // lookup. Its reserved namespace must never become an ordinary project.
791
+ // Retain a lexical check as well, so a replacement link cannot escape it.
792
+ const store = dirname(registryPath());
793
+ const lexicalKey = (path) => (process.platform === 'win32' ? resolve(path).toLowerCase() : resolve(path));
794
+ const requested = lexicalKey(root);
795
+ if (
796
+ isWithin(store, root) ||
797
+ [store, canonicalPath(store)].some(
798
+ (path) => requested === lexicalKey(path) || requested.startsWith(lexicalKey(path) + sep)
799
+ )
800
+ )
801
+ throw worktreeError('WORKTREE_MISSING', undefined, { path: root });
802
+ return run();
803
+ }
804
+ return withRepoLock(entry.repoCommonDir, async () => {
805
+ const record = await getRecord(entry.id);
806
+ if (!record || ((action === 'write' || action === 'restore') && record.state !== 'ready'))
807
+ throw worktreeError('WORKTREE_MISSING', undefined, { entryId: entry.id });
808
+ const scope = validateRecoveryScope(record, { root, directory });
809
+ let scopes = recoveryScopes(record);
810
+ if (!scopes.some((item) => item.root === root && item.directory === directory)) {
811
+ if (scopes.length >= 64) scopes = await retainedRecoveryScopes(record);
812
+ if (scopes.length >= 64) throw worktreeError('WORKTREE_FILE_RECOVERY');
813
+ record.editorRecoveryStores = [...scopes, scope];
814
+ await putRecord(record);
815
+ }
816
+ return run();
817
+ });
818
+ }
819
+
519
820
  /** Mark an entry disposable: startup reconcile may remove it once clean and merged. */
520
821
  export async function markWorktreeDisposable(entryId, disposable = true) {
521
822
  const record = await getRecord(entryId);
@@ -536,7 +837,7 @@ export async function markWorktreeDisposable(entryId, disposable = true) {
536
837
  * WORKTREE_REVISION_CONFLICT for another session's transaction, a ready
537
838
  * worktree of another repository, or an attempt that is still being cleaned up.
538
839
  */
539
- async function settleSessionAttempts({ txId, owner, repoCommonDir }) {
840
+ async function settleSessionAttempts({ txId, owner, repoCommonDir, multiple = false }) {
540
841
  const doc = await readRegistry();
541
842
  const sameRepo = (record) => samePath(record.repoCommonDir, repoCommonDir);
542
843
 
@@ -550,6 +851,7 @@ async function settleSessionAttempts({ txId, owner, repoCommonDir }) {
550
851
  }
551
852
 
552
853
  for (const record of Object.values(doc.entries).filter((item) => item.ownerSessionId === owner.sessionId)) {
854
+ if (multiple && record.txId !== txId) continue;
553
855
  if (record.state === 'ready') {
554
856
  if (!sameRepo(record)) {
555
857
  throw worktreeError(
@@ -600,8 +902,20 @@ async function diskFullFailure(error, { needed, root }) {
600
902
  }
601
903
 
602
904
  /**
905
+ * `carryChanges` decides the project folder's uncommitted changes (tracked
906
+ * and untracked, ignored excluded), judged under the repository lock:
907
+ * omitted/null leaves them unexamined; `ask` refuses with
908
+ * WORKTREE_DIRTY_CHOICE (`details: { count, paths }`, paths capped) while
909
+ * there are any; `leave` keeps them in the project folder; `carry` moves
910
+ * them into the new worktree (carry.mjs: whole checkout, ignored files stay)
911
+ * — refused (WORKTREE_DIRTY) during a merge, rebase or cherry-pick, with
912
+ * unmerged entries, dirty submodules, filtered (LFS) paths or oversized
913
+ * untracked files. The result's `changes` (`{ mode: 'carried'|'left',
914
+ * count, recoveryRef? }`) reports what happened to them; a carried move's
915
+ * `recoveryRef` stays until dropCarryRecoveryRef / restoreCarriedChanges.
603
916
  * @param {{ projectPath: string, sessionId: string, runtimeToken: string, baseBranch?: string|null,
604
- * nameHint?: string, confirmLarge?: boolean, txId: string, signal?: AbortSignal }} input
917
+ * nameHint?: string, confirmLarge?: boolean, txId: string, signal?: AbortSignal,
918
+ * carryChanges?: 'ask'|'leave'|'carry'|null }} input
605
919
  */
606
920
  export async function createManagedWorktree({
607
921
  projectPath,
@@ -612,17 +926,40 @@ export async function createManagedWorktree({
612
926
  confirmLarge = false,
613
927
  txId,
614
928
  signal,
929
+ carryChanges = null,
930
+ branch: requestedBranch = null,
931
+ existingBranch = false,
615
932
  } = {}) {
616
933
  requireText(projectPath, 'projectPath');
617
934
  requireText(txId, 'txId');
935
+ if (![null, 'ask', 'leave', 'carry'].includes(carryChanges)) {
936
+ throw new TypeError('carryChanges must be ask, leave or carry.');
937
+ }
618
938
  const owner = normalizeWorktreeOwner({ sessionId, runtimeToken, pid: process.pid });
619
939
  throwIfAborted(signal);
620
940
  const repo = await inspectRepo(projectPath, { signal });
621
941
  return withRepoLock(repo.repoCommonDir, async () => {
622
- 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
+ });
623
948
  if (existing) return existing;
624
949
  throwIfAborted(signal);
625
950
 
951
+ const projectSubdir = projectSubdirOf(repo.topLevel, repo.projectPath);
952
+ // A carry is judged by the same status read that counts the changes.
953
+ let pendingChanges = [];
954
+ if (carryChanges === 'carry') pendingChanges = await assertCarryable(repo.topLevel, projectSubdir);
955
+ else if (carryChanges) pendingChanges = await uncommittedChanges(repo.topLevel, projectSubdir);
956
+ if (pendingChanges.length && carryChanges === 'ask') {
957
+ throw worktreeError('WORKTREE_DIRTY_CHOICE', undefined, {
958
+ count: pendingChanges.length,
959
+ paths: pendingChanges.slice(0, DIRTY_CHOICE_PREVIEW),
960
+ });
961
+ }
962
+
626
963
  const settings = await currentWorktreeSettings();
627
964
  let baseCommit = repo.headCommit;
628
965
  let mergeTarget = repo.currentBranch;
@@ -634,7 +971,11 @@ export async function createManagedWorktree({
634
971
  mergeTarget = baseBranch;
635
972
  }
636
973
 
637
- 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
+ });
638
979
  const tracked = await trackedCheckoutBytes(repo.topLevel, baseCommit, { signal });
639
980
  const contentBytes = tracked.bytes + includePlan.copyBytes;
640
981
  const root = managedWorktreeRoot(repo.repoCommonDir);
@@ -670,11 +1011,18 @@ export async function createManagedWorktree({
670
1011
  return reserved;
671
1012
  };
672
1013
  const addCheckout = (target) =>
673
- runGit(repo.projectPath, ['worktree', 'add', '-b', target.branch, target.path, baseCommit], {
674
- signal,
675
- timeoutMs: WORKTREE_ADD_TIMEOUT_MS,
676
- });
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
+ );
677
1024
  let record = await reserve();
1025
+ let carried = null;
678
1026
 
679
1027
  try {
680
1028
  await mkdir(root, { recursive: true });
@@ -682,6 +1030,7 @@ export async function createManagedWorktree({
682
1030
  try {
683
1031
  await addCheckout(record);
684
1032
  } catch (error) {
1033
+ if (requestedBranch) throw error;
685
1034
  // Another process took the branch name (or folder) after the
686
1035
  // reservation: git refused before making anything, so reserve the
687
1036
  // next free name once and try again.
@@ -708,16 +1057,65 @@ export async function createManagedWorktree({
708
1057
  await setResource(record, 'owner', 'pending');
709
1058
  await writeOwnerLease(record, owner);
710
1059
  await setResource(record, 'owner', 'created');
1060
+ // Carried before the include entries are copied, so an include never
1061
+ // stands in the way of a carried file; the status is read again here,
1062
+ // right before acting.
1063
+ let changes = pendingChanges.length ? { mode: 'left', count: pendingChanges.length } : null;
1064
+ if (carryChanges === 'carry') {
1065
+ carried = await carryUncommittedChanges({
1066
+ topLevel: repo.topLevel,
1067
+ projectSubdir,
1068
+ worktreePath: record.path,
1069
+ baseCommit,
1070
+ label: record.id,
1071
+ });
1072
+ // The recovery ref stays until the caller has committed the move
1073
+ // (dropCarryRecoveryRef) or put the changes back (restoreCarriedChanges).
1074
+ changes = carried.count ? { mode: 'carried', count: carried.count, recoveryRef: carried.recoveryRef } : null;
1075
+ }
711
1076
  const applied = await applyIncludes(includePlan, record.path, (ledger) => recordInclude(record, ledger));
712
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);
713
1087
  record.warnings = [...record.warnings, ...applied.warnings];
714
1088
  record.state = 'ready';
715
1089
  await putRecord(record);
716
- return entryView(record);
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 } : {}) };
717
1101
  } catch (error) {
1102
+ // Carried already: the changes go back to the project folder before the
1103
+ // worktree that holds them is rolled back (the recovery ref keeps them
1104
+ // either way).
1105
+ let carryRestore = null;
1106
+ if (carried?.recoveryRef) {
1107
+ carryRestore = await restoreFromRecoveryRef(repo.topLevel, projectSubdir, carried.recoveryRef)
1108
+ .then(() => ({ restored: true, recoveryRef: carried.recoveryRef }))
1109
+ .catch((restoreError) => ({
1110
+ restored: false,
1111
+ recoveryRef: carried.recoveryRef,
1112
+ error: String(restoreError?.message || restoreError),
1113
+ }));
1114
+ }
718
1115
  const rollback = await rollbackCreation(record, error);
719
1116
  const failure = isDiskFullError(error) ? await diskFullFailure(error, { needed, root }) : error;
720
1117
  if (!rollback.complete) failure.rollback = rollback;
1118
+ if (carryRestore) failure.carryRestore = carryRestore;
721
1119
  throw failure;
722
1120
  }
723
1121
 
@@ -730,7 +1128,7 @@ export async function createManagedWorktree({
730
1128
  branches.has(`${settings.branchPrefix}${candidate}`.toLowerCase()) ||
731
1129
  existsSync(join(root, candidate))
732
1130
  );
733
- const branch = worktreeBranchName(slug, settings.branchPrefix);
1131
+ const branch = requestedBranch ?? worktreeBranchName(slug, settings.branchPrefix);
734
1132
  if (!(await validBranchName(repo.projectPath, branch))) {
735
1133
  throw worktreeError('WORKTREE_UNSUPPORTED_REPO', `Branch name ${branch} is not valid.`, { branch });
736
1134
  }
@@ -746,6 +1144,8 @@ export async function createManagedWorktree({
746
1144
  mergeTarget,
747
1145
  createdAt: Date.now(),
748
1146
  ownerSessionId: owner.sessionId,
1147
+ existingBranch,
1148
+ exactBranch: requestedBranch !== null,
749
1149
  state: 'creating',
750
1150
  disposable: false,
751
1151
  warnings: [...(tracked.submodules ? ['submodules'] : []), ...includePlan.warnings],
@@ -758,6 +1158,30 @@ export async function createManagedWorktree({
758
1158
  });
759
1159
  }
760
1160
 
1161
+ /**
1162
+ * After a carry whose session move failed: put the changes kept at
1163
+ * `recoveryRef` back into the project folder (refused, ref kept, when the
1164
+ * folder changed meanwhile). The ref stays; drop it with dropCarryRecoveryRef.
1165
+ * @param {{ projectPath: string, recoveryRef: string }} input
1166
+ */
1167
+ export async function restoreCarriedChanges({ projectPath, recoveryRef } = {}) {
1168
+ requireText(projectPath, 'projectPath');
1169
+ const repo = await inspectRepo(projectPath);
1170
+ return withRepoLock(repo.repoCommonDir, () =>
1171
+ restoreFromRecoveryRef(repo.topLevel, projectSubdirOf(repo.topLevel, repo.projectPath), recoveryRef)
1172
+ );
1173
+ }
1174
+
1175
+ /**
1176
+ * Forget a carry's recovery ref once the session move it protected is committed.
1177
+ * @param {{ projectPath: string, recoveryRef: string }} input
1178
+ */
1179
+ export async function dropCarryRecoveryRef({ projectPath, recoveryRef } = {}) {
1180
+ requireText(projectPath, 'projectPath');
1181
+ const repo = await inspectRepo(projectPath);
1182
+ return withRepoLock(repo.repoCommonDir, () => dropRecoveryRef(repo.topLevel, recoveryRef));
1183
+ }
1184
+
761
1185
  /**
762
1186
  * Claim the live lease of a retained worktree for one runtime of its session.
763
1187
  * @param {string} entryId
@@ -771,10 +1195,21 @@ export async function claimWorktreeOwner(entryId, owner) {
771
1195
  const current = await getRecord(entryId);
772
1196
  if (current?.state !== 'ready') throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
773
1197
  if (current.ownerSessionId && current.ownerSessionId !== claimant.sessionId) {
774
- throw worktreeError('WORKTREE_BRANCH_OCCUPIED', 'The worktree is retained by another session.', {
775
- entryId,
776
- sessionId: current.ownerSessionId,
777
- });
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
+ };
778
1213
  }
779
1214
  if (!current.ownerSessionId) {
780
1215
  const other = findRecord(
@@ -799,13 +1234,23 @@ export async function claimWorktreeOwner(entryId, owner) {
799
1234
  * Release the live lease when (and only when) it is held by exactly `owner`.
800
1235
  * Durable ownership (the session retaining the worktree) is unchanged.
801
1236
  */
802
- export async function releaseWorktreeOwner(entryId, owner) {
1237
+ export async function releaseWorktreeOwner(entryId, owner, { forgetReference = false } = {}) {
803
1238
  const claimant = normalizeWorktreeOwner(owner);
804
1239
  const record = await getRecord(entryId);
805
1240
  if (!record) return { released: false };
806
1241
  return withRepoLock(record.repoCommonDir, async () => {
807
1242
  const current = await getRecord(entryId);
808
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
+ }
809
1254
  const lease = await readOwnerLease(current);
810
1255
  if (!sameOwner(lease, claimant)) return { released: false };
811
1256
  await removeOwnerLease(current);
@@ -1051,15 +1496,39 @@ async function assertRemovable(record) {
1051
1496
  }
1052
1497
  }
1053
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
+
1054
1520
  /**
1055
1521
  * The refusal removeManagedWorktree would raise, checked before anything
1056
1522
  * moves (a session leaves its worktree only for a removal that can proceed).
1057
- * Unknown entries are WORKTREE_MISSING; `force` and non-ready entries pass.
1523
+ * Unknown entries are WORKTREE_MISSING. Recovery records block even `force`
1524
+ * and non-ready entries; ordinary dirty/unmerged checks apply only to ready ones.
1058
1525
  * @param {{ entryId: string, force?: boolean }} input
1059
1526
  */
1060
- export async function assertWorktreeRemovable({ entryId, force = false } = {}) {
1527
+ export async function assertWorktreeRemovable({ entryId, force = false, sessionId } = {}) {
1061
1528
  const record = await getRecord(entryId);
1062
1529
  if (!record) throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
1530
+ assertNotShared(record, sessionId);
1531
+ await assertRecoveryStoresClear(record);
1063
1532
  if (record.state !== 'ready') return;
1064
1533
  await assertManagedCheckoutPath(record);
1065
1534
  if (!force) await assertRemovable(record);
@@ -1067,25 +1536,34 @@ export async function assertWorktreeRemovable({ entryId, force = false } = {}) {
1067
1536
 
1068
1537
  /**
1069
1538
  * Remove a managed worktree. Refuses dirty (incl. untracked and modified
1070
- * include copies) or unmerged worktrees unless `force`.
1539
+ * include copies) or unmerged worktrees unless `force`. Retained file recovery
1540
+ * data blocks removal regardless of force.
1071
1541
  * @param {{ entryId: string, force?: boolean }} input
1072
1542
  * @returns {Promise<{ removed: boolean, retryable?: boolean, remaining?: object[], branchDeleted?: boolean }>}
1073
1543
  */
1074
- 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
+ } = {}) {
1075
1551
  const record = await getRecord(entryId);
1076
1552
  if (!record) throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
1077
1553
  return withRepoLock(record.repoCommonDir, async () => {
1078
1554
  const current = await getRecord(entryId);
1079
1555
  if (!current) throw worktreeError('WORKTREE_MISSING', undefined, { entryId });
1556
+ assertNotShared(current, sessionId);
1080
1557
  if (current.state === 'creating' || current.state === 'recovery') {
1081
1558
  const rollback = await rollbackCreation(current);
1082
1559
  return rollback.complete ? { removed: true } : { removed: false, retryable: true, remaining: rollback.remaining };
1083
1560
  }
1084
1561
  if (current.state === 'ready') {
1562
+ await assertRecoveryStoresClear(current);
1085
1563
  await assertManagedCheckoutPath(current);
1086
1564
  if (!force) await assertRemovable(current);
1087
1565
  current.state = 'deleting';
1088
- current.deleting = { force: force === true, startedAt: Date.now(), remaining: [] };
1566
+ current.deleting = { force: force === true, preserveBranch, safeOnly, startedAt: Date.now(), remaining: [] };
1089
1567
  await putRecord(current);
1090
1568
  }
1091
1569
  return finishDeletion(current);
@@ -1101,7 +1579,7 @@ function leaseProcessAlive(lease) {
1101
1579
  * Every managed worktree with who keeps it: `retained` (its session is in
1102
1580
  * `retainedSessionIds`, i.e. still in the session store), `ownerLive` (a
1103
1581
  * live session or a running lease holder), and `removable` (what a prune
1104
- * would remove: neither, or disposable and not live).
1582
+ * would remove: neither, or disposable and not live, with no retained recovery data).
1105
1583
  * @param {{ liveSessionIds?: string[], retainedSessionIds?: string[] }} input
1106
1584
  */
1107
1585
  export async function listManagedWorktrees({ liveSessionIds = [], retainedSessionIds = [] } = {}) {
@@ -1112,12 +1590,23 @@ export async function listManagedWorktrees({ liveSessionIds = [], retainedSessio
1112
1590
  const ownerLive =
1113
1591
  (!!record.ownerSessionId && live.has(record.ownerSessionId)) || leaseProcessAlive(await readOwnerLease(record));
1114
1592
  const isRetained = !!record.ownerSessionId && retained.has(record.ownerSessionId);
1593
+ let removable =
1594
+ !record.sharedSessionIds?.length &&
1595
+ (record.state !== 'ready' || (!ownerLive && (record.disposable === true || !isRetained)));
1596
+ if (removable) {
1597
+ try {
1598
+ await assertRecoveryStoresClear(record);
1599
+ } catch (error) {
1600
+ if (error?.code !== 'WORKTREE_FILE_RECOVERY') throw error;
1601
+ removable = false;
1602
+ }
1603
+ }
1115
1604
  rows.push({
1116
1605
  ...entryView(record),
1117
1606
  projectPath: record.projectPath,
1118
1607
  retained: isRetained,
1119
1608
  ownerLive,
1120
- removable: record.state !== 'ready' || (!ownerLive && (record.disposable === true || !isRetained)),
1609
+ removable,
1121
1610
  });
1122
1611
  }
1123
1612
  return rows;
@@ -1164,6 +1653,10 @@ export async function reconcileManagedWorktrees({
1164
1653
  for (const record of records) {
1165
1654
  const isRetained = !!record.ownerSessionId && retained.has(record.ownerSessionId);
1166
1655
  try {
1656
+ if (record.sharedSessionIds?.length) {
1657
+ report.kept.push({ id: record.id, reason: 'shared' });
1658
+ continue;
1659
+ }
1167
1660
  if (record.state === 'creating' || record.state === 'recovery') {
1168
1661
  if (isRetained) {
1169
1662
  report.kept.push({ id: record.id, reason: 'retained' });
@@ -1194,6 +1687,7 @@ export async function reconcileManagedWorktrees({
1194
1687
  report.kept.push({ id: record.id, reason: dirty.length ? 'dirty' : 'unmerged' });
1195
1688
  continue;
1196
1689
  }
1690
+ await assertRecoveryStoresClear(record);
1197
1691
  record.state = 'deleting';
1198
1692
  record.deleting = { force: false, startedAt: Date.now(), remaining: [] };
1199
1693
  await putRecord(record);