mixdog 1.1.2 → 1.2.1

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 (424) hide show
  1. package/README.md +8 -5
  2. package/package.json +2 -1
  3. package/scripts/lib/run-node-tests.mjs +9 -0
  4. package/scripts/prune-desktop-runtime.mjs +3 -0
  5. package/scripts/test-direct.mjs +4 -2
  6. package/src/defaults/shipped-history.json +10 -0
  7. package/src/defaults/skills/pptx/SKILL.md +6 -6
  8. package/src/defaults/skills/pptx/references/review.md +6 -4
  9. package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
  10. package/src/defaults/skills/setup/references/actions.md +18 -5
  11. package/src/defaults/skills/visualize/SKILL.md +160 -0
  12. package/src/defaults/skills/visualize/references/craft.md +88 -0
  13. package/src/headless-command.mjs +56 -3
  14. package/src/headless-exec.mjs +17 -1
  15. package/src/help.mjs +2 -0
  16. package/src/rules/shared/60-verification.md +3 -1
  17. package/src/rules/shared/70-delivery.md +4 -2
  18. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  19. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
  21. package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
  22. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  23. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
  24. package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
  25. package/src/runtime/agent/orchestrator/config.mjs +19 -5
  26. package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
  27. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
  28. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
  29. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +133 -15
  30. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +55 -8
  31. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +86 -8
  32. package/src/runtime/agent/orchestrator/mcp/client.mjs +474 -164
  33. package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
  34. package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
  35. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
  36. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +108 -17
  37. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
  40. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -12
  41. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
  43. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
  44. package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
  45. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +444 -0
  46. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
  47. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
  49. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
  50. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +21 -6
  51. package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
  52. package/src/runtime/agent/orchestrator/providers/gemini.mjs +41 -28
  53. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  54. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  55. package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
  56. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
  57. package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
  58. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
  59. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
  60. package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
  61. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +82 -0
  62. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +556 -0
  63. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
  64. package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
  65. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
  66. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +15 -0
  67. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
  68. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +2 -1
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +15 -4
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
  76. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +4 -4
  77. package/src/runtime/agent/orchestrator/providers/registry.mjs +47 -7
  78. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
  79. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
  80. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -11
  81. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
  82. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
  83. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +278 -84
  85. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
  86. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
  87. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
  88. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -3
  89. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
  90. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
  91. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
  92. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
  93. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +268 -0
  94. package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
  95. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
  96. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
  97. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +24 -13
  98. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
  99. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
  100. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
  101. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
  102. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
  103. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
  104. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
  105. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
  106. package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
  107. package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
  108. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
  109. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
  110. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
  111. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
  112. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
  113. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
  114. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +5 -3
  115. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -4
  116. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +21 -10
  117. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
  118. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
  119. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -3
  120. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +15 -3
  121. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +22 -12
  122. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +9 -2
  123. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +9 -3
  124. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
  125. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
  126. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
  127. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +17 -4
  128. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
  129. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
  130. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
  131. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
  132. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
  133. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
  136. package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
  137. package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
  138. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +33 -22
  139. package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
  140. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
  141. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
  142. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
  143. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
  144. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +817 -0
  145. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +36 -0
  146. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +23 -12
  147. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
  148. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +202 -374
  149. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
  150. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
  151. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
  152. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +48 -9
  153. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +161 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
  155. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
  157. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
  158. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
  159. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
  160. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
  161. package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
  162. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +203 -33
  163. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
  164. package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
  165. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
  166. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
  167. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -68
  168. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
  169. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +20 -3
  170. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
  171. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
  172. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
  173. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
  174. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
  175. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
  176. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
  177. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
  178. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
  179. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
  180. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
  181. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
  182. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  183. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
  184. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
  185. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
  186. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
  187. package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
  188. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
  189. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
  190. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
  191. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
  192. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
  193. package/src/runtime/attachments/store.d.mts +3 -0
  194. package/src/runtime/attachments/store.mjs +38 -0
  195. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
  196. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
  197. package/src/runtime/channels/lib/worker-main.mjs +17 -14
  198. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
  199. package/src/runtime/effort-judge/judge-client.mjs +56 -18
  200. package/src/runtime/memory/lib/memory.mjs +1 -1
  201. package/src/runtime/memory/lib/session-ingest.mjs +2 -2
  202. package/src/runtime/shared/atomic-file.mjs +33 -6
  203. package/src/runtime/shared/best-effort-stderr.mjs +8 -0
  204. package/src/runtime/shared/code-graph-state.mjs +12 -0
  205. package/src/runtime/shared/defaults-separation.mjs +2 -0
  206. package/src/runtime/shared/extension-scopes.mjs +5 -3
  207. package/src/runtime/shared/file-lock.mjs +37 -11
  208. package/src/runtime/shared/git-runner-worker.mjs +3 -2
  209. package/src/runtime/shared/git-runner.mjs +4 -3
  210. package/src/runtime/shared/llm/anthropic-effort.mjs +14 -0
  211. package/src/runtime/shared/llm/default-context-window.mjs +6 -0
  212. package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
  213. package/src/runtime/shared/llm/model-catalog.mjs +240 -130
  214. package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
  215. package/src/runtime/shared/llm/provider-catalog-registry.mjs +162 -0
  216. package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
  217. package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
  218. package/src/runtime/shared/model-proxy-mode.mjs +19 -0
  219. package/src/runtime/shared/plugin-manifest.mjs +18 -9
  220. package/src/runtime/shared/pristine-execution-contract.json +9 -2
  221. package/src/runtime/shared/pristine-execution.mjs +28 -4
  222. package/src/runtime/shared/provider-accounts.mjs +125 -21
  223. package/src/runtime/shared/provider-api-key.mjs +33 -1
  224. package/src/runtime/shared/schedule-model-ref.mjs +11 -2
  225. package/src/runtime/shared/session-usage-totals.mjs +19 -0
  226. package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
  227. package/src/runtime/shared/shell-display.mjs +9 -3
  228. package/src/runtime/shared/singleton-owner.mjs +36 -6
  229. package/src/runtime/shared/skill-mentions.d.mts +10 -0
  230. package/src/runtime/shared/skill-mentions.mjs +133 -0
  231. package/src/runtime/shared/skill-script-runtime.mjs +264 -0
  232. package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
  233. package/src/runtime/shared/tool-primitives.mjs +35 -2
  234. package/src/runtime/shared/tool-surface.mjs +7 -0
  235. package/src/runtime/shared/tool-work-units.mjs +14 -0
  236. package/src/runtime/shared/transcript-metadata.mjs +17 -1
  237. package/src/runtime/shared/turn-changes.mjs +791 -0
  238. package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
  239. package/src/runtime/shared/turn-snapshot.mjs +690 -81
  240. package/src/runtime/shared/turn-worktree-snapshot.mjs +185 -15
  241. package/src/runtime/shared/worker-requests.mjs +15 -2
  242. package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
  243. package/src/runtime/shared/worktrees/contract.mjs +551 -0
  244. package/src/runtime/shared/worktrees/disk.mjs +57 -0
  245. package/src/runtime/shared/worktrees/git.mjs +247 -0
  246. package/src/runtime/shared/worktrees/include.mjs +480 -0
  247. package/src/runtime/shared/worktrees/lock.mjs +46 -0
  248. package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
  249. package/src/runtime/shared/worktrees/manager.mjs +1215 -0
  250. package/src/runtime/shared/worktrees/merge.mjs +182 -0
  251. package/src/runtime/shared/worktrees/owner.mjs +46 -0
  252. package/src/runtime/shared/worktrees/paths.mjs +62 -0
  253. package/src/runtime/shared/worktrees/registry.mjs +110 -0
  254. package/src/runtime/shared/worktrees/settings.mjs +84 -0
  255. package/src/runtime/shared/worktrees/slug.mjs +62 -0
  256. package/src/runtime/shared/worktrees/test-support.mjs +96 -0
  257. package/src/runtime/visualize-bridge/client.mjs +181 -0
  258. package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
  259. package/src/session-runtime/automation-agents.mjs +8 -2
  260. package/src/session-runtime/boot/apis.mjs +90 -0
  261. package/src/session-runtime/boot/config.mjs +7 -2
  262. package/src/session-runtime/boot/facade.mjs +21 -0
  263. package/src/session-runtime/boot/infrastructure.mjs +30 -18
  264. package/src/session-runtime/boot/session.mjs +1 -0
  265. package/src/session-runtime/boot/tools.mjs +4 -0
  266. package/src/session-runtime/config-lifecycle.mjs +10 -1
  267. package/src/session-runtime/conversation-import/discovery.mjs +698 -0
  268. package/src/session-runtime/conversation-import/service.mjs +369 -0
  269. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +21 -6
  270. package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
  271. package/src/session-runtime/global-extensions.mjs +2 -2
  272. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
  273. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
  274. package/src/session-runtime/internal-tool-executor.mjs +9 -1
  275. package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
  276. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
  277. package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
  278. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
  279. package/src/session-runtime/mcp-glue/connect.mjs +68 -16
  280. package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
  281. package/src/session-runtime/mcp-glue/server-catalog.mjs +122 -33
  282. package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
  283. package/src/session-runtime/mcp-glue.mjs +22 -2
  284. package/src/session-runtime/model-route/route-persist.mjs +3 -0
  285. package/src/session-runtime/model-route/set-route.mjs +15 -2
  286. package/src/session-runtime/new-session-config.mjs +3 -2
  287. package/src/session-runtime/notification-bus.mjs +18 -3
  288. package/src/session-runtime/provider-auth/accounts.mjs +33 -15
  289. package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
  290. package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
  291. package/src/session-runtime/provider-readiness.mjs +16 -10
  292. package/src/session-runtime/resource-extension-sync.mjs +45 -7
  293. package/src/session-runtime/resource-mcp-api.mjs +121 -84
  294. package/src/session-runtime/resource-plugins-api.mjs +29 -41
  295. package/src/session-runtime/resource-scope-decorate.mjs +6 -8
  296. package/src/session-runtime/resource-skills-api.mjs +23 -2
  297. package/src/session-runtime/route-resolution.mjs +10 -1
  298. package/src/session-runtime/route-state.mjs +16 -2
  299. package/src/session-runtime/runtime-core.mjs +5 -0
  300. package/src/session-runtime/runtime-facade.mjs +8 -0
  301. package/src/session-runtime/runtime-review-api.mjs +9 -0
  302. package/src/session-runtime/schedule-session-run.mjs +1 -1
  303. package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
  304. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
  305. package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
  306. package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
  307. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
  308. package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
  309. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
  310. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
  311. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  312. package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
  313. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
  314. package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
  315. package/src/session-runtime/services/agent-tool.mjs +1 -0
  316. package/src/session-runtime/services/channel-session-router.mjs +4 -1
  317. package/src/session-runtime/services/daemon-candidate.mjs +3 -2
  318. package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
  319. package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
  320. package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
  321. package/src/session-runtime/services/provider-admin.mjs +64 -18
  322. package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
  323. package/src/session-runtime/session-create/session-options.mjs +10 -1
  324. package/src/session-runtime/session-turn-api.mjs +1 -0
  325. package/src/session-runtime/settings-api.mjs +4 -2
  326. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
  327. package/src/session-runtime/settings-worktree-api.mjs +17 -0
  328. package/src/session-runtime/setup-tool/executor.mjs +23 -5
  329. package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
  330. package/src/session-runtime/setup-tool/settings-contract.mjs +4 -1
  331. package/src/session-runtime/setup-tool/tool-defs.mjs +38 -4
  332. package/src/session-runtime/skills-api.mjs +7 -3
  333. package/src/session-runtime/turn/agent-ops.mjs +12 -1
  334. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +14 -3
  335. package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
  336. package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
  337. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
  338. package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
  339. package/src/session-runtime/turn/turn-run.mjs +6 -7
  340. package/src/session-runtime/webhook-session-run.mjs +16 -4
  341. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
  342. package/src/session-runtime/workspace/admission-gate.mjs +197 -0
  343. package/src/session-runtime/workspace/project-branches.mjs +136 -0
  344. package/src/session-runtime/workspace/transition-steps.mjs +364 -0
  345. package/src/session-runtime/workspace/transitions.mjs +651 -0
  346. package/src/session-runtime/workspace/workspace-state.mjs +112 -0
  347. package/src/skill-script-entry.mjs +36 -0
  348. package/src/standalone/channel-transport/http-routes.mjs +17 -0
  349. package/src/standalone/channel-transport.mjs +13 -1
  350. package/src/standalone/daemon-desktop-runtime.mjs +1 -0
  351. package/src/standalone/daemon-owner.mjs +372 -0
  352. package/src/standalone/daemon-stored-session-views.mjs +15 -4
  353. package/src/standalone/daemon.mjs +179 -8
  354. package/src/standalone/desktop-service-registry.mjs +20 -0
  355. package/src/standalone/local-session-runtime.mjs +11 -0
  356. package/src/standalone/session-client/attachment-calls.mjs +3 -1
  357. package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
  358. package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
  359. package/src/standalone/session-client.mjs +104 -13
  360. package/src/standalone/session-protocol.mjs +21 -0
  361. package/src/standalone/session-proxy.mjs +3 -0
  362. package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
  363. package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
  364. package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
  365. package/src/standalone/session-service/projection/revisions.mjs +2 -0
  366. package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
  367. package/src/standalone/session-service/projection/workspace.mjs +14 -0
  368. package/src/standalone/session-service/session-calls/session-action.mjs +17 -4
  369. package/src/standalone/session-transport/call-route.mjs +13 -0
  370. package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
  371. package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
  372. package/src/standalone/session-transport/client-registry.mjs +20 -3
  373. package/src/standalone/session-transport/routes.mjs +79 -2
  374. package/src/standalone/session-transport/stack.mjs +2 -0
  375. package/src/standalone/session-transport.mjs +24 -1
  376. package/src/tui/App.jsx +9 -1
  377. package/src/tui/app/app-view/floating-panel.jsx +9 -1
  378. package/src/tui/app/app-view.jsx +6 -0
  379. package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
  380. package/src/tui/app/message-selector.mjs +2 -1
  381. package/src/tui/app/model-options.mjs +37 -5
  382. package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
  383. package/src/tui/app/model-picker.mjs +4 -2
  384. package/src/tui/app/skill-mention-palette.mjs +88 -0
  385. package/src/tui/app/slash-palette.mjs +23 -7
  386. package/src/tui/app/transcript-row-estimate.mjs +11 -3
  387. package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
  388. package/src/tui/app/use-prompt-state.mjs +5 -0
  389. package/src/tui/components/Message.jsx +22 -1
  390. package/src/tui/components/PromptInput.jsx +21 -3
  391. package/src/tui/components/SlashCommandPalette.jsx +12 -3
  392. package/src/tui/components/TranscriptItem.jsx +9 -1
  393. package/src/tui/components/prompt-input/key-handler.mjs +21 -8
  394. package/src/tui/components/user-message-skills.mjs +38 -0
  395. package/src/tui/dist/index.mjs +839 -207
  396. package/src/tui/index.jsx +10 -2
  397. package/src/tui/session/compaction-view.mjs +147 -0
  398. package/src/tui/session/context-state.mjs +6 -0
  399. package/src/tui/session/initial-state.mjs +3 -0
  400. package/src/tui/session/queue-helpers.mjs +7 -2
  401. package/src/tui/session/quota-resume.mjs +2 -1
  402. package/src/tui/session/session-api/commands.mjs +10 -2
  403. package/src/tui/session/session-api/controls.mjs +4 -0
  404. package/src/tui/session/session-api/extensions.mjs +4 -2
  405. package/src/tui/session/session-api/intake/rewind.mjs +2 -1
  406. package/src/tui/session/session-api/integrations.mjs +17 -0
  407. package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
  408. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
  409. package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
  410. package/src/tui/session/session-api/routes.mjs +10 -1
  411. package/src/tui/session/session-api/settings.mjs +9 -0
  412. package/src/tui/session/session-api/worktree.mjs +60 -0
  413. package/src/tui/session/session-api.mjs +2 -0
  414. package/src/tui/session/session-flow/auto-clear.mjs +31 -12
  415. package/src/tui/session/session-flow/drain.mjs +27 -3
  416. package/src/tui/session/session-flow/session-reset.mjs +4 -3
  417. package/src/tui/session/session-flow.mjs +2 -0
  418. package/src/tui/session/transcript-intake.mjs +14 -5
  419. package/src/tui/session/tui-steering-persist.mjs +25 -9
  420. package/src/tui/session/turn-stream.mjs +32 -4
  421. package/src/tui/session/turn-tool-cards.mjs +10 -2
  422. package/src/tui/session/turn.mjs +56 -10
  423. package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
  424. package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Keep coalesced child observations private until the caller's exact windows
