mixdog 1.2.3 → 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 (465) hide show
  1. package/package.json +1 -1
  2. package/scripts/gate-local.mjs +6 -4
  3. package/scripts/lib/gate-scheduler.mjs +29 -11
  4. package/scripts/probe-provider-catalog.mjs +31 -0
  5. package/scripts/provider-pingpong-live.mjs +128 -38
  6. package/scripts/session-sweep.mjs +5 -25
  7. package/src/agents/maintainer/AGENT.md +1 -1
  8. package/src/agents/maintainer/agent.json +1 -1
  9. package/src/agents/reviewer/AGENT.md +2 -4
  10. package/src/agents/security/AGENT.md +1 -2
  11. package/src/agents/writer/AGENT.md +2 -3
  12. package/src/defaults/shipped-history.json +6 -1
  13. package/src/defaults/skills/image/SKILL.md +21 -23
  14. package/src/lib/keychain-cjs.cjs +13 -11
  15. package/src/rules/agent/AGENT.md +7 -5
  16. package/src/rules/lead/LEAD.md +7 -9
  17. package/src/rules/shared/45-destructive.md +4 -0
  18. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  19. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +28 -7
  20. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -11
  21. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +101 -23
  22. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +2 -0
  23. package/src/runtime/agent/orchestrator/providers/anthropic-api-key-fetch.mjs +36 -0
  24. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +1 -1
  25. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -3
  26. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  27. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +26 -44
  28. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +2 -5
  29. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +4 -0
  30. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +41 -12
  31. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +13 -1
  32. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +81 -10
  33. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +109 -5
  34. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +2 -1
  35. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +107 -37
  36. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +4 -1
  37. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +3 -1
  38. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/cursor-native-context.mjs +48 -0
  40. package/src/runtime/agent/orchestrator/providers/cursor-wire-exec.mjs +144 -34
  41. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +28 -4
  42. package/src/runtime/agent/orchestrator/providers/cursor-wire-request.mjs +8 -4
  43. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +41 -11
  44. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +67 -5
  45. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +14 -4
  46. package/src/runtime/agent/orchestrator/providers/cursor.mjs +14 -1
  47. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +9 -6
  48. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -2
  49. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +26 -4
  50. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +12 -6
  51. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +14 -6
  52. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +4 -4
  53. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +1 -0
  54. package/src/runtime/agent/orchestrator/providers/gemini-rest-stream/watchdogs.mjs +16 -5
  55. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +6 -1
  56. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +22 -10
  57. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -2
  58. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +33 -7
  59. package/src/runtime/agent/orchestrator/providers/gemini.mjs +11 -2
  60. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  61. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +9 -2
  62. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +9 -5
  63. package/src/runtime/agent/orchestrator/providers/lib/request-compression.mjs +86 -0
  64. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +4 -2
  65. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -5
  66. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +7 -1
  67. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +60 -16
  68. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +11 -13
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +30 -19
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +5 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +42 -5
  73. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +13 -9
  74. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +1 -3
  75. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +18 -7
  76. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +36 -3
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +51 -3
  78. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +11 -1
  79. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +64 -52
  80. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +3 -1
  81. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +2 -1
  82. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +7 -1
  83. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +1 -4
  84. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +0 -1
  85. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +7 -6
  86. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +13 -2
  87. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +4 -1
  88. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +1 -2
  89. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +5 -4
  90. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -1
  91. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +2 -1
  92. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +11 -4
  93. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +14 -4
  94. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +15 -3
  95. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +6 -2
  96. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +8 -6
  97. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +4 -1
  98. package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +30 -2
  99. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +3 -2
  100. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -3
  101. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +3 -1
  102. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge-contract.mjs +6 -0
  103. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +10 -10
  104. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +3 -1
  105. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +46 -8
  106. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -8
  107. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -2
  108. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +24 -0
  109. package/src/runtime/agent/orchestrator/session/cache/tool-result-reuse.mjs +10 -2
  110. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -3
  111. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -11
  112. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +6 -22
  113. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +2 -0
  114. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +16 -2
  115. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +28 -0
  116. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  117. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +0 -6
  118. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +0 -6
  119. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +1 -1
  120. package/src/runtime/agent/orchestrator/session/historical-session-closure.mjs +10 -0
  121. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +2 -5
  122. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +2 -4
  123. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +3 -3
  124. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +0 -27
  125. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +1 -1
  126. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -10
  127. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -9
  128. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +4 -1
  129. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +4 -1
  130. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +1 -4
  131. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +76 -0
  132. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +5 -1
  133. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +8 -9
  134. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +6 -0
  135. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -2
  136. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +1 -4
  137. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -4
  138. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +33 -3
  139. package/src/runtime/agent/orchestrator/session/manager/ask-support.mjs +0 -4
  140. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +21 -1
  141. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +38 -13
  142. package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +33 -0
  143. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +5 -0
  144. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +19 -1
  145. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +57 -13
  146. package/src/runtime/agent/orchestrator/session/manager/failed-turn-rewind.mjs +16 -0
  147. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +6 -120
  148. package/src/runtime/agent/orchestrator/session/manager/pending-persist-queue.mjs +1 -1
  149. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +29 -8
  150. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +42 -49
  151. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +50 -0
  152. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +32 -14
  153. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +1 -1
  154. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +17 -33
  155. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +29 -13
  156. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -3
  157. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +45 -0
  158. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -9
  159. package/src/runtime/agent/orchestrator/session/send-instrumentation.mjs +2 -1
  160. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -2
  161. package/src/runtime/agent/orchestrator/session/side-question.mjs +12 -7
  162. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +5 -12
  163. package/src/runtime/agent/orchestrator/session/store/lifecycle-read.mjs +1 -1
  164. package/src/runtime/agent/orchestrator/session/store/listing.mjs +2 -6
  165. package/src/runtime/agent/orchestrator/session/store/live-cache-eviction.mjs +1 -1
  166. package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +46 -37
  167. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +1 -1
  168. package/src/runtime/agent/orchestrator/session/store/load-session.mjs +1 -1
  169. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +5 -11
  170. package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +59 -0
  171. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +1 -1
  172. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +1 -1
  173. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +27 -32
  174. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +0 -8
  175. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +6 -18
  176. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +33 -106
  177. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +1 -1
  178. package/src/runtime/agent/orchestrator/session/store-agent-worker-pool.mjs +5 -8
  179. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +136 -196
  180. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +50 -116
  181. package/src/runtime/agent/orchestrator/session/store-summary-locations.mjs +3 -29
  182. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +1 -1
  183. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +7 -8
  184. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +9 -20
  185. package/src/runtime/agent/orchestrator/session/store.mjs +3 -16
  186. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +11 -12
  187. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +37 -13
  188. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +15 -4
  189. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +4 -2
  190. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +72 -15
  191. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +45 -0
  192. package/src/runtime/agent/orchestrator/stall-policy.mjs +50 -0
  193. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +5 -1
  194. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +6 -2
  195. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +204 -10
  196. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +160 -39
  197. package/src/runtime/agent/orchestrator/tools/builtin/git-partial-stage.mjs +7 -5
  198. package/src/runtime/agent/orchestrator/tools/builtin/grep-formatting.mjs +14 -5
  199. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +4 -3
  200. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-output.mjs +2 -1
  201. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-path-fanout.mjs +48 -44
  202. package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +10 -3
  203. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +200 -17
  204. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +5 -4
  205. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +6 -1
  206. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +20 -0
  207. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +101 -2
  208. package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +1 -1
  209. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +67 -0
  210. package/src/runtime/agent/orchestrator/tools/lib/process-table-fixture.mjs +50 -0
  211. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +303 -63
  212. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -0
  213. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +108 -18
  214. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +77 -11
  215. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +81 -5
  216. package/src/runtime/attachments/store.d.mts +9 -0
  217. package/src/runtime/attachments/store.mjs +80 -9
  218. package/src/runtime/browser-bridge/browser-action-contract.d.mts +0 -1
  219. package/src/runtime/browser-bridge/browser-action-contract.mjs +0 -2
  220. package/src/runtime/computer-bridge/action-schema.mjs +178 -178
  221. package/src/runtime/computer-bridge/linux-readiness.d.mts +0 -2
  222. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -16
  223. package/src/runtime/effort-judge/judge-client.mjs +21 -4
  224. package/src/runtime/effort-judge/judge-worker.mjs +4 -1
  225. package/src/runtime/github/tool.mjs +118 -17
  226. package/src/runtime/media/adapters/codex-image.mjs +140 -21
  227. package/src/runtime/media/catalog.mjs +4 -4
  228. package/src/runtime/media/chatgpt-usage.mjs +55 -0
  229. package/src/runtime/media/defaults.mjs +37 -0
  230. package/src/runtime/media/jobs.mjs +12 -2
  231. package/src/runtime/media/lanes.mjs +12 -0
  232. package/src/runtime/media/tool-defs.mjs +15 -4
  233. package/src/runtime/media/tool.mjs +41 -12
  234. package/src/runtime/media/upstream-error.mjs +72 -6
  235. package/src/runtime/memory/lib/ingest/transcript-rows.mjs +1 -0
  236. package/src/runtime/memory/lib/pg/process.mjs +5 -7
  237. package/src/runtime/memory/lib/pg/supervisor.mjs +4 -1
  238. package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
  239. package/src/runtime/office/authoring/html-browser.mjs +18 -8
  240. package/src/runtime/office/authoring/pdf-html-runner.mjs +1 -1
  241. package/src/runtime/office/authoring/pptx-html-measure.mjs +1 -1
  242. package/src/runtime/office/authoring/xlsx-html-runner.mjs +1 -1
  243. package/src/runtime/office/core/office-write-ledger.mjs +1 -5
  244. package/src/runtime/office/index.mjs +2 -3
  245. package/src/runtime/office/pdf/pdf-analysis.mjs +0 -68
  246. package/src/runtime/session-search/session-search-ingest.mjs +1 -1
  247. package/src/runtime/shared/agent-reap-state.mjs +13 -0
  248. package/src/runtime/shared/atomic-file.d.mts +1 -1
  249. package/src/runtime/shared/background-tasks.mjs +6 -1
  250. package/src/runtime/shared/bounded-line-diff.mjs +11 -2
  251. package/src/runtime/shared/browser-launch.mjs +15 -0
  252. package/src/runtime/shared/err-text.mjs +42 -4
  253. package/src/runtime/shared/file-lock.mjs +6 -5
  254. package/src/runtime/shared/llm/anthropic-betas.mjs +1 -2
  255. package/src/runtime/shared/llm/provider-model-identities.mjs +1 -0
  256. package/src/runtime/shared/model-route-chain.d.mts +8 -0
  257. package/src/runtime/shared/model-route-chain.mjs +48 -0
  258. package/src/runtime/shared/model-route-fallback.mjs +22 -0
  259. package/src/runtime/shared/pid-liveness.mjs +12 -0
  260. package/src/runtime/shared/provider-api-key.mjs +6 -0
  261. package/src/runtime/shared/provider-timeout-phase.mjs +17 -0
  262. package/src/runtime/shared/quiet-progress.d.mts +35 -0
  263. package/src/runtime/shared/quiet-progress.mjs +101 -0
  264. package/src/runtime/shared/quota-resume-store.mjs +32 -1
  265. package/src/runtime/shared/read-row-numbers.mjs +44 -0
  266. package/src/runtime/shared/remote-intent.mjs +2 -42
  267. package/src/runtime/shared/slow-span.mjs +8 -1
  268. package/src/runtime/shared/statusline/statusline-segments.mjs +24 -2
  269. package/src/runtime/shared/tool-card-model/card-header.mjs +2 -1
  270. package/src/runtime/shared/tool-output-budget.mjs +2 -0
  271. package/src/runtime/shared/tool-result-summary.mjs +16 -2
  272. package/src/runtime/shared/tool-status.mjs +3 -2
  273. package/src/runtime/shared/tool-surface.mjs +2 -3
  274. package/src/runtime/shared/tool-work-units.mjs +22 -15
  275. package/src/runtime/shared/transcript-metadata.mjs +49 -1
  276. package/src/runtime/shared/transcript-turn-keys.mjs +33 -0
  277. package/src/runtime/shared/transcript-writer/transcript-file.mjs +10 -1
  278. package/src/runtime/shared/transcript-writer.mjs +14 -4
  279. package/src/runtime/shared/turn-changes.mjs +2 -1
  280. package/src/runtime/shared/turn-snapshot.mjs +11 -3
  281. package/src/runtime/shared/worktrees/activity-gate.mjs +175 -0
  282. package/src/runtime/shared/worktrees/branch-management.mjs +179 -0
  283. package/src/runtime/shared/worktrees/carry.mjs +6 -4
  284. package/src/runtime/shared/worktrees/contract.mjs +27 -1
  285. package/src/runtime/shared/worktrees/dependencies.mjs +456 -0
  286. package/src/runtime/shared/worktrees/file-recovery.mjs +29 -13
  287. package/src/runtime/shared/worktrees/git-head.mjs +22 -13
  288. package/src/runtime/shared/worktrees/include.mjs +125 -7
  289. package/src/runtime/shared/worktrees/lock.mjs +1 -1
  290. package/src/runtime/shared/worktrees/maintenance.mjs +20 -2
  291. package/src/runtime/shared/worktrees/manager.mjs +378 -32
  292. package/src/runtime/shared/worktrees/owner.mjs +2 -1
  293. package/src/runtime/shared/worktrees/preparation-report.mjs +79 -0
  294. package/src/runtime/shared/worktrees/setup-commands.mjs +232 -0
  295. package/src/runtime/shared/worktrees/workspace-visibility.mjs +17 -0
  296. package/src/runtime/terminal-bridge/client.mjs +14 -10
  297. package/src/session-runtime/boot/apis.mjs +7 -1
  298. package/src/session-runtime/boot/config.mjs +2 -0
  299. package/src/session-runtime/boot/facade.mjs +3 -0
  300. package/src/session-runtime/boot/session.mjs +3 -2
  301. package/src/session-runtime/boot/tools.mjs +3 -0
  302. package/src/session-runtime/conversation-import/service.mjs +1 -1
  303. package/src/session-runtime/goal-facade-api.mjs +2 -2
  304. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +44 -18
  305. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +4 -1
  306. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +22 -8
  307. package/src/session-runtime/internal-tool-executor.mjs +2 -0
  308. package/src/session-runtime/lifecycle/cold-heir.mjs +0 -1
  309. package/src/session-runtime/lifecycle/session-catalog.mjs +14 -16
  310. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +18 -53
  311. package/src/session-runtime/lifecycle/session-switch.mjs +1 -1
  312. package/src/session-runtime/lifecycle/shared.mjs +5 -6
  313. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -5
  314. package/src/session-runtime/lifecycle/teardown.mjs +7 -16
  315. package/src/session-runtime/media-api.mjs +8 -0
  316. package/src/session-runtime/model-route/route-ownership.mjs +60 -0
  317. package/src/session-runtime/model-route/route-persist.mjs +3 -2
  318. package/src/session-runtime/model-route/set-route.mjs +114 -34
  319. package/src/session-runtime/model-route/web-search-route.mjs +7 -2
  320. package/src/session-runtime/model-route-api.mjs +4 -2
  321. package/src/session-runtime/native-web-search/routes.mjs +37 -24
  322. package/src/session-runtime/native-web-search.mjs +9 -8
  323. package/src/session-runtime/provider-auth/setup.mjs +4 -0
  324. package/src/session-runtime/provider-models/catalog-load.mjs +9 -0
  325. package/src/session-runtime/provider-models/web-search-catalog.mjs +23 -3
  326. package/src/session-runtime/provider-models.mjs +1 -0
  327. package/src/session-runtime/provider-usage.mjs +4 -0
  328. package/src/session-runtime/route-resolution.mjs +3 -1
  329. package/src/session-runtime/runtime-core.mjs +4 -0
  330. package/src/session-runtime/runtime-facade.mjs +6 -0
  331. package/src/session-runtime/services/agent-task-status.mjs +2 -0
  332. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +0 -3
  333. package/src/session-runtime/services/agent-tool/close-flow.mjs +6 -7
  334. package/src/session-runtime/services/agent-tool/execute-send.mjs +2 -2
  335. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +36 -3
  336. package/src/session-runtime/services/agent-tool/execute.mjs +9 -1
  337. package/src/session-runtime/services/agent-tool/helpers.mjs +30 -7
  338. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +7 -2
  339. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +6 -3
  340. package/src/session-runtime/services/agent-tool/lead-worker-index/lead-rows.mjs +3 -4
  341. package/src/session-runtime/services/agent-tool/lead-worker-index/reap-timers.mjs +9 -3
  342. package/src/session-runtime/services/agent-tool/lead-worker-index/row-settlement.mjs +7 -18
  343. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +9 -6
  344. package/src/session-runtime/services/agent-tool/model-select.mjs +82 -0
  345. package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -6
  346. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -1
  347. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -5
  348. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -4
  349. package/src/session-runtime/services/agent-tool/spawn-flow.mjs +2 -1
  350. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  351. package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +1 -3
  352. package/src/session-runtime/services/agent-tool/tag-registry/tag-refresh.mjs +1 -3
  353. package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -9
  354. package/src/session-runtime/services/agent-tool/terminal-reaper/scan-admission.mjs +8 -41
  355. package/src/session-runtime/services/agent-tool/terminal-reaper/stale-rows.mjs +3 -3
  356. package/src/session-runtime/services/agent-tool/terminal-reaper.mjs +6 -23
  357. package/src/session-runtime/services/agent-tool/terminal-trace.mjs +0 -1
  358. package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
  359. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +32 -20
  360. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -2
  361. package/src/session-runtime/services/agent-tool/worker-index.mjs +28 -8
  362. package/src/session-runtime/services/agent-tool.mjs +4 -0
  363. package/src/session-runtime/services/agent-watchdog-registry.mjs +2 -0
  364. package/src/session-runtime/services/provider-admin.mjs +77 -0
  365. package/src/session-runtime/session-create/session-options.mjs +7 -5
  366. package/src/session-runtime/session-create/session-start-hook.mjs +2 -1
  367. package/src/session-runtime/session-transcript.mjs +4 -5
  368. package/src/session-runtime/session-turn-api.mjs +3 -9
  369. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -6
  370. package/src/session-runtime/setup-tool/executor.mjs +23 -5
  371. package/src/session-runtime/setup-tool/extended-actions.mjs +6 -6
  372. package/src/session-runtime/setup-tool/settings-contract.mjs +7 -2
  373. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -1
  374. package/src/session-runtime/tool-policy-refresh.mjs +83 -32
  375. package/src/session-runtime/turn/agent-ops.mjs +49 -2
  376. package/src/session-runtime/turn/side-question-ops.mjs +1 -4
  377. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +2 -0
  378. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +4 -0
  379. package/src/session-runtime/turn/turn-run/turn-settle.mjs +8 -2
  380. package/src/session-runtime/turn/turn-run.mjs +1 -36
  381. package/src/session-runtime/usage-stats-api.mjs +4 -1
  382. package/src/session-runtime/workflow-agents-api/agent-route.mjs +16 -1
  383. package/src/session-runtime/workspace/admission-gate.mjs +44 -15
  384. package/src/session-runtime/workspace/project-branches.mjs +28 -36
  385. package/src/session-runtime/workspace/transition-steps.mjs +96 -95
  386. package/src/session-runtime/workspace/transitions.mjs +248 -68
  387. package/src/standalone/channel-transport.mjs +3 -4
  388. package/src/standalone/daemon-desktop-runtime.mjs +32 -1
  389. package/src/standalone/daemon-pg-stop.mjs +1 -3
  390. package/src/standalone/daemon-session-control.mjs +88 -0
  391. package/src/standalone/daemon-stored-session-views.mjs +23 -3
  392. package/src/standalone/daemon.mjs +2 -0
  393. package/src/standalone/local-session-runtime.mjs +11 -12
  394. package/src/standalone/session-protocol.mjs +7 -0
  395. package/src/standalone/session-runtime-inline-host.mjs +2 -0
  396. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +91 -49
  397. package/src/standalone/session-service/agent-tree/agent-child.mjs +19 -1
  398. package/src/standalone/session-service/agent-tree/agent-registry.mjs +25 -0
  399. package/src/standalone/session-service/agent-tree/agent-turns.mjs +9 -1
  400. package/src/standalone/session-service/agent-tree.mjs +28 -2
  401. package/src/standalone/session-service/entries/session-loads.mjs +16 -5
  402. package/src/standalone/session-service/entries.mjs +1 -0
  403. package/src/standalone/session-service/projection/transcript-window.mjs +39 -0
  404. package/src/standalone/session-service/quota-resume.mjs +28 -13
  405. package/src/standalone/session-service/session-calls/session-turns.mjs +17 -6
  406. package/src/standalone/session-service/session-calls/session-views.mjs +6 -0
  407. package/src/standalone/session-tool-output.mjs +82 -0
  408. package/src/tui/app/app-view.jsx +2 -1
  409. package/src/tui/app/provider-setup/provider-items.mjs +8 -0
  410. package/src/tui/app/provider-setup-picker.mjs +15 -3
  411. package/src/tui/app/slash-commands.mjs +6 -0
  412. package/src/tui/app/slash-dispatch/session-commands.mjs +49 -0
  413. package/src/tui/components/Spinner.jsx +4 -0
  414. package/src/tui/components/SpinnerByline.jsx +21 -2
  415. package/src/tui/components/status-line/local-line.mjs +10 -2
  416. package/src/tui/dist/index.mjs +478 -251
  417. package/src/tui/session/active-tool-summary.mjs +1 -1
  418. package/src/tui/session/context-state.mjs +0 -3
  419. package/src/tui/session/goal-continuation/continuation-queue.mjs +2 -3
  420. package/src/tui/session/goal-continuation/deadline-delivery.mjs +4 -2
  421. package/src/tui/session/goal-continuation/session-boundary.mjs +21 -0
  422. package/src/tui/session/quota-resume.mjs +140 -61
  423. package/src/tui/session/session-api/commands.mjs +4 -10
  424. package/src/tui/session/session-api/controls.mjs +12 -0
  425. package/src/tui/session/session-api/lifecycle/dispose.mjs +2 -9
  426. package/src/tui/session/session-api/lifecycle/resume.mjs +2 -7
  427. package/src/tui/session/session-api/lifecycle.mjs +13 -11
  428. package/src/tui/session/session-api/media.mjs +2 -0
  429. package/src/tui/session/session-api/routes.mjs +81 -57
  430. package/src/tui/session/session-api/settings.mjs +3 -3
  431. package/src/tui/session/session-api/worktree.mjs +3 -2
  432. package/src/tui/session/session-flow/auto-clear.mjs +1 -3
  433. package/src/tui/session/session-flow/drain.mjs +6 -4
  434. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -0
  435. package/src/tui/session/session-flow/queue/take-entries.mjs +2 -1
  436. package/src/tui/session/session-flow/queue.mjs +1 -1
  437. package/src/tui/session/tool-approval.mjs +17 -8
  438. package/src/tui/session/transcript-store/streaming-tail.mjs +1 -1
  439. package/src/tui/session/tui-steering-persist.mjs +3 -5
  440. package/src/tui/session/turn-quiet.mjs +53 -0
  441. package/src/tui/session/turn-stream.mjs +5 -1
  442. package/src/tui/session/turn.mjs +38 -31
  443. package/src/tui/session/worktree-setup-notice.mjs +38 -0
  444. package/src/tui/spinner-meta.mjs +10 -0
  445. package/src/tui/spinner-verbs.mjs +3 -0
  446. package/src/ui/statusline.mjs +13 -3
  447. package/src/runtime/agent/orchestrator/providers/model-pricing-rates.mjs +0 -1
  448. package/src/runtime/agent/orchestrator/session/manager/foreign-pending-messages.mjs +0 -346
  449. package/src/runtime/agent/orchestrator/session/manager/session-owner-liveness.mjs +0 -117
  450. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +0 -302
  451. package/src/session-runtime/services/agent-tool/terminal-reaper/reap-timers.mjs +0 -142
  452. package/src/session-runtime/turn/share-ops.mjs +0 -77
  453. package/src/tui/session/cross-surface-share.mjs +0 -406
  454. package/src/tui/session/live-share/delta.mjs +0 -153
  455. package/src/tui/session/live-share/mirror.mjs +0 -106
  456. package/src/tui/session/live-share/owner.mjs +0 -254
  457. package/src/tui/session/live-share/retry.mjs +0 -54
  458. package/src/tui/session/live-share/viewer/frame-router.mjs +0 -32
  459. package/src/tui/session/live-share/viewer/pipe-client.mjs +0 -108
  460. package/src/tui/session/live-share/viewer/submit-acks.mjs +0 -65
  461. package/src/tui/session/live-share/viewer/sync-waiters.mjs +0 -53
  462. package/src/tui/session/live-share/viewer.mjs +0 -99
  463. package/src/tui/session/live-share/wire.mjs +0 -44
  464. package/src/tui/session/live-share.mjs +0 -154
  465. package/src/tui/session/shared-dir-watch.mjs +0 -69
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Automatic dependency preparation for a new managed worktree, for every
3
+ * supported stack the committed files reveal; nothing to configure.
4
+ *
5
+ * A project folder is the root, a workspace package the root declares
6
+ * (package.json `workspaces`, as npm, yarn and bun read it, or
7
+ * pnpm-workspace.yaml `packages`), or any other nested folder whose
8
+ * counterpart in the main checkout holds the stack's installed artefact
9
+ * (node_modules, .venv): the user installed there, so it is a real project,
10
+ * while a test fixture never is. Nothing inside node_modules counts. Per
11
+ * folder and stack, the committed lockfile or manifest decides:
12
+ *
13
+ * JS package-lock.json / pnpm-lock.yaml / yarn.lock / bun.lock(b)
14
+ * → npm ci, pnpm|bun install --frozen-lockfile, yarn install
15
+ * --immutable (Yarn 2+) or --frozen-lockfile (Yarn 1)
16
+ * Python uv.lock → uv sync --frozen; poetry.lock → poetry install
17
+ * --no-root; Pipfile.lock → pipenv sync; requirements*.txt alone
18
+ * → a .venv and pip install -r
19
+ *
20
+ * Further stacks are one STACKS entry each (see there). Java (Gradle, Maven),
21
+ * Rust and Go fetch on their own and need nothing.
22
+ *
23
+ * Only node_modules is ever linked: when the main checkout's same folder has
24
+ * it installed (a real directory, never a link) and the lockfile is
25
+ * byte-identical, the worktree's becomes a link to it (directory junction on
26
+ * Windows, symlink elsewhere) at creation, recorded with the worktree's
27
+ * include links so removal unlinks it before the checkout goes and never
28
+ * traverses into the target. Everything else (a virtualenv is not
29
+ * relocatable) is installed in the worktree folder at the start of the
30
+ * session's first turn (setup-commands.mjs). A folder whose artefact already
31
+ * exists in the worktree is left alone, so an install never runs through a
32
+ * link into the main checkout; a link standing in for an artefact that is
33
+ * never linked (a .venv) is noted as not prepared. A tool that is not on PATH
34
+ * is skipped with a note.
35
+ */
36
+ import { lstat, readFile } from 'node:fs/promises';
37
+ import { join } from 'node:path';
38
+ import { which } from '../../tidy/process.mjs';
39
+ import { runGit } from './git.mjs';
40
+ import { linkDirectory } from './include.mjs';
41
+ import { runWorktreeSetupCommands } from './setup-commands.mjs';
42
+
43
+ /** JS lockfile → package manager, in the order a folder with several picks one. */
44
+ const JS_LOCKFILES = [
45
+ ['package-lock.json', 'npm'],
46
+ ['pnpm-lock.yaml', 'pnpm'],
47
+ ['yarn.lock', 'yarn'],
48
+ ['bun.lock', 'bun'],
49
+ ['bun.lockb', 'bun'],
50
+ ];
51
+ /**
52
+ * Every Python install builds the worktree's own `.venv`, never an inherited
53
+ * or activated environment: the activation variables are removed (`uv sync`,
54
+ * the `py` launcher, poetry and pipenv all honour VIRTUAL_ENV; poetry also a
55
+ * conda prefix; uv also UV_PROJECT_ENVIRONMENT), and poetry and pipenv are
56
+ * told to create the environment in the project folder.
57
+ */
58
+ const PYTHON_ENV = Object.freeze({
59
+ VIRTUAL_ENV: null,
60
+ CONDA_PREFIX: null,
61
+ UV_PROJECT_ENVIRONMENT: null,
62
+ POETRY_VIRTUALENVS_CREATE: 'true',
63
+ POETRY_VIRTUALENVS_IN_PROJECT: 'true',
64
+ PIPENV_VENV_IN_PROJECT: '1',
65
+ });
66
+ const VERSION_TIMEOUT_MS = 30_000;
67
+ const quote = (value) => (/[\s"&|<>^()]/.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value);
68
+ const escapeRegExp = (text) => text.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
69
+
70
+ async function yarnInstallFlag(tool, cwd, signal) {
71
+ const report = await runWorktreeSetupCommands({
72
+ commands: [{ command: `"${tool}" --version`, file: tool, args: ['--version'], cwd }],
73
+ signal,
74
+ timeoutMs: VERSION_TIMEOUT_MS,
75
+ });
76
+ const major = Number.parseInt(report.steps[0]?.output.trim() ?? '', 10);
77
+ if (report.status !== 'completed' || !Number.isInteger(major)) return null;
78
+ return major >= 2 ? '--immutable' : '--frozen-lockfile';
79
+ }
80
+
81
+ /**
82
+ * The supported stacks. Adding one (Ruby, PHP, .NET, Elixir, Dart, …) is one
83
+ * entry here and nothing else:
84
+ * stack its name;
85
+ * names the committed basenames it reads;
86
+ * globs committed basename globs it reads (`*` within a name), optional;
87
+ * artefact the folder an install leaves: the main checkout having it
88
+ * admits a nested folder, the worktree having it skips the folder;
89
+ * linkable only when the main checkout's artefact works as is through a
90
+ * link (then an identical `lockfile` is required); never for an
91
+ * install that embeds its own path (a virtualenv);
92
+ * pick(files) the folder's committed basenames → { manager, lockfile?, … }
93
+ * or null;
94
+ * install(match, { cwd, tool, signal })
95
+ * → { tools, args, followUp?, env? } (tools: names tried on
96
+ * PATH in order; args: an argument array, run without a shell;
97
+ * followUp: a second step `{ file, args }`; env: variables
98
+ * to set, or null to remove, for the run), or null when it
99
+ * cannot be run. Called without `tool` to name the install, then
100
+ * with the resolved tool path, for arguments that depend on its
101
+ * version.
102
+ */
103
+ const STACKS = [
104
+ {
105
+ stack: 'js',
106
+ names: ['package.json', ...JS_LOCKFILES.map(([name]) => name)],
107
+ artefact: 'node_modules',
108
+ linkable: true,
109
+ pick(files) {
110
+ if (!files.has('package.json')) return null;
111
+ const match = JS_LOCKFILES.find(([name]) => files.has(name));
112
+ return match ? { manager: match[1], lockfile: match[0] } : null;
113
+ },
114
+ async install({ manager }, { cwd, tool, signal }) {
115
+ if (manager === 'npm') return { tools: ['npm'], args: ['ci'] };
116
+ if (manager !== 'yarn') return { tools: [manager], args: ['install', '--frozen-lockfile'] };
117
+ if (!tool) return { tools: ['yarn'], args: ['install', '--frozen-lockfile'] };
118
+ const flag = await yarnInstallFlag(tool, cwd, signal);
119
+ return flag ? { tools: ['yarn'], args: ['install', flag] } : null;
120
+ },
121
+ },
122
+ {
123
+ stack: 'python',
124
+ names: ['uv.lock', 'poetry.lock', 'Pipfile.lock'],
125
+ globs: ['requirements*.txt'],
126
+ artefact: '.venv',
127
+ pick(files) {
128
+ if (files.has('uv.lock')) return { manager: 'uv', lockfile: 'uv.lock' };
129
+ if (files.has('poetry.lock')) return { manager: 'poetry', lockfile: 'poetry.lock' };
130
+ if (files.has('Pipfile.lock')) return { manager: 'pipenv', lockfile: 'Pipfile.lock' };
131
+ const requirements = [...files]
132
+ .filter((name) => /^requirements.*\.txt$/i.test(name))
133
+ .sort((a, b) => (a === 'requirements.txt' ? -1 : b === 'requirements.txt' ? 1 : a.localeCompare(b)));
134
+ return requirements.length ? { manager: 'pip', requirements } : null;
135
+ },
136
+ install({ manager, requirements }, { cwd }) {
137
+ const env = PYTHON_ENV;
138
+ if (manager === 'uv') return { tools: ['uv'], args: ['sync', '--frozen'], env };
139
+ if (manager === 'poetry') return { tools: ['poetry'], args: ['install', '--no-root'], env };
140
+ if (manager === 'pipenv') return { tools: ['pipenv'], args: ['sync'], env };
141
+ // requirements*.txt without a lockfile: a project-local virtualenv.
142
+ const venvPython = join(
143
+ cwd,
144
+ ...(process.platform === 'win32' ? ['.venv', 'Scripts', 'python.exe'] : ['.venv', 'bin', 'python'])
145
+ );
146
+ return {
147
+ tools: process.platform === 'win32' ? ['py', 'python', 'python3'] : ['python3', 'python'],
148
+ args: ['-m', 'venv', '.venv'],
149
+ followUp: { file: venvPython, args: ['-m', 'pip', 'install', ...requirements.flatMap((name) => ['-r', name])] },
150
+ env,
151
+ };
152
+ },
153
+ },
154
+ {
155
+ stack: 'ruby',
156
+ names: ['Gemfile.lock'],
157
+ artefact: '.bundle',
158
+ pick: (files) => (files.has('Gemfile.lock') ? { manager: 'bundler', lockfile: 'Gemfile.lock' } : null),
159
+ install: () => ({ tools: ['bundle'], args: ['install', '--frozen'] }),
160
+ },
161
+ {
162
+ stack: 'php',
163
+ names: ['composer.lock'],
164
+ artefact: 'vendor',
165
+ pick: (files) => (files.has('composer.lock') ? { manager: 'composer', lockfile: 'composer.lock' } : null),
166
+ install: () => ({ tools: ['composer'], args: ['install', '--no-interaction'] }),
167
+ },
168
+ {
169
+ stack: 'dotnet',
170
+ names: ['packages.lock.json'],
171
+ globs: ['*.csproj'],
172
+ artefact: 'obj',
173
+ pick(files) {
174
+ if (files.has('packages.lock.json')) return { manager: 'dotnet', lockfile: 'packages.lock.json' };
175
+ return [...files].some((name) => /\.csproj$/i.test(name)) ? { manager: 'dotnet' } : null;
176
+ },
177
+ install: ({ lockfile }) => ({ tools: ['dotnet'], args: lockfile ? ['restore', '--locked-mode'] : ['restore'] }),
178
+ },
179
+ {
180
+ stack: 'elixir',
181
+ names: ['mix.lock'],
182
+ artefact: 'deps',
183
+ pick: (files) => (files.has('mix.lock') ? { manager: 'mix', lockfile: 'mix.lock' } : null),
184
+ install: () => ({ tools: ['mix'], args: ['deps.get', '--check-locked'] }),
185
+ },
186
+ {
187
+ stack: 'dart',
188
+ names: ['pubspec.lock'],
189
+ artefact: '.dart_tool',
190
+ pick: (files) => (files.has('pubspec.lock') ? { manager: 'pub', lockfile: 'pubspec.lock' } : null),
191
+ async install(_match, { cwd }) {
192
+ const pubspec = await readOrNull(join(cwd, 'pubspec.yaml'));
193
+ const flutter = pubspec && /^\s+flutter\s*:/m.test(pubspec.toString('utf8'));
194
+ return { tools: [flutter ? 'flutter' : 'dart'], args: ['pub', 'get', '--enforce-lockfile'] };
195
+ },
196
+ },
197
+ ];
198
+
199
+ const STACK_NAMES = new Set(STACKS.flatMap((stack) => stack.names));
200
+ const STACK_GLOBS = STACKS.flatMap((stack) => stack.globs ?? []);
201
+ const STACK_GLOB_MATCHERS = STACK_GLOBS.map((glob) => new RegExp(`^${segmentPattern(glob)}$`, 'i'));
202
+
203
+ /** A glob path segment (`*` within the segment) as a regular expression source. */
204
+ function segmentPattern(glob) {
205
+ return escapeRegExp(glob).replace(/\*/g, '[^/]*');
206
+ }
207
+
208
+ function relevantName(name) {
209
+ return STACK_NAMES.has(name) || STACK_GLOB_MATCHERS.some((matcher) => matcher.test(name));
210
+ }
211
+
212
+ async function lstatOrNull(path) {
213
+ try {
214
+ return await lstat(path);
215
+ } catch (error) {
216
+ if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') return null;
217
+ throw error;
218
+ }
219
+ }
220
+
221
+ async function readOrNull(path) {
222
+ try {
223
+ return await readFile(path);
224
+ } catch {
225
+ return null;
226
+ }
227
+ }
228
+
229
+ function folderPath(root, dir) {
230
+ return dir ? join(root, ...dir.split('/')) : root;
231
+ }
232
+
233
+ function folderLabel(dir) {
234
+ return dir || 'project root';
235
+ }
236
+
237
+ /** The `packages:` list of a pnpm-workspace.yaml (block or flow style). */
238
+ function pnpmWorkspacePatterns(text) {
239
+ const patterns = [];
240
+ let inPackages = false;
241
+ for (const raw of text.split(/\r?\n/)) {
242
+ const line = raw.replace(/\s+#.*$/, '');
243
+ if (!line.trim()) continue;
244
+ const top = /^packages\s*:\s*(.*)$/.exec(line);
245
+ if (top) {
246
+ const flow = /^\[(.*)\]$/.exec(top[1].trim());
247
+ if (flow) patterns.push(...flow[1].split(','));
248
+ inPackages = !flow;
249
+ continue;
250
+ }
251
+ if (!inPackages) continue;
252
+ const item = /^\s+-\s*(.+)$/.exec(line);
253
+ if (item) patterns.push(item[1]);
254
+ else if (!/^\s/.test(line)) inPackages = false;
255
+ }
256
+ return patterns.map((pattern) => pattern.trim().replace(/^(['"])(.*)\1$/, '$2')).filter(Boolean);
257
+ }
258
+
259
+ /** The workspace package patterns the root package.json and pnpm-workspace.yaml declare. */
260
+ async function workspacePatterns(projectDir) {
261
+ const patterns = [];
262
+ try {
263
+ const { workspaces } = JSON.parse(String(await readOrNull(join(projectDir, 'package.json'))));
264
+ const list = Array.isArray(workspaces) ? workspaces : workspaces?.packages;
265
+ if (Array.isArray(list)) patterns.push(...list.filter((entry) => typeof entry === 'string'));
266
+ } catch {
267
+ /* no readable package.json: no declared workspaces */
268
+ }
269
+ const pnpm = await readOrNull(join(projectDir, 'pnpm-workspace.yaml'));
270
+ if (pnpm) patterns.push(...pnpmWorkspacePatterns(pnpm.toString('utf8')));
271
+ return patterns;
272
+ }
273
+
274
+ /** A workspace glob (`*` one segment, `**` any depth, `!` excludes) as a matcher of folders. */
275
+ function workspaceMatcher(patterns) {
276
+ const compile = (pattern) => {
277
+ const body = pattern
278
+ .replace(/^\.\//, '')
279
+ .replace(/\/+$/, '')
280
+ .split('/')
281
+ .map((part) => (part === '**' ? '.*' : segmentPattern(part)))
282
+ .join('/')
283
+ .replace(/\/\.\*$/, '(?:/.*)?')
284
+ .replace(/^\.\*\//, '(?:.*/)?');
285
+ return new RegExp(`^${body}$`);
286
+ };
287
+ const include = patterns.filter((pattern) => !pattern.startsWith('!')).map(compile);
288
+ const exclude = patterns.filter((pattern) => pattern.startsWith('!')).map((pattern) => compile(pattern.slice(1)));
289
+ return (dir) => include.some((re) => re.test(dir)) && !exclude.some((re) => re.test(dir));
290
+ }
291
+
292
+ /** Whether `dir` holds the installed `artefact`: a real directory, never a link. */
293
+ async function installedArtefact(dir, artefact) {
294
+ const info = await lstatOrNull(join(dir, artefact));
295
+ return !!info?.isDirectory() && !info.isSymbolicLink();
296
+ }
297
+
298
+ /**
299
+ * What to prepare (forward-slash `dir` relative to `projectDir`; '' is the
300
+ * project itself), root first: one entry per project folder and stack, with
301
+ * the manager its committed files select. `mainDir`: the same folder in the
302
+ * main checkout, whose installed artefacts admit nested folders.
303
+ * @returns {Promise<Array<{ dir: string, stack: string, manager: string, artefact: string, linkable?: boolean,
304
+ * lockfile?: string, requirements?: string[] }>>}
305
+ */
306
+ export async function findLockfileFolders(projectDir, { mainDir = null, signal } = {}) {
307
+ const pathspecs = [...STACK_NAMES, ...STACK_GLOBS].map((name) => `:(glob)**/${name}`);
308
+ const result = await runGit(projectDir, ['-c', 'core.quotePath=false', 'ls-files', '-z', '--', ...pathspecs], {
309
+ signal,
310
+ readOnly: true,
311
+ allowFailure: true,
312
+ });
313
+ if (result.code !== 0) return [];
314
+ const folders = new Map();
315
+ for (const path of result.stdout.split('\0')) {
316
+ const parts = path.split('/');
317
+ const name = parts.pop();
318
+ if (!name || !relevantName(name) || parts.includes('node_modules')) continue;
319
+ const dir = parts.join('/');
320
+ if (!folders.has(dir)) folders.set(dir, new Set());
321
+ folders.get(dir).add(name);
322
+ }
323
+ const isWorkspace = workspaceMatcher(await workspacePatterns(projectDir));
324
+ const found = [];
325
+ for (const [dir, files] of folders) {
326
+ for (const stack of STACKS) {
327
+ const match = stack.pick(files);
328
+ if (!match) continue;
329
+ const admitted =
330
+ !dir || isWorkspace(dir) || (mainDir && (await installedArtefact(folderPath(mainDir, dir), stack.artefact)));
331
+ if (admitted) {
332
+ found.push({ dir, stack: stack.stack, artefact: stack.artefact, linkable: stack.linkable === true, ...match });
333
+ }
334
+ }
335
+ }
336
+ return found.sort((a, b) => a.dir.split('/').length - b.dir.split('/').length || a.dir.localeCompare(b.dir));
337
+ }
338
+
339
+ /** Whether the main checkout's installed artefact can stand in for the worktree's. */
340
+ async function linkSource(mainDir, worktreeDir, { dir, lockfile, artefact }) {
341
+ if (!lockfile || !(await installedArtefact(folderPath(mainDir, dir), artefact))) return null;
342
+ const [mainLock, worktreeLock] = await Promise.all([
343
+ readOrNull(join(folderPath(mainDir, dir), lockfile)),
344
+ readOrNull(join(folderPath(worktreeDir, dir), lockfile)),
345
+ ]);
346
+ return mainLock && worktreeLock && mainLock.equals(worktreeLock) ? join(folderPath(mainDir, dir), artefact) : null;
347
+ }
348
+
349
+ /**
350
+ * Link each linkable folder's artefact (node_modules) from the main checkout
351
+ * where its lockfile is identical (creation time). `record(ledger)` is
352
+ * awaited with status 'pending' before each link is made and 'created'
353
+ * after, like an include link; a link that cannot be made is left to the
354
+ * first-turn install.
355
+ * @param {{ mainDir: string, worktreeDir: string, worktreeRel: string, signal?: AbortSignal,
356
+ * record: (ledger: object) => Promise<void> }} input `worktreeRel`: the project
357
+ * folder relative to the worktree's top level ('' at the top).
358
+ * @returns {Promise<string[]>} The linked folders.
359
+ */
360
+ export async function linkWorktreeDependencies({ mainDir, worktreeDir, worktreeRel, signal, record }) {
361
+ const linked = [];
362
+ for (const folder of await findLockfileFolders(worktreeDir, { mainDir, signal })) {
363
+ if (!folder.linkable) continue;
364
+ const dest = join(folderPath(worktreeDir, folder.dir), folder.artefact);
365
+ if (await lstatOrNull(dest)) continue;
366
+ const source = await linkSource(mainDir, worktreeDir, folder);
367
+ if (!source) continue;
368
+ const rel = [worktreeRel, folder.dir, folder.artefact].filter(Boolean).join('/');
369
+ const ledger = { mode: 'link', rel, path: dest, status: 'pending', digest: null, source, dependency: true };
370
+ await record(ledger);
371
+ try {
372
+ await linkDirectory(source, dest);
373
+ } catch {
374
+ continue;
375
+ }
376
+ ledger.status = 'created';
377
+ await record(ledger);
378
+ linked.push(folder.dir);
379
+ }
380
+ return linked;
381
+ }
382
+
383
+ /** Display text of a command (never run through a shell). */
384
+ const displayLine = (tool, args) => `${tool} ${args.map(quote).join(' ')}`;
385
+
386
+ function commandLine(tool, { args, followUp }) {
387
+ return `${displayLine(tool, args)}${followUp ? ` && ${displayLine(quote(followUp.file), followUp.args)}` : ''}`;
388
+ }
389
+
390
+ /**
391
+ * What the first turn still has to do in the worktree folder `worktreeDir`:
392
+ * the installs (`commands`, setup-commands entries) and one line per folder
393
+ * it leaves alone (`notes`: linked, nothing found, tool not on PATH, an
394
+ * artefact that is a link it must not install through).
395
+ * `mainDir`: the same folder in the main checkout (findLockfileFolders).
396
+ * `env`: where the tools are looked up (PATH).
397
+ * @param {{ worktreeDir: string, mainDir?: string|null, signal?: AbortSignal, env?: NodeJS.ProcessEnv }} input
398
+ */
399
+ export async function planDependencySetup({ worktreeDir, mainDir = null, signal, env = process.env }) {
400
+ const folders = await findLockfileFolders(worktreeDir, { mainDir, signal });
401
+ if (!folders.length) {
402
+ return { commands: [], notes: ['No lockfile or dependency manifest found; nothing was installed.'] };
403
+ }
404
+ const commands = [];
405
+ const notes = [];
406
+ const linked = [];
407
+ for (const entry of folders) {
408
+ const cwd = folderPath(worktreeDir, entry.dir);
409
+ const where = folderLabel(entry.dir);
410
+ const present = await lstatOrNull(join(cwd, entry.artefact));
411
+ if (present && (!present.isSymbolicLink() || entry.linkable)) {
412
+ if (present.isSymbolicLink()) linked.push(where);
413
+ continue;
414
+ }
415
+ const stack = STACKS.find((candidate) => candidate.stack === entry.stack);
416
+ const named = await stack.install(entry, { cwd, signal });
417
+ if (!named) continue;
418
+ if (present) {
419
+ // A link where the stack needs its own install (a virtualenv): not
420
+ // prepared, and never installed through into whatever it points at.
421
+ notes.push(
422
+ `${entry.artefact} in ${where} is a link, not an environment of its own; ${where} was not prepared (replace it with \`${commandLine(named.tools[0], named)}\` there if tests need it).`
423
+ );
424
+ continue;
425
+ }
426
+ const found = named.tools.find((candidate) => which(candidate, { env }));
427
+ if (!found) {
428
+ notes.push(
429
+ `${named.tools[0]} is not on PATH; ${where} was not prepared (run \`${commandLine(named.tools[0], named)}\` there if tests need it).`
430
+ );
431
+ continue;
432
+ }
433
+ const tool = which(found, { env });
434
+ const spec = await stack.install(entry, { cwd, tool, signal });
435
+ if (!spec) {
436
+ notes.push(`${found} could not be queried; ${where} was not prepared.`);
437
+ continue;
438
+ }
439
+ const label = `${entry.manager} (${where})`;
440
+ const specEnv = spec.env ? { env: spec.env } : {};
441
+ commands.push({
442
+ command: displayLine(`"${tool}"`, spec.args),
443
+ file: tool,
444
+ args: spec.args,
445
+ cwd,
446
+ label,
447
+ ...specEnv,
448
+ });
449
+ if (spec.followUp) {
450
+ const { file, args } = spec.followUp;
451
+ commands.push({ command: displayLine(`"${file}"`, args), file, args, cwd, label, ...specEnv });
452
+ }
453
+ }
454
+ if (linked.length) notes.unshift(`Linked node_modules from the main checkout: ${linked.join(', ')}.`);
455
+ return { commands, notes };
456
+ }
@@ -8,14 +8,23 @@ import { basename, dirname, isAbsolute, resolve } from 'node:path';
8
8
  import { worktreeError } from './contract.mjs';
9
9
  import { isWithin } from './paths.mjs';
10
10
 
11
- const fail = (record, root) => worktreeError('WORKTREE_FILE_RECOVERY', undefined, {
12
- entryId: record.id, path: record.path, recoveryRoot: root,
13
- });
11
+ const fail = (record, root) =>
12
+ worktreeError('WORKTREE_FILE_RECOVERY', undefined, {
13
+ entryId: record.id,
14
+ path: record.path,
15
+ recoveryRoot: root,
16
+ });
14
17
  export function validateRecoveryScope(record, scope) {
15
- if (!scope || typeof scope.root !== 'string' || !isAbsolute(scope.root) ||
16
- typeof scope.directory !== 'string' || !isAbsolute(scope.directory) ||
17
- !isWithin(record.path, scope.root) || basename(dirname(scope.directory)) !== 'editor-recovery' ||
18
- basename(scope.directory) !== createHash('sha256').update(resolve(scope.root)).digest('hex'))
18
+ if (
19
+ !scope ||
20
+ typeof scope.root !== 'string' ||
21
+ !isAbsolute(scope.root) ||
22
+ typeof scope.directory !== 'string' ||
23
+ !isAbsolute(scope.directory) ||
24
+ !isWithin(record.path, scope.root) ||
25
+ basename(dirname(scope.directory)) !== 'editor-recovery' ||
26
+ basename(scope.directory) !== createHash('sha256').update(resolve(scope.root)).digest('hex')
27
+ )
19
28
  throw fail(record, record.path);
20
29
  return scope;
21
30
  }
@@ -30,16 +39,23 @@ async function recoveryStoreIsEmpty(scope) {
30
39
  // stores are permitted because registration deliberately precedes mkdir.
31
40
  for (const directory of [dirname(dirname(scope.directory)), dirname(scope.directory), scope.directory]) {
32
41
  let info;
33
- try { info = await lstat(directory); }
34
- catch (error) { if (error?.code === 'ENOENT') continue; throw error; }
42
+ try {
43
+ info = await lstat(directory);
44
+ } catch (error) {
45
+ if (error?.code === 'ENOENT') continue;
46
+ throw error;
47
+ }
35
48
  // Use the same path spelling as the file host's store validation;
36
49
  // async/native realpath can expand Windows short names differently.
37
- if (!info.isDirectory() || info.isSymbolicLink() || realpathSync(directory) !== resolve(directory))
38
- return false;
50
+ if (!info.isDirectory() || info.isSymbolicLink() || realpathSync(directory) !== resolve(directory)) return false;
39
51
  }
40
52
  let directory;
41
- try { directory = await opendir(scope.directory); }
42
- catch (error) { if (error?.code === 'ENOENT') return true; throw error; }
53
+ try {
54
+ directory = await opendir(scope.directory);
55
+ } catch (error) {
56
+ if (error?.code === 'ENOENT') return true;
57
+ throw error;
58
+ }
43
59
  for await (const entry of directory) {
44
60
  // Also retain unknown/tampered entries, rather than guessing they are
45
61
  // expendable. No manifest or phase can authorize deleting the workspace.
@@ -19,42 +19,48 @@ const notRepos = new Map();
19
19
  /** git dir → last HEAD read, kept across a half-written HEAD. */
20
20
  const lastHeads = new Map();
21
21
 
22
- function readText(path) {
22
+ function readText(path, strict = false) {
23
23
  try {
24
24
  return readFileSync(path, 'utf8');
25
- } catch {
25
+ } catch (error) {
26
+ if (strict && error?.code !== 'ENOENT') throw error;
26
27
  return null;
27
28
  }
28
29
  }
29
30
 
30
- function isDirectory(path) {
31
+ function isDirectory(path, strict = false) {
31
32
  try {
32
33
  return statSync(path).isDirectory();
33
- } catch {
34
+ } catch (error) {
35
+ if (strict && !['ENOENT', 'ENOTDIR'].includes(error?.code)) throw error;
34
36
  return false;
35
37
  }
36
38
  }
37
39
 
38
- function isFile(path) {
40
+ function isFile(path, strict = false) {
39
41
  try {
40
42
  return statSync(path).isFile();
41
- } catch {
43
+ } catch (error) {
44
+ if (strict && !['ENOENT', 'ENOTDIR'].includes(error?.code)) throw error;
42
45
  return false;
43
46
  }
44
47
  }
45
48
 
46
- function layoutAbove(start) {
49
+ function layoutAbove(start, strict = false) {
47
50
  for (let dir = start; ; dir = dirname(dir)) {
48
51
  const dotGit = resolve(dir, '.git');
49
52
  let gitDir = null;
50
- if (isDirectory(dotGit)) {
53
+ if (isDirectory(dotGit, strict)) {
51
54
  gitDir = dotGit;
52
- } else if (isFile(dotGit)) {
53
- const pointer = /^gitdir:\s*(.+?)\s*$/m.exec(readText(dotGit) ?? '');
55
+ } else if (isFile(dotGit, strict)) {
56
+ const pointer = /^gitdir:\s*(.+?)\s*$/m.exec(readText(dotGit, strict) ?? '');
57
+ if (strict && !pointer) throw new Error(`Cannot inspect Git directory: ${dotGit}`);
54
58
  if (pointer) gitDir = resolve(dir, pointer[1]);
55
59
  }
56
- if (gitDir && isFile(resolve(gitDir, 'HEAD'))) {
57
- const common = readText(resolve(gitDir, 'commondir'))?.trim();
60
+ if (gitDir && strict && !isFile(resolve(gitDir, 'HEAD'), true))
61
+ throw new Error(`Cannot inspect Git HEAD: ${gitDir}`);
62
+ if (gitDir && isFile(resolve(gitDir, 'HEAD'), strict)) {
63
+ const common = readText(resolve(gitDir, 'commondir'), strict)?.trim();
58
64
  return {
59
65
  topLevel: canonicalPath(dir),
60
66
  gitDir,
@@ -66,8 +72,11 @@ function layoutAbove(start) {
66
72
  }
67
73
 
68
74
  /** The checkout layout containing `cwd`, or null outside a repository. */
69
- export function findGitLayout(cwd) {
75
+ export function findGitLayout(cwd, { strict = false } = {}) {
70
76
  const start = resolve(String(cwd || '.'));
77
+ if (strict) {
78
+ return layoutAbove(start, true);
79
+ }
71
80
  const known = layouts.get(start);
72
81
  if (known && isFile(resolve(known.gitDir, 'HEAD'))) return known;
73
82
  layouts.delete(start);