mixdog 1.4.2 → 1.4.3

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 (417) hide show
  1. package/package.json +2 -2
  2. package/scripts/build-shipped-history.mjs +15 -5
  3. package/scripts/gate-local.mjs +65 -19
  4. package/scripts/graph-parity.mjs +8 -3
  5. package/scripts/lib/gate-scheduler.mjs +2 -1
  6. package/scripts/lib/run-node-tests.mjs +17 -5
  7. package/scripts/lib/test-failure-summary.mjs +1 -8
  8. package/scripts/lib/test-spawn-ledger.mjs +33 -2
  9. package/scripts/post-release-check.mjs +26 -16
  10. package/scripts/provider-pingpong-live.mjs +1 -1
  11. package/scripts/prune-embedding-runtime.mjs +3 -1
  12. package/scripts/release-paths.mjs +7 -1
  13. package/src/defaults/shipped-history.json +20 -0
  14. package/src/defaults/skills/setup/SKILL.md +2 -2
  15. package/src/defaults/skills/setup/references/actions.md +16 -3
  16. package/src/defaults/skills/setup/references/surfaces.md +3 -0
  17. package/src/defaults/skills/setup-guide/SKILL.md +6 -1
  18. package/src/lib/instruction-text.cjs +36 -0
  19. package/src/lib/rules-builder.cjs +34 -20
  20. package/src/orchestration/balanced.md +23 -0
  21. package/src/orchestration/focused.md +22 -0
  22. package/src/orchestration/none.md +5 -0
  23. package/src/orchestration/swarm.md +24 -0
  24. package/src/rules/agent-mode/AGENT_MODE.md +3 -1
  25. package/src/rules/agent-mode/MIXY.md +2 -2
  26. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +3 -3
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +0 -1
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +4 -2
  29. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +0 -9
  30. package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +2 -2
  31. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +67 -0
  32. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +1 -0
  33. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +4 -4
  34. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -1
  35. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +199 -46
  36. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +3 -5
  37. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +9 -3
  38. package/src/runtime/agent/orchestrator/mcp/client.mjs +3 -5
  39. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +1 -1
  40. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +8 -2
  41. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +1 -1
  42. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +14 -0
  43. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  45. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-stop.mjs +3 -3
  46. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  47. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +2 -2
  48. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +4 -6
  49. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2784 -2872
  50. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +5 -8
  51. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +288 -300
  52. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +10 -1
  53. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +10 -30
  54. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +30 -0
  55. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +0 -11
  56. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +8 -0
  57. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +17 -3
  58. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +47 -27
  59. package/src/runtime/agent/orchestrator/runtime-core/goal-deadlines.mjs +141 -10
  60. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +26 -63
  61. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +28 -3
  62. package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +36 -11
  63. package/src/runtime/agent/orchestrator/runtime-core/goal-state.mjs +88 -56
  64. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +4 -1
  65. package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +51 -32
  66. package/src/runtime/agent/orchestrator/runtime-core/goal-text.mjs +45 -145
  67. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +15 -98
  68. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +32 -99
  69. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +23 -78
  70. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +69 -48
  71. package/src/runtime/agent/orchestrator/runtime-core/todo-list.mjs +367 -0
  72. package/src/runtime/agent/orchestrator/runtime-core/todo-tool-defs.mjs +47 -0
  73. package/src/runtime/agent/orchestrator/runtime-core/tool-availability.mjs +155 -0
  74. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +9 -0
  75. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +119 -35
  76. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  78. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +28 -22
  79. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +30 -0
  80. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +1 -1
  81. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +2 -2
  82. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -2
  83. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +1 -1
  84. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -1
  85. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +3 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +1 -1
  87. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -12
  88. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +1 -1
  89. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +7 -3
  92. package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +1 -1
  93. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +1 -2
  94. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +1 -1
  95. package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +11 -1
  97. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +8 -1
  99. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/todo-check.mjs +196 -0
  100. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +116 -50
  101. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +8 -1
  102. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +1 -1
  103. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +1 -1
  104. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -1
  105. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +5 -6
  106. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +6 -1
  107. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  108. package/src/runtime/agent/orchestrator/session/loop/turn-report.mjs +35 -0
  109. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +1 -1
  110. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +57 -26
  111. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +9 -7
  112. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +21 -3
  113. package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +27 -11
  114. package/src/runtime/agent/orchestrator/session/manager/ask-turn-open.mjs +37 -34
  115. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +15 -0
  116. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +25 -15
  117. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +79 -24
  118. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +3 -0
  119. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +12 -1
  120. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +1 -1
  121. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +6 -1
  122. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +9 -1
  123. package/src/runtime/agent/orchestrator/session/manager/pending-spool-path.mjs +2 -2
  124. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +1 -1
  125. package/src/runtime/agent/orchestrator/session/manager/prompt-settings.mjs +598 -0
  126. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +19 -0
  127. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +19 -10
  128. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +5 -0
  129. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +1 -1
  130. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +31 -0
  131. package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +31 -0
  132. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +42 -6
  133. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +2 -0
  134. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +11 -1
  135. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +8 -9
  136. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +339 -88
  137. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +135 -11
  138. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +377 -104
  139. package/src/runtime/agent/orchestrator/session/manager/turn-durability.mjs +144 -0
  140. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +1 -1
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +4 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +6 -0
  143. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +1 -1
  144. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +8 -1
  145. package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +5 -1
  146. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +1 -1
  147. package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +2 -2
  148. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +23 -2
  149. package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +3 -3
  150. package/src/runtime/agent/orchestrator/session/store/transcript-durability.mjs +88 -0
  151. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +5 -0
  152. package/src/runtime/agent/orchestrator/session/store-summary-log.mjs +1 -1
  153. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +14 -5
  154. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +2 -2
  155. package/src/runtime/agent/orchestrator/session/store.mjs +5 -0
  156. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +7 -1
  157. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +9 -1
  158. package/src/runtime/agent/orchestrator/session/transcript-restore/agent-envelope.mjs +1 -1
  159. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +3 -1
  160. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +2 -2
  161. package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -5
  162. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -1
  163. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +21 -3
  164. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  165. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/worktree-add-guard.mjs +77 -0
  166. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +0 -1
  167. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +7 -3
  168. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +27 -9
  169. package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +2 -2
  170. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +1 -1
  171. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +0 -1
  172. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +0 -10
  173. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +1 -2
  174. package/src/runtime/agent/orchestrator/tools/builtin.mjs +1 -24
  175. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary/child-kill.mjs +4 -3
  176. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +1 -2
  177. package/src/runtime/agent/orchestrator/tools/code-graph/modes/calls.mjs +1 -1
  178. package/src/runtime/agent/orchestrator/tools/code-graph/modes/symbols.mjs +2 -2
  179. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +4 -5
  180. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +3 -4
  181. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4 -22
  182. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +2 -2
  183. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +1 -1
  184. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +0 -4
  185. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +0 -1
  186. package/src/runtime/attachments/attachment-reference.mjs +1 -1
  187. package/src/runtime/attachments/pdf-extract.mjs +1 -1
  188. package/src/runtime/attachments/store.d.mts +1 -0
  189. package/src/runtime/attachments/store.mjs +2 -2
  190. package/src/runtime/channels/lib/config-reload-watch.mjs +42 -0
  191. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +29 -9
  192. package/src/runtime/channels/lib/scheduler.mjs +33 -0
  193. package/src/runtime/channels/lib/schedules-db.mjs +11 -4
  194. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +4 -1
  195. package/src/runtime/channels/lib/webhooks-db.mjs +9 -3
  196. package/src/runtime/channels/lib/whisper-server.mjs +12 -12
  197. package/src/runtime/channels/lib/worker-main.mjs +9 -31
  198. package/src/runtime/computer-bridge/linux-readiness.mjs +10 -14
  199. package/src/runtime/connectors/connector-accounts-mcp.mjs +169 -33
  200. package/src/runtime/connectors/gmail-app-password.mjs +2 -2
  201. package/src/runtime/connectors/google-mcp.mjs +1 -1
  202. package/src/runtime/effort-judge/judge-client.mjs +2 -2
  203. package/src/runtime/effort-judge/judge-input.mjs +4 -4
  204. package/src/runtime/explore/explorer.mjs +0 -5
  205. package/src/runtime/explore/tool.mjs +3 -3
  206. package/src/runtime/local-provider/hardware.mjs +1 -1
  207. package/src/runtime/media/adapters/codex-image.mjs +3 -3
  208. package/src/runtime/media/adapters/gemini-video.mjs +1 -1
  209. package/src/runtime/media/media-usage.mjs +1 -1
  210. package/src/runtime/media/store.mjs +1 -1
  211. package/src/runtime/memory/index.mjs +11 -1
  212. package/src/runtime/memory/lib/embedding-reindex.mjs +27 -0
  213. package/src/runtime/memory/lib/memory-embed.mjs +83 -14
  214. package/src/runtime/memory/lib/memory-process-lock.mjs +159 -36
  215. package/src/runtime/memory/lib/memory-text-utils.mjs +6 -1
  216. package/src/runtime/memory/lib/memory.mjs +17 -0
  217. package/src/runtime/memory/lib/pg/process.mjs +1 -1
  218. package/src/runtime/memory/lib/pg/supervisor.mjs +16 -96
  219. package/src/runtime/memory/lib/recall-hybrid/hybrid-sql.mjs +12 -3
  220. package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
  221. package/src/runtime/office/authoring/pptx-author-action.mjs +1 -1
  222. package/src/runtime/office/authoring/xlsx-html-build.mjs +1 -1
  223. package/src/runtime/office/authoring/xlsx-html-runner.mjs +1 -1
  224. package/src/runtime/office/capabilities.mjs +1 -1
  225. package/src/runtime/office/core/office-core.mjs +1 -1
  226. package/src/runtime/office/design/content-model.mjs +3 -3
  227. package/src/runtime/office/design/docx/design-docx/section.mjs +6 -6
  228. package/src/runtime/office/design/xlsx/design-xlsx-components.mjs +1 -1
  229. package/src/runtime/office/design/xlsx/design-xlsx.mjs +1 -1
  230. package/src/runtime/office/pdf/ocr-engine.mjs +1 -1
  231. package/src/runtime/office/pdf/pdf-writer.mjs +4 -4
  232. package/src/runtime/office/portable/portable-docx-operations.mjs +2 -2
  233. package/src/runtime/office/portable/portable-soffice.mjs +8 -6
  234. package/src/runtime/office/portable/xlsx-contract.mjs +1 -1
  235. package/src/runtime/shared/atomic-file.d.mts +8 -0
  236. package/src/runtime/shared/background-tasks.mjs +22 -4
  237. package/src/runtime/shared/background-wait.mjs +66 -0
  238. package/src/runtime/shared/child-guardian.mjs +17 -7
  239. package/src/runtime/shared/config.mjs +313 -16
  240. package/src/runtime/shared/connector-catalog.d.mts +4 -5
  241. package/src/runtime/shared/connector-catalog.mjs +11 -1
  242. package/src/runtime/shared/file-lock.mjs +8 -3
  243. package/src/runtime/shared/git-runner.mjs +7 -2
  244. package/src/runtime/shared/idle-gc.mjs +30 -7
  245. package/src/runtime/shared/llm/http-agent.mjs +8 -5
  246. package/src/runtime/shared/llm/system-proxy.mjs +2 -1
  247. package/src/runtime/shared/llm/usage-ledger-quota.mjs +31 -12
  248. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +84 -8
  249. package/src/runtime/shared/llm/usage-ledger.mjs +3 -0
  250. package/src/runtime/shared/llm/usage-rollup.mjs +48 -0
  251. package/src/runtime/shared/maintenance-schedule.mjs +109 -0
  252. package/src/runtime/shared/process-runner.mjs +8 -4
  253. package/src/runtime/shared/routine-trigger.d.mts +13 -0
  254. package/src/runtime/shared/routine-trigger.mjs +34 -0
  255. package/src/runtime/shared/run-child.d.mts +2 -1
  256. package/src/runtime/shared/run-child.mjs +70 -18
  257. package/src/runtime/shared/session-scratch.mjs +14 -2
  258. package/src/runtime/shared/shipped-definitions.mjs +7 -1
  259. package/src/runtime/shared/tool-card-model.golden.json +3 -12
  260. package/src/runtime/shared/tool-execution-contract.mjs +2 -0
  261. package/src/runtime/shared/tool-work-units.mjs +31 -6
  262. package/src/runtime/shared/turn-snapshot.mjs +26 -4
  263. package/src/runtime/shared/turn-worktree-snapshot.mjs +6 -11
  264. package/src/runtime/shared/worker-requests.mjs +17 -3
  265. package/src/runtime/shared/worktrees/branch-management.mjs +1 -5
  266. package/src/runtime/shared/worktrees/contract.mjs +16 -6
  267. package/src/runtime/shared/worktrees/dependencies.mjs +127 -89
  268. package/src/runtime/shared/worktrees/disk.mjs +79 -2
  269. package/src/runtime/shared/worktrees/git.mjs +44 -11
  270. package/src/runtime/shared/worktrees/include.mjs +6 -4
  271. package/src/runtime/shared/worktrees/maintenance.mjs +422 -30
  272. package/src/runtime/shared/worktrees/manager.mjs +772 -75
  273. package/src/runtime/shared/worktrees/settings.mjs +33 -7
  274. package/src/runtime/shared/worktrees/test-support.mjs +5 -1
  275. package/src/runtime/shared/worktrees/worker-snapshots.mjs +82 -0
  276. package/src/runtime/tidy/process.mjs +2 -1
  277. package/src/runtime/web-search/index.mjs +3 -22
  278. package/src/runtime/web-search/lib/web-tools.mjs +2 -2
  279. package/src/runtime/web-search/tool-defs.mjs +17 -26
  280. package/src/session-runtime/agent-profile-changes.mjs +55 -4
  281. package/src/session-runtime/boot/apis.mjs +27 -4
  282. package/src/session-runtime/boot/begin.mjs +2 -0
  283. package/src/session-runtime/boot/config.mjs +10 -1
  284. package/src/session-runtime/boot/facade.mjs +1 -0
  285. package/src/session-runtime/boot/providers.mjs +70 -3
  286. package/src/session-runtime/boot/session.mjs +2 -0
  287. package/src/session-runtime/boot/tools.mjs +69 -25
  288. package/src/session-runtime/channel-config-api.mjs +1 -0
  289. package/src/session-runtime/config-change-effects.mjs +179 -0
  290. package/src/session-runtime/config-lifecycle/config-writers.mjs +1 -0
  291. package/src/session-runtime/config-lifecycle.mjs +160 -24
  292. package/src/session-runtime/conversation-import/discovery.mjs +86 -7
  293. package/src/session-runtime/conversation-import/service.mjs +3 -2
  294. package/src/session-runtime/goal-facade-api.mjs +49 -3
  295. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +3 -0
  296. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -2
  297. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +3 -11
  298. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +18 -4
  299. package/src/session-runtime/internal-tool-executor.mjs +12 -19
  300. package/src/session-runtime/lifecycle/cold-heir.mjs +7 -0
  301. package/src/session-runtime/lifecycle/memory-ingest.mjs +3 -3
  302. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +53 -5
  303. package/src/session-runtime/lifecycle/session-switch.mjs +13 -2
  304. package/src/session-runtime/lifecycle/teardown/session-close.mjs +4 -4
  305. package/src/session-runtime/lifecycle-api.mjs +1 -0
  306. package/src/session-runtime/mcp-glue/server-catalog.mjs +14 -3
  307. package/src/session-runtime/mcp-glue/server-connection.mjs +34 -8
  308. package/src/session-runtime/model-route/set-route.mjs +40 -7
  309. package/src/session-runtime/native-web-search/routes.mjs +3 -3
  310. package/src/session-runtime/provider-auth/refresh.mjs +6 -3
  311. package/src/session-runtime/provider-auth/setup.mjs +1 -1
  312. package/src/session-runtime/provider-readiness.mjs +53 -4
  313. package/src/session-runtime/resource-api.mjs +1 -1
  314. package/src/session-runtime/resource-extension-sync.mjs +4 -8
  315. package/src/session-runtime/resource-mcp-connector-accounts.mjs +5 -1
  316. package/src/session-runtime/resource-mcp-secrets.mjs +3 -3
  317. package/src/session-runtime/resource-plugins-api.mjs +4 -0
  318. package/src/session-runtime/routine-executor.mjs +152 -0
  319. package/src/session-runtime/routine-home-turn.mjs +46 -5
  320. package/src/session-runtime/runtime-facade.mjs +5 -0
  321. package/src/session-runtime/runtime-feature-gates.mjs +2 -11
  322. package/src/session-runtime/schedule-session-run.mjs +1 -0
  323. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +6 -1
  324. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +7 -0
  325. package/src/session-runtime/services/agent-tool/send-flow.mjs +110 -6
  326. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +12 -0
  327. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +5 -0
  328. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +24 -6
  329. package/src/session-runtime/services/agent-tool.mjs +51 -1
  330. package/src/session-runtime/services/channel-admin.mjs +18 -0
  331. package/src/session-runtime/services/hook-bus/command-handler.mjs +7 -11
  332. package/src/session-runtime/services/plugin-admin.mjs +4 -0
  333. package/src/session-runtime/services/seeds.mjs +7 -0
  334. package/src/session-runtime/services/usage-stats-model.mjs +32 -11
  335. package/src/session-runtime/session-create/materialize.mjs +4 -2
  336. package/src/session-runtime/session-create/session-options.mjs +2 -0
  337. package/src/session-runtime/settings-builtin-tools-api.mjs +1 -9
  338. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +1 -1
  339. package/src/session-runtime/setup-tool/executor.mjs +8 -1
  340. package/src/session-runtime/setup-tool/extended-actions.mjs +2 -0
  341. package/src/session-runtime/setup-tool/settings-contract.mjs +28 -0
  342. package/src/session-runtime/setup-tool/tool-defs.mjs +26 -1
  343. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +40 -43
  344. package/src/session-runtime/tool-policy/mode-switch.mjs +1 -1
  345. package/src/session-runtime/tool-policy/policy-stage.mjs +106 -8
  346. package/src/session-runtime/tool-policy/workflow-overlay.mjs +2 -1
  347. package/src/session-runtime/tool-policy-refresh.mjs +182 -5
  348. package/src/session-runtime/tool-surface.mjs +38 -1
  349. package/src/session-runtime/turn/agent-ops.mjs +10 -2
  350. package/src/session-runtime/turn/session-ops.mjs +2 -0
  351. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +5 -0
  352. package/src/session-runtime/turn/turn-run.mjs +1 -1
  353. package/src/session-runtime/webhook-session-run.mjs +2 -0
  354. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +1 -4
  355. package/src/session-runtime/workflow-agents-api/agent-route.mjs +3 -10
  356. package/src/session-runtime/workflow-agents-api/instruction-files.mjs +165 -0
  357. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +5 -4
  358. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +2 -3
  359. package/src/session-runtime/workflow-agents-api.mjs +2 -0
  360. package/src/session-runtime/workspace/project-branches.mjs +1 -1
  361. package/src/session-runtime/workspace/session-view.mjs +1 -1
  362. package/src/session-runtime/workspace/transitions.mjs +20 -4
  363. package/src/session-runtime/workspace/worktree-maintenance.mjs +126 -6
  364. package/src/session-runtime/workspace/worktree-setup.mjs +13 -1
  365. package/src/session-runtime/workspace/worktree-tool.mjs +16 -0
  366. package/src/standalone/daemon-agent-control.mjs +25 -8
  367. package/src/standalone/daemon-desktop-runtime.mjs +6 -2
  368. package/src/standalone/daemon-lag-profiler.mjs +7 -7
  369. package/src/standalone/daemon.mjs +54 -2
  370. package/src/standalone/local-session-runtime.mjs +16 -0
  371. package/src/standalone/session-client/daemon-unavailable.mjs +1 -1
  372. package/src/standalone/session-protocol.mjs +11 -0
  373. package/src/standalone/session-service/agent-route-follow.mjs +23 -20
  374. package/src/standalone/session-service/agent-tree/agent-turns.mjs +18 -6
  375. package/src/standalone/session-service/quota-resume.mjs +9 -0
  376. package/src/standalone/session-service/session-calls/session-action.mjs +51 -1
  377. package/src/standalone/session-service/session-calls/session-turns.mjs +37 -18
  378. package/src/standalone/session-service/session-calls/turn-gate.mjs +34 -0
  379. package/src/standalone/session-service/session-calls.mjs +2 -1
  380. package/src/tui/App.jsx +8 -0
  381. package/src/tui/app/clipboard.mjs +1 -1
  382. package/src/tui/app/create-app-pickers.mjs +2 -0
  383. package/src/tui/app/doctor.mjs +2 -2
  384. package/src/tui/app/panel-signature.mjs +1 -1
  385. package/src/tui/app/settings-picker.mjs +53 -8
  386. package/src/tui/app/shell-layout/row-budgets.mjs +2 -2
  387. package/src/tui/app/slash-commands.mjs +1 -1
  388. package/src/tui/app/slash-dispatch/route-commands.mjs +1 -1
  389. package/src/tui/app/slash-dispatch/session-commands.mjs +1 -1
  390. package/src/tui/app/use-prompt-queue-history.mjs +2 -2
  391. package/src/tui/app/use-transcript-activity.mjs +1 -1
  392. package/src/tui/app/use-transcript-window/geometry.mjs +1 -1
  393. package/src/tui/components/prompt-input/escape-policy.d.mts +0 -1
  394. package/src/tui/components/prompt-input/escape-policy.mjs +1 -1
  395. package/src/tui/components/question-panel-model.mjs +4 -4
  396. package/src/tui/components/tool-execution/standard-card.jsx +3 -4
  397. package/src/tui/components/user-message-skills.mjs +1 -1
  398. package/src/tui/dist/index.mjs +309 -30
  399. package/src/tui/markdown/render-ansi.mjs +1 -1
  400. package/src/tui/session/agent-job-feed/notification-router.mjs +10 -0
  401. package/src/tui/session/agent-job-feed.mjs +2 -0
  402. package/src/tui/session/context-state.mjs +3 -0
  403. package/src/tui/session/quota-resume.mjs +1 -1
  404. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  405. package/src/tui/session/session-api/lifecycle/session-reset.mjs +7 -0
  406. package/src/tui/session/session-api/routes-agent-definitions.mjs +5 -0
  407. package/src/tui/session/session-api/worktree.mjs +13 -1
  408. package/src/tui/session/session-flow/drain.mjs +11 -14
  409. package/src/tui/session/tool-card-results/grouped-result.mjs +2 -2
  410. package/src/tui/session/turn-quiet.mjs +1 -1
  411. package/src/tui/session/turn.mjs +3 -0
  412. package/src/tui/session/worktree-setup-notice.mjs +1 -1
  413. package/src/tui/spinner-meta.mjs +3 -3
  414. package/src/tui/spinner-verbs.mjs +2 -2
  415. package/src/defaults/skills/goal-management/SKILL.md +0 -169
  416. package/src/runtime/agent/orchestrator/runtime-core/goal-tasks.mjs +0 -156
  417. package/src/session-runtime/goal-tasks.mjs +0 -1