3
+ * have been restored. A fresh child may outlive the batch's original cache
4
+ * generation; retain that child's lease, never renew the whole batch.
5
+ */
6
+ import { AsyncLocalStorage } from 'node:async_hooks';
7
+ import { canPublishReadCache, runReadCachePublication } from '../cache-layers.mjs';
8
+ import { fileVersion, sameFileVersion } from '../file-version.mjs';
9
+ import { mergeReadRanges } from '../read-ranges.mjs';
10
+
11
+ // Streamed reads have numbered rows but no total-line footer. Prefer the
12
+ // rows themselves so smart-elided gaps never become delivered evidence.
13
+ export function renderedBatchReadRanges(body) {
14
+ if (typeof body !== 'string') return [];
15
+ const rows = [...body.matchAll(/^(\d+)[\t│→]/gm)]
16
+ .map((match) => Number(match[1]))
17
+ .filter((line) => Number.isSafeInteger(line) && line > 0)
18
+ .map((line) => ({ startLine: line, endLine: line }));
19
+ if (rows.length) return mergeReadRanges(rows);
20
+ const footer = /\[lines\s+(\d+)-(\d+)\s+of\s+\d+/.exec(body);
21
+ return footer ? mergeReadRanges([{ startLine: Number(footer[1]), endLine: Number(footer[2]) }]) : [];
22
+ }
23
+
24
+ function hasRenderedLines(body) {
25
+ return renderedBatchReadRanges(body).length > 0;
26
+ }
27
+
28
+ export async function runBatchReadSnapshotPublication(executeChildBuiltinTool, helpers, run) {
29
+ const tasks = [];
30
+ const records = [];
31
+ const corrections = new Map();
32
+ let closed = false;
33
+
34
+ function capture(task, fullPath, st, scope, meta) {
35
+ if (closed) return;
36
+ const identity = fileVersion(st);
37
+ const record = {
38
+ fullPath,
39
+ scope: scope ?? null,
40
+ identity,
41
+ ranges: mergeReadRanges(meta?.ranges),
42
+ isCurrent: () => false,
43
+ publish: () => {},
44
+ release: () => {},
45
+ done: Promise.resolve(),
46
+ };
47
+ task.records.push(record);
48
+ records.push(record);
49
+ // Acquiring a new lease must not rehabilitate an already stale producer.
50
+ if (!identity || !canPublishReadCache(fullPath)) return;
51
+ const decision = Promise.withResolvers();
52
+ record.publish = (replacement = meta) => decision.resolve(replacement);
53
+ record.release = () => decision.resolve(null);
54
+ record.done = runReadCachePublication(fullPath, async () => {
55
+ record.isCurrent = AsyncLocalStorage.bind(() => canPublishReadCache(fullPath));
56
+ const selected = await decision.promise;
57
+ if (selected) helpers._recordReadSnapshot(fullPath, identity, scope, selected);
58
+ });
59
+ }
60
+
61
+ const executeChild = async (name, args, workDir, options = {}) => {
62
+ if (name !== 'read' || (args.mode && args.mode !== 'full')) {
63
+ return executeChildBuiltinTool(name, args, workDir, options);
64
+ }
65
+ const task = {
66
+ entry: args,
67
+ fullPath: helpers.resolveAgainstCwd(args.path, workDir),
68
+ records: [],
69
+ body: null,
70
+ };
71
+ tasks.push(task);
72
+ task.body = await executeChildBuiltinTool(name, args, workDir, {
73
+ ...options,
74
+ _readSnapshotRecorder: (...snapshot) => capture(task, ...snapshot),
75
+ });
76
+ return task.body;
77
+ };
78
+ const snapshotHelpers = {
79
+ ...helpers,
80
+ _filterReadSnapshotRanges: (entry, fullPath, scope, ranges) => {
81
+ const task = tasks.find((task) => task.entry === entry);
82
+ const observation = task?.records.findLast(
83
+ (record) => record.fullPath === fullPath && record.scope === (scope ?? null)
84
+ );
85
+ // A numbered row may end halfway through its content at the byte cap.
86
+ // Only this slot's producer can certify complete lines; another read of
87
+ // the same path must not authorize hashing this slot's partial text.
88
+ return mergeReadRanges(ranges.flatMap((range) => (observation?.ranges ?? []).map((complete) => ({
89
+ startLine: Math.max(range.startLine, complete.startLine),
90
+ endLine: Math.min(range.endLine, complete.endLine),
91
+ }))));
92
+ },
93
+ _recordReadSnapshot: (fullPath, _st, scope, meta) => {
94
+ corrections.set(fullPath, { scope: scope ?? null, meta });
95
+ },
96
+ };
97
+
98
+ try {
99
+ const result = await run(executeChild, snapshotHelpers);
100
+ // reject_partial refuses the entire payload: staged bodies were not
101
+ // delivered, so none of their snapshots may become session evidence.
102
+ if (result?.isError === true || (typeof result === 'string' && helpers.classifyResultKind(result) === 'error')) {
103
+ return result;
104
+ }
105
+ for (const [fullPath, correction] of corrections) {
106
+ const delivered = tasks.filter((task) => task.fullPath === fullPath && hasRenderedLines(task.body));
107
+ const observations = delivered.map((task) => task.records.findLast(
108
+ (record) => record.fullPath === fullPath && record.scope === correction.scope
109
+ ));
110
+ // Never combine different versions or bless stale/missing observations.
111
+ // The eventual recorder rechecks the retained lease after all awaits.
112
+ if (observations.length && observations.every((record) =>
113
+ record?.isCurrent() && sameFileVersion(record.identity, observations[0].identity)
114
+ )) {
115
+ observations[0].publish(correction.meta);
116
+ }
117
+ }
118
+ // Rich media/binary results have no line slices. Preserve their original
119
+ // evidence, still under the producer's lease rather than the batch's.
120
+ for (const task of tasks) {
121
+ if (hasRenderedLines(task.body) || task.body?.isError === true ||
122
+ (typeof task.body === 'string' && helpers.classifyResultKind(task.body) === 'error')) continue;
123
+ for (const record of task.records) {
124
+ if (!corrections.has(record.fullPath)) record.publish();
125
+ }
126
+ }
127
+ for (const record of records) record.release();
128
+ await Promise.all(records.map((record) => record.done));
129
+ return result;
130
+ } finally {
131
+ // Promise.all in the dispatcher may reject while other children remain
132
+ // active. Late captures then become no-ops and cannot leak a held lease.
133
+ closed = true;
134
+ for (const record of records) record.release();
135
+ await Promise.allSettled(records.map((record) => record.done));
136
+ }
137
+ }
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { readFile } from 'node:fs/promises';
8
8
  import { rangeTextOf } from '../snapshot-helpers.mjs';
9
+ import { renderedBatchReadRanges } from './batch-publication.mjs';
9
10
 
10
11
  // Coalesced batch reads fetch the union window from disk; every caller slot
11
12
  // must be sliced back to its original request window (_orig*), not the
@@ -32,22 +33,22 @@ export function restoreCallerSlots(results, origEntries, entryMap, options, { sl
32
33
  function deliveredRangesByPath(
33
34
  orderedResults,
34
35
  workDir,
35
- { classifyResultKind, resolveAgainstCwd, _rangeHashesFromRenderedReadText }
36
+ readStateScope,
37
+ { classifyResultKind, resolveAgainstCwd, _rangeHashesFromRenderedReadText, _filterReadSnapshotRanges }
36
38
  ) {
37
39
  const exactRangesByPath = new Map();
38
40
  const rangeHashesByPath = new Map();
39
41
  for (const r of orderedResults) {
40
42
  if (r?.mode !== 'full' || classifyResultKind(String(r.body || '')) === 'error') continue;
41
- const m = String(r.body || '').match(/\[lines\s+(\d+)-(\d+)\s+of\s+(\d+)/);
42
- if (!m) continue;
43
- const startLine = Number(m[1]);
44
- const endLine = Number(m[2]);
45
- if (!Number.isFinite(startLine) || !Number.isFinite(endLine)) continue;
46
43
  const fullPath = resolveAgainstCwd(r.path, workDir);
44
+ const renderedRanges = renderedBatchReadRanges(r.body);
45
+ const ranges = _filterReadSnapshotRanges
46
+ ? _filterReadSnapshotRanges(r.readEntry, fullPath, readStateScope, renderedRanges)
47
+ : renderedRanges;
48
+ if (ranges.length === 0) continue;
47
49
  if (!exactRangesByPath.has(fullPath)) exactRangesByPath.set(fullPath, []);
48
- const range = { startLine, endLine };
49
- exactRangesByPath.get(fullPath).push(range);
50
- const renderedHashes = _rangeHashesFromRenderedReadText(r.body, [range]);
50
+ exactRangesByPath.get(fullPath).push(...ranges);
51
+ const renderedHashes = _rangeHashesFromRenderedReadText(r.body, ranges);
51
52
  if (renderedHashes.length > 0) {
52
53
  if (!rangeHashesByPath.has(fullPath)) rangeHashesByPath.set(fullPath, []);
53
54
  rangeHashesByPath.get(fullPath).push(...renderedHashes);
@@ -68,7 +69,7 @@ async function rawRangeHashes(fullPath, mergedRanges, { _hashText }) {
68
69
 
69
70
  export async function recordSlicedBatchSnapshots(orderedResults, workDir, readStateScope, helpers) {
70
71
  const { _mergeReadRanges, _recordReadSnapshot } = helpers;
71
- const { exactRangesByPath, rangeHashesByPath } = deliveredRangesByPath(orderedResults, workDir, helpers);
72
+ const { exactRangesByPath, rangeHashesByPath } = deliveredRangesByPath(orderedResults, workDir, readStateScope, helpers);
72
73
  for (const [fullPath, ranges] of exactRangesByPath) {
73
74
  const mergedRanges = _mergeReadRanges(ranges);
74
75
  let rangeHashes = rangeHashesByPath.get(fullPath) || [];
@@ -18,6 +18,8 @@ import { normalizeObjectBatchArgs, batchEntriesFromArgs, primaryReadIndexes } fr
18
18
  import { dispatchBatchReads } from './read-tool/batch-dispatch.mjs';
19
19
  import { restoreCallerSlots, recordSlicedBatchSnapshots } from './read-tool/batch-snapshot.mjs';
20
20
  import { renderBatchResults } from './read-tool/batch-render.mjs';
21
+ import { runBatchReadSnapshotPublication } from './read-tool/batch-publication.mjs';
22
+ import { runReadCachePublication } from './cache-layers.mjs';
21
23
 
22
24
  function hasLineCoordinate(path) {
23
25
  return typeof path === 'string' && /(?:#L\d+|:\d+(?:-\d+)?(?::|$))/i.test(path);
@@ -54,17 +56,25 @@ async function executeBatchRead(args, workDir, readStateScope, executeChildBuilt
54
56
  const origEntries = Array.isArray(args._readsOrigEntries) ? args._readsOrigEntries : null;
55
57
  const entryMap = Array.isArray(args._readsEntryToDeduped) ? args._readsEntryToDeduped : null;
56
58
  const readIndexFor = primaryReadIndexes(entries, overrides, (p) => helpers.resolveAgainstCwd(p, workDir));
57
- const results = await dispatchBatchReads({
58
- entries,
59
- readIndexFor,
60
- workDir,
61
- options,
62
- helpers,
63
- executeChildBuiltinTool,
64
- });
65
- const orderedResults = origEntries ? restoreCallerSlots(results, origEntries, entryMap, options, helpers) : results;
66
- if (origEntries) await recordSlicedBatchSnapshots(orderedResults, workDir, readStateScope, helpers);
67
- return renderBatchResults(orderedResults, args, helpers);
59
+ const run = async (executeChild = executeChildBuiltinTool, snapshotHelpers = helpers) => {
60
+ const results = await dispatchBatchReads({
61
+ entries,
62
+ readIndexFor,
63
+ workDir,
64
+ options,
65
+ helpers,
66
+ executeChildBuiltinTool: executeChild,
67
+ });
68
+ const orderedResults = origEntries ? restoreCallerSlots(results, origEntries, entryMap, options, helpers) : results;
69
+ if (origEntries) await recordSlicedBatchSnapshots(orderedResults, workDir, readStateScope, snapshotHelpers);
70
+ return renderBatchResults(orderedResults, args, helpers);
71
+ };
72
+ if (!origEntries) return run();
73
+ // Keep the original batch lease for glance/media paths outside the scalar
74
+ // pipeline. Sliced evidence uses the producing child's retained lease, so
75
+ // a hash rejection never requires renewing this potentially stale parent.
76
+ const paths = entries.filter((entry) => entry?.path).map((entry) => helpers.resolveAgainstCwd(entry.path, workDir));
77
+ return runReadCachePublication(paths, () => runBatchReadSnapshotPublication(executeChildBuiltinTool, helpers, run));
68
78
  }
69
79
 
70
80
  // Glance modes served by their own child tool: the tool name and the args it
@@ -102,7 +102,7 @@ function findRepositoryRoot(startDirectory, { maxDepth = 64 } = {}) {
102
102
  const marker = pathJoin(current, '.git');
103
103
  try {
104
104
  const stats = statSync(marker);
105
- return { root: current, gitDirectory: stats.isDirectory() ? marker : null };
105
+ return { root: current, gitDirectory: stats.isDirectory() ? marker : _linkedGitDirectory(marker, current) };
106
106
  } catch {}
107
107
  const parent = pathDirname(current);
108
108
  if (parent === current) break;
@@ -111,6 +111,17 @@ function findRepositoryRoot(startDirectory, { maxDepth = 64 } = {}) {
111
111
  return null;
112
112
  }
113
113
 
114
+ // A linked worktree's `.git` file names its own git directory (`gitdir: …`),
115
+ // which holds that worktree's HEAD.
116
+ function _linkedGitDirectory(markerFile, root) {
117
+ try {
118
+ const target = /^gitdir:\s*(.+)$/m.exec(readFileSync(markerFile, 'utf8'))?.[1]?.trim();
119
+ return target ? pathResolveAbsolute(root, target) : null;
120
+ } catch {
121
+ return null;
122
+ }
123
+ }
124
+
114
125
  function _headBranch(gitDirectory) {
115
126
  if (!gitDirectory) return null;
116
127
  try {
@@ -254,8 +254,8 @@ function absolutePaths(lines, rgCwd) {
254
254
  return lines.map((line) => (isAbsolute(line) ? line : resolveAgainstCwd(line, rgCwd)));
255
255
  }
256
256
 
257
- function missingGlobRoot(scan, root, rgCwd, err) {
258
- const hint = buildNotFoundHint(scan.workDir, rgCwd, 'Search', err?.code);
257
+ async function missingGlobRoot(scan, root, rgCwd, err) {
258
+ const hint = await buildNotFoundHint(scan.workDir, rgCwd, 'Search', err?.code);
259
259
  return {
260
260
  error: `path does not exist: ${normalizeOutputPath(rgCwd)} (${err?.code || 'ENOENT'})${finalizeReadFamilyEnoentTail(hint, root, err?.code)}`,
261
261
  paths: [],
@@ -422,14 +422,14 @@ function globMoreSuffix({ rgWindowIncomplete, remaining, totalBeforeOffset, next
422
422
  return '';
423
423
  }
424
424
 
425
- function globEmptyDiagnostic(scan, { totalBeforeOffset }) {
425
+ async function globEmptyDiagnostic(scan, { totalBeforeOffset }) {
426
426
  const { patterns, basePath, baseEntries, workDir, statCache, offset } = scan;
427
427
  const patternStr = patterns.length === 1 ? JSON.stringify(patterns[0]) : JSON.stringify(patterns);
428
428
  const baseLabel = normalizeOutputPath(basePath);
429
429
  if (totalBeforeOffset > 0 && offset >= totalBeforeOffset) {
430
430
  return `(no entries after offset=${offset}; total=${totalBeforeOffset}) pattern=${patternStr} path=${baseLabel}`;
431
431
  }
432
- const diagnostic = basePathDiagnostic(
432
+ const diagnostic = await basePathDiagnostic(
433
433
  baseEntries.map((e) => e.root),
434
434
  workDir,
435
435
  statCache
@@ -473,7 +473,7 @@ async function scanGlobGroups(scan) {
473
473
  const shown = await directAbsoluteMatches({ patterns, headLimit, offset });
474
474
  if (shown.length > 0) return patternCapNote + shown.join('\n');
475
475
  }
476
- const out = patternCapNote + renderGlobPage(scan, merged, { capped, remaining, totalBeforeOffset, miss });
476
+ const out = patternCapNote + (await renderGlobPage(scan, merged, { capped, remaining, totalBeforeOffset, miss }));
477
477
  settleGlobScanCache(scan, merged, out, remaining);
478
478
  reportToolProgress(options, `found ${totalBeforeOffset} files`);
479
479
  return out;
@@ -482,7 +482,7 @@ async function scanGlobGroups(scan) {
482
482
  // The page body with its more/integrity suffixes. Empty partials are not
483
483
  // proof of absence: diagnostics are preserved even when there is no path
484
484
  // body, rather than silently dropping the integrity suffix.
485
- function renderGlobPage(scan, merged, { capped, remaining, totalBeforeOffset, miss }) {
485
+ async function renderGlobPage(scan, merged, { capped, remaining, totalBeforeOffset, miss }) {
486
486
  const { offset } = scan;
487
487
  const errSuffix = globIntegritySuffix(merged);
488
488
  const moreSuffix = globMoreSuffix({
@@ -492,7 +492,7 @@ function renderGlobPage(scan, merged, { capped, remaining, totalBeforeOffset, mi
492
492
  nextOffset: offset + capped.length,
493
493
  });
494
494
  if (capped.length > 0) return `${capped.join('\n')}${moreSuffix}${errSuffix}`;
495
- const emptyDiag = miss ? globEmptyDiagnostic(scan, { totalBeforeOffset }) : '';
495
+ const emptyDiag = miss ? await globEmptyDiagnostic(scan, { totalBeforeOffset }) : '';
496
496
  const emptyBody = (emptyDiag || '(no files found)').replace(
497
497
  '(no files found)',
498
498
  merged.rgStdoutPartial || merged.rgWindowIncomplete ? '(no files found yet)' : '(no files found)'
@@ -608,9 +608,12 @@ export async function executeGlobTool(args, workDir, options = {}) {
608
608
  };
609
609
  return await runScopedSearchInFlight(
610
610
  cacheKey,
611
- ({ signal: sharedSignal, scopedCacheOutcome }) => scanGlobGroups({
612
- ...scan, sharedSignal, options: { ...options, scopedCacheOutcome },
613
- }),
611
+ ({ signal: sharedSignal, scopedCacheOutcome }) =>
612
+ scanGlobGroups({
613
+ ...scan,
614
+ sharedSignal,
615
+ options: { ...options, scopedCacheOutcome },
616
+ }),
614
617
  { signal: options?.signal || options?.abortSignal || null, scopes },
615
618
  options?.scopedCacheOutcome
616
619
  );
@@ -211,7 +211,7 @@ async function runGrepSearch(request, sharedSignal) {
211
211
 
212
212
  async function grepMissingPathResult({ workDir, grepResolvedPath, searchPath, executeChildBuiltinTool }, err) {
213
213
  const msg = `Error: path does not exist: ${normalizeOutputPath(grepResolvedPath)} (${err?.code || 'ENOENT'})`;
214
- let hint = buildNotFoundHintBody(workDir, grepResolvedPath, 'Search', err?.code, {});
214
+ let hint = await buildNotFoundHintBody(workDir, grepResolvedPath, 'Search', err?.code, {});
215
215
  if (!hint) hint = await _suggestIndexedPaths(grepResolvedPath, executeChildBuiltinTool, workDir);
216
216
  return msg + finalizeReadFamilyEnoentTail(appendNotFoundCwdNote(hint, workDir, err?.code), searchPath, err?.code);
217
217
  }
@@ -65,10 +65,10 @@ const ENOENT_ABSENT =
65
65
 
66
66
  // Share diagnostic lookup results within one invocation without changing
67
67
  // the requested operand or executing a replacement operation.
68
- function cachedFileByBasename(workDir, missingPath, cache) {
68
+ async function cachedFileByBasename(workDir, missingPath, cache) {
69
69
  if (!cache) return findFileByBasename(workDir, missingPath);
70
70
  if (!('fileHits' in cache)) cache.fileHits = findFileByBasename(workDir, missingPath);
71
- return cache.fileHits;
71
+ return await cache.fileHits;
72
72
  }
73
73
 
74
74
  // A missing ABSOLUTE path that resolves outside the project cannot be
@@ -180,15 +180,15 @@ export function appendNotFoundCwdNote(hint, workDir, errCode = 'ENOENT') {
180
180
  return `${hint} (relative paths resolve from ${normalizeOutputPath(workDir)})`;
181
181
  }
182
182
 
183
- export function buildNotFoundHint(workDir, missingPath, actionVerb, errCode = 'ENOENT', cache = null) {
183
+ export async function buildNotFoundHint(workDir, missingPath, actionVerb, errCode = 'ENOENT', cache = null) {
184
184
  return appendNotFoundCwdNote(
185
- buildNotFoundHintBody(workDir, missingPath, actionVerb, errCode, cache),
185
+ await buildNotFoundHintBody(workDir, missingPath, actionVerb, errCode, cache),
186
186
  workDir,
187
187
  errCode
188
188
  );
189
189
  }
190
190
 
191
- export function buildNotFoundHintBody(workDir, missingPath, actionVerb, errCode = 'ENOENT', cache = null) {
191
+ export async function buildNotFoundHintBody(workDir, missingPath, actionVerb, errCode = 'ENOENT', cache = null) {
192
192
  if (!NOT_FOUND_CODES.has(String(errCode || 'ENOENT'))) return '';
193
193
  // `C:tmpsmp` shape: a Windows path whose separators were eaten by JSON
194
194
  // escaping (`"C:\\tmp\\smp"` written as `"C:\tmp\smp"`). Name the cause —
@@ -200,12 +200,12 @@ export function buildNotFoundHintBody(workDir, missingPath, actionVerb, errCode
200
200
  if (missingPathIsOutsideProject(workDir, missingPath)) {
201
201
  return outsideProjectNotFoundHint(missingPath, actionVerb);
202
202
  }
203
- const elsewhere = cachedFileByBasename(workDir, missingPath, cache);
203
+ const elsewhere = await cachedFileByBasename(workDir, missingPath, cache);
204
204
  if (elsewhere.length) {
205
205
  return ` Not found at this path; the same filename exists at: ${elsewhere.map((p) => `"${normalizeOutputPath(p)}"`).join(', ')}. ${actionVerb} that path directly.`;
206
206
  }
207
207
  if (isDirectoryPathGuess(missingPath)) {
208
- const dirHits = findDirectoryByBasename(workDir, missingPath, { limit: 5 });
208
+ const dirHits = await findDirectoryByBasename(workDir, missingPath, { limit: 5 });
209
209
  if (dirHits.length > 1) {
210
210
  return ` Not found at this path; same-named directories exist at: ${dirHits
211
211
  .slice(0, 5)
@@ -268,9 +268,9 @@ export function uncRefusalMessage(toolName, original, resolved) {
268
268
  return `Error: ${toolName} refuses UNC/SMB path ${JSON.stringify(shown)}; remote share access is blocked to prevent NTLM credential leaks`;
269
269
  }
270
270
 
271
- export function basePathDiagnostic(basePaths, workDir, statCache = null) {
272
- return basePaths
273
- .map((basePath) => {
271
+ export async function basePathDiagnostic(basePaths, workDir, statCache = null) {
272
+ const parts = await Promise.all(
273
+ basePaths.map(async (basePath) => {
274
274
  const resolved = resolveSearchScope(basePath, workDir);
275
275
  // Reuse the caller's call-scoped stat cache (glob preflight + per-group
276
276
  // rg runs already stat'd this same resolved root) instead of re-stating.
@@ -291,8 +291,13 @@ export function basePathDiagnostic(basePaths, workDir, statCache = null) {
291
291
  }
292
292
  return (
293
293
  `${normalizeOutputPath(basePath)}: path does not exist (${err?.code || 'ENOENT'})` +
294
- finalizeReadFamilyEnoentTail(buildNotFoundHint(workDir, resolved, 'Search', err?.code), basePath, err?.code)
294
+ finalizeReadFamilyEnoentTail(
295
+ await buildNotFoundHint(workDir, resolved, 'Search', err?.code),
296
+ basePath,
297
+ err?.code
298
+ )
295
299
  );
296
300
  })
297
- .join('; ');
301
+ );
302
+ return parts.join('; ');
298
303
  }
@@ -180,7 +180,10 @@ async function waitForShellTask(task, taskId, args, options) {
180
180
  return interruptedByUser;
181
181
  }
182
182
 
183
- async function cancelShellTask(task, taskId, options) {
183
+ // Outcome of a shell cancel: `outcome` is 'cancelled' only when the process is
184
+ // confirmed gone (or never had one), 'unconfirmed' when the kill was delivered
185
+ // but the exit could not be confirmed, 'not-found' for an unknown task.
186
+ async function cancelShellTaskOutcome(task, taskId, options) {
184
187
  // A tracked task with no native job is still cancellable: the
185
188
  // registry's own cancel hook owns it (descendants that outlived
186
189
  // their shell). Remember that it WAS live so the outcome is
@@ -196,11 +199,14 @@ async function cancelShellTask(task, taskId, options) {
196
199
  if (confirmed && confirmed.status === 'running') {
197
200
  // Watcher and notify context stay armed: the eventual exit
198
201
  // must still reach the owner.
199
- return [
200
- 'status: cancel-unconfirmed',
201
- `task_id: ${taskId}`,
202
- 'the cancellation was delivered but the process has not exited yet. It stays tracked and its completion is still delivered; re-run task cancel or task read to check again.',
203
- ].join('\n');
202
+ return {
203
+ outcome: 'unconfirmed',
204
+ text: [
205
+ 'status: cancel-unconfirmed',
206
+ `task_id: ${taskId}`,
207
+ 'the cancellation was delivered but the process has not exited yet. It stays tracked and its completion is still delivered; re-run task cancel or task read to check again.',
208
+ ].join('\n'),
209
+ };
204
210
  }
205
211
  if (!confirmed) {
206
212
  // No terminal event and no observable task: absence of
@@ -209,19 +215,56 @@ async function cancelShellTask(task, taskId, options) {
209
215
  cancelBackgroundShellJobWatch(taskId);
210
216
  clearShellJobNotifyCtx(taskId);
211
217
  cancelBackgroundTask(taskId, 'cancelled by task control; termination unconfirmed');
212
- return [
213
- 'status: cancel-unconfirmed',
214
- `task_id: ${taskId}`,
215
- 'the cancellation was delivered but this task is no longer observable, so its termination could not be confirmed. Verify the process directly if it must be gone.',
216
- ].join('\n');
218
+ return {
219
+ outcome: 'unconfirmed',
220
+ text: [
221
+ 'status: cancel-unconfirmed',
222
+ `task_id: ${taskId}`,
223
+ 'the cancellation was delivered but this task is no longer observable, so its termination could not be confirmed. Verify the process directly if it must be gone.',
224
+ ].join('\n'),
225
+ };
217
226
  }
218
227
  }
219
228
  cancelBackgroundShellJobWatch(taskId);
220
229
  clearShellJobNotifyCtx(taskId);
221
230
  cancelBackgroundTask(taskId, 'cancelled by task control');
222
231
  return job || wasRunning
223
- ? renderTaskCancelSuccess(taskId, getBackgroundTask(taskId, { context: options }) || task)
224
- : buildJobNotFoundMessage(taskId);
232
+ ? {
233
+ outcome: 'cancelled',
234
+ text: renderTaskCancelSuccess(taskId, getBackgroundTask(taskId, { context: options }) || task),
235
+ }
236
+ : { outcome: 'not-found', text: buildJobNotFoundMessage(taskId) };
237
+ }
238
+
239
+ async function cancelShellTask(task, taskId, options) {
240
+ return (await cancelShellTaskOutcome(task, taskId, options)).text;
241
+ }
242
+
243
+ /**
244
+ * Stop one BACKGROUND shell job of the caller's session (desktop stop button).
245
+ * The same cancel the `task` tool runs, with a structured answer: `stopped` is
246
+ * true only when the job was actually cancelled. Only promoted/async jobs hold
247
+ * a background task, so a foreground command can never match; the session
248
+ * scope is the caller's, so another session's job reads as unknown.
249
+ */
250
+ export async function stopBackgroundShellJob(taskId, options = {}) {
251
+ const id = typeof taskId === 'string' ? taskId.trim() : '';
252
+ if (!id) return { stopped: false, error: 'task_id is required' };
253
+ const task = getBackgroundTask(id, { context: options });
254
+ if (!task || task.surface !== 'shell') return { stopped: false, error: `task not found: ${id}` };
255
+ const job = peekShellJob(id);
256
+ const status = job ? job.status : task.status;
257
+ if (status !== 'running') return { stopped: false, error: `task already ${status}: ${id}` };
258
+ try {
259
+ const { outcome, text } = await cancelShellTaskOutcome(task, id, options);
260
+ if (outcome === 'cancelled') return { stopped: true };
261
+ return {
262
+ stopped: false,
263
+ error: outcome === 'unconfirmed' ? `cancellation not confirmed: ${id}` : text.replace(/^Error:\s*/, ''),
264
+ };
265
+ } catch (error) {
266
+ return { stopped: false, error: error instanceof Error ? error.message : String(error) };
267
+ }
225
268
  }
226
269
 
227
270
  export async function executeTaskTool(args, options = {}) {
@@ -573,6 +573,64 @@ export function listCachedCodeGraphRoots() {
573
573
  }
574
574
  }
575
575
 
576
+ // Remove one root (exact canonical match) from the disk cache: its resident
577
+ // entry, its staged call-site sidecar, its manifest row and its <hash>.json /
578
+ // <hash>.calls.json files. The resident drop is synchronous and happens first,
579
+ // so a flush already scheduled for other roots can no longer write this one
580
+ // back; the manifest is edited under the same cross-process lock the persist
581
+ // path takes, so a concurrent writer merges the result instead of restoring
582
+ // the row. Never creates the cache directory. Returns { removed, kept }; kept
583
+ // are cache files that matched but could not be deleted.
584
+ export function forgetDiskCodeGraphRoot(root) {
585
+ const key = _canonicalGraphCwd(root);
586
+ const removed = [];
587
+ const kept = [];
588
+ if (_diskCodeGraphCache.has(key)) removed.push(`resident:${key}`);
589
+ _dropDiskEntry(key);
590
+ if (_pendingCallsSidecars.delete(key)) removed.push(`pending-calls-sidecar:${key}`);
591
+ const dir = _codeGraphDiskDir();
592
+ if (!existsSync(dir)) return { removed, kept };
593
+ withFileLockSync(
594
+ join(dir, '.persist.lock'),
595
+ () => {
596
+ const manifest = _readPreservedManifest(dir);
597
+ const hashes = new Set([_hashCwd(key)]);
598
+ let changed = false;
599
+ for (const [cwd, meta] of Object.entries(manifest)) {
600
+ let same = false;
601
+ try {
602
+ same = _canonicalGraphCwd(cwd) === key;
603
+ } catch {
604
+ /* a malformed key is not this root */
605
+ }
606
+ if (!same) continue;
607
+ if (_isCodeGraphCacheHash(meta?.hash)) hashes.add(meta.hash);
608
+ delete manifest[cwd];
609
+ changed = true;
610
+ removed.push(`manifest:${cwd}`);
611
+ }
612
+ for (const hash of hashes) {
613
+ for (const file of [join(dir, `${hash}.json`), callsSidecarPath(dir, hash)]) {
614
+ if (!existsSync(file)) continue;
615
+ try {
616
+ unlinkSync(file);
617
+ removed.push(file);
618
+ } catch {
619
+ kept.push(file);
620
+ }
621
+ }
622
+ }
623
+ if (changed) writeJsonAtomicSync(join(dir, 'manifest.json'), manifest, { compact: true, lock: false });
624
+ if (_diskManifest) {
625
+ delete _diskManifest[key];
626
+ if (changed) _diskManifest = manifest;
627
+ }
628
+ },
629
+ { timeoutMs: 10_000 }
630
+ );
631
+ return { removed, kept };
632
+ }
633
+
576
634
  // ── call-site sidecar ───────────────────────────────────────────────────────
577
635
  // Resolve the sidecar file of a canonical cwd. The manifest hash wins when the
578
636
  // entry is known (it is what the persist loop wrote); otherwise the hash is
@@ -79,10 +79,10 @@ export function resolveAggregateAnchorRoot(name, args, baseCwd, { explicitCwdArg
79
79
  * One `file` anchor: an error string when it does not exist, otherwise the
80
80
  * root it re-homes the call to (null keeps the current cwd).
81
81
  */
82
- export function resolveFileAnchorRoot(name, args, fileArg, baseCwd) {
82
+ export async function resolveFileAnchorRoot(name, args, fileArg, baseCwd) {
83
83
  const abs = _absFrom(baseCwd, fileArg);
84
84
  if (!existsSync(abs)) {
85
- const elsewhere = findFileByBasename(pathResolve(baseCwd), abs);
85
+ const elsewhere = await findFileByBasename(pathResolve(baseCwd), abs);
86
86
  const hint = elsewhere.length
87
87
  ? ` Same filename exists at: ${elsewhere.map((p) => `"${toDisplayPath(p, baseCwd).replace(/\\/g, '/')}"`).join(', ')}. Use that path.`
88
88
  : '';
@@ -345,7 +345,7 @@ async function executeCodeGraphToolRaw(name, rawArgs, cwd, signal = null, option
345
345
  args = resolved.args;
346
346
  }
347
347
  if (fileArg && !hasAggregateFileArgs) {
348
- const anchor = resolveFileAnchorRoot(name, args, fileArg, baseCwd);
348
+ const anchor = await resolveFileAnchorRoot(name, args, fileArg, baseCwd);
349
349
  if (anchor.error) return anchor.error;
350
350
  if (anchor.root) effectiveCwd = anchor.root;
351
351
  }
@@ -1,6 +1,6 @@
1
1
  import { statSync } from 'node:fs';
2
2
  import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
3
- import { listProjects } from '../../../../shared/projects.mjs';
3
+ import { listProjects, removeProject } from '../../../../shared/projects.mjs';
4
4
  import { explicitSessionCwd, readLastSessionCwd } from '../../../../shared/user-cwd.mjs';
5
5
  import { canonicalGraphPath } from '../code-graph-state.mjs';
6
6
  import { listCachedCodeGraphRoots } from './disk-cache.mjs';
@@ -85,6 +85,30 @@ export function collectTrustedCodeGraphRoots(
85
85
  return [...found.values()].sort((a, b) => a.localeCompare(b));
86
86
  }
87
87
 
88
+ /**
89
+ * Stop trusting one root as a federation target. Trust is derived from the
90
+ * Project registry, the session's selected cwd and the cache manifest; the
91
+ * manifest row goes with forgetDiskCodeGraphRoot() and a selected cwd is
92
+ * dropped by the directory check once the folder is gone, so the registry is
93
+ * the only stored source left. Only the exact root is removed (nested Projects
94
+ * stay). Returns { removed, kept }; kept are registered paths that matched but
95
+ * could not be unregistered.
96
+ */
97
+ export function forgetTrustedCodeGraphRoot(
98
+ root,
99
+ { registered = () => listProjects().map((entry) => entry.path), unregister = removeProject } = {}
100
+ ) {
101
+ const removed = [];
102
+ const kept = [];
103
+ const key = canonicalGraphPath(root);
104
+ for (const path of registered()) {
105
+ if (!path || canonicalGraphPath(path) !== key) continue;
106
+ if (unregister(path)) removed.push(path);
107
+ else kept.push(path);
108
+ }
109
+ return { removed, kept };
110
+ }
111
+
88
112
  export function owningTrustedCodeGraphRoot(file, roots) {
89
113
  const matches = (roots || []).filter((root) => _pathIsWithin(root, file));
90
114
  matches.sort((a, b) => resolve(b).length - resolve(a).length);