@@ -20,24 +20,21 @@
20
20
  * Further stacks are one STACKS entry each (see there). Java (Gradle, Maven),
21
21
  * Rust and Go fetch on their own and need nothing.
22
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.
23
+ * Nothing is shared with the main checkout: every artefact is installed in
24
+ * the worktree folder at the start of the session's first turn
25
+ * (setup-commands.mjs). A linked node_modules is never safe: `npm ci` lists
26
+ * the folder it finds and deletes each entry, which through a link (junction
27
+ * or symlink) are the main checkout's, and other installs prune or rebuild in
28
+ * place the same way. A folder whose artefact already exists in the worktree
29
+ * is left alone; while any is a link (made by hand, or by an earlier version
30
+ * and not yet detached, manager detachDependencyLinks), nothing in the
31
+ * worktree is installed, since an install can reach other folders than its
32
+ * own. A tool that is not on PATH is skipped with a note.
35
33
  */
36
34
  import { lstat, readFile } from 'node:fs/promises';
37
35
  import { join } from 'node:path';
38
36
  import { which } from '../../tidy/process.mjs';
39
37
  import { runGit } from './git.mjs';
40
- import { linkDirectory } from './include.mjs';
41
38
  import { runWorktreeSetupCommands } from './setup-commands.mjs';
42
39
 
43
40
  /** JS lockfile → package manager, in the order a folder with several picks one. */
@@ -86,9 +83,18 @@ async function yarnInstallFlag(tool, cwd, signal) {
86
83
  * globs committed basename globs it reads (`*` within a name), optional;
87
84
  * artefact the folder an install leaves: the main checkout having it
88
85
  * 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);
86
+ * reinstallable the artefact's whole contents come back from the install,
87
+ * so an automatic worktree removal may drop it (dependencyArtefacts);
88
+ * optional. node_modules is one by deliberate choice: hand edits
89
+ * inside it (say, before patch-package saved them) are NOT
90
+ * protected and go with the worktree — protecting it would stop
91
+ * the cleanup of nearly every JS worktree, which only removes
92
+ * merged, disposable or aged ones, and installs treat the folder
93
+ * as their output (npm ci deletes and rebuilds it). Not so: .venv (pip reads a pip.conf inside it, index
94
+ * credentials included), .bundle (local Bundler config, gem
95
+ * source credentials), vendor and deps (dev and git dependencies
96
+ * are git checkouts that can hold local commits), obj and
97
+ * .dart_tool (build and tool state an install does not restore);
92
98
  * pick(files) the folder's committed basenames → { manager, lockfile?, … }
93
99
  * or null;
94
100
  * install(match, { cwd, tool, signal })
@@ -105,7 +111,7 @@ const STACKS = [
105
111
  stack: 'js',
106
112
  names: ['package.json', ...JS_LOCKFILES.map(([name]) => name)],
107
113
  artefact: 'node_modules',
108
- linkable: true,
114
+ reinstallable: true,
109
115
  pick(files) {
110
116
  if (!files.has('package.json')) return null;
111
117
  const match = JS_LOCKFILES.find(([name]) => files.has(name));
@@ -289,29 +295,21 @@ function workspaceMatcher(patterns) {
289
295
  return (dir) => include.some((re) => re.test(dir)) && !exclude.some((re) => re.test(dir));
290
296
  }
291
297
 
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
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[] }>>}
299
+ * The folders (forward-slash, relative to `projectDir`, '' itself) holding
300
+ * committed files some stack reads, with those basenames; nothing inside
301
+ * node_modules. Empty when git cannot list them.
302
+ * @returns {Promise<Map<string, Set<string>>>}
305
303
  */
306
- export async function findLockfileFolders(projectDir, { mainDir = null, signal } = {}) {
304
+ async function committedFolders(projectDir, signal) {
307
305
  const pathspecs = [...STACK_NAMES, ...STACK_GLOBS].map((name) => `:(glob)**/${name}`);
308
306
  const result = await runGit(projectDir, ['-c', 'core.quotePath=false', 'ls-files', '-z', '--', ...pathspecs], {
309
307
  signal,
310
308
  readOnly: true,
311
309
  allowFailure: true,
312
310
  });
313
- if (result.code !== 0) return [];
314
311
  const folders = new Map();
312
+ if (result.code !== 0) return folders;
315
313
  for (const path of result.stdout.split('\0')) {
316
314
  const parts = path.split('/');
317
315
  const name = parts.pop();
@@ -320,6 +318,72 @@ export async function findLockfileFolders(projectDir, { mainDir = null, signal }
320
318
  if (!folders.has(dir)) folders.set(dir, new Set());
321
319
  folders.get(dir).add(name);
322
320
  }
321
+ return folders;
322
+ }
323
+
324
+ /** Whether `stack` reads the committed basename `name`. */
325
+ function stackReads(stack, name) {
326
+ return stack.names.includes(name) || (stack.globs ?? []).some((glob) => matchesSegment(glob, name));
327
+ }
328
+
329
+ function matchesSegment(glob, name) {
330
+ return new RegExp(`^${segmentPattern(glob)}$`, 'i').test(name);
331
+ }
332
+
333
+ /**
334
+ * Every installed artefact in `projectDir` that is a link (forward-slash
335
+ * paths): the artefact of each folder with committed files of its stack, so
336
+ * a node_modules beside every committed package.json, the workspace packages
337
+ * that share the root lockfile too. Only the artefact itself is examined
338
+ * (lstat), never what a link points at. An install reaches beyond its own
339
+ * folder (`npm ci` at the root resets every workspace package's
340
+ * node_modules), so any one blocks every install in the worktree.
341
+ * @returns {Promise<string[]>}
342
+ */
343
+ export async function linkedArtefacts(projectDir, { signal } = {}) {
344
+ const links = [];
345
+ for (const [dir, files] of await committedFolders(projectDir, signal)) {
346
+ for (const stack of STACKS) {
347
+ if (![...files].some((name) => stackReads(stack, name))) continue;
348
+ const info = await lstatOrNull(join(folderPath(projectDir, dir), stack.artefact));
349
+ if (info?.isSymbolicLink()) links.push([dir, stack.artefact].filter(Boolean).join('/'));
350
+ }
351
+ }
352
+ return links;
353
+ }
354
+
355
+ /**
356
+ * The artefacts an install restores whole in `projectDir` (forward-slash,
357
+ * relative to it): the artefact of every folder whose committed files a
358
+ * `reinstallable` stack installs from, wherever the folder is.
359
+ * @returns {Promise<string[]>}
360
+ */
361
+ export async function dependencyArtefacts(projectDir, { signal } = {}) {
362
+ const artefacts = [];
363
+ for (const [dir, files] of await committedFolders(projectDir, signal)) {
364
+ for (const stack of STACKS) {
365
+ if (stack.reinstallable && stack.pick(files)) artefacts.push([dir, stack.artefact].filter(Boolean).join('/'));
366
+ }
367
+ }
368
+ return artefacts;
369
+ }
370
+
371
+ /** Whether `dir` holds the installed `artefact`: a real directory, never a link. */
372
+ async function installedArtefact(dir, artefact) {
373
+ const info = await lstatOrNull(join(dir, artefact));
374
+ return !!info?.isDirectory() && !info.isSymbolicLink();
375
+ }
376
+
377
+ /**
378
+ * What to prepare (forward-slash `dir` relative to `projectDir`; '' is the
379
+ * project itself), root first: one entry per project folder and stack, with
380
+ * the manager its committed files select. `mainDir`: the same folder in the
381
+ * main checkout, whose installed artefacts admit nested folders.
382
+ * @returns {Promise<Array<{ dir: string, stack: string, manager: string, artefact: string,
383
+ * lockfile?: string, requirements?: string[] }>>}
384
+ */
385
+ export async function findLockfileFolders(projectDir, { mainDir = null, signal } = {}) {
386
+ const folders = await committedFolders(projectDir, signal);
323
387
  const isWorkspace = workspaceMatcher(await workspacePatterns(projectDir));
324
388
  const found = [];
325
389
  for (const [dir, files] of folders) {
@@ -329,56 +393,17 @@ export async function findLockfileFolders(projectDir, { mainDir = null, signal }
329
393
  const admitted =
330
394
  !dir || isWorkspace(dir) || (mainDir && (await installedArtefact(folderPath(mainDir, dir), stack.artefact)));
331
395
  if (admitted) {
332
- found.push({ dir, stack: stack.stack, artefact: stack.artefact, linkable: stack.linkable === true, ...match });
396
+ found.push({ dir, stack: stack.stack, artefact: stack.artefact, ...match });
333
397
  }
334
398
  }
335
399
  }
336
400
  return found.sort((a, b) => a.dir.split('/').length - b.dir.split('/').length || a.dir.localeCompare(b.dir));
337
401
  }
338
402
 
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
- }
403
+ const sharedClause = (count) =>
404
+ `${count > 1 ? 'are' : 'is'} shared from the main checkout by .worktreeinclude and not installed here`;
348
405
 
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
- }
406
+ const sharedNote = (artefact, where) => `${artefact} in ${where} ${sharedClause(1)}.`;
382
407
 
383
408
  /** Display text of a command (never run through a shell). */
384
409
  const displayLine = (tool, args) => `${tool} ${args.map(quote).join(' ')}`;
@@ -390,39 +415,53 @@ function commandLine(tool, { args, followUp }) {
390
415
  /**
391
416
  * What the first turn still has to do in the worktree folder `worktreeDir`:
392
417
  * 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).
418
+ * it leaves alone (`notes`: nothing found, tool not on PATH, an artefact that
419
+ * is a link it must not install through).
395
420
  * `mainDir`: the same folder in the main checkout (findLockfileFolders).
396
421
  * `env`: where the tools are looked up (PATH).
397
- * @param {{ worktreeDir: string, mainDir?: string|null, signal?: AbortSignal, env?: NodeJS.ProcessEnv }} input
422
+ * `shared`: artefacts (forward-slash, relative to `worktreeDir`) that are links
423
+ * the user asked for in .worktreeinclude; they are never installed through or
424
+ * replaced, and are reported as shared rather than as a link to remove.
425
+ * @param {{ worktreeDir: string, mainDir?: string|null, shared?: string[], signal?: AbortSignal, env?: NodeJS.ProcessEnv }} input
398
426
  */
399
- export async function planDependencySetup({ worktreeDir, mainDir = null, signal, env = process.env }) {
427
+ export async function planDependencySetup({ worktreeDir, mainDir = null, shared = [], signal, env = process.env }) {
400
428
  const folders = await findLockfileFolders(worktreeDir, { mainDir, signal });
401
429
  if (!folders.length) {
402
430
  return { commands: [], notes: ['No lockfile or dependency manifest found; nothing was installed.'] };
403
431
  }
404
432
  const commands = [];
405
433
  const notes = [];
406
- const linked = [];
434
+ // While any artefact is a link nothing is installed (linkedArtefacts).
435
+ const links = await linkedArtefacts(worktreeDir, { signal });
407
436
  for (const entry of folders) {
408
437
  const cwd = folderPath(worktreeDir, entry.dir);
409
438
  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
- }
439
+ const info = await lstatOrNull(join(cwd, entry.artefact));
440
+ if (info && !info.isSymbolicLink()) continue;
415
441
  const stack = STACKS.find((candidate) => candidate.stack === entry.stack);
416
442
  const named = await stack.install(entry, { cwd, signal });
417
443
  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.
444
+ if (info && shared.includes([entry.dir, entry.artefact].filter(Boolean).join('/'))) {
445
+ notes.push(sharedNote(entry.artefact, where));
446
+ continue;
447
+ }
448
+ if (info) {
421
449
  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).`
450
+ `${entry.artefact} in ${where} is a link, not an install of its own; ${where} was not prepared (replace it with \`${commandLine(named.tools[0], named)}\` there if tests need it).`
423
451
  );
424
452
  continue;
425
453
  }
454
+ const others = links.filter((rel) => !shared.includes(rel));
455
+ if (others.length) {
456
+ notes.push(
457
+ `${where} was not prepared while ${others.join(', ')} ${others.length > 1 ? 'are links' : 'is a link'} an install could reach through (run \`${commandLine(named.tools[0], named)}\` there once replaced if tests need it).`
458
+ );
459
+ continue;
460
+ }
461
+ if (links.length) {
462
+ notes.push(`${where} was not prepared; ${links.join(', ')} ${sharedClause(links.length)}.`);
463
+ continue;
464
+ }
426
465
  const found = named.tools.find((candidate) => which(candidate, { env }));
427
466
  if (!found) {
428
467
  notes.push(
@@ -451,6 +490,5 @@ export async function planDependencySetup({ worktreeDir, mainDir = null, signal,
451
490
  commands.push({ command: displayLine(`"${file}"`, args), file, args, cwd, label, ...specEnv });
452
491
  }
453
492
  }
454
- if (linked.length) notes.unshift(`Linked node_modules from the main checkout: ${linked.join(', ')}.`);
455
493
  return { commands, notes };
456
494
  }
@@ -4,9 +4,10 @@
4
4
  * space on the DESTINATION volume.
5
5
  */
6
6
  import { existsSync } from 'node:fs';
7
- import { statfs } from 'node:fs/promises';
8
- import { dirname, resolve } from 'node:path';
7
+ import { lstat, readdir, statfs } from 'node:fs/promises';
8
+ import { dirname, join, resolve } from 'node:path';
9
9
  import { runGit } from './git.mjs';
10
+ import { pathKey } from './paths.mjs';
10
11
 
11
12
  const MARGIN_MIN_BYTES = 64 * 1024 * 1024;
12
13
  const MARGIN_RATIO = 0.1;
@@ -44,6 +45,82 @@ export function requiredBytes(contentBytes) {
44
45
  return contentBytes + Math.max(MARGIN_MIN_BYTES, Math.ceil(contentBytes * MARGIN_RATIO));
45
46
  }
46
47
 
48
+ /**
49
+ * Bytes of the files under `path`. Links are not followed (linked dependency
50
+ * folders belong to the project folder); unreadable entries are skipped. The
51
+ * walk stops after `maxEntries`, reporting `complete: false` (a lower bound).
52
+ * @returns {Promise<{ bytes: number, complete: boolean }>}
53
+ */
54
+ async function folderBytes(path, { maxEntries = 250_000 } = {}) {
55
+ let bytes = 0;
56
+ let seen = 0;
57
+ const pending = [path];
58
+ while (pending.length) {
59
+ const dir = pending.pop();
60
+ let entries;
61
+ try {
62
+ entries = await readdir(dir, { withFileTypes: true });
63
+ } catch {
64
+ continue;
65
+ }
66
+ for (const entry of entries) {
67
+ if (++seen > maxEntries) return { bytes, complete: false };
68
+ const child = join(dir, entry.name);
69
+ if (entry.isDirectory()) pending.push(child);
70
+ else if (entry.isFile())
71
+ bytes += await lstat(child).then(
72
+ (info) => info.size,
73
+ () => 0
74
+ );
75
+ }
76
+ }
77
+ return { bytes, complete: true };
78
+ }
79
+
80
+ // path key → { bytes, complete, at, refresh }: the worktree list's sizes.
81
+ const sizeCache = new Map();
82
+ const FOLDER_SIZE_TTL_MS = 5 * 60_000;
83
+ // The repository's comparison key: canonical, case-folded only on Windows.
84
+ const sizeKey = (path, platform) => pathKey(path, platform);
85
+
86
+ /**
87
+ * folderBytes through a per-path cache: a fresh entry (younger than
88
+ * `ttlMs`) is returned as is; a stale one is returned at once while one
89
+ * background walk refreshes it; only a path never measured waits for its walk.
90
+ * forgetFolderSize drops an entry (the worktree was removed or created).
91
+ * @returns {Promise<{ bytes: number, complete: boolean, at: number }>}
92
+ */
93
+ export async function cachedFolderBytes(
94
+ path,
95
+ { ttlMs = FOLDER_SIZE_TTL_MS, measure = folderBytes, platform = process.platform } = {}
96
+ ) {
97
+ const key = sizeKey(path, platform);
98
+ const cached = sizeCache.get(key);
99
+ const walk = () => {
100
+ const refresh = measure(path).then(
101
+ (value) => {
102
+ const entry = { ...value, at: Date.now(), refresh: null };
103
+ if (sizeCache.get(key)?.refresh === refresh) sizeCache.set(key, entry);
104
+ return entry;
105
+ },
106
+ (error) => {
107
+ if (sizeCache.get(key)?.refresh === refresh) sizeCache.delete(key);
108
+ throw error;
109
+ }
110
+ );
111
+ sizeCache.set(key, { ...(cached ?? { bytes: 0, complete: false, at: 0 }), refresh });
112
+ return refresh;
113
+ };
114
+ if (!cached) return walk();
115
+ if (!cached.refresh && Date.now() - cached.at >= ttlMs) walk().catch(() => {});
116
+ if (!cached.at) return cached.refresh;
117
+ return { bytes: cached.bytes, complete: cached.complete, at: cached.at };
118
+ }
119
+
120
+ export function forgetFolderSize(path, platform = process.platform) {
121
+ if (typeof path === 'string' && path) sizeCache.delete(sizeKey(path, platform));
122
+ }
123
+
47
124
  /** Free bytes available to this user on the volume holding `path`. */
48
125
  export async function freeBytesAt(path) {
49
126
  let dir = resolve(path);
@@ -68,14 +68,16 @@ const READ_ONLY_FLAGS = ['--no-optional-locks', '-c', 'core.fsmonitor=false'];
68
68
  /**
69
69
  * @param {string} cwd
70
70
  * @param {string[]} args
71
- * @param {{ signal?: AbortSignal, timeoutMs?: number, maxBytes?: number, allowFailure?: boolean, readOnly?: boolean, env?: Record<string, string> }} [options]
71
+ * @param {{ signal?: AbortSignal, timeoutMs?: number, maxBytes?: number, allowFailure?: boolean, readOnly?: boolean, env?: Record<string, string>, waitForExitMs?: number }} [options]
72
72
  * `env`: variables set on top of the pinned environment (a temporary GIT_INDEX_FILE, a commit identity).
73
+ * `waitForExitMs`: a timeout rejects only once the killed git exited (or after that bound;
74
+ * the error's `childExited` says which).
73
75
  * @returns {Promise<{ code: number, stdout: string, stderr: string }>}
74
76
  */
75
77
  export async function runGit(
76
78
  cwd,
77
79
  args,
78
- { signal, timeoutMs, maxBytes, allowFailure = false, readOnly = false, env = null } = {}
80
+ { signal, timeoutMs, maxBytes, allowFailure = false, readOnly = false, env = null, waitForExitMs = 0 } = {}
79
81
  ) {
80
82
  throwIfAborted(signal);
81
83
  const result = await runGitOffThread([...(readOnly ? READ_ONLY_FLAGS : []), ...SCOPED_CONFIG, ...args], {
@@ -83,6 +85,7 @@ export async function runGit(
83
85
  env: env ? { ...gitEnvironment(), ...env } : gitEnvironment(),
84
86
  timeoutMs: timeoutMs ?? DEFAULT_TIMEOUT_MS,
85
87
  maxBytes: maxBytes ?? DEFAULT_MAX_BYTES,
88
+ waitForExitMs,
86
89
  });
87
90
  if (result.code !== 0 && !allowFailure) throw gitFailure(args, result);
88
91
  return result;
@@ -225,10 +228,14 @@ export async function branchCommit(cwd, branch, { signal } = {}) {
225
228
  * answer is not exactly two commit ids: the caller then reads each with
226
229
  * branchCommit(). */
227
230
  export async function branchCommitPair(cwd, first, second, { signal } = {}) {
228
- const result = await runGit(cwd, ['rev-parse', `refs/heads/${first}^{commit}`, `refs/heads/${second}^{commit}`, '--'], {
229
- signal,
230
- allowFailure: true,
231
- });
231
+ const result = await runGit(
232
+ cwd,
233
+ ['rev-parse', `refs/heads/${first}^{commit}`, `refs/heads/${second}^{commit}`, '--'],
234
+ {
235
+ signal,
236
+ allowFailure: true,
237
+ }
238
+ );
232
239
  const [a, b, dashes, ...rest] = String(result.stdout || '')
233
240
  .split(/\r?\n/)
234
241
  .map((line) => line.trim());
@@ -243,6 +250,23 @@ export async function localBranches(cwd, { signal, prefix = '' } = {}) {
243
250
  return lines(result.stdout);
244
251
  }
245
252
 
253
+ /**
254
+ * Every local branch with the committer time of its tip (ISO-8601), in
255
+ * refname order. Ref names cannot hold a tab, so it separates the fields.
256
+ * @returns {Promise<Array<{ name: string, committedAt: string }>>}
257
+ */
258
+ export async function localBranchCommits(cwd, { signal } = {}) {
259
+ const result = await runGit(
260
+ cwd,
261
+ ['for-each-ref', '--format=%(refname:short)%09%(committerdate:iso-strict)', 'refs/heads/'],
262
+ { signal }
263
+ );
264
+ return lines(result.stdout).map((line) => {
265
+ const tab = line.indexOf('\t');
266
+ return tab < 0 ? { name: line, committedAt: '' } : { name: line.slice(0, tab), committedAt: line.slice(tab + 1) };
267
+ });
268
+ }
269
+
246
270
  /** The local branch names the repository's remotes declare as their default (`refs/remotes/<remote>/HEAD`). */
247
271
  export async function remoteDefaultBranches(cwd) {
248
272
  const result = await runGit(cwd, ['for-each-ref', '--format=%(refname) %(symref)', 'refs/remotes'], {
@@ -307,18 +331,27 @@ export async function listWorktrees(cwd, { signal } = {}) {
307
331
  }
308
332
 
309
333
  /**
310
- * Paths reported by `git status --porcelain=v1 -z`.
334
+ * Entries `{ code, path }` reported by `git status --porcelain=v1 -z` (`!!`:
335
+ * an ignored path, a folder with a trailing slash).
311
336
  * @param {string} raw
312
337
  */
313
- export function parseStatusPaths(raw) {
338
+ export function parseStatusEntries(raw) {
314
339
  const fields = String(raw || '').split('\0');
315
- const paths = [];
340
+ const entries = [];
316
341
  for (let index = 0; index < fields.length; index++) {
317
342
  const field = fields[index];
318
343
  if (field.length < 4) continue;
319
344
  const code = field.slice(0, 2);
320
- paths.push(field.slice(3));
345
+ entries.push({ code, path: field.slice(3) });
321
346
  if (code.includes('R') || code.includes('C')) index++;
322
347
  }
323
- return paths;
348
+ return entries;
349
+ }
350
+
351
+ /**
352
+ * Paths reported by `git status --porcelain=v1 -z`.
353
+ * @param {string} raw
354
+ */
355
+ export function parseStatusPaths(raw) {
356
+ return parseStatusEntries(raw).map((entry) => entry.path);
324
357
  }
@@ -135,7 +135,7 @@ async function exists(path) {
135
135
  }
136
136
 
137
137
  /** Link the directory `source` at `dest`: a directory junction on Windows, a symlink elsewhere. */
138
- export function linkDirectory(source, dest) {
138
+ function linkDirectory(source, dest) {
139
139
  return symlink(source, dest, process.platform === 'win32' ? 'junction' : 'dir');
140
140
  }
141
141
 
@@ -559,14 +559,16 @@ export async function removeIncludeLinks(items) {
559
559
  /**
560
560
  * Put back the links removeIncludeLinks took away (those that record their
561
561
  * `source`) after a removal that did not go through, each with its recorded
562
- * `linkType` (a record without one — dependency links, older records — is a
563
- * directory link). A path that exists again is left alone.
562
+ * `linkType` (a record without one, an older record, is a directory link). A
563
+ * path that exists again is left alone. An earlier version's dependency link
564
+ * (node_modules into the main checkout) is never put back: a reset through
565
+ * it empties the main checkout's folder.
564
566
  * @returns {Promise<Array<{ path: string, error: string }>>} Links that could not be restored.
565
567
  */
566
568
  export async function restoreIncludeLinks(items) {
567
569
  const failures = [];
568
570
  for (const item of items || []) {
569
- if (item.mode !== 'link' || !item.source || item.status !== 'created') continue;
571
+ if (item.mode !== 'link' || !item.source || item.status !== 'created' || item.dependency) continue;
570
572
  if (await exists(item.path)) continue;
571
573
  try {
572
574
  await makeLinkOfType(item.linkType || 'directory', item.source, item.path